2025-10-26 09:15:11 +00:00
|
|
|
<script lang="ts">
|
2025-11-27 12:39:08 +00:00
|
|
|
import { onDestroy, tick } from "svelte";
|
|
|
|
|
import { Platform, setIcon, type EventRef } from "obsidian";
|
2025-10-29 19:35:19 +00:00
|
|
|
import type { UserInputService } from "Services/UserInputService";
|
|
|
|
|
import type { ISearchState, SearchStateStore } from "Stores/SearchStateStore";
|
|
|
|
|
import { Resolve } from "Services/DependencyService";
|
|
|
|
|
import { Services } from "Services/Services";
|
2025-12-11 12:41:14 +00:00
|
|
|
import { isSearchTrigger, fromInput, toNode, triggerToText } from "Enums/SearchTrigger";
|
2025-10-29 19:35:19 +00:00
|
|
|
import ChatSearchResults from "./ChatSearchResults.svelte";
|
|
|
|
|
import type { Writable } from "svelte/store";
|
2025-10-29 19:46:20 +00:00
|
|
|
import type { InputService } from "Services/InputService";
|
2025-11-02 20:16:06 +00:00
|
|
|
import UserInstruction from "./UserInstruction.svelte";
|
2025-11-27 12:39:08 +00:00
|
|
|
import DiffControls from "./DiffControls.svelte";
|
|
|
|
|
import type { EventService } from "Services/EventService";
|
|
|
|
|
import { Event } from "Enums/Event";
|
|
|
|
|
import type { DiffService } from "Services/DiffService";
|
2025-10-26 09:15:11 +00:00
|
|
|
|
|
|
|
|
export let hasNoApiKey: boolean;
|
|
|
|
|
export let isSubmitting: boolean;
|
|
|
|
|
export let editModeActive: boolean;
|
2025-10-29 19:35:19 +00:00
|
|
|
export let onsubmit: (userRequest: string, formattedRequest: string) => void;
|
2025-10-26 09:15:11 +00:00
|
|
|
export let ontoggleeditmode: () => void;
|
|
|
|
|
export let onstop: () => void;
|
|
|
|
|
|
2025-10-29 19:46:20 +00:00
|
|
|
const inputService: InputService = Resolve<InputService>(Services.InputService);
|
2025-10-29 19:35:19 +00:00
|
|
|
const userInputService: UserInputService = Resolve<UserInputService>(Services.UserInputService);
|
|
|
|
|
const searchStateStore: SearchStateStore = Resolve<SearchStateStore>(Services.SearchStateStore);
|
2025-11-27 12:39:08 +00:00
|
|
|
const diffService: DiffService = Resolve<DiffService>(Services.DiffService);
|
|
|
|
|
const eventService: EventService = Resolve<EventService>(Services.EventService);
|
2025-10-29 19:35:19 +00:00
|
|
|
|
|
|
|
|
const searchState: Writable<ISearchState> = searchStateStore.searchState;
|
|
|
|
|
|
2025-10-26 09:15:11 +00:00
|
|
|
let textareaElement: HTMLDivElement;
|
2025-11-02 20:16:06 +00:00
|
|
|
let userInstructionButton: HTMLButtonElement;
|
2025-10-26 09:15:11 +00:00
|
|
|
let submitButton: HTMLButtonElement;
|
|
|
|
|
let editModeButton: HTMLButtonElement;
|
2025-11-02 20:16:06 +00:00
|
|
|
|
|
|
|
|
let userInstructionActive = false;
|
2025-10-26 09:15:11 +00:00
|
|
|
let userRequest = "";
|
|
|
|
|
|
2025-11-27 12:39:08 +00:00
|
|
|
let diffOpen: boolean = false;
|
2025-12-07 13:43:09 +00:00
|
|
|
|
2025-12-13 10:40:20 +00:00
|
|
|
const diffOpenedRef: EventRef = eventService.on(Event.DiffOpened, () => { diffOpen = true; focusInput(); });
|
|
|
|
|
const diffClosedRef: EventRef = eventService.on(Event.DiffClosed, () => { diffOpen = false; focusInput(); });
|
2025-11-27 12:39:08 +00:00
|
|
|
|
|
|
|
|
onDestroy(() => {
|
|
|
|
|
eventService.offref(diffOpenedRef);
|
|
|
|
|
eventService.offref(diffClosedRef);
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-13 10:40:20 +00:00
|
|
|
export function focusInput(onMobile: boolean = false) {
|
|
|
|
|
// don't focus on mobile, it's annoying
|
|
|
|
|
if (onMobile || !Platform.isMobile) {
|
|
|
|
|
tick().then(() => {
|
|
|
|
|
textareaElement?.focus();
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-10-26 09:15:11 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-02 20:16:06 +00:00
|
|
|
$: if (userInstructionButton) {
|
|
|
|
|
setIcon(userInstructionButton, "user-round-pen");
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-29 19:35:19 +00:00
|
|
|
$: if (submitButton) {
|
2025-12-05 23:34:52 +00:00
|
|
|
if (diffOpen) {
|
|
|
|
|
setIcon(submitButton, userRequest.trim() === "" ? "square" : "send-horizontal");
|
|
|
|
|
} else {
|
|
|
|
|
setIcon(submitButton, isSubmitting ? "square" : "send-horizontal");
|
|
|
|
|
}
|
2025-10-29 19:35:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$: if (editModeButton) {
|
|
|
|
|
setIcon(editModeButton, editModeActive ? "pencil" : "pencil-off");
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 09:15:11 +00:00
|
|
|
function handleStop() {
|
|
|
|
|
onstop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSubmit() {
|
|
|
|
|
if (userRequest.trim() === "" || isSubmitting) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-11-27 12:39:08 +00:00
|
|
|
const result = requestFromInput();
|
|
|
|
|
onsubmit(result.request, result.formattedRequest);
|
|
|
|
|
}
|
2025-10-26 09:15:11 +00:00
|
|
|
|
2025-11-27 12:39:08 +00:00
|
|
|
function handleSuggestion() {
|
|
|
|
|
if (userRequest.trim() === "" || !diffOpen) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const suggestion = requestFromInput().formattedRequest;
|
|
|
|
|
diffService.onSuggest(suggestion);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function requestFromInput() {
|
2025-10-31 15:03:40 +00:00
|
|
|
const request = textareaElement.innerHTML;
|
2025-11-10 12:59:48 +00:00
|
|
|
const formattedRequest = triggerToText(request);
|
2025-10-31 15:03:40 +00:00
|
|
|
|
2025-10-26 09:15:11 +00:00
|
|
|
textareaElement.textContent = "";
|
|
|
|
|
userRequest = "";
|
|
|
|
|
|
2025-11-12 00:06:54 +00:00
|
|
|
if (Platform.isMobile) {
|
|
|
|
|
textareaElement.blur();
|
|
|
|
|
} else {
|
|
|
|
|
focusInput();
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-27 12:39:08 +00:00
|
|
|
return { request: request, formattedRequest: formattedRequest };
|
2025-10-26 09:15:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toggleEditMode() {
|
|
|
|
|
ontoggleeditmode();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-29 19:35:19 +00:00
|
|
|
async function handleKeydown(e: KeyboardEvent) {
|
2025-11-02 20:16:06 +00:00
|
|
|
userInstructionActive = false;
|
2025-10-29 19:35:19 +00:00
|
|
|
if ($searchState.active) {
|
|
|
|
|
await continueSearch(e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (e.key === "Enter") {
|
2025-11-12 22:05:12 +00:00
|
|
|
if (e.shiftKey || Platform.isMobile) {
|
2025-10-26 09:15:11 +00:00
|
|
|
return;
|
2025-10-29 19:35:19 +00:00
|
|
|
}
|
|
|
|
|
e.preventDefault();
|
2025-11-27 12:39:08 +00:00
|
|
|
diffOpen ? handleSuggestion() : handleSubmit();
|
2025-10-29 19:35:19 +00:00
|
|
|
}
|
2025-11-12 22:05:12 +00:00
|
|
|
}
|
2025-10-29 19:35:19 +00:00
|
|
|
|
2025-11-12 22:05:12 +00:00
|
|
|
// Detect search triggers on character insertion
|
|
|
|
|
function handleBeforeInput(e: InputEvent) {
|
|
|
|
|
// This works reliably on both desktop and mobile (including virtual keyboards)
|
|
|
|
|
if (e.inputType === "insertText" && e.data && isSearchTrigger(e.data)) {
|
2025-10-29 21:33:45 +00:00
|
|
|
const position = inputService.getCursorPosition(textareaElement);
|
2025-11-12 22:05:12 +00:00
|
|
|
const trigger = fromInput(e.data);
|
2025-10-29 19:35:19 +00:00
|
|
|
searchStateStore.initializeSearch(trigger, position);
|
|
|
|
|
}
|
2025-11-12 22:05:12 +00:00
|
|
|
// Let the character insert, handleInput() will synchronize the search query
|
2025-10-29 19:35:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function continueSearch(e: KeyboardEvent) {
|
2025-10-31 15:03:40 +00:00
|
|
|
if (!$searchState.trigger) {
|
|
|
|
|
searchStateStore.resetSearch();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-29 19:35:19 +00:00
|
|
|
if (e.key === "Escape") {
|
|
|
|
|
searchStateStore.resetSearch();
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (e.key === "Enter") {
|
2025-10-31 15:03:40 +00:00
|
|
|
e.preventDefault();
|
2025-11-02 20:16:06 +00:00
|
|
|
handleSearchResultAcceptance();
|
2025-10-29 19:35:19 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (e.key === "Backspace" || e.key === "Delete") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (e.key.startsWith("Arrow")) {
|
|
|
|
|
if (e.key === "ArrowUp") {
|
2025-10-26 09:15:11 +00:00
|
|
|
e.preventDefault();
|
2025-10-29 19:35:19 +00:00
|
|
|
searchStateStore.setSelectedResultToPrevious();
|
2025-10-26 09:15:11 +00:00
|
|
|
}
|
2025-10-29 19:35:19 +00:00
|
|
|
if (e.key === "ArrowDown") {
|
|
|
|
|
e.preventDefault();
|
2025-10-29 21:33:45 +00:00
|
|
|
searchStateStore.setSelectedResultToNext();
|
2025-10-29 19:35:19 +00:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only append printable characters to the query
|
2025-10-29 19:46:20 +00:00
|
|
|
if (inputService.isPrintableKey(e.key, e.ctrlKey, e.metaKey)) {
|
2025-10-29 19:35:19 +00:00
|
|
|
searchStateStore.appendToQuery(e.key);
|
|
|
|
|
userInputService.performSearch();
|
2025-10-26 09:15:11 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-13 21:15:38 +00:00
|
|
|
function handleSearchResultAcceptance(e?: MouseEvent) {
|
2025-11-02 20:16:06 +00:00
|
|
|
if ($searchState.selectedResult !== "" && $searchState.position != null && $searchState.trigger != null) {
|
2025-11-10 12:59:48 +00:00
|
|
|
const node = toNode($searchState.trigger, $searchState.selectedResult);
|
2025-11-02 20:16:06 +00:00
|
|
|
|
|
|
|
|
inputService.deleteTextRange($searchState.position, inputService.getCursorPosition(textareaElement), textareaElement);
|
|
|
|
|
inputService.insertElementAtCursor(node, textareaElement);
|
2025-11-15 11:35:23 +00:00
|
|
|
|
|
|
|
|
searchStateStore.resetSearch();
|
2025-11-02 20:16:06 +00:00
|
|
|
}
|
2025-11-13 21:15:38 +00:00
|
|
|
|
|
|
|
|
e?.preventDefault();
|
2025-12-13 10:40:20 +00:00
|
|
|
focusInput(true);
|
2025-11-02 20:16:06 +00:00
|
|
|
}
|
|
|
|
|
|
2025-10-26 09:15:11 +00:00
|
|
|
function handleInput() {
|
|
|
|
|
if (textareaElement) {
|
|
|
|
|
userRequest = textareaElement.textContent || "";
|
2025-10-29 19:35:19 +00:00
|
|
|
|
|
|
|
|
if (textareaElement.innerHTML !== textareaElement.textContent) {
|
2025-10-31 15:03:40 +00:00
|
|
|
if (inputService.hasUnauthorizedHTML(textareaElement)) {
|
|
|
|
|
inputService.sanitizeToPlainText(textareaElement);
|
|
|
|
|
}
|
2025-10-29 19:35:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If in search mode, synchronize the query with actual text content
|
|
|
|
|
if ($searchState.active && $searchState.position !== null) {
|
|
|
|
|
const fullText = textareaElement.textContent || "";
|
|
|
|
|
const triggerPos = $searchState.position;
|
|
|
|
|
|
2025-12-11 17:11:41 +00:00
|
|
|
// Extract the query portion (from trigger to cursor position)
|
|
|
|
|
const currentCursorPos = inputService.getCursorPosition(textareaElement);
|
|
|
|
|
const actualQuery = fullText.substring(triggerPos + 1, currentCursorPos);
|
2025-10-29 19:35:19 +00:00
|
|
|
|
|
|
|
|
// Only update if the query has changed
|
|
|
|
|
if (actualQuery !== $searchState.query) {
|
|
|
|
|
searchStateStore.setQuery(actualQuery);
|
|
|
|
|
userInputService.performSearch();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-31 15:03:40 +00:00
|
|
|
|
2025-10-26 09:15:11 +00:00
|
|
|
if (userRequest.trim() === "") {
|
|
|
|
|
textareaElement.textContent = "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-29 19:35:19 +00:00
|
|
|
function handlePaste(e: ClipboardEvent) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
2025-10-29 19:46:20 +00:00
|
|
|
const plainText = inputService.getPlainTextFromClipboard(e.clipboardData);
|
2025-10-29 19:35:19 +00:00
|
|
|
|
|
|
|
|
if (!plainText) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-29 19:46:20 +00:00
|
|
|
inputService.insertTextAtCursor(plainText);
|
2025-10-29 19:35:19 +00:00
|
|
|
handleInput();
|
2025-10-26 09:15:11 +00:00
|
|
|
}
|
|
|
|
|
|
2025-10-29 19:35:19 +00:00
|
|
|
function handleCopy(e: ClipboardEvent) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
const selection = window.getSelection();
|
2025-10-31 15:03:40 +00:00
|
|
|
|
2025-10-29 19:35:19 +00:00
|
|
|
if (!selection) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const selectedText = selection.toString();
|
2025-10-31 15:03:40 +00:00
|
|
|
e.clipboardData?.setData("text/plain", selectedText);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDrop(e: DragEvent) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
const plainText = e.dataTransfer?.getData("text/plain") || "";
|
|
|
|
|
|
|
|
|
|
if (!plainText) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inputService.insertTextAtCursor(plainText);
|
|
|
|
|
handleInput();
|
2025-10-29 19:35:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleCursorPositionChange() {
|
|
|
|
|
if (!$searchState.active || $searchState.position === null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-29 19:46:20 +00:00
|
|
|
const currentPosition = inputService.getCursorPosition(textareaElement);
|
2025-10-29 19:35:19 +00:00
|
|
|
|
2025-10-29 19:46:20 +00:00
|
|
|
if (!inputService.isInSearchZone(currentPosition, $searchState.position)) {
|
2025-10-29 19:35:19 +00:00
|
|
|
searchStateStore.resetSearch();
|
|
|
|
|
}
|
2025-10-26 09:15:11 +00:00
|
|
|
}
|
2025-11-02 23:19:11 +00:00
|
|
|
|
|
|
|
|
function handleFocusOut() {
|
|
|
|
|
searchStateStore.resetSearch();
|
|
|
|
|
}
|
2025-10-26 09:15:11 +00:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<div id="input-container" class:edit-mode={editModeActive}>
|
2025-11-27 12:39:08 +00:00
|
|
|
<div id="diff-controls-container" style:padding-top={diffOpen ? "var(--size-4-2)" : 0} style:display={diffOpen ? "inline" : "none"}>
|
|
|
|
|
<DiffControls/>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-10-29 21:33:45 +00:00
|
|
|
<div id="input-search-results-container" style:padding-top={$searchState.results.length > 0 ? "var(--size-4-2)" : 0}>
|
2025-11-02 20:16:06 +00:00
|
|
|
<ChatSearchResults searchState={$searchState} onResultAccept={handleSearchResultAcceptance}/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="user-instruction-container" style:padding-top={userInstructionActive ? "var(--size-4-2)" : 0}>
|
2025-11-13 21:15:38 +00:00
|
|
|
<UserInstruction focusInput={focusInput} bind:userInstructionActive={userInstructionActive}/>
|
2025-10-29 19:35:19 +00:00
|
|
|
</div>
|
|
|
|
|
|
2025-11-02 20:16:06 +00:00
|
|
|
<button
|
|
|
|
|
id="user-instruction-button"
|
2025-11-03 17:14:32 +00:00
|
|
|
class:instruction-active={userInstructionActive}
|
2025-11-02 20:16:06 +00:00
|
|
|
bind:this={userInstructionButton}
|
|
|
|
|
on:click={() => { userInstructionActive = !userInstructionActive; searchStateStore.resetSearch() }}
|
|
|
|
|
aria-label="User Instruction">
|
|
|
|
|
</button>
|
|
|
|
|
|
2025-10-26 09:15:11 +00:00
|
|
|
<div
|
|
|
|
|
id="input-field"
|
|
|
|
|
class:error={hasNoApiKey}
|
|
|
|
|
class:edit-mode={editModeActive && !hasNoApiKey}
|
|
|
|
|
bind:this={textareaElement}
|
2025-10-31 15:03:40 +00:00
|
|
|
contenteditable="plaintext-only"
|
2025-10-26 09:15:11 +00:00
|
|
|
on:keydown={handleKeydown}
|
2025-11-12 22:05:12 +00:00
|
|
|
on:beforeinput={handleBeforeInput}
|
2025-10-26 09:15:11 +00:00
|
|
|
on:input={handleInput}
|
2025-10-29 19:35:19 +00:00
|
|
|
on:paste={handlePaste}
|
|
|
|
|
on:copy={handleCopy}
|
2025-10-31 15:03:40 +00:00
|
|
|
on:drop={handleDrop}
|
2025-10-29 19:35:19 +00:00
|
|
|
on:click={handleCursorPositionChange}
|
|
|
|
|
on:keyup={handleCursorPositionChange}
|
2025-11-02 23:19:11 +00:00
|
|
|
on:focusout={handleFocusOut}
|
2025-11-27 12:39:08 +00:00
|
|
|
data-placeholder={diffOpen ? "Make a suggestion..." : "Type a message..."}
|
2025-10-26 09:15:11 +00:00
|
|
|
role="textbox"
|
|
|
|
|
aria-multiline="true"
|
|
|
|
|
tabindex="0">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
id="edit-mode-button"
|
|
|
|
|
class:edit-mode={editModeActive}
|
|
|
|
|
bind:this={editModeButton}
|
|
|
|
|
on:click={() => { toggleEditMode() }}
|
|
|
|
|
disabled={isSubmitting}
|
|
|
|
|
aria-label={editModeActive ? "Turn off Agent Mode" : "Turn on Agent Mode"}>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
id="submit-button"
|
|
|
|
|
class:edit-mode={editModeActive}
|
|
|
|
|
bind:this={submitButton}
|
2025-12-05 23:34:52 +00:00
|
|
|
on:click={() => {
|
|
|
|
|
if (diffOpen) {
|
|
|
|
|
userRequest.trim() === "" ? handleStop() : handleSuggestion();
|
|
|
|
|
} else {
|
|
|
|
|
isSubmitting ? handleStop() : handleSubmit();
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
disabled={diffOpen ? false : !isSubmitting && userRequest.trim() === ""}
|
|
|
|
|
aria-label={diffOpen ? (userRequest.trim() === "" ? "Cancel" : "Make Suggestion") : (isSubmitting ? "Cancel" : "Send Message")}>
|
2025-10-26 09:15:11 +00:00
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
#input-container {
|
|
|
|
|
grid-row: 2;
|
|
|
|
|
grid-column: 1;
|
|
|
|
|
display: grid;
|
2025-11-27 12:39:08 +00:00
|
|
|
grid-template-rows: auto auto var(--size-4-3) 1fr var(--size-4-3);
|
2025-11-02 20:16:06 +00:00
|
|
|
grid-template-columns: var(--size-4-3) auto var(--size-4-2) 1fr var(--size-4-2) auto var(--size-4-2) auto var(--size-4-3);
|
2025-10-26 09:15:11 +00:00
|
|
|
border-radius: var(--modal-radius);
|
|
|
|
|
background-color: var(--background-primary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#input-container.edit-mode {
|
|
|
|
|
border-color: var(--alt-interactive-accent);
|
|
|
|
|
transition: border-color 0.5s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-27 12:39:08 +00:00
|
|
|
#diff-controls-container {
|
2025-10-29 19:35:19 +00:00
|
|
|
grid-row: 1;
|
2025-11-02 20:16:06 +00:00
|
|
|
grid-column: 2 / 9;
|
2025-10-29 19:35:19 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-27 12:39:08 +00:00
|
|
|
#input-search-results-container {
|
|
|
|
|
grid-row: 2;
|
|
|
|
|
grid-column: 2 / 9;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-02 20:16:06 +00:00
|
|
|
#user-instruction-container {
|
2025-11-27 12:39:08 +00:00
|
|
|
grid-row: 2;
|
2025-11-02 20:16:06 +00:00
|
|
|
grid-column: 2 / 9;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#user-instruction-button {
|
2025-11-27 12:39:08 +00:00
|
|
|
grid-row: 4;
|
2025-10-26 09:15:11 +00:00
|
|
|
grid-column: 2;
|
2025-11-02 20:16:06 +00:00
|
|
|
border-radius: var(--button-radius);
|
|
|
|
|
align-self: end;
|
|
|
|
|
transition-duration: 0.5s;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 22:51:22 +00:00
|
|
|
:global(.is-mobile) #user-instruction-button {
|
|
|
|
|
max-height: 2rem;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-03 17:14:32 +00:00
|
|
|
#user-instruction-button.instruction-active {
|
|
|
|
|
box-shadow: 0px 0px 4px 1px var(--color-accent);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-02 20:16:06 +00:00
|
|
|
#input-field {
|
2025-11-27 12:39:08 +00:00
|
|
|
grid-row: 4;
|
2025-11-02 20:16:06 +00:00
|
|
|
grid-column: 4;
|
2025-10-26 09:15:11 +00:00
|
|
|
height: 100%;
|
|
|
|
|
max-height: 30vh;
|
|
|
|
|
border-radius: var(--input-radius);
|
|
|
|
|
font-weight: var(--input-font-weight);
|
|
|
|
|
border-width: var(--input-border-width);
|
|
|
|
|
border-style: solid;
|
|
|
|
|
border-color: var(--background-modifier-border);
|
|
|
|
|
padding: var(--size-2-2) var(--size-2-3);
|
|
|
|
|
background-color: var(--background-primary);
|
|
|
|
|
font-family: var(--font-interface-theme);
|
|
|
|
|
resize: none;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
overflow-x: hidden;
|
|
|
|
|
scroll-behavior: smooth;
|
|
|
|
|
color: var(--font-interface-theme);
|
|
|
|
|
transition: border-color 0.5s ease-out;
|
|
|
|
|
word-wrap: break-word;
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 22:51:22 +00:00
|
|
|
:global(.is-mobile) #input-field {
|
|
|
|
|
align-content: end;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 09:15:11 +00:00
|
|
|
#input-field:focus {
|
|
|
|
|
border-color: var(--color-accent);
|
|
|
|
|
box-shadow: 0px 0px 4px 1px var(--color-accent);
|
|
|
|
|
transition: border-color 0.5s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#input-field.edit-mode:focus {
|
|
|
|
|
border-color: var(--alt-interactive-accent);
|
|
|
|
|
box-shadow: 0px 0px 3px 1px var(--alt-interactive-accent);
|
|
|
|
|
transition: border-color 0.5s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#input-field.error,
|
|
|
|
|
#input-field.error:focus {
|
|
|
|
|
border-color: var(--color-red);
|
|
|
|
|
box-shadow: 0px 0px 4px 1px var(--color-red);
|
|
|
|
|
transition: border-color 0.5s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#input-field::-webkit-scrollbar {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#input-field:empty::before {
|
|
|
|
|
content: attr(data-placeholder);
|
|
|
|
|
color: var(--text-muted);
|
|
|
|
|
opacity: 0.75;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#input-field[contenteditable]:focus {
|
|
|
|
|
outline: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#edit-mode-button {
|
2025-11-27 12:39:08 +00:00
|
|
|
grid-row: 4;
|
2025-11-02 20:16:06 +00:00
|
|
|
grid-column: 6;
|
2025-10-26 09:15:11 +00:00
|
|
|
border-radius: var(--button-radius);
|
|
|
|
|
align-self: end;
|
|
|
|
|
transition-duration: 0.5s;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 22:51:22 +00:00
|
|
|
:global(.is-mobile) #edit-mode-button {
|
|
|
|
|
max-height: 2rem;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 09:15:11 +00:00
|
|
|
#submit-button {
|
2025-11-27 12:39:08 +00:00
|
|
|
grid-row: 4;
|
2025-11-02 20:16:06 +00:00
|
|
|
grid-column: 8;
|
2025-10-26 09:15:11 +00:00
|
|
|
border-radius: var(--button-radius);
|
|
|
|
|
padding-left: var(--size-4-5);
|
|
|
|
|
padding-right: var(--size-4-5);
|
|
|
|
|
align-self: end;
|
|
|
|
|
transition-duration: 0.5s;
|
|
|
|
|
background-color: var(--interactive-accent);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 22:51:22 +00:00
|
|
|
:global(.is-mobile) #submit-button {
|
|
|
|
|
max-height: 2rem;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 09:15:11 +00:00
|
|
|
#submit-button:not(:disabled):hover {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
background-color: var(--interactive-accent-hover);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#submit-button.edit-mode {
|
|
|
|
|
background-color: var(--alt-interactive-accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#submit-button.edit-mode:not(:disabled):hover {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
background-color: var(--alt-interactive-accent-hover);
|
|
|
|
|
}
|
|
|
|
|
</style>
|