diff --git a/package.json b/package.json index ae5ab32..8bc887c 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "build": "vinxi build", "start": "vinxi start", "version": "vinxi version", - "test": "vitest --coverage --browser=chromium", + "test": "vitest --coverage", "test:ci": "vitest run" } } \ No newline at end of file diff --git a/src/components/textarea/textarea.module.css b/src/components/textarea/textarea.module.css index 70e634e..fadd9e2 100644 --- a/src/components/textarea/textarea.module.css +++ b/src/components/textarea/textarea.module.css @@ -27,6 +27,10 @@ position: absolute; inset-inline-end: 0; inset-block-start: 0; + + display: block grid; + grid-auto-flow: row; + gap: .5em; } .suggestions { diff --git a/src/components/textarea/textarea.tsx b/src/components/textarea/textarea.tsx index 39c20f8..34a2a29 100644 --- a/src/components/textarea/textarea.tsx +++ b/src/components/textarea/textarea.tsx @@ -4,6 +4,7 @@ import { isServer } from 'solid-js/web'; import { createEditContext } from '~/features/editor'; import { createSource } from '~/features/source'; import css from './textarea.module.css'; +import { debounce } from '@solid-primitives/scheduled'; interface TextareaProps { class?: string; @@ -17,9 +18,11 @@ interface TextareaProps { } export function Textarea(props: TextareaProps) { + const [replacement, setReplacement] = createSignal(''); const [editorRef, setEditorRef] = createSignal(); + const source = createSource(() => props.value); - const [text] = createEditContext(editorRef, () => source.out); + const [text, { mutate }] = createEditContext(editorRef, () => source.out); createEffect(() => { source.out = text(); @@ -41,9 +44,17 @@ export function Textarea(props: TextareaProps) { createHighlights(ref, 'search-results', errors); })); + const replace = () => { + mutate(text => text.replaceAll(source.query, replacement())); + }; + return <> - - source.query = e.target.value} /> +
+ source.query = e.target.value} /> + setReplacement(e.target.value)} /> + +
+
; } -const Suggestions: Component = () => { - const [selection] = createSelection(); - const [suggestionRef, setSuggestionRef] = createSignal(); - const [suggestions, setSuggestions] = createSignal([]); - - const marker = createMemo(() => { - if (isServer) { - return; - } - - const [n] = selection(); - const s = window.getSelection(); - - if (n === null || s === null || s.rangeCount < 1) { - return; - } - - return (findMarkerNode(s.getRangeAt(0)?.commonAncestorContainer) ?? undefined) as HTMLElement | undefined; - }); - - createEffect((prev) => { - if (prev) { - prev.style.setProperty('anchor-name', null); - } - - const m = marker(); - const ref = untrack(() => suggestionRef()!); - - if (m === undefined) { - ref.hidePopover(); - - return; - } - - m.style.setProperty('anchor-name', '--suggestions'); - ref.showPopover(); - ref.focus() - - return m; - }); - - createEffect(() => { - marker(); - - setSuggestions(Array(Math.ceil(Math.random() * 5)).fill('').map((_, i) => `suggestion ${i}`)); - }); - - const onPointerDown = (e: PointerEvent) => { - marker()?.replaceWith(document.createTextNode(e.target.textContent)); - }; - - const onKeyDown = (e: KeyboardEvent) => { - console.log(e); - } - - return - { - suggestion =>
  • {suggestion}
  • - }
    -
    ; -}; - -const findMarkerNode = (node: Node | null) => { - while (node !== null) { - if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).hasAttribute('data-marker')) { - break; - } - - node = node.parentNode; - } - - return node; -}; - - - const createHighlights = (node: Node, type: string, ranges: [number, number][]) => { queueMicrotask(() => { const nodes = getTextNodes(node); diff --git a/src/features/editor/__screenshots__/context.spec.tsx/createEditor-should-create-1.png b/src/features/editor/__screenshots__/context.spec.tsx/createEditor-should-create-1.png deleted file mode 100644 index d550b17..0000000 Binary files a/src/features/editor/__screenshots__/context.spec.tsx/createEditor-should-create-1.png and /dev/null differ diff --git a/src/features/editor/context.ts b/src/features/editor/context.ts index 9a80f65..c102c98 100644 --- a/src/features/editor/context.ts +++ b/src/features/editor/context.ts @@ -7,11 +7,14 @@ import { createMap } from './map'; import { splice } from "~/utilities"; import rehypeParse from "rehype-parse"; -type Editor = [Accessor]; +type Editor = [Accessor, { select(range: Range): void, mutate(setter: (text: string) => string): void }]; export function createEditor(ref: Accessor, value: Accessor): Editor { if (isServer) { - return [value]; + return [value, { + select(range) { }, + mutate() { }, + }]; } if (!("EditContext" in window)) { @@ -187,7 +190,17 @@ export function createEditor(ref: Accessor, value: Accessor } }); - return [createMemo(() => store.text)]; + return [ + createMemo(() => store.text), + { + select(range: Range) { + updateSelection(range); + }, + + mutate(setter) { + setStore('text', setter); + }, + }]; } const equals = (a: Range, b: Range): boolean => { diff --git a/vitest.config.ts b/vitest.config.ts index b74bc35..8debf4c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -43,6 +43,7 @@ function reportWith(...reporter: CoverageReporter[]): Plugin { provider: 'playwright', enabled: true, headless: true, + screenshotFailures: false, instances: [{ browser: 'chromium' }] }; }