diff --git a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreview.svelte b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreview.svelte index 0efe052b1f..8e89491723 100644 --- a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreview.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreview.svelte @@ -5,6 +5,7 @@ ChatAttachmentsPreviewNavButtons, ChatAttachmentsPreviewThumbnailStrip } from '$lib/components/app'; + import { UI_DATA_ATTRS } from '$lib/constants'; import { modelsStore } from '$lib/stores'; import { createBase64DataUrl, @@ -90,7 +91,7 @@ const index = currentIndex; setTimeout(() => { - const thumbnail = document.querySelector(`[data-thumbnail-index="${index}"]`); + const thumbnail = document.querySelector(`[${UI_DATA_ATTRS.THUMBNAIL_INDEX}="${index}"]`); thumbnail?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); }, 0); diff --git a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewThumbnailStrip.svelte b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewThumbnailStrip.svelte index 60b72077d8..366c8372b9 100644 --- a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewThumbnailStrip.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewThumbnailStrip.svelte @@ -1,7 +1,7 @@ -{#if useContenteditable} +{#if useRichInput} - import { CODE_BLOCK } from '$lib/constants'; - import { ColorMode } from '$lib/enums'; + import { CODE_BLOCK, CODE_TOKEN_ATTR, UI_DATA_ATTRS } from '$lib/constants'; + import { BooleanString, ChatFormInputRichTokenKind, ColorMode } from '$lib/enums'; import { isMobile } from '$lib/stores'; import type { ChatFormInputRichToken } from '$lib/types'; import type { SourceHistoryEntry } from '$lib/utils'; @@ -53,7 +53,7 @@ // browser's native undo stack. const history = new SourceHistory(); - // Browsers disagree on what an empty contenteditable contains (`
`, + // Browsers disagree on what an empty rich chat form input contains (`
`, // `

`, or nothing), so emptiness is decided by the // serialized source, not the DOM shape. function syncEmptyState(serialized?: string) { @@ -61,7 +61,7 @@ const source = serialized ?? serializeContent(rootElement); - rootElement.dataset.empty = source.length === 0 ? 'true' : 'false'; + rootElement.dataset.empty = source.length === 0 ? BooleanString.TRUE : BooleanString.FALSE; } function renderTokens(tokens: ChatFormInputRichToken[]) { @@ -69,7 +69,7 @@ const caret = rangeToTextOffset(rootElement, safeRange()); - // eslint-disable-next-line svelte/no-dom-manipulating -- the token layer is owned imperatively; Svelte renders only the contenteditable host, never its children + // eslint-disable-next-line svelte/no-dom-manipulating -- the token layer is owned imperatively; Svelte renders only the rich chat form input host, never its children rootElement.replaceChildren(buildFragment(tokens)); syncCodeBlockHatches(rootElement); @@ -127,7 +127,9 @@ } function highlightCodeBlocks(root: HTMLElement) { - for (const el of root.querySelectorAll('code[data-code-token="code_block"]')) { + for (const el of root.querySelectorAll( + `code[${CODE_TOKEN_ATTR}="${ChatFormInputRichTokenKind.CODE_BLOCK}"]` + )) { highlightCodeBlockElement(el); } } @@ -151,7 +153,10 @@ } while (node && node !== rootElement) { - if (node instanceof HTMLElement && node.dataset.codeToken === 'code_block') { + if ( + node instanceof HTMLElement && + node.getAttribute(CODE_TOKEN_ATTR) === ChatFormInputRichTokenKind.CODE_BLOCK + ) { const caret = rangeToTextOffset(rootElement, range); if (highlightCodeBlockElement(node)) { @@ -189,11 +194,13 @@ * (deduped via the data attribute) swapped on mode change. */ function loadHighlightTheme(isDark: boolean) { - document.querySelectorAll('style[data-highlight-theme-preview]').forEach((s) => s.remove()); + document + .querySelectorAll(`style[${UI_DATA_ATTRS.HIGHLIGHT_THEME_PREVIEW}]`) + .forEach((s) => s.remove()); const style = document.createElement('style'); - style.setAttribute('data-highlight-theme-preview', 'true'); + style.setAttribute(UI_DATA_ATTRS.HIGHLIGHT_THEME_PREVIEW, BooleanString.TRUE); style.textContent = isDark ? githubDarkCss : githubLightCss; document.head.appendChild(style); @@ -311,7 +318,7 @@ source[source.length - 2] !== '\n' && last?.nodeType === Node.TEXT_NODE ) { - // eslint-disable-next-line svelte/no-dom-manipulating -- the token layer is owned imperatively; Svelte renders only the contenteditable host, never its children + // eslint-disable-next-line svelte/no-dom-manipulating -- the token layer is owned imperatively; Svelte renders only the rich chat form input host, never its children rootElement.appendChild(document.createTextNode('\n')); restoreCaret(source.length); resizeHeight(); @@ -404,7 +411,10 @@ let node: Node | null = container.parentNode; while (node && node !== rootElement) { - if (node instanceof HTMLElement && node.dataset.codeToken === 'code_block') { + if ( + node instanceof HTMLElement && + node.getAttribute(CODE_TOKEN_ATTR) === ChatFormInputRichTokenKind.CODE_BLOCK + ) { const tail = document.createRange(); tail.setStart(container, offset); @@ -462,7 +472,11 @@ const first = rootElement.firstChild; - if (!(first instanceof HTMLElement) || first.dataset.codeToken !== 'code_block') return false; + if ( + !(first instanceof HTMLElement) || + first.getAttribute(CODE_TOKEN_ATTR) !== ChatFormInputRichTokenKind.CODE_BLOCK + ) + return false; const range = safeRange(); @@ -483,7 +497,7 @@ if (firstLineEnd !== -1 && caret > firstLineEnd) return false; } - // eslint-disable-next-line svelte/no-dom-manipulating -- the token layer is owned imperatively; Svelte renders only the contenteditable host, never its children + // eslint-disable-next-line svelte/no-dom-manipulating -- the token layer is owned imperatively; Svelte renders only the rich chat form input host, never its children rootElement.prepend(document.createElement('br')); restoreCaret(0, extend); @@ -507,7 +521,11 @@ const second = first.nextSibling; - if (!(second instanceof HTMLElement) || second.dataset.codeToken !== 'code_block') return; + if ( + !(second instanceof HTMLElement) || + second.getAttribute(CODE_TOKEN_ATTR) !== ChatFormInputRichTokenKind.CODE_BLOCK + ) + return; const range = safeRange(); const onHatch = diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerList.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerList.svelte index 3ebb29b9b3..160c14ce8e 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerList.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerList.svelte @@ -1,7 +1,7 @@ -