@juxtui/ui 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -64
- package/dist/JTooltip-CAL9lsPF.js.map +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/nuxt.js.map +1 -1
- package/dist/style.css +368 -368
- package/package.json +1 -1
package/dist/editor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor.js","names":["_hoisted_1","_openBlock","_createBlock","JTooltip","_withCtx","_createTextVNode","_toDisplayString","JKbd","_createCommentVNode","_createElementVNode","_normalizeClass","_withModifiers","_renderSlot","_sfc_main","$attrs","$slots","_useModel","_openBlock","_createBlock","JFieldShell","_unref","_normalizeClass","_normalizeStyle","_createSlots","_withCtx","_createElementVNode","_createElementBlock","_Fragment","_createVNode","JDropdownMenu","_withModifiers","_resolveDynamicComponent","_toDisplayString","_renderList","JDropdownMenuItem","_createTextVNode","_createCommentVNode","JEditorButton","_renderSlot","JPortal","_Transition","_withDirectives","_vModelText","_sfc_main"],"sources":["../src/editor/icons.ts","../src/editor/commands.ts","../src/editor/JEditorButton.vue","../src/editor/JEditorButton.vue","../src/editor/slash.ts","../src/editor/JEditor.vue","../src/editor/JEditor.vue"],"sourcesContent":["import type { FunctionalComponent, SVGAttributes } from 'vue'\nimport { h } from 'vue'\n\n/*\n * Editor glyphs, drawn on the same 16px grid and 1.5px stroke as the rest of\n * juxt.ui so the toolbar sits at the weight of 13–14px text.\n */\n\ntype Icon = FunctionalComponent<SVGAttributes>\n\nfunction icon(name: string, children: () => ReturnType<typeof h>[]): Icon {\n const component: Icon = props =>\n h(\n 'svg',\n {\n 'xmlns': 'http://www.w3.org/2000/svg',\n 'viewBox': '0 0 16 16',\n 'width': 16,\n 'height': 16,\n 'fill': 'none',\n 'stroke': 'currentColor',\n 'stroke-width': 1.5,\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n 'aria-hidden': 'true',\n 'focusable': 'false',\n ...props,\n },\n children(),\n )\n component.displayName = name\n return component\n}\n\nconst p = (d: string) => h('path', { d })\n\nexport const IconBold = icon('IconBold', () => [p('M4.5 3h4.25a2.5 2.5 0 0 1 0 5H4.5zM4.5 8h5a2.5 2.5 0 0 1 0 5h-5z')])\nexport const IconItalic = icon('IconItalic', () => [p('M10 3H6.5M9.5 13H6M9 3 7 13')])\nexport const IconUnderline = icon('IconUnderline', () => [p('M4.5 2.75V7a3.5 3.5 0 0 0 7 0V2.75M3.5 13.5h9')])\nexport const IconStrike = icon('IconStrike', () => [p('M11.5 4.25C11 3.2 9.8 2.75 8.2 2.75 6.1 2.75 4.75 3.8 4.75 5.25c0 1 .6 1.7 1.7 2.1M3 8.25h10M11.25 10.25c.1.3.15.6.15.9 0 1.5-1.45 2.6-3.6 2.6-1.8 0-3.1-.6-3.65-1.75')])\nexport const IconCode = icon('IconCode', () => [p('M6 4.5 2.75 8 6 11.5M10 4.5 13.25 8 10 11.5')])\nexport const IconLink = icon('IconLink', () => [p('M6.75 9.25a2.75 2.75 0 0 0 3.9 0l2-2a2.75 2.75 0 0 0-3.9-3.9l-.6.6M9.25 6.75a2.75 2.75 0 0 0-3.9 0l-2 2a2.75 2.75 0 0 0 3.9 3.9l.6-.6')])\nexport const IconUnlink = icon('IconUnlink', () => [p('M9.8 3.95l.6-.6a2.75 2.75 0 0 1 3.9 3.9l-1.2 1.2M6.2 12.05l-.6.6a2.75 2.75 0 0 1-3.9-3.9l1.2-1.2M2.5 2.5l11 11')])\nexport const IconExternal = icon('IconExternal', () => [p('M9.5 2.75h3.75V6.5M13.25 2.75 7.5 8.5M11.5 9.5v2.75a1 1 0 0 1-1 1h-6.75a1 1 0 0 1-1-1V5.5a1 1 0 0 1 1-1H6.5')])\nexport const IconHighlight = icon('IconHighlight', () => [p('M9.75 3.25l3 3-5.5 5.5H4.25v-3zM2.75 13.5h10.5')])\nexport const IconBulletList = icon('IconBulletList', () => [\n p('M6.5 4h7M6.5 8h7M6.5 12h7'),\n h('circle', { 'cx': 3, 'cy': 4, 'r': 0.6, 'fill': 'currentColor', 'stroke-width': 1 }),\n h('circle', { 'cx': 3, 'cy': 8, 'r': 0.6, 'fill': 'currentColor', 'stroke-width': 1 }),\n h('circle', { 'cx': 3, 'cy': 12, 'r': 0.6, 'fill': 'currentColor', 'stroke-width': 1 }),\n])\nexport const IconOrderedList = icon('IconOrderedList', () => [p('M6.75 4h6.75M6.75 8h6.75M6.75 12h6.75M2.5 3l1-.5v3M2.5 5.5h2M2.5 9.25c0-.5.45-.9 1-.9s1 .35 1 .85c0 .9-2 1.3-2 2.3h2')])\nexport const IconTaskList = icon('IconTaskList', () => [p('M8 4.25h5.5M8 11.75h5.5M2.5 4.25l1.25 1.25L6 3.25'), h('rect', { x: 2.5, y: 9.75, width: 3.5, height: 3.5, rx: 0.75 })])\nexport const IconQuote = icon('IconQuote', () => [p('M3 4v8M6 5.5h7M6 8h7M6 10.5h4.5')])\nexport const IconCodeBlock = icon('IconCodeBlock', () => [h('rect', { x: 2, y: 2.75, width: 12, height: 10.5, rx: 2 }), p('M6.25 6.5 4.75 8l1.5 1.5M9.75 6.5l1.5 1.5-1.5 1.5')])\nexport const IconDivider = icon('IconDivider', () => [p('M2.5 8h11M5 4.5h6M5 11.5h6')])\nexport const IconUndo = icon('IconUndo', () => [p('M5.5 3.5 2.75 6.25 5.5 9M3 6.25h6.25a3.75 3.75 0 0 1 0 7.5H7')])\nexport const IconRedo = icon('IconRedo', () => [p('M10.5 3.5l2.75 2.75L10.5 9M13 6.25H6.75a3.75 3.75 0 0 0 0 7.5H9')])\nexport const IconText = icon('IconText', () => [p('M3.5 4V3h9v1M8 3v10M6.25 13h3.5')])\nexport const IconHeading1 = icon('IconHeading1', () => [p('M2.5 3.5v9M8 3.5v9M2.5 8H8M11 6.25l1.75-1.25v7.5')])\nexport const IconHeading2 = icon('IconHeading2', () => [p('M2.5 3.5v9M7.5 3.5v9M2.5 8h5M10.25 6.5c0-.95.75-1.6 1.6-1.6s1.65.6 1.65 1.55c0 1.95-3.25 2.8-3.25 5.05h3.3')])\nexport const IconHeading3 = icon('IconHeading3', () => [p('M2.5 3.5v9M7.5 3.5v9M2.5 8h5M10.25 5.25h3.25L11.75 7.7c1 0 1.75.7 1.75 1.75 0 1.2-.9 1.95-1.9 1.95-.7 0-1.2-.3-1.5-.8')])\nexport const IconChevronDown = icon('IconChevronDown', () => [p('M4.5 6.25 8 9.75l3.5-3.5')])\nexport const IconCornerDownLeft = icon('IconCornerDownLeft', () => [p('M12.5 3.5v4.25a2 2 0 0 1-2 2h-7M6 7l-2.5 2.75L6 12.5')])\n","import type { Editor, Range } from '@tiptap/core'\nimport type { Component } from 'vue'\nimport {\n IconBulletList,\n IconCodeBlock,\n IconDivider,\n IconHeading1,\n IconHeading2,\n IconHeading3,\n IconOrderedList,\n IconQuote,\n IconTaskList,\n IconText,\n} from './icons'\n\n/** A block the user can turn the current line into, from the toolbar or the slash menu. */\nexport interface BlockCommand {\n id: string\n label: string\n description: string\n icon: Component\n /** The Markdown shortcut that does the same thing, shown as a hint. */\n markdown?: string\n keywords: string[]\n /** Whether the selection is currently this kind of block. */\n isActive?: (editor: Editor) => boolean\n /** Only offered as a \"turn into\" target, not as an insertable block. */\n convertible: boolean\n run: (editor: Editor, range?: Range) => void\n}\n\n// Start from the slash-command range when there is one, so the `/query` text is removed.\nconst chain = (editor: Editor, range?: Range) => (range ? editor.chain().focus().deleteRange(range) : editor.chain().focus())\n\nexport const blockCommands: BlockCommand[] = [\n {\n id: 'text',\n label: 'Text',\n description: 'Plain paragraph',\n icon: IconText,\n keywords: ['paragraph', 'p', 'body'],\n convertible: true,\n isActive: editor => editor.isActive('paragraph') && !editor.isActive('bulletList') && !editor.isActive('orderedList') && !editor.isActive('taskList') && !editor.isActive('blockquote'),\n run: (editor, range) => chain(editor, range).setParagraph().run(),\n },\n {\n id: 'h1',\n label: 'Heading 1',\n description: 'Large section title',\n icon: IconHeading1,\n markdown: '#',\n keywords: ['title', 'h1', 'big'],\n convertible: true,\n isActive: editor => editor.isActive('heading', { level: 1 }),\n run: (editor, range) => chain(editor, range).setHeading({ level: 1 }).run(),\n },\n {\n id: 'h2',\n label: 'Heading 2',\n description: 'Medium section title',\n icon: IconHeading2,\n markdown: '##',\n keywords: ['subtitle', 'h2'],\n convertible: true,\n isActive: editor => editor.isActive('heading', { level: 2 }),\n run: (editor, range) => chain(editor, range).setHeading({ level: 2 }).run(),\n },\n {\n id: 'h3',\n label: 'Heading 3',\n description: 'Small section title',\n icon: IconHeading3,\n markdown: '###',\n keywords: ['h3'],\n convertible: true,\n isActive: editor => editor.isActive('heading', { level: 3 }),\n run: (editor, range) => chain(editor, range).setHeading({ level: 3 }).run(),\n },\n {\n id: 'bullet',\n label: 'Bulleted list',\n description: 'A simple list',\n icon: IconBulletList,\n markdown: '-',\n keywords: ['ul', 'unordered', 'bullets'],\n convertible: true,\n isActive: editor => editor.isActive('bulletList'),\n run: (editor, range) => chain(editor, range).toggleBulletList().run(),\n },\n {\n id: 'ordered',\n label: 'Numbered list',\n description: 'A list with numbers',\n icon: IconOrderedList,\n markdown: '1.',\n keywords: ['ol', 'ordered', 'numbers'],\n convertible: true,\n isActive: editor => editor.isActive('orderedList'),\n run: (editor, range) => chain(editor, range).toggleOrderedList().run(),\n },\n {\n id: 'task',\n label: 'To-do list',\n description: 'Checkboxes you can tick off',\n icon: IconTaskList,\n markdown: '[ ]',\n keywords: ['todo', 'task', 'checkbox', 'checklist'],\n convertible: true,\n isActive: editor => editor.isActive('taskList'),\n run: (editor, range) => chain(editor, range).toggleTaskList().run(),\n },\n {\n id: 'quote',\n label: 'Quote',\n description: 'Set a passage apart',\n icon: IconQuote,\n markdown: '>',\n keywords: ['blockquote', 'citation'],\n convertible: true,\n isActive: editor => editor.isActive('blockquote'),\n run: (editor, range) => chain(editor, range).toggleBlockquote().run(),\n },\n {\n id: 'code',\n label: 'Code block',\n description: 'Monospaced, whitespace kept',\n icon: IconCodeBlock,\n markdown: '```',\n keywords: ['pre', 'snippet', 'code'],\n convertible: true,\n isActive: editor => editor.isActive('codeBlock'),\n run: (editor, range) => chain(editor, range).toggleCodeBlock().run(),\n },\n {\n id: 'divider',\n label: 'Divider',\n description: 'A line between sections',\n icon: IconDivider,\n markdown: '---',\n keywords: ['hr', 'rule', 'separator', 'line'],\n convertible: false,\n run: (editor, range) => chain(editor, range).setHorizontalRule().run(),\n },\n]\n","<script setup lang=\"ts\">\n/** Internal: a toolbar / bubble-menu button with a tooltip that shows its shortcut. */\nimport JKbd from '../components/kbd/JKbd.vue'\nimport JTooltip from '../components/tooltip/JTooltip.vue'\n\ndefineProps<{\n label: string\n /** Hotkey syntax, e.g. `mod+b`. */\n shortcut?: string\n /** Toggle state; renders `aria-pressed`. Leave undefined for plain actions. */\n active?: boolean\n disabled?: boolean\n side?: 'top' | 'bottom'\n}>()\n\nconst emit = defineEmits<{ click: [event: MouseEvent] }>()\n\ndefineSlots<{ default?: () => unknown }>()\n</script>\n\n<template>\n <JTooltip :side=\"side ?? 'top'\" :delay=\"500\" :disabled=\"disabled\">\n <button\n type=\"button\"\n class=\"j-editor-button j-focusable\"\n :class=\"{ 'is-active': active }\"\n :aria-label=\"label\"\n :aria-pressed=\"active === undefined ? undefined : active\"\n :disabled=\"disabled\"\n data-editor-control\n @pointerdown.prevent\n @click=\"emit('click', $event)\"\n >\n <slot />\n </button>\n <template #content>\n {{ label }}<JKbd v-if=\"shortcut\" :keys=\"shortcut\" size=\"sm\" class=\"j-editor-button__keys\" />\n </template>\n </JTooltip>\n</template>\n\n<style>\n@layer theme, base, juxt, components, utilities;\n@layer juxt {\n.j-editor-button {\n display: inline-grid;\n flex: none;\n place-items: center;\n width: var(--juxt-control-sm);\n height: var(--juxt-control-sm);\n padding: 0;\n border: 0;\n border-radius: var(--juxt-radius-sm);\n background: transparent;\n color: var(--juxt-fg-secondary);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition-property: background-color, color, outline-color, outline-offset, transform;\n}\n\n.j-editor-button > svg {\n width: 1rem;\n height: 1rem;\n}\n\n.j-editor-button:hover:not(:disabled) {\n background: var(--juxt-surface-hover);\n color: var(--juxt-fg);\n}\n\n.j-editor-button:active:not(:disabled) {\n transform: scale(0.94);\n transition-duration: var(--juxt-duration-instant);\n}\n\n.j-editor-button.is-active {\n background: var(--juxt-surface-active);\n color: var(--juxt-fg);\n}\n\n.j-editor-button:disabled {\n color: var(--juxt-fg-disabled);\n cursor: not-allowed;\n}\n\n.j-editor-button__keys {\n margin-left: var(--juxt-space-1-5);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-editor-button:active:not(:disabled) {\n transform: none;\n }\n}\n}\n</style>\n","<script setup lang=\"ts\">\n/** Internal: a toolbar / bubble-menu button with a tooltip that shows its shortcut. */\nimport JKbd from '../components/kbd/JKbd.vue'\nimport JTooltip from '../components/tooltip/JTooltip.vue'\n\ndefineProps<{\n label: string\n /** Hotkey syntax, e.g. `mod+b`. */\n shortcut?: string\n /** Toggle state; renders `aria-pressed`. Leave undefined for plain actions. */\n active?: boolean\n disabled?: boolean\n side?: 'top' | 'bottom'\n}>()\n\nconst emit = defineEmits<{ click: [event: MouseEvent] }>()\n\ndefineSlots<{ default?: () => unknown }>()\n</script>\n\n<template>\n <JTooltip :side=\"side ?? 'top'\" :delay=\"500\" :disabled=\"disabled\">\n <button\n type=\"button\"\n class=\"j-editor-button j-focusable\"\n :class=\"{ 'is-active': active }\"\n :aria-label=\"label\"\n :aria-pressed=\"active === undefined ? undefined : active\"\n :disabled=\"disabled\"\n data-editor-control\n @pointerdown.prevent\n @click=\"emit('click', $event)\"\n >\n <slot />\n </button>\n <template #content>\n {{ label }}<JKbd v-if=\"shortcut\" :keys=\"shortcut\" size=\"sm\" class=\"j-editor-button__keys\" />\n </template>\n </JTooltip>\n</template>\n\n<style>\n@layer theme, base, juxt, components, utilities;\n@layer juxt {\n.j-editor-button {\n display: inline-grid;\n flex: none;\n place-items: center;\n width: var(--juxt-control-sm);\n height: var(--juxt-control-sm);\n padding: 0;\n border: 0;\n border-radius: var(--juxt-radius-sm);\n background: transparent;\n color: var(--juxt-fg-secondary);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition-property: background-color, color, outline-color, outline-offset, transform;\n}\n\n.j-editor-button > svg {\n width: 1rem;\n height: 1rem;\n}\n\n.j-editor-button:hover:not(:disabled) {\n background: var(--juxt-surface-hover);\n color: var(--juxt-fg);\n}\n\n.j-editor-button:active:not(:disabled) {\n transform: scale(0.94);\n transition-duration: var(--juxt-duration-instant);\n}\n\n.j-editor-button.is-active {\n background: var(--juxt-surface-active);\n color: var(--juxt-fg);\n}\n\n.j-editor-button:disabled {\n color: var(--juxt-fg-disabled);\n cursor: not-allowed;\n}\n\n.j-editor-button__keys {\n margin-left: var(--juxt-space-1-5);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-editor-button:active:not(:disabled) {\n transform: none;\n }\n}\n}\n</style>\n","import type { Editor, Range } from '@tiptap/core'\nimport type { BlockCommand } from './commands'\nimport { Extension } from '@tiptap/core'\nimport { PluginKey } from '@tiptap/pm/state'\nimport Suggestion from '@tiptap/suggestion'\nimport { matchText } from '../components/command-palette/search'\nimport { blockCommands } from './commands'\n\n/** Reactive state the editor component renders the slash menu from. */\nexport interface SlashState {\n open: boolean\n query: string\n items: BlockCommand[]\n index: number\n rect: DOMRect | null\n select: ((item: BlockCommand) => void) | null\n}\n\nexport function filterCommands(query: string): BlockCommand[] {\n const q = query.trim()\n if (!q) return blockCommands\n return blockCommands\n .map((command) => {\n const score = Math.max(\n matchText(q, command.label).score,\n ...command.keywords.map(keyword => matchText(q, keyword).score * 0.9),\n )\n return { command, score }\n })\n .filter(entry => entry.score > 0)\n .sort((a, b) => b.score - a.score)\n .map(entry => entry.command)\n}\n\nexport const slashPluginKey = new PluginKey('juxtSlash')\n\n/**\n * Typing `/` opens a menu of blocks at the caret. The menu itself is rendered\n * by JEditor from `state`; this extension only feeds it and handles keys.\n */\nexport function createSlashCommands(state: SlashState) {\n return Extension.create({\n name: 'juxtSlashCommands',\n addProseMirrorPlugins() {\n return [\n Suggestion<BlockCommand>({\n editor: this.editor,\n pluginKey: slashPluginKey,\n char: '/',\n // Only in plain paragraphs: not in code, headings or mid-word.\n allow: ({ state: editorState, range }) => {\n const $from = editorState.doc.resolve(range.from)\n return $from.parent.type.name === 'paragraph' && !$from.parent.type.spec.code\n },\n items: ({ query }) => filterCommands(query),\n command: ({ editor, range, props }: { editor: Editor, range: Range, props: BlockCommand }) => props.run(editor, range),\n render: () => {\n let select: ((item: BlockCommand) => void) | null = null\n return {\n onStart: (props) => {\n select = item => props.command(item)\n Object.assign(state, {\n open: true,\n query: props.query,\n items: props.items,\n index: 0,\n rect: props.clientRect?.() ?? null,\n select,\n })\n },\n onUpdate: (props) => {\n select = item => props.command(item)\n // A new query puts the best match back under the cursor.\n const index = props.query !== state.query ? 0 : Math.min(state.index, Math.max(0, props.items.length - 1))\n Object.assign(state, {\n query: props.query,\n items: props.items,\n index,\n rect: props.clientRect?.() ?? null,\n select,\n })\n },\n onKeyDown: ({ event }) => {\n if (!state.open) return false\n const count = state.items.length\n if (event.key === 'ArrowDown') {\n if (count) state.index = (state.index + 1) % count\n return true\n }\n if (event.key === 'ArrowUp') {\n if (count) state.index = (state.index - 1 + count) % count\n return true\n }\n if (event.key === 'Enter' || event.key === 'Tab') {\n const item = state.items[state.index]\n if (!item) return false\n select?.(item)\n return true\n }\n if (event.key === 'Escape') {\n state.open = false\n return true\n }\n return false\n },\n onExit: () => {\n Object.assign(state, { open: false, items: [], query: '', rect: null, select: null })\n },\n }\n },\n }),\n ]\n },\n })\n}\n","<script setup lang=\"ts\">\nimport type { Editor, JSONContent } from '@tiptap/core'\nimport type { FieldProps } from '../components/field/useField'\nimport type { BlockCommand } from './commands'\nimport type { SlashState } from './slash'\nimport { Extension } from '@tiptap/core'\nimport { Highlight } from '@tiptap/extension-highlight'\nimport { TaskItem, TaskList } from '@tiptap/extension-list'\nimport { Typography } from '@tiptap/extension-typography'\nimport { CharacterCount, Placeholder } from '@tiptap/extensions'\nimport { StarterKit } from '@tiptap/starter-kit'\nimport { EditorContent, useEditor } from '@tiptap/vue-3'\nimport { BubbleMenu } from '@tiptap/vue-3/menus'\nimport { computed, nextTick, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue'\nimport JDropdownMenu from '../components/dropdown-menu/JDropdownMenu.vue'\nimport JDropdownMenuItem from '../components/dropdown-menu/JDropdownMenuItem.vue'\nimport JFieldShell from '../components/field/JFieldShell.vue'\nimport { useField } from '../components/field/useField'\nimport JPortal from '../components/primitives/JPortal.vue'\nimport { useFloating } from '../composables/useFloating'\nimport { useLayer } from '../composables/useLayer'\nimport { blockCommands } from './commands'\nimport {\n IconBold,\n IconChevronDown,\n IconCode,\n IconCornerDownLeft,\n IconExternal,\n IconHighlight,\n IconItalic,\n IconLink,\n IconRedo,\n IconStrike,\n IconUnderline,\n IconUndo,\n IconUnlink,\n} from './icons'\nimport JEditorButton from './JEditorButton.vue'\nimport { createSlashCommands } from './slash'\n\nexport type EditorFormat = 'html' | 'json'\n\nexport interface EditorProps extends FieldProps {\n placeholder?: string\n /** Shape of `v-model`: an HTML string, or Tiptap/ProseMirror JSON. */\n format?: EditorFormat\n /** `full` shows every tool, `minimal` just the inline marks and lists, `false` hides the toolbar. */\n toolbar?: 'full' | 'minimal' | false\n /** Formatting menu that floats above selected text. */\n bubbleMenu?: boolean\n /** Type `/` for a menu of blocks. */\n slashCommands?: boolean\n /** Maximum number of characters. Shown in the footer and enforced while typing. */\n limit?: number\n /** Show word and character counts. */\n showCount?: boolean\n /** Height of the writing area before it grows. Any CSS length. */\n minHeight?: string\n /** Height after which the writing area scrolls. Any CSS length. */\n maxHeight?: string\n readonly?: boolean\n autofocus?: boolean | 'start' | 'end'\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<EditorProps>(), {\n placeholder: 'Write something, or type / for blocks…',\n format: 'html',\n toolbar: 'full',\n bubbleMenu: true,\n slashCommands: true,\n showCount: false,\n minHeight: '10rem',\n error: undefined,\n})\n\nconst model = defineModel<string | JSONContent | null>({ default: '' })\n\nconst emit = defineEmits<{\n /** The editor instance is ready, for advanced use. */\n ready: [editor: Editor]\n focus: []\n blur: []\n}>()\n\ndefineSlots<{\n /** Extra controls at the end of the toolbar. */\n 'toolbar-end'?: (props: { editor: Editor }) => unknown\n 'label'?: () => unknown\n}>()\n\nconst field = useField(props)\nconst root = ref<HTMLElement | null>(null)\nconst slashId = `${field.id.value}-blocks`\n\n// --- Slash menu state -----------------------------------------------------\nconst slash = reactive<SlashState>({ open: false, query: '', items: [], index: 0, rect: null, select: null })\n\n// --- Editor ----------------------------------------------------------------\nlet lastEmitted: string | null = null\nconst serialize = (value: unknown) => (typeof value === 'string' ? value : JSON.stringify(value ?? ''))\n\nfunction contentFromModel() {\n const value = model.value\n if (!value) return ''\n return value\n}\n\nconst LinkShortcut = Extension.create({\n name: 'juxtLinkShortcut',\n addKeyboardShortcuts() {\n return {\n 'Mod-k': () => {\n openLink()\n return true\n },\n }\n },\n})\n\nconst editor = useEditor({\n content: contentFromModel(),\n editable: !props.disabled && !props.readonly,\n autofocus: props.autofocus ?? false,\n extensions: [\n StarterKit.configure({\n heading: { levels: [1, 2, 3] },\n link: {\n openOnClick: false,\n autolink: true,\n defaultProtocol: 'https',\n HTMLAttributes: { rel: 'noopener noreferrer nofollow', target: null },\n },\n codeBlock: { HTMLAttributes: { spellcheck: 'false' } },\n dropcursor: { color: 'var(--juxt-accent)', width: 2 },\n }),\n TaskList,\n TaskItem.configure({ nested: true }),\n Highlight,\n Typography,\n Placeholder.configure({\n // Empty document: the placeholder. Empty heading: its level. Empty line\n // under the caret: a quiet hint that blocks are a slash away.\n placeholder: ({ editor: instance, node }) => {\n if (node.type.name === 'heading') return `Heading ${node.attrs.level}`\n if (instance.isEmpty) return props.placeholder\n return props.slashCommands ? 'Type / for blocks' : ''\n },\n }),\n CharacterCount.configure({ limit: props.limit ?? null }),\n LinkShortcut,\n ...(props.slashCommands ? [createSlashCommands(slash)] : []),\n ],\n editorProps: {\n attributes: editorAttributes(),\n },\n onCreate: ({ editor: instance }) => emit('ready', instance),\n onFocus: () => emit('focus'),\n onBlur: () => emit('blur'),\n onUpdate: ({ editor: instance }) => {\n const value = props.format === 'json' ? instance.getJSON() : instance.isEmpty ? '' : instance.getHTML()\n lastEmitted = serialize(value)\n model.value = value\n },\n})\n\nfunction editorAttributes(): Record<string, string> {\n const attrs: Record<string, string> = {\n 'role': 'textbox',\n 'aria-multiline': 'true',\n 'class': 'j-editor__prose',\n }\n if (props.label || props.id) attrs['aria-labelledby'] = field.labelId.value\n if (field.describedBy.value) attrs['aria-describedby'] = field.describedBy.value\n if (field.invalid.value) attrs['aria-invalid'] = 'true'\n if (props.required) attrs['aria-required'] = 'true'\n if (props.disabled || props.readonly) attrs['aria-readonly'] = 'true'\n if (slash.open) {\n attrs['aria-controls'] = slashId\n attrs['aria-expanded'] = 'true'\n const active = slash.items[slash.index]\n if (active) attrs['aria-activedescendant'] = `${slashId}-${active.id}`\n }\n return attrs\n}\n\n// Keep ARIA on the editable element in sync with the field and the slash menu.\nwatch(\n [() => field.describedBy.value, () => field.invalid.value, () => props.disabled, () => props.readonly, () => slash.open, () => slash.index, () => slash.items],\n () => editor.value?.setOptions({ editorProps: { attributes: editorAttributes() } }),\n)\n\n// Outside changes to v-model replace the content; our own echoes don't.\nwatch(model, (value) => {\n const instance = editor.value\n if (!instance || serialize(value) === lastEmitted) return\n instance.commands.setContent(value || '', { emitUpdate: false })\n lastEmitted = serialize(value)\n})\n\nwatch(() => [props.disabled, props.readonly], () => editor.value?.setEditable(!props.disabled && !props.readonly))\n\n// --- Toolbar state -----------------------------------------------------------\nconst currentBlock = computed<BlockCommand>(() => {\n const instance = editor.value\n const fallback = blockCommands[0]!\n if (!instance) return fallback\n return blockCommands.find(command => command.convertible && command.id !== 'text' && command.isActive?.(instance)) ?? fallback\n})\n\nconst can = computed(() => ({\n undo: !!editor.value?.can().undo(),\n redo: !!editor.value?.can().redo(),\n}))\n\nconst editable = computed(() => !!editor.value && !props.disabled && !props.readonly)\n\nfunction run(fn: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>) {\n const instance = editor.value\n if (!instance) return\n fn(instance.chain().focus()).run()\n}\n\n// Arrow keys move along the toolbar; Tab moves past it (roving tabindex).\nfunction onToolbarKeydown(event: KeyboardEvent) {\n if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft' && event.key !== 'Home' && event.key !== 'End') return\n const toolbar = event.currentTarget as HTMLElement\n const items = Array.from(toolbar.querySelectorAll<HTMLElement>('[data-editor-control]:not(:disabled)'))\n const index = items.indexOf(document.activeElement as HTMLElement)\n if (index === -1) return\n event.preventDefault()\n let next = index\n if (event.key === 'ArrowRight') next = (index + 1) % items.length\n else if (event.key === 'ArrowLeft') next = (index - 1 + items.length) % items.length\n else if (event.key === 'Home') next = 0\n else next = items.length - 1\n items.forEach((item, i) => (item.tabIndex = i === next ? 0 : -1))\n items[next]!.focus()\n}\n\nfunction initRovingTabindex(el: unknown) {\n const toolbar = el as HTMLElement | null\n if (!toolbar) return\n nextTick(() => {\n const items = Array.from(toolbar.querySelectorAll<HTMLElement>('[data-editor-control]'))\n if (items.some(item => item.tabIndex === 0)) return\n items.forEach((item, i) => (item.tabIndex = i === 0 ? 0 : -1))\n })\n}\n\n// --- Floating panels positioned at the text -------------------------------\nfunction virtualAt(getRect: () => DOMRect | null) {\n return {\n getBoundingClientRect: () => getRect() ?? new DOMRect(),\n get contextElement() {\n return editor.value?.view.dom\n },\n } as unknown as HTMLElement\n}\n\n// Slash menu\nconst slashEl = ref<HTMLElement | null>(null)\nconst slashRef = shallowRef<HTMLElement | null>(virtualAt(() => slash.rect))\nconst { styles: slashStyles, update: updateSlash } = useFloating(slashRef, slashEl, { placement: 'bottom-start', offset: 6 })\nwatch(() => slash.rect, () => nextTick(updateSlash))\nwatch(() => slash.index, () => nextTick(() => document.getElementById(`${slashId}-${slash.items[slash.index]?.id}`)?.scrollIntoView({ block: 'nearest' })))\n\n// Link editor\nconst link = reactive({ open: false, url: '', rect: null as DOMRect | null, active: false })\nconst linkEl = ref<HTMLElement | null>(null)\nconst linkInput = ref<HTMLInputElement | null>(null)\nconst linkRef = shallowRef<HTMLElement | null>(virtualAt(() => link.rect))\nconst { styles: linkStyles, update: updateLink } = useFloating(linkRef, linkEl, { placement: 'bottom-start', offset: 8 })\n\nfunction selectionRect(instance: Editor): DOMRect {\n const { from, to } = instance.state.selection\n const start = instance.view.coordsAtPos(from)\n const end = instance.view.coordsAtPos(to)\n const left = Math.min(start.left, end.left)\n const right = Math.max(start.right, end.right)\n return new DOMRect(left, start.top, Math.max(1, right - left), end.bottom - start.top)\n}\n\nfunction openLink() {\n const instance = editor.value\n if (!instance || !editable.value) return\n if (instance.isActive('link')) instance.chain().extendMarkRange('link').run()\n link.active = instance.isActive('link')\n link.url = (instance.getAttributes('link').href as string | undefined) ?? ''\n link.rect = selectionRect(instance)\n link.open = true\n nextTick(() => {\n updateLink()\n linkInput.value?.focus()\n linkInput.value?.select()\n })\n}\n\nfunction closeLink(refocus = true) {\n link.open = false\n if (refocus) editor.value?.commands.focus()\n}\n\nfunction normalizeUrl(raw: string): string | null {\n const value = raw.trim()\n if (!value) return null\n if (/^(?:javascript|data|vbscript):/i.test(value)) return null\n if (/^(?:https?:|mailto:|tel:|\\/|#)/i.test(value)) return value\n if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value)) return `mailto:${value}`\n return `https://${value}`\n}\n\nfunction applyLink() {\n const instance = editor.value\n if (!instance) return\n const href = normalizeUrl(link.url)\n if (!href) {\n removeLink()\n return\n }\n if (instance.state.selection.empty && !instance.isActive('link')) {\n instance.chain().focus().insertContent({ type: 'text', text: link.url.trim(), marks: [{ type: 'link', attrs: { href } }] }).run()\n } else {\n instance.chain().focus().extendMarkRange('link').setLink({ href }).run()\n }\n closeLink(false)\n}\n\nfunction removeLink() {\n editor.value?.chain().focus().extendMarkRange('link').unsetLink().run()\n closeLink(false)\n}\n\nuseLayer({\n active: computed(() => link.open),\n elements: () => [linkEl.value],\n onEscape: () => closeLink(),\n onPointerDownOutside: () => closeLink(false),\n})\n\n// --- Bubble menu -------------------------------------------------------------\nfunction shouldShowBubble({ editor: instance, from, to }: { editor: Editor, from: number, to: number }) {\n if (!props.bubbleMenu || !instance.isEditable || link.open || slash.open) return false\n if (from === to || instance.isActive('codeBlock')) return false\n return instance.state.doc.textBetween(from, to).trim().length > 0\n}\n\n// --- Counts ------------------------------------------------------------------\nconst counts = computed(() => {\n const storage = editor.value?.storage.characterCount as { characters: () => number, words: () => number } | undefined\n return { characters: storage?.characters() ?? 0, words: storage?.words() ?? 0 }\n})\nconst nearLimit = computed(() => !!props.limit && counts.value.characters >= props.limit * 0.9)\nconst atLimit = computed(() => !!props.limit && counts.value.characters >= props.limit)\n\nfunction focusEditor(event: MouseEvent) {\n // Clicking the empty space under the text puts the caret at the end.\n if (event.target === event.currentTarget && editable.value) editor.value?.commands.focus('end')\n}\n\nonBeforeUnmount(() => editor.value?.destroy())\n\ndefineExpose({\n /** The underlying Tiptap editor. */\n editor,\n focus: (position?: 'start' | 'end') => editor.value?.commands.focus(position ?? null),\n clear: () => editor.value?.commands.clearContent(true),\n})\n</script>\n\n<template>\n <JFieldShell\n :id=\"field.id.value\"\n :class=\"$attrs.class\"\n :style=\"$attrs.style\"\n :label-id=\"field.labelId.value\"\n :description-id=\"field.descriptionId.value\"\n :error-id=\"field.errorId.value\"\n :label=\"label\"\n :description=\"description\"\n :error-message=\"field.errorMessage.value\"\n :show-description=\"field.showDescription.value\"\n :required=\"required\"\n :disabled=\"disabled\"\n :label-for=\"null\"\n >\n <template v-if=\"$slots.label\" #label>\n <slot name=\"label\" />\n </template>\n\n <div\n ref=\"root\"\n class=\"j-editor\"\n :class=\"{ 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }\"\n :style=\"{ '--j-editor-min': minHeight, '--j-editor-max': maxHeight ?? 'none' }\"\n >\n <!-- Toolbar -->\n <div\n v-if=\"toolbar\"\n :ref=\"initRovingTabindex\"\n role=\"toolbar\"\n aria-label=\"Formatting\"\n class=\"j-editor__toolbar\"\n @keydown=\"onToolbarKeydown\"\n >\n <template v-if=\"toolbar === 'full'\">\n <JDropdownMenu :disabled=\"!editable\">\n <template #trigger>\n <button\n type=\"button\"\n class=\"j-editor__block j-focusable\"\n :disabled=\"!editable\"\n aria-label=\"Block type\"\n data-editor-control\n @pointerdown.prevent\n >\n <component :is=\"currentBlock.icon\" />\n <span class=\"j-editor__block-label\">{{ currentBlock.label }}</span>\n <IconChevronDown class=\"j-editor__block-chevron\" />\n </button>\n </template>\n <JDropdownMenuItem\n v-for=\"command in blockCommands.filter(c => c.convertible)\"\n :key=\"command.id\"\n :checked=\"command.id === currentBlock.id\"\n @select=\"editor && command.run(editor)\"\n >\n <template #leading>\n <component :is=\"command.icon\" />\n </template>\n {{ command.label }}\n </JDropdownMenuItem>\n </JDropdownMenu>\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\n </template>\n\n <JEditorButton label=\"Bold\" shortcut=\"mod+b\" :active=\"!!editor?.isActive('bold')\" :disabled=\"!editable\" @click=\"run(c => c.toggleBold())\">\n <IconBold />\n </JEditorButton>\n <JEditorButton label=\"Italic\" shortcut=\"mod+i\" :active=\"!!editor?.isActive('italic')\" :disabled=\"!editable\" @click=\"run(c => c.toggleItalic())\">\n <IconItalic />\n </JEditorButton>\n <JEditorButton label=\"Underline\" shortcut=\"mod+u\" :active=\"!!editor?.isActive('underline')\" :disabled=\"!editable\" @click=\"run(c => c.toggleUnderline())\">\n <IconUnderline />\n </JEditorButton>\n <JEditorButton v-if=\"toolbar === 'full'\" label=\"Strikethrough\" shortcut=\"mod+shift+s\" :active=\"!!editor?.isActive('strike')\" :disabled=\"!editable\" @click=\"run(c => c.toggleStrike())\">\n <IconStrike />\n </JEditorButton>\n <JEditorButton v-if=\"toolbar === 'full'\" label=\"Inline code\" shortcut=\"mod+e\" :active=\"!!editor?.isActive('code')\" :disabled=\"!editable\" @click=\"run(c => c.toggleCode())\">\n <IconCode />\n </JEditorButton>\n <JEditorButton label=\"Link\" shortcut=\"mod+k\" :active=\"!!editor?.isActive('link')\" :disabled=\"!editable\" @click=\"openLink\">\n <IconLink />\n </JEditorButton>\n\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\n\n <JEditorButton\n v-for=\"command in blockCommands.filter(c => (toolbar === 'full' ? ['bullet', 'ordered', 'task', 'quote', 'code', 'divider'] : ['bullet', 'ordered']).includes(c.id))\"\n :key=\"command.id\"\n :label=\"command.label\"\n :active=\"command.isActive ? !!(editor && command.isActive(editor)) : undefined\"\n :disabled=\"!editable\"\n @click=\"editor && command.run(editor)\"\n >\n <component :is=\"command.icon\" />\n </JEditorButton>\n\n <span class=\"j-editor__spacer\" />\n\n <slot v-if=\"editor\" name=\"toolbar-end\" :editor=\"editor\" />\n <JEditorButton label=\"Undo\" shortcut=\"mod+z\" :disabled=\"!editable || !can.undo\" @click=\"run(c => c.undo())\">\n <IconUndo />\n </JEditorButton>\n <JEditorButton label=\"Redo\" shortcut=\"mod+shift+z\" :disabled=\"!editable || !can.redo\" @click=\"run(c => c.redo())\">\n <IconRedo />\n </JEditorButton>\n </div>\n\n <!-- Writing area -->\n <div class=\"j-editor__content\" @click=\"focusEditor\">\n <EditorContent v-if=\"editor\" :editor=\"editor\" />\n <p v-else class=\"j-editor__prose j-editor__ssr\" aria-hidden=\"true\">\n {{ placeholder }}\n </p>\n </div>\n\n <!-- Footer -->\n <div v-if=\"showCount || limit\" class=\"j-editor__footer\" aria-live=\"polite\">\n <span>{{ counts.words }} {{ counts.words === 1 ? 'word' : 'words' }}</span>\n <span :class=\"{ 'is-near': nearLimit, 'is-at': atLimit }\">\n {{ counts.characters }}<template v-if=\"limit\"> / {{ limit }}</template> characters\n </span>\n </div>\n </div>\n\n <!-- Selection toolbar -->\n <BubbleMenu\n v-if=\"editor && bubbleMenu\"\n :editor=\"editor\"\n :should-show=\"shouldShowBubble\"\n :options=\"{ placement: 'top', offset: 8 }\"\n class=\"j-editor__bubble\"\n >\n <JEditorButton label=\"Bold\" shortcut=\"mod+b\" side=\"bottom\" :active=\"editor.isActive('bold')\" @click=\"run(c => c.toggleBold())\">\n <IconBold />\n </JEditorButton>\n <JEditorButton label=\"Italic\" shortcut=\"mod+i\" side=\"bottom\" :active=\"editor.isActive('italic')\" @click=\"run(c => c.toggleItalic())\">\n <IconItalic />\n </JEditorButton>\n <JEditorButton label=\"Underline\" shortcut=\"mod+u\" side=\"bottom\" :active=\"editor.isActive('underline')\" @click=\"run(c => c.toggleUnderline())\">\n <IconUnderline />\n </JEditorButton>\n <JEditorButton label=\"Strikethrough\" shortcut=\"mod+shift+s\" side=\"bottom\" :active=\"editor.isActive('strike')\" @click=\"run(c => c.toggleStrike())\">\n <IconStrike />\n </JEditorButton>\n <JEditorButton label=\"Inline code\" shortcut=\"mod+e\" side=\"bottom\" :active=\"editor.isActive('code')\" @click=\"run(c => c.toggleCode())\">\n <IconCode />\n </JEditorButton>\n <JEditorButton label=\"Highlight\" shortcut=\"mod+shift+h\" side=\"bottom\" :active=\"editor.isActive('highlight')\" @click=\"run(c => c.toggleHighlight())\">\n <IconHighlight />\n </JEditorButton>\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\n <JEditorButton label=\"Link\" shortcut=\"mod+k\" side=\"bottom\" :active=\"editor.isActive('link')\" @click=\"openLink\">\n <IconLink />\n </JEditorButton>\n </BubbleMenu>\n\n <JPortal>\n <!-- Slash menu -->\n <Transition name=\"j-pop\">\n <div\n v-if=\"slash.open\"\n :id=\"slashId\"\n ref=\"slashEl\"\n role=\"listbox\"\n aria-label=\"Blocks\"\n class=\"j-editor__slash j-popover-surface\"\n :style=\"slashStyles\"\n @pointerdown.prevent\n >\n <template v-if=\"slash.items.length\">\n <div class=\"j-editor__slash-label\" role=\"presentation\">\n Blocks\n </div>\n <div\n v-for=\"(item, i) in slash.items\"\n :id=\"`${slashId}-${item.id}`\"\n :key=\"item.id\"\n role=\"option\"\n class=\"j-editor__slash-item\"\n :class=\"{ 'is-active': i === slash.index }\"\n :aria-selected=\"i === slash.index\"\n @pointermove=\"slash.index = i\"\n @click=\"slash.select?.(item)\"\n >\n <span class=\"j-editor__slash-icon\"><component :is=\"item.icon\" /></span>\n <span class=\"j-editor__slash-text\">\n <span class=\"j-editor__slash-title\">{{ item.label }}</span>\n <span class=\"j-editor__slash-description\">{{ item.description }}</span>\n </span>\n <kbd v-if=\"item.markdown\" class=\"j-editor__slash-md\">{{ item.markdown }}</kbd>\n </div>\n </template>\n <div v-else class=\"j-editor__slash-empty\">\n No blocks match “{{ slash.query }}”\n </div>\n </div>\n </Transition>\n\n <!-- Link editor -->\n <Transition name=\"j-pop\">\n <form\n v-if=\"link.open\"\n ref=\"linkEl\"\n class=\"j-editor__link j-popover-surface\"\n :style=\"linkStyles\"\n @submit.prevent=\"applyLink\"\n >\n <IconLink class=\"j-editor__link-icon\" />\n <input\n ref=\"linkInput\"\n v-model=\"link.url\"\n class=\"j-editor__link-input\"\n type=\"text\"\n inputmode=\"url\"\n placeholder=\"Paste or type a link\"\n aria-label=\"Link address\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <button type=\"submit\" class=\"j-editor-button j-focusable\" aria-label=\"Apply link\">\n <IconCornerDownLeft />\n </button>\n <a\n v-if=\"link.active && normalizeUrl(link.url)\"\n :href=\"normalizeUrl(link.url)!\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"j-editor-button j-focusable\"\n aria-label=\"Open link in a new tab\"\n >\n <IconExternal />\n </a>\n <button v-if=\"link.active\" type=\"button\" class=\"j-editor-button j-focusable\" aria-label=\"Remove link\" @click=\"removeLink\">\n <IconUnlink />\n </button>\n </form>\n </Transition>\n </JPortal>\n </JFieldShell>\n</template>\n\n<style>\n@layer theme, base, juxt, components, utilities;\n@layer juxt {\n/* Frame: the same border, focus halo and invalid states as Input. */\n.j-editor {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--juxt-border);\n border-radius: var(--juxt-radius-md);\n background: var(--juxt-surface);\n box-shadow: var(--juxt-shadow-xs);\n color: var(--juxt-fg);\n font-family: var(--juxt-font-sans);\n transition:\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\n box-shadow var(--juxt-duration-normal) var(--juxt-ease-out);\n}\n\n.dark .j-editor,\n[data-theme='dark'] .j-editor {\n background: var(--juxt-surface-sunken);\n}\n\n.j-editor:hover:not(.is-disabled, :focus-within) {\n border-color: var(--juxt-border-strong);\n}\n\n.j-editor:has(.ProseMirror-focused) {\n border-color: var(--juxt-accent);\n box-shadow: 0 0 0 3px var(--juxt-ring-soft);\n}\n\n.j-editor.is-invalid {\n border-color: var(--juxt-danger-border);\n}\n\n.j-editor.is-invalid:has(.ProseMirror-focused) {\n border-color: var(--juxt-danger);\n box-shadow: 0 0 0 3px var(--juxt-danger-ring);\n}\n\n.j-editor.is-disabled {\n background: var(--juxt-surface-sunken);\n box-shadow: none;\n opacity: 0.7;\n}\n\n/* Toolbar: stays reachable while you scroll a long document. */\n.j-editor__toolbar {\n position: sticky;\n top: var(--j-editor-sticky-top, 0);\n z-index: 1;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: var(--juxt-space-1);\n overflow-x: auto;\n border-bottom: 1px solid var(--juxt-border-subtle);\n border-radius: var(--juxt-radius-md) var(--juxt-radius-md) 0 0;\n background: inherit;\n scrollbar-width: none;\n}\n\n.j-editor__toolbar::-webkit-scrollbar {\n display: none;\n}\n\n.dark .j-editor__toolbar,\n[data-theme='dark'] .j-editor__toolbar {\n border-bottom-color: var(--juxt-border);\n}\n\n.j-editor__divider {\n flex: none;\n width: 1px;\n height: 1rem;\n margin: 0 var(--juxt-space-1);\n background: var(--juxt-border);\n}\n\n.j-editor__spacer {\n flex: 1;\n min-width: var(--juxt-space-2);\n}\n\n.j-editor__block {\n display: inline-flex;\n flex: none;\n align-items: center;\n gap: var(--juxt-space-1-5);\n height: var(--juxt-control-sm);\n padding: 0 var(--juxt-space-2);\n border: 0;\n border-radius: var(--juxt-radius-sm);\n background: transparent;\n color: var(--juxt-fg);\n font: inherit;\n font-size: var(--juxt-text-sm);\n font-weight: var(--juxt-weight-medium);\n cursor: pointer;\n transition-property: background-color, outline-color, outline-offset;\n}\n\n.j-editor__block:hover:not(:disabled) {\n background: var(--juxt-surface-hover);\n}\n\n.j-editor__block:disabled {\n color: var(--juxt-fg-disabled);\n cursor: not-allowed;\n}\n\n.j-editor__block > svg {\n width: 1rem;\n height: 1rem;\n color: var(--juxt-fg-muted);\n}\n\n.j-editor__block-label {\n min-width: 5.5rem;\n text-align: left;\n}\n\n.j-editor__block .j-editor__block-chevron {\n width: 0.75rem;\n height: 0.75rem;\n}\n\n/* Writing area */\n.j-editor__content {\n flex: 1;\n min-height: var(--j-editor-min);\n max-height: var(--j-editor-max);\n padding: var(--juxt-space-3) var(--juxt-space-4);\n overflow-y: auto;\n cursor: text;\n}\n\n.j-editor.is-disabled .j-editor__content,\n.j-editor.is-readonly .j-editor__content {\n cursor: default;\n}\n\n.j-editor__ssr {\n margin: 0;\n color: var(--juxt-fg-muted);\n}\n\n.j-editor__footer {\n display: flex;\n justify-content: space-between;\n gap: var(--juxt-space-3);\n padding: var(--juxt-space-1-5) var(--juxt-space-4);\n border-top: 1px solid var(--juxt-border-subtle);\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-xs);\n font-variant-numeric: tabular-nums;\n}\n\n.dark .j-editor__footer,\n[data-theme='dark'] .j-editor__footer {\n border-top-color: var(--juxt-border);\n}\n\n.j-editor__footer .is-near {\n color: var(--juxt-warning-text);\n}\n\n.j-editor__footer .is-at {\n color: var(--juxt-danger-text);\n}\n\n/* ---------------------------------------------------------------------------\n * Document typography. Deliberately close to the docs prose: calm, readable,\n * a clear hierarchy without shouting.\n * ------------------------------------------------------------------------ */\n.j-editor__prose {\n min-height: calc(var(--j-editor-min) - var(--juxt-space-6));\n color: var(--juxt-fg);\n font-size: 0.9375rem;\n line-height: 1.7;\n letter-spacing: var(--juxt-tracking-tight);\n white-space: pre-wrap;\n word-wrap: break-word;\n outline: none;\n font-variant-ligatures: none;\n font-feature-settings: 'liga' 0;\n}\n\n.j-editor__prose > * + * {\n margin-top: 0.75em;\n}\n\n.j-editor__prose p {\n margin: 0;\n}\n\n.j-editor__prose :is(h1, h2, h3) {\n margin: 1.4em 0 0;\n color: var(--juxt-fg);\n font-weight: var(--juxt-weight-semibold);\n line-height: 1.3;\n letter-spacing: var(--juxt-tracking-tighter);\n}\n\n.j-editor__prose > :is(h1, h2, h3):first-child {\n margin-top: 0;\n}\n\n.j-editor__prose h1 {\n font-size: 1.625rem;\n}\n\n.j-editor__prose h2 {\n font-size: 1.3125rem;\n}\n\n.j-editor__prose h3 {\n font-size: 1.0625rem;\n}\n\n.j-editor__prose :is(ul, ol) {\n margin-bottom: 0;\n padding-left: 1.375em;\n}\n\n.j-editor__prose li + li {\n margin-top: 0.25em;\n}\n\n.j-editor__prose li > p {\n margin: 0;\n}\n\n.j-editor__prose li::marker {\n color: var(--juxt-fg-muted);\n}\n\n.j-editor__prose ul ul {\n list-style-type: circle;\n}\n\n/* To-do items look and feel like JCheckbox. */\n.j-editor__prose ul[data-type='taskList'] {\n padding-left: 0.125em;\n list-style: none;\n}\n\n.j-editor__prose ul[data-type='taskList'] li {\n display: flex;\n align-items: flex-start;\n gap: 0.625em;\n}\n\n.j-editor__prose ul[data-type='taskList'] li > label {\n display: inline-flex;\n flex: none;\n margin-top: 0.2em;\n user-select: none;\n}\n\n.j-editor__prose ul[data-type='taskList'] li > div {\n flex: 1;\n min-width: 0;\n}\n\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox'] {\n display: grid;\n place-content: center;\n width: 1rem;\n height: 1rem;\n margin: 0;\n border: 1px solid var(--juxt-border-strong);\n border-radius: var(--juxt-radius-xs);\n background: var(--juxt-surface);\n cursor: pointer;\n appearance: none;\n transition:\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard);\n}\n\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']::after {\n content: '';\n width: 0.5rem;\n height: 0.28rem;\n margin-top: -0.1rem;\n border-bottom: 2px solid var(--juxt-accent-fg);\n border-left: 2px solid var(--juxt-accent-fg);\n opacity: 0;\n transform: rotate(-45deg) scale(0.6);\n transition:\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\n}\n\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:checked {\n border-color: var(--juxt-accent);\n background: var(--juxt-accent);\n}\n\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:checked::after {\n opacity: 1;\n transform: rotate(-45deg);\n}\n\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:focus-visible {\n outline: 2px solid var(--juxt-ring);\n outline-offset: 2px;\n}\n\n.j-editor__prose li[data-checked='true'] > div {\n color: var(--juxt-fg-muted);\n text-decoration: line-through;\n text-decoration-color: var(--juxt-border-strong);\n}\n\n.j-editor__prose blockquote {\n margin-left: 0;\n margin-right: 0;\n padding-left: 1em;\n border-left: 2px solid var(--juxt-border-strong);\n color: var(--juxt-fg-secondary);\n}\n\n.j-editor__prose a {\n color: var(--juxt-fg);\n text-decoration: underline;\n text-decoration-color: var(--juxt-border-strong);\n text-underline-offset: 3px;\n cursor: text;\n transition: text-decoration-color var(--juxt-duration-fast) var(--juxt-ease-standard);\n}\n\n.j-editor__prose a:hover {\n text-decoration-color: var(--juxt-accent);\n}\n\n.j-editor__prose code {\n padding: 0.1em 0.3em;\n border: 1px solid var(--juxt-border-subtle);\n border-radius: var(--juxt-radius-xs);\n background: var(--juxt-surface-sunken);\n font-family: var(--juxt-font-mono);\n font-size: 0.85em;\n}\n\n.dark .j-editor__prose code,\n[data-theme='dark'] .j-editor__prose code {\n border-color: var(--juxt-border);\n background: var(--juxt-surface-raised);\n}\n\n.j-editor__prose pre {\n padding: 0.875em 1em;\n overflow-x: auto;\n border: 1px solid var(--juxt-border-subtle);\n border-radius: var(--juxt-radius-md);\n background: var(--juxt-surface-sunken);\n font-family: var(--juxt-font-mono);\n font-size: 0.8125rem;\n line-height: 1.65;\n white-space: pre;\n}\n\n.dark .j-editor__prose pre,\n[data-theme='dark'] .j-editor__prose pre {\n border-color: var(--juxt-border);\n background: var(--juxt-bg);\n}\n\n.j-editor__prose pre code {\n padding: 0;\n border: 0;\n background: none;\n font-size: inherit;\n}\n\n.j-editor__prose hr {\n height: 1px;\n margin: 1.5em 0;\n border: 0;\n background: var(--juxt-border);\n}\n\n.j-editor__prose hr.ProseMirror-selectednode {\n outline: 2px solid var(--juxt-ring);\n outline-offset: 4px;\n}\n\n.j-editor__prose mark {\n padding: 0.05em 0.1em;\n border-radius: 2px;\n background: var(--juxt-warning-soft);\n color: inherit;\n}\n\n.j-editor__prose ::selection {\n background: var(--juxt-accent-soft);\n}\n\n/* Placeholder: on an empty document, and on each empty heading. */\n.j-editor__prose .is-empty::before {\n content: attr(data-placeholder);\n float: left;\n height: 0;\n color: var(--juxt-fg-muted);\n pointer-events: none;\n}\n\n/* The per-line hint is quieter than the document placeholder. */\n.j-editor__prose .is-empty:not(.is-editor-empty):not(h1, h2, h3)::before {\n color: var(--juxt-fg-disabled);\n}\n\n.j-editor__prose .ProseMirror-gapcursor {\n display: none;\n position: relative;\n pointer-events: none;\n}\n\n.j-editor__prose.ProseMirror-focused .ProseMirror-gapcursor {\n display: block;\n}\n\n.j-editor__prose .ProseMirror-gapcursor::after {\n content: '';\n position: absolute;\n top: -2px;\n display: block;\n width: 20px;\n border-top: 1px solid var(--juxt-fg);\n animation: j-editor-cursor 1.1s steps(2, start) infinite;\n}\n\n@keyframes j-editor-cursor {\n to {\n visibility: hidden;\n }\n}\n\n/* Selection toolbar */\n.j-editor__bubble {\n z-index: var(--juxt-z-popover);\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 3px;\n border: 1px solid var(--juxt-border);\n border-radius: var(--juxt-radius-md);\n background: var(--juxt-surface-raised);\n box-shadow: var(--juxt-shadow-md);\n animation: j-editor-bubble-in var(--juxt-duration-fast) var(--juxt-ease-out);\n}\n\n@keyframes j-editor-bubble-in {\n from {\n opacity: 0;\n transform: translateY(calc(var(--juxt-motion-shift) * 0.5)) scale(var(--juxt-motion-scale));\n }\n}\n\n/* Slash menu */\n.j-editor__slash {\n z-index: var(--juxt-z-popover);\n width: 17rem;\n max-height: min(20rem, var(--j-available-height, 20rem));\n padding: var(--juxt-space-1);\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.j-editor__slash-label {\n padding: var(--juxt-space-1-5) var(--juxt-space-2) var(--juxt-space-1);\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-xs);\n font-weight: var(--juxt-weight-medium);\n}\n\n.j-editor__slash-item {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--juxt-space-2-5);\n padding: var(--juxt-space-1-5) var(--juxt-space-2);\n border-radius: var(--juxt-radius-sm);\n cursor: pointer;\n user-select: none;\n}\n\n.j-editor__slash-item.is-active {\n background: var(--juxt-surface-hover);\n}\n\n.j-editor__slash-item.is-active::before {\n content: '';\n position: absolute;\n top: 0.625rem;\n bottom: 0.625rem;\n left: 0;\n width: 2px;\n border-radius: 0 2px 2px 0;\n background: var(--juxt-accent);\n}\n\n.j-editor__slash-icon {\n display: grid;\n flex: none;\n place-items: center;\n width: 2rem;\n height: 2rem;\n border: 1px solid var(--juxt-border);\n border-radius: var(--juxt-radius-sm);\n background: var(--juxt-surface);\n color: var(--juxt-fg-secondary);\n}\n\n.j-editor__slash-icon > svg {\n width: 1rem;\n height: 1rem;\n}\n\n.j-editor__slash-text {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-width: 0;\n}\n\n.j-editor__slash-title {\n color: var(--juxt-fg);\n font-size: var(--juxt-text-sm);\n font-weight: var(--juxt-weight-medium);\n}\n\n.j-editor__slash-description {\n overflow: hidden;\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-xs);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.j-editor__slash-md {\n color: var(--juxt-fg-muted);\n font-family: var(--juxt-font-mono);\n font-size: var(--juxt-text-2xs);\n}\n\n.j-editor__slash-empty {\n padding: var(--juxt-space-3) var(--juxt-space-2);\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-sm);\n}\n\n/* Link editor */\n.j-editor__link {\n z-index: var(--juxt-z-popover);\n display: flex;\n align-items: center;\n gap: 2px;\n width: min(22rem, calc(100vw - 16px));\n padding: 3px 3px 3px var(--juxt-space-2-5);\n}\n\n.j-editor__link-icon {\n flex: none;\n width: 0.875rem;\n height: 0.875rem;\n color: var(--juxt-fg-muted);\n}\n\n.j-editor__link-input {\n flex: 1;\n min-width: 0;\n height: var(--juxt-control-sm);\n padding: 0 var(--juxt-space-1-5);\n border: 0;\n outline: none;\n background: transparent;\n color: var(--juxt-fg);\n font: inherit;\n font-size: var(--juxt-text-sm);\n}\n\n.j-editor__link-input::placeholder {\n color: var(--juxt-fg-muted);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-editor__bubble {\n animation: none;\n }\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport type { Editor, JSONContent } from '@tiptap/core'\nimport type { FieldProps } from '../components/field/useField'\nimport type { BlockCommand } from './commands'\nimport type { SlashState } from './slash'\nimport { Extension } from '@tiptap/core'\nimport { Highlight } from '@tiptap/extension-highlight'\nimport { TaskItem, TaskList } from '@tiptap/extension-list'\nimport { Typography } from '@tiptap/extension-typography'\nimport { CharacterCount, Placeholder } from '@tiptap/extensions'\nimport { StarterKit } from '@tiptap/starter-kit'\nimport { EditorContent, useEditor } from '@tiptap/vue-3'\nimport { BubbleMenu } from '@tiptap/vue-3/menus'\nimport { computed, nextTick, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue'\nimport JDropdownMenu from '../components/dropdown-menu/JDropdownMenu.vue'\nimport JDropdownMenuItem from '../components/dropdown-menu/JDropdownMenuItem.vue'\nimport JFieldShell from '../components/field/JFieldShell.vue'\nimport { useField } from '../components/field/useField'\nimport JPortal from '../components/primitives/JPortal.vue'\nimport { useFloating } from '../composables/useFloating'\nimport { useLayer } from '../composables/useLayer'\nimport { blockCommands } from './commands'\nimport {\n IconBold,\n IconChevronDown,\n IconCode,\n IconCornerDownLeft,\n IconExternal,\n IconHighlight,\n IconItalic,\n IconLink,\n IconRedo,\n IconStrike,\n IconUnderline,\n IconUndo,\n IconUnlink,\n} from './icons'\nimport JEditorButton from './JEditorButton.vue'\nimport { createSlashCommands } from './slash'\n\nexport type EditorFormat = 'html' | 'json'\n\nexport interface EditorProps extends FieldProps {\n placeholder?: string\n /** Shape of `v-model`: an HTML string, or Tiptap/ProseMirror JSON. */\n format?: EditorFormat\n /** `full` shows every tool, `minimal` just the inline marks and lists, `false` hides the toolbar. */\n toolbar?: 'full' | 'minimal' | false\n /** Formatting menu that floats above selected text. */\n bubbleMenu?: boolean\n /** Type `/` for a menu of blocks. */\n slashCommands?: boolean\n /** Maximum number of characters. Shown in the footer and enforced while typing. */\n limit?: number\n /** Show word and character counts. */\n showCount?: boolean\n /** Height of the writing area before it grows. Any CSS length. */\n minHeight?: string\n /** Height after which the writing area scrolls. Any CSS length. */\n maxHeight?: string\n readonly?: boolean\n autofocus?: boolean | 'start' | 'end'\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<EditorProps>(), {\n placeholder: 'Write something, or type / for blocks…',\n format: 'html',\n toolbar: 'full',\n bubbleMenu: true,\n slashCommands: true,\n showCount: false,\n minHeight: '10rem',\n error: undefined,\n})\n\nconst model = defineModel<string | JSONContent | null>({ default: '' })\n\nconst emit = defineEmits<{\n /** The editor instance is ready, for advanced use. */\n ready: [editor: Editor]\n focus: []\n blur: []\n}>()\n\ndefineSlots<{\n /** Extra controls at the end of the toolbar. */\n 'toolbar-end'?: (props: { editor: Editor }) => unknown\n 'label'?: () => unknown\n}>()\n\nconst field = useField(props)\nconst root = ref<HTMLElement | null>(null)\nconst slashId = `${field.id.value}-blocks`\n\n// --- Slash menu state -----------------------------------------------------\nconst slash = reactive<SlashState>({ open: false, query: '', items: [], index: 0, rect: null, select: null })\n\n// --- Editor ----------------------------------------------------------------\nlet lastEmitted: string | null = null\nconst serialize = (value: unknown) => (typeof value === 'string' ? value : JSON.stringify(value ?? ''))\n\nfunction contentFromModel() {\n const value = model.value\n if (!value) return ''\n return value\n}\n\nconst LinkShortcut = Extension.create({\n name: 'juxtLinkShortcut',\n addKeyboardShortcuts() {\n return {\n 'Mod-k': () => {\n openLink()\n return true\n },\n }\n },\n})\n\nconst editor = useEditor({\n content: contentFromModel(),\n editable: !props.disabled && !props.readonly,\n autofocus: props.autofocus ?? false,\n extensions: [\n StarterKit.configure({\n heading: { levels: [1, 2, 3] },\n link: {\n openOnClick: false,\n autolink: true,\n defaultProtocol: 'https',\n HTMLAttributes: { rel: 'noopener noreferrer nofollow', target: null },\n },\n codeBlock: { HTMLAttributes: { spellcheck: 'false' } },\n dropcursor: { color: 'var(--juxt-accent)', width: 2 },\n }),\n TaskList,\n TaskItem.configure({ nested: true }),\n Highlight,\n Typography,\n Placeholder.configure({\n // Empty document: the placeholder. Empty heading: its level. Empty line\n // under the caret: a quiet hint that blocks are a slash away.\n placeholder: ({ editor: instance, node }) => {\n if (node.type.name === 'heading') return `Heading ${node.attrs.level}`\n if (instance.isEmpty) return props.placeholder\n return props.slashCommands ? 'Type / for blocks' : ''\n },\n }),\n CharacterCount.configure({ limit: props.limit ?? null }),\n LinkShortcut,\n ...(props.slashCommands ? [createSlashCommands(slash)] : []),\n ],\n editorProps: {\n attributes: editorAttributes(),\n },\n onCreate: ({ editor: instance }) => emit('ready', instance),\n onFocus: () => emit('focus'),\n onBlur: () => emit('blur'),\n onUpdate: ({ editor: instance }) => {\n const value = props.format === 'json' ? instance.getJSON() : instance.isEmpty ? '' : instance.getHTML()\n lastEmitted = serialize(value)\n model.value = value\n },\n})\n\nfunction editorAttributes(): Record<string, string> {\n const attrs: Record<string, string> = {\n 'role': 'textbox',\n 'aria-multiline': 'true',\n 'class': 'j-editor__prose',\n }\n if (props.label || props.id) attrs['aria-labelledby'] = field.labelId.value\n if (field.describedBy.value) attrs['aria-describedby'] = field.describedBy.value\n if (field.invalid.value) attrs['aria-invalid'] = 'true'\n if (props.required) attrs['aria-required'] = 'true'\n if (props.disabled || props.readonly) attrs['aria-readonly'] = 'true'\n if (slash.open) {\n attrs['aria-controls'] = slashId\n attrs['aria-expanded'] = 'true'\n const active = slash.items[slash.index]\n if (active) attrs['aria-activedescendant'] = `${slashId}-${active.id}`\n }\n return attrs\n}\n\n// Keep ARIA on the editable element in sync with the field and the slash menu.\nwatch(\n [() => field.describedBy.value, () => field.invalid.value, () => props.disabled, () => props.readonly, () => slash.open, () => slash.index, () => slash.items],\n () => editor.value?.setOptions({ editorProps: { attributes: editorAttributes() } }),\n)\n\n// Outside changes to v-model replace the content; our own echoes don't.\nwatch(model, (value) => {\n const instance = editor.value\n if (!instance || serialize(value) === lastEmitted) return\n instance.commands.setContent(value || '', { emitUpdate: false })\n lastEmitted = serialize(value)\n})\n\nwatch(() => [props.disabled, props.readonly], () => editor.value?.setEditable(!props.disabled && !props.readonly))\n\n// --- Toolbar state -----------------------------------------------------------\nconst currentBlock = computed<BlockCommand>(() => {\n const instance = editor.value\n const fallback = blockCommands[0]!\n if (!instance) return fallback\n return blockCommands.find(command => command.convertible && command.id !== 'text' && command.isActive?.(instance)) ?? fallback\n})\n\nconst can = computed(() => ({\n undo: !!editor.value?.can().undo(),\n redo: !!editor.value?.can().redo(),\n}))\n\nconst editable = computed(() => !!editor.value && !props.disabled && !props.readonly)\n\nfunction run(fn: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>) {\n const instance = editor.value\n if (!instance) return\n fn(instance.chain().focus()).run()\n}\n\n// Arrow keys move along the toolbar; Tab moves past it (roving tabindex).\nfunction onToolbarKeydown(event: KeyboardEvent) {\n if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft' && event.key !== 'Home' && event.key !== 'End') return\n const toolbar = event.currentTarget as HTMLElement\n const items = Array.from(toolbar.querySelectorAll<HTMLElement>('[data-editor-control]:not(:disabled)'))\n const index = items.indexOf(document.activeElement as HTMLElement)\n if (index === -1) return\n event.preventDefault()\n let next = index\n if (event.key === 'ArrowRight') next = (index + 1) % items.length\n else if (event.key === 'ArrowLeft') next = (index - 1 + items.length) % items.length\n else if (event.key === 'Home') next = 0\n else next = items.length - 1\n items.forEach((item, i) => (item.tabIndex = i === next ? 0 : -1))\n items[next]!.focus()\n}\n\nfunction initRovingTabindex(el: unknown) {\n const toolbar = el as HTMLElement | null\n if (!toolbar) return\n nextTick(() => {\n const items = Array.from(toolbar.querySelectorAll<HTMLElement>('[data-editor-control]'))\n if (items.some(item => item.tabIndex === 0)) return\n items.forEach((item, i) => (item.tabIndex = i === 0 ? 0 : -1))\n })\n}\n\n// --- Floating panels positioned at the text -------------------------------\nfunction virtualAt(getRect: () => DOMRect | null) {\n return {\n getBoundingClientRect: () => getRect() ?? new DOMRect(),\n get contextElement() {\n return editor.value?.view.dom\n },\n } as unknown as HTMLElement\n}\n\n// Slash menu\nconst slashEl = ref<HTMLElement | null>(null)\nconst slashRef = shallowRef<HTMLElement | null>(virtualAt(() => slash.rect))\nconst { styles: slashStyles, update: updateSlash } = useFloating(slashRef, slashEl, { placement: 'bottom-start', offset: 6 })\nwatch(() => slash.rect, () => nextTick(updateSlash))\nwatch(() => slash.index, () => nextTick(() => document.getElementById(`${slashId}-${slash.items[slash.index]?.id}`)?.scrollIntoView({ block: 'nearest' })))\n\n// Link editor\nconst link = reactive({ open: false, url: '', rect: null as DOMRect | null, active: false })\nconst linkEl = ref<HTMLElement | null>(null)\nconst linkInput = ref<HTMLInputElement | null>(null)\nconst linkRef = shallowRef<HTMLElement | null>(virtualAt(() => link.rect))\nconst { styles: linkStyles, update: updateLink } = useFloating(linkRef, linkEl, { placement: 'bottom-start', offset: 8 })\n\nfunction selectionRect(instance: Editor): DOMRect {\n const { from, to } = instance.state.selection\n const start = instance.view.coordsAtPos(from)\n const end = instance.view.coordsAtPos(to)\n const left = Math.min(start.left, end.left)\n const right = Math.max(start.right, end.right)\n return new DOMRect(left, start.top, Math.max(1, right - left), end.bottom - start.top)\n}\n\nfunction openLink() {\n const instance = editor.value\n if (!instance || !editable.value) return\n if (instance.isActive('link')) instance.chain().extendMarkRange('link').run()\n link.active = instance.isActive('link')\n link.url = (instance.getAttributes('link').href as string | undefined) ?? ''\n link.rect = selectionRect(instance)\n link.open = true\n nextTick(() => {\n updateLink()\n linkInput.value?.focus()\n linkInput.value?.select()\n })\n}\n\nfunction closeLink(refocus = true) {\n link.open = false\n if (refocus) editor.value?.commands.focus()\n}\n\nfunction normalizeUrl(raw: string): string | null {\n const value = raw.trim()\n if (!value) return null\n if (/^(?:javascript|data|vbscript):/i.test(value)) return null\n if (/^(?:https?:|mailto:|tel:|\\/|#)/i.test(value)) return value\n if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value)) return `mailto:${value}`\n return `https://${value}`\n}\n\nfunction applyLink() {\n const instance = editor.value\n if (!instance) return\n const href = normalizeUrl(link.url)\n if (!href) {\n removeLink()\n return\n }\n if (instance.state.selection.empty && !instance.isActive('link')) {\n instance.chain().focus().insertContent({ type: 'text', text: link.url.trim(), marks: [{ type: 'link', attrs: { href } }] }).run()\n } else {\n instance.chain().focus().extendMarkRange('link').setLink({ href }).run()\n }\n closeLink(false)\n}\n\nfunction removeLink() {\n editor.value?.chain().focus().extendMarkRange('link').unsetLink().run()\n closeLink(false)\n}\n\nuseLayer({\n active: computed(() => link.open),\n elements: () => [linkEl.value],\n onEscape: () => closeLink(),\n onPointerDownOutside: () => closeLink(false),\n})\n\n// --- Bubble menu -------------------------------------------------------------\nfunction shouldShowBubble({ editor: instance, from, to }: { editor: Editor, from: number, to: number }) {\n if (!props.bubbleMenu || !instance.isEditable || link.open || slash.open) return false\n if (from === to || instance.isActive('codeBlock')) return false\n return instance.state.doc.textBetween(from, to).trim().length > 0\n}\n\n// --- Counts ------------------------------------------------------------------\nconst counts = computed(() => {\n const storage = editor.value?.storage.characterCount as { characters: () => number, words: () => number } | undefined\n return { characters: storage?.characters() ?? 0, words: storage?.words() ?? 0 }\n})\nconst nearLimit = computed(() => !!props.limit && counts.value.characters >= props.limit * 0.9)\nconst atLimit = computed(() => !!props.limit && counts.value.characters >= props.limit)\n\nfunction focusEditor(event: MouseEvent) {\n // Clicking the empty space under the text puts the caret at the end.\n if (event.target === event.currentTarget && editable.value) editor.value?.commands.focus('end')\n}\n\nonBeforeUnmount(() => editor.value?.destroy())\n\ndefineExpose({\n /** The underlying Tiptap editor. */\n editor,\n focus: (position?: 'start' | 'end') => editor.value?.commands.focus(position ?? null),\n clear: () => editor.value?.commands.clearContent(true),\n})\n</script>\n\n<template>\n <JFieldShell\n :id=\"field.id.value\"\n :class=\"$attrs.class\"\n :style=\"$attrs.style\"\n :label-id=\"field.labelId.value\"\n :description-id=\"field.descriptionId.value\"\n :error-id=\"field.errorId.value\"\n :label=\"label\"\n :description=\"description\"\n :error-message=\"field.errorMessage.value\"\n :show-description=\"field.showDescription.value\"\n :required=\"required\"\n :disabled=\"disabled\"\n :label-for=\"null\"\n >\n <template v-if=\"$slots.label\" #label>\n <slot name=\"label\" />\n </template>\n\n <div\n ref=\"root\"\n class=\"j-editor\"\n :class=\"{ 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }\"\n :style=\"{ '--j-editor-min': minHeight, '--j-editor-max': maxHeight ?? 'none' }\"\n >\n <!-- Toolbar -->\n <div\n v-if=\"toolbar\"\n :ref=\"initRovingTabindex\"\n role=\"toolbar\"\n aria-label=\"Formatting\"\n class=\"j-editor__toolbar\"\n @keydown=\"onToolbarKeydown\"\n >\n <template v-if=\"toolbar === 'full'\">\n <JDropdownMenu :disabled=\"!editable\">\n <template #trigger>\n <button\n type=\"button\"\n class=\"j-editor__block j-focusable\"\n :disabled=\"!editable\"\n aria-label=\"Block type\"\n data-editor-control\n @pointerdown.prevent\n >\n <component :is=\"currentBlock.icon\" />\n <span class=\"j-editor__block-label\">{{ currentBlock.label }}</span>\n <IconChevronDown class=\"j-editor__block-chevron\" />\n </button>\n </template>\n <JDropdownMenuItem\n v-for=\"command in blockCommands.filter(c => c.convertible)\"\n :key=\"command.id\"\n :checked=\"command.id === currentBlock.id\"\n @select=\"editor && command.run(editor)\"\n >\n <template #leading>\n <component :is=\"command.icon\" />\n </template>\n {{ command.label }}\n </JDropdownMenuItem>\n </JDropdownMenu>\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\n </template>\n\n <JEditorButton label=\"Bold\" shortcut=\"mod+b\" :active=\"!!editor?.isActive('bold')\" :disabled=\"!editable\" @click=\"run(c => c.toggleBold())\">\n <IconBold />\n </JEditorButton>\n <JEditorButton label=\"Italic\" shortcut=\"mod+i\" :active=\"!!editor?.isActive('italic')\" :disabled=\"!editable\" @click=\"run(c => c.toggleItalic())\">\n <IconItalic />\n </JEditorButton>\n <JEditorButton label=\"Underline\" shortcut=\"mod+u\" :active=\"!!editor?.isActive('underline')\" :disabled=\"!editable\" @click=\"run(c => c.toggleUnderline())\">\n <IconUnderline />\n </JEditorButton>\n <JEditorButton v-if=\"toolbar === 'full'\" label=\"Strikethrough\" shortcut=\"mod+shift+s\" :active=\"!!editor?.isActive('strike')\" :disabled=\"!editable\" @click=\"run(c => c.toggleStrike())\">\n <IconStrike />\n </JEditorButton>\n <JEditorButton v-if=\"toolbar === 'full'\" label=\"Inline code\" shortcut=\"mod+e\" :active=\"!!editor?.isActive('code')\" :disabled=\"!editable\" @click=\"run(c => c.toggleCode())\">\n <IconCode />\n </JEditorButton>\n <JEditorButton label=\"Link\" shortcut=\"mod+k\" :active=\"!!editor?.isActive('link')\" :disabled=\"!editable\" @click=\"openLink\">\n <IconLink />\n </JEditorButton>\n\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\n\n <JEditorButton\n v-for=\"command in blockCommands.filter(c => (toolbar === 'full' ? ['bullet', 'ordered', 'task', 'quote', 'code', 'divider'] : ['bullet', 'ordered']).includes(c.id))\"\n :key=\"command.id\"\n :label=\"command.label\"\n :active=\"command.isActive ? !!(editor && command.isActive(editor)) : undefined\"\n :disabled=\"!editable\"\n @click=\"editor && command.run(editor)\"\n >\n <component :is=\"command.icon\" />\n </JEditorButton>\n\n <span class=\"j-editor__spacer\" />\n\n <slot v-if=\"editor\" name=\"toolbar-end\" :editor=\"editor\" />\n <JEditorButton label=\"Undo\" shortcut=\"mod+z\" :disabled=\"!editable || !can.undo\" @click=\"run(c => c.undo())\">\n <IconUndo />\n </JEditorButton>\n <JEditorButton label=\"Redo\" shortcut=\"mod+shift+z\" :disabled=\"!editable || !can.redo\" @click=\"run(c => c.redo())\">\n <IconRedo />\n </JEditorButton>\n </div>\n\n <!-- Writing area -->\n <div class=\"j-editor__content\" @click=\"focusEditor\">\n <EditorContent v-if=\"editor\" :editor=\"editor\" />\n <p v-else class=\"j-editor__prose j-editor__ssr\" aria-hidden=\"true\">\n {{ placeholder }}\n </p>\n </div>\n\n <!-- Footer -->\n <div v-if=\"showCount || limit\" class=\"j-editor__footer\" aria-live=\"polite\">\n <span>{{ counts.words }} {{ counts.words === 1 ? 'word' : 'words' }}</span>\n <span :class=\"{ 'is-near': nearLimit, 'is-at': atLimit }\">\n {{ counts.characters }}<template v-if=\"limit\"> / {{ limit }}</template> characters\n </span>\n </div>\n </div>\n\n <!-- Selection toolbar -->\n <BubbleMenu\n v-if=\"editor && bubbleMenu\"\n :editor=\"editor\"\n :should-show=\"shouldShowBubble\"\n :options=\"{ placement: 'top', offset: 8 }\"\n class=\"j-editor__bubble\"\n >\n <JEditorButton label=\"Bold\" shortcut=\"mod+b\" side=\"bottom\" :active=\"editor.isActive('bold')\" @click=\"run(c => c.toggleBold())\">\n <IconBold />\n </JEditorButton>\n <JEditorButton label=\"Italic\" shortcut=\"mod+i\" side=\"bottom\" :active=\"editor.isActive('italic')\" @click=\"run(c => c.toggleItalic())\">\n <IconItalic />\n </JEditorButton>\n <JEditorButton label=\"Underline\" shortcut=\"mod+u\" side=\"bottom\" :active=\"editor.isActive('underline')\" @click=\"run(c => c.toggleUnderline())\">\n <IconUnderline />\n </JEditorButton>\n <JEditorButton label=\"Strikethrough\" shortcut=\"mod+shift+s\" side=\"bottom\" :active=\"editor.isActive('strike')\" @click=\"run(c => c.toggleStrike())\">\n <IconStrike />\n </JEditorButton>\n <JEditorButton label=\"Inline code\" shortcut=\"mod+e\" side=\"bottom\" :active=\"editor.isActive('code')\" @click=\"run(c => c.toggleCode())\">\n <IconCode />\n </JEditorButton>\n <JEditorButton label=\"Highlight\" shortcut=\"mod+shift+h\" side=\"bottom\" :active=\"editor.isActive('highlight')\" @click=\"run(c => c.toggleHighlight())\">\n <IconHighlight />\n </JEditorButton>\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\n <JEditorButton label=\"Link\" shortcut=\"mod+k\" side=\"bottom\" :active=\"editor.isActive('link')\" @click=\"openLink\">\n <IconLink />\n </JEditorButton>\n </BubbleMenu>\n\n <JPortal>\n <!-- Slash menu -->\n <Transition name=\"j-pop\">\n <div\n v-if=\"slash.open\"\n :id=\"slashId\"\n ref=\"slashEl\"\n role=\"listbox\"\n aria-label=\"Blocks\"\n class=\"j-editor__slash j-popover-surface\"\n :style=\"slashStyles\"\n @pointerdown.prevent\n >\n <template v-if=\"slash.items.length\">\n <div class=\"j-editor__slash-label\" role=\"presentation\">\n Blocks\n </div>\n <div\n v-for=\"(item, i) in slash.items\"\n :id=\"`${slashId}-${item.id}`\"\n :key=\"item.id\"\n role=\"option\"\n class=\"j-editor__slash-item\"\n :class=\"{ 'is-active': i === slash.index }\"\n :aria-selected=\"i === slash.index\"\n @pointermove=\"slash.index = i\"\n @click=\"slash.select?.(item)\"\n >\n <span class=\"j-editor__slash-icon\"><component :is=\"item.icon\" /></span>\n <span class=\"j-editor__slash-text\">\n <span class=\"j-editor__slash-title\">{{ item.label }}</span>\n <span class=\"j-editor__slash-description\">{{ item.description }}</span>\n </span>\n <kbd v-if=\"item.markdown\" class=\"j-editor__slash-md\">{{ item.markdown }}</kbd>\n </div>\n </template>\n <div v-else class=\"j-editor__slash-empty\">\n No blocks match “{{ slash.query }}”\n </div>\n </div>\n </Transition>\n\n <!-- Link editor -->\n <Transition name=\"j-pop\">\n <form\n v-if=\"link.open\"\n ref=\"linkEl\"\n class=\"j-editor__link j-popover-surface\"\n :style=\"linkStyles\"\n @submit.prevent=\"applyLink\"\n >\n <IconLink class=\"j-editor__link-icon\" />\n <input\n ref=\"linkInput\"\n v-model=\"link.url\"\n class=\"j-editor__link-input\"\n type=\"text\"\n inputmode=\"url\"\n placeholder=\"Paste or type a link\"\n aria-label=\"Link address\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n />\n <button type=\"submit\" class=\"j-editor-button j-focusable\" aria-label=\"Apply link\">\n <IconCornerDownLeft />\n </button>\n <a\n v-if=\"link.active && normalizeUrl(link.url)\"\n :href=\"normalizeUrl(link.url)!\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"j-editor-button j-focusable\"\n aria-label=\"Open link in a new tab\"\n >\n <IconExternal />\n </a>\n <button v-if=\"link.active\" type=\"button\" class=\"j-editor-button j-focusable\" aria-label=\"Remove link\" @click=\"removeLink\">\n <IconUnlink />\n </button>\n </form>\n </Transition>\n </JPortal>\n </JFieldShell>\n</template>\n\n<style>\n@layer theme, base, juxt, components, utilities;\n@layer juxt {\n/* Frame: the same border, focus halo and invalid states as Input. */\n.j-editor {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--juxt-border);\n border-radius: var(--juxt-radius-md);\n background: var(--juxt-surface);\n box-shadow: var(--juxt-shadow-xs);\n color: var(--juxt-fg);\n font-family: var(--juxt-font-sans);\n transition:\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\n box-shadow var(--juxt-duration-normal) var(--juxt-ease-out);\n}\n\n.dark .j-editor,\n[data-theme='dark'] .j-editor {\n background: var(--juxt-surface-sunken);\n}\n\n.j-editor:hover:not(.is-disabled, :focus-within) {\n border-color: var(--juxt-border-strong);\n}\n\n.j-editor:has(.ProseMirror-focused) {\n border-color: var(--juxt-accent);\n box-shadow: 0 0 0 3px var(--juxt-ring-soft);\n}\n\n.j-editor.is-invalid {\n border-color: var(--juxt-danger-border);\n}\n\n.j-editor.is-invalid:has(.ProseMirror-focused) {\n border-color: var(--juxt-danger);\n box-shadow: 0 0 0 3px var(--juxt-danger-ring);\n}\n\n.j-editor.is-disabled {\n background: var(--juxt-surface-sunken);\n box-shadow: none;\n opacity: 0.7;\n}\n\n/* Toolbar: stays reachable while you scroll a long document. */\n.j-editor__toolbar {\n position: sticky;\n top: var(--j-editor-sticky-top, 0);\n z-index: 1;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: var(--juxt-space-1);\n overflow-x: auto;\n border-bottom: 1px solid var(--juxt-border-subtle);\n border-radius: var(--juxt-radius-md) var(--juxt-radius-md) 0 0;\n background: inherit;\n scrollbar-width: none;\n}\n\n.j-editor__toolbar::-webkit-scrollbar {\n display: none;\n}\n\n.dark .j-editor__toolbar,\n[data-theme='dark'] .j-editor__toolbar {\n border-bottom-color: var(--juxt-border);\n}\n\n.j-editor__divider {\n flex: none;\n width: 1px;\n height: 1rem;\n margin: 0 var(--juxt-space-1);\n background: var(--juxt-border);\n}\n\n.j-editor__spacer {\n flex: 1;\n min-width: var(--juxt-space-2);\n}\n\n.j-editor__block {\n display: inline-flex;\n flex: none;\n align-items: center;\n gap: var(--juxt-space-1-5);\n height: var(--juxt-control-sm);\n padding: 0 var(--juxt-space-2);\n border: 0;\n border-radius: var(--juxt-radius-sm);\n background: transparent;\n color: var(--juxt-fg);\n font: inherit;\n font-size: var(--juxt-text-sm);\n font-weight: var(--juxt-weight-medium);\n cursor: pointer;\n transition-property: background-color, outline-color, outline-offset;\n}\n\n.j-editor__block:hover:not(:disabled) {\n background: var(--juxt-surface-hover);\n}\n\n.j-editor__block:disabled {\n color: var(--juxt-fg-disabled);\n cursor: not-allowed;\n}\n\n.j-editor__block > svg {\n width: 1rem;\n height: 1rem;\n color: var(--juxt-fg-muted);\n}\n\n.j-editor__block-label {\n min-width: 5.5rem;\n text-align: left;\n}\n\n.j-editor__block .j-editor__block-chevron {\n width: 0.75rem;\n height: 0.75rem;\n}\n\n/* Writing area */\n.j-editor__content {\n flex: 1;\n min-height: var(--j-editor-min);\n max-height: var(--j-editor-max);\n padding: var(--juxt-space-3) var(--juxt-space-4);\n overflow-y: auto;\n cursor: text;\n}\n\n.j-editor.is-disabled .j-editor__content,\n.j-editor.is-readonly .j-editor__content {\n cursor: default;\n}\n\n.j-editor__ssr {\n margin: 0;\n color: var(--juxt-fg-muted);\n}\n\n.j-editor__footer {\n display: flex;\n justify-content: space-between;\n gap: var(--juxt-space-3);\n padding: var(--juxt-space-1-5) var(--juxt-space-4);\n border-top: 1px solid var(--juxt-border-subtle);\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-xs);\n font-variant-numeric: tabular-nums;\n}\n\n.dark .j-editor__footer,\n[data-theme='dark'] .j-editor__footer {\n border-top-color: var(--juxt-border);\n}\n\n.j-editor__footer .is-near {\n color: var(--juxt-warning-text);\n}\n\n.j-editor__footer .is-at {\n color: var(--juxt-danger-text);\n}\n\n/* ---------------------------------------------------------------------------\n * Document typography. Deliberately close to the docs prose: calm, readable,\n * a clear hierarchy without shouting.\n * ------------------------------------------------------------------------ */\n.j-editor__prose {\n min-height: calc(var(--j-editor-min) - var(--juxt-space-6));\n color: var(--juxt-fg);\n font-size: 0.9375rem;\n line-height: 1.7;\n letter-spacing: var(--juxt-tracking-tight);\n white-space: pre-wrap;\n word-wrap: break-word;\n outline: none;\n font-variant-ligatures: none;\n font-feature-settings: 'liga' 0;\n}\n\n.j-editor__prose > * + * {\n margin-top: 0.75em;\n}\n\n.j-editor__prose p {\n margin: 0;\n}\n\n.j-editor__prose :is(h1, h2, h3) {\n margin: 1.4em 0 0;\n color: var(--juxt-fg);\n font-weight: var(--juxt-weight-semibold);\n line-height: 1.3;\n letter-spacing: var(--juxt-tracking-tighter);\n}\n\n.j-editor__prose > :is(h1, h2, h3):first-child {\n margin-top: 0;\n}\n\n.j-editor__prose h1 {\n font-size: 1.625rem;\n}\n\n.j-editor__prose h2 {\n font-size: 1.3125rem;\n}\n\n.j-editor__prose h3 {\n font-size: 1.0625rem;\n}\n\n.j-editor__prose :is(ul, ol) {\n margin-bottom: 0;\n padding-left: 1.375em;\n}\n\n.j-editor__prose li + li {\n margin-top: 0.25em;\n}\n\n.j-editor__prose li > p {\n margin: 0;\n}\n\n.j-editor__prose li::marker {\n color: var(--juxt-fg-muted);\n}\n\n.j-editor__prose ul ul {\n list-style-type: circle;\n}\n\n/* To-do items look and feel like JCheckbox. */\n.j-editor__prose ul[data-type='taskList'] {\n padding-left: 0.125em;\n list-style: none;\n}\n\n.j-editor__prose ul[data-type='taskList'] li {\n display: flex;\n align-items: flex-start;\n gap: 0.625em;\n}\n\n.j-editor__prose ul[data-type='taskList'] li > label {\n display: inline-flex;\n flex: none;\n margin-top: 0.2em;\n user-select: none;\n}\n\n.j-editor__prose ul[data-type='taskList'] li > div {\n flex: 1;\n min-width: 0;\n}\n\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox'] {\n display: grid;\n place-content: center;\n width: 1rem;\n height: 1rem;\n margin: 0;\n border: 1px solid var(--juxt-border-strong);\n border-radius: var(--juxt-radius-xs);\n background: var(--juxt-surface);\n cursor: pointer;\n appearance: none;\n transition:\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard);\n}\n\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']::after {\n content: '';\n width: 0.5rem;\n height: 0.28rem;\n margin-top: -0.1rem;\n border-bottom: 2px solid var(--juxt-accent-fg);\n border-left: 2px solid var(--juxt-accent-fg);\n opacity: 0;\n transform: rotate(-45deg) scale(0.6);\n transition:\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\n}\n\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:checked {\n border-color: var(--juxt-accent);\n background: var(--juxt-accent);\n}\n\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:checked::after {\n opacity: 1;\n transform: rotate(-45deg);\n}\n\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:focus-visible {\n outline: 2px solid var(--juxt-ring);\n outline-offset: 2px;\n}\n\n.j-editor__prose li[data-checked='true'] > div {\n color: var(--juxt-fg-muted);\n text-decoration: line-through;\n text-decoration-color: var(--juxt-border-strong);\n}\n\n.j-editor__prose blockquote {\n margin-left: 0;\n margin-right: 0;\n padding-left: 1em;\n border-left: 2px solid var(--juxt-border-strong);\n color: var(--juxt-fg-secondary);\n}\n\n.j-editor__prose a {\n color: var(--juxt-fg);\n text-decoration: underline;\n text-decoration-color: var(--juxt-border-strong);\n text-underline-offset: 3px;\n cursor: text;\n transition: text-decoration-color var(--juxt-duration-fast) var(--juxt-ease-standard);\n}\n\n.j-editor__prose a:hover {\n text-decoration-color: var(--juxt-accent);\n}\n\n.j-editor__prose code {\n padding: 0.1em 0.3em;\n border: 1px solid var(--juxt-border-subtle);\n border-radius: var(--juxt-radius-xs);\n background: var(--juxt-surface-sunken);\n font-family: var(--juxt-font-mono);\n font-size: 0.85em;\n}\n\n.dark .j-editor__prose code,\n[data-theme='dark'] .j-editor__prose code {\n border-color: var(--juxt-border);\n background: var(--juxt-surface-raised);\n}\n\n.j-editor__prose pre {\n padding: 0.875em 1em;\n overflow-x: auto;\n border: 1px solid var(--juxt-border-subtle);\n border-radius: var(--juxt-radius-md);\n background: var(--juxt-surface-sunken);\n font-family: var(--juxt-font-mono);\n font-size: 0.8125rem;\n line-height: 1.65;\n white-space: pre;\n}\n\n.dark .j-editor__prose pre,\n[data-theme='dark'] .j-editor__prose pre {\n border-color: var(--juxt-border);\n background: var(--juxt-bg);\n}\n\n.j-editor__prose pre code {\n padding: 0;\n border: 0;\n background: none;\n font-size: inherit;\n}\n\n.j-editor__prose hr {\n height: 1px;\n margin: 1.5em 0;\n border: 0;\n background: var(--juxt-border);\n}\n\n.j-editor__prose hr.ProseMirror-selectednode {\n outline: 2px solid var(--juxt-ring);\n outline-offset: 4px;\n}\n\n.j-editor__prose mark {\n padding: 0.05em 0.1em;\n border-radius: 2px;\n background: var(--juxt-warning-soft);\n color: inherit;\n}\n\n.j-editor__prose ::selection {\n background: var(--juxt-accent-soft);\n}\n\n/* Placeholder: on an empty document, and on each empty heading. */\n.j-editor__prose .is-empty::before {\n content: attr(data-placeholder);\n float: left;\n height: 0;\n color: var(--juxt-fg-muted);\n pointer-events: none;\n}\n\n/* The per-line hint is quieter than the document placeholder. */\n.j-editor__prose .is-empty:not(.is-editor-empty):not(h1, h2, h3)::before {\n color: var(--juxt-fg-disabled);\n}\n\n.j-editor__prose .ProseMirror-gapcursor {\n display: none;\n position: relative;\n pointer-events: none;\n}\n\n.j-editor__prose.ProseMirror-focused .ProseMirror-gapcursor {\n display: block;\n}\n\n.j-editor__prose .ProseMirror-gapcursor::after {\n content: '';\n position: absolute;\n top: -2px;\n display: block;\n width: 20px;\n border-top: 1px solid var(--juxt-fg);\n animation: j-editor-cursor 1.1s steps(2, start) infinite;\n}\n\n@keyframes j-editor-cursor {\n to {\n visibility: hidden;\n }\n}\n\n/* Selection toolbar */\n.j-editor__bubble {\n z-index: var(--juxt-z-popover);\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 3px;\n border: 1px solid var(--juxt-border);\n border-radius: var(--juxt-radius-md);\n background: var(--juxt-surface-raised);\n box-shadow: var(--juxt-shadow-md);\n animation: j-editor-bubble-in var(--juxt-duration-fast) var(--juxt-ease-out);\n}\n\n@keyframes j-editor-bubble-in {\n from {\n opacity: 0;\n transform: translateY(calc(var(--juxt-motion-shift) * 0.5)) scale(var(--juxt-motion-scale));\n }\n}\n\n/* Slash menu */\n.j-editor__slash {\n z-index: var(--juxt-z-popover);\n width: 17rem;\n max-height: min(20rem, var(--j-available-height, 20rem));\n padding: var(--juxt-space-1);\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.j-editor__slash-label {\n padding: var(--juxt-space-1-5) var(--juxt-space-2) var(--juxt-space-1);\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-xs);\n font-weight: var(--juxt-weight-medium);\n}\n\n.j-editor__slash-item {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--juxt-space-2-5);\n padding: var(--juxt-space-1-5) var(--juxt-space-2);\n border-radius: var(--juxt-radius-sm);\n cursor: pointer;\n user-select: none;\n}\n\n.j-editor__slash-item.is-active {\n background: var(--juxt-surface-hover);\n}\n\n.j-editor__slash-item.is-active::before {\n content: '';\n position: absolute;\n top: 0.625rem;\n bottom: 0.625rem;\n left: 0;\n width: 2px;\n border-radius: 0 2px 2px 0;\n background: var(--juxt-accent);\n}\n\n.j-editor__slash-icon {\n display: grid;\n flex: none;\n place-items: center;\n width: 2rem;\n height: 2rem;\n border: 1px solid var(--juxt-border);\n border-radius: var(--juxt-radius-sm);\n background: var(--juxt-surface);\n color: var(--juxt-fg-secondary);\n}\n\n.j-editor__slash-icon > svg {\n width: 1rem;\n height: 1rem;\n}\n\n.j-editor__slash-text {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-width: 0;\n}\n\n.j-editor__slash-title {\n color: var(--juxt-fg);\n font-size: var(--juxt-text-sm);\n font-weight: var(--juxt-weight-medium);\n}\n\n.j-editor__slash-description {\n overflow: hidden;\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-xs);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.j-editor__slash-md {\n color: var(--juxt-fg-muted);\n font-family: var(--juxt-font-mono);\n font-size: var(--juxt-text-2xs);\n}\n\n.j-editor__slash-empty {\n padding: var(--juxt-space-3) var(--juxt-space-2);\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-sm);\n}\n\n/* Link editor */\n.j-editor__link {\n z-index: var(--juxt-z-popover);\n display: flex;\n align-items: center;\n gap: 2px;\n width: min(22rem, calc(100vw - 16px));\n padding: 3px 3px 3px var(--juxt-space-2-5);\n}\n\n.j-editor__link-icon {\n flex: none;\n width: 0.875rem;\n height: 0.875rem;\n color: var(--juxt-fg-muted);\n}\n\n.j-editor__link-input {\n flex: 1;\n min-width: 0;\n height: var(--juxt-control-sm);\n padding: 0 var(--juxt-space-1-5);\n border: 0;\n outline: none;\n background: transparent;\n color: var(--juxt-fg);\n font: inherit;\n font-size: var(--juxt-text-sm);\n}\n\n.j-editor__link-input::placeholder {\n color: var(--juxt-fg-muted);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-editor__bubble {\n animation: none;\n }\n}\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"editor.js","names":["_hoisted_1","_openBlock","_createBlock","JTooltip","_withCtx","_createTextVNode","_toDisplayString","JKbd","_createCommentVNode","_createElementVNode","_normalizeClass","_withModifiers","_renderSlot","_sfc_main","$attrs","$slots","_useModel","_openBlock","_createBlock","JFieldShell","_unref","_normalizeClass","_normalizeStyle","_createSlots","_withCtx","_createElementVNode","_createElementBlock","_Fragment","_createVNode","JDropdownMenu","_withModifiers","_resolveDynamicComponent","_toDisplayString","_renderList","JDropdownMenuItem","_createTextVNode","_createCommentVNode","JEditorButton","_renderSlot","JPortal","_Transition","_withDirectives","_vModelText","_sfc_main"],"sources":["../src/editor/icons.ts","../src/editor/commands.ts","../src/editor/JEditorButton.vue","../src/editor/JEditorButton.vue","../src/editor/slash.ts","../src/editor/JEditor.vue","../src/editor/JEditor.vue"],"sourcesContent":["import type { FunctionalComponent, SVGAttributes } from 'vue'\r\nimport { h } from 'vue'\r\n\r\n/*\r\n * Editor glyphs, drawn on the same 16px grid and 1.5px stroke as the rest of\r\n * juxt.ui so the toolbar sits at the weight of 13–14px text.\r\n */\r\n\r\ntype Icon = FunctionalComponent<SVGAttributes>\r\n\r\nfunction icon(name: string, children: () => ReturnType<typeof h>[]): Icon {\r\n const component: Icon = props =>\r\n h(\r\n 'svg',\r\n {\r\n 'xmlns': 'http://www.w3.org/2000/svg',\r\n 'viewBox': '0 0 16 16',\r\n 'width': 16,\r\n 'height': 16,\r\n 'fill': 'none',\r\n 'stroke': 'currentColor',\r\n 'stroke-width': 1.5,\r\n 'stroke-linecap': 'round',\r\n 'stroke-linejoin': 'round',\r\n 'aria-hidden': 'true',\r\n 'focusable': 'false',\r\n ...props,\r\n },\r\n children(),\r\n )\r\n component.displayName = name\r\n return component\r\n}\r\n\r\nconst p = (d: string) => h('path', { d })\r\n\r\nexport const IconBold = icon('IconBold', () => [p('M4.5 3h4.25a2.5 2.5 0 0 1 0 5H4.5zM4.5 8h5a2.5 2.5 0 0 1 0 5h-5z')])\r\nexport const IconItalic = icon('IconItalic', () => [p('M10 3H6.5M9.5 13H6M9 3 7 13')])\r\nexport const IconUnderline = icon('IconUnderline', () => [p('M4.5 2.75V7a3.5 3.5 0 0 0 7 0V2.75M3.5 13.5h9')])\r\nexport const IconStrike = icon('IconStrike', () => [p('M11.5 4.25C11 3.2 9.8 2.75 8.2 2.75 6.1 2.75 4.75 3.8 4.75 5.25c0 1 .6 1.7 1.7 2.1M3 8.25h10M11.25 10.25c.1.3.15.6.15.9 0 1.5-1.45 2.6-3.6 2.6-1.8 0-3.1-.6-3.65-1.75')])\r\nexport const IconCode = icon('IconCode', () => [p('M6 4.5 2.75 8 6 11.5M10 4.5 13.25 8 10 11.5')])\r\nexport const IconLink = icon('IconLink', () => [p('M6.75 9.25a2.75 2.75 0 0 0 3.9 0l2-2a2.75 2.75 0 0 0-3.9-3.9l-.6.6M9.25 6.75a2.75 2.75 0 0 0-3.9 0l-2 2a2.75 2.75 0 0 0 3.9 3.9l.6-.6')])\r\nexport const IconUnlink = icon('IconUnlink', () => [p('M9.8 3.95l.6-.6a2.75 2.75 0 0 1 3.9 3.9l-1.2 1.2M6.2 12.05l-.6.6a2.75 2.75 0 0 1-3.9-3.9l1.2-1.2M2.5 2.5l11 11')])\r\nexport const IconExternal = icon('IconExternal', () => [p('M9.5 2.75h3.75V6.5M13.25 2.75 7.5 8.5M11.5 9.5v2.75a1 1 0 0 1-1 1h-6.75a1 1 0 0 1-1-1V5.5a1 1 0 0 1 1-1H6.5')])\r\nexport const IconHighlight = icon('IconHighlight', () => [p('M9.75 3.25l3 3-5.5 5.5H4.25v-3zM2.75 13.5h10.5')])\r\nexport const IconBulletList = icon('IconBulletList', () => [\r\n p('M6.5 4h7M6.5 8h7M6.5 12h7'),\r\n h('circle', { 'cx': 3, 'cy': 4, 'r': 0.6, 'fill': 'currentColor', 'stroke-width': 1 }),\r\n h('circle', { 'cx': 3, 'cy': 8, 'r': 0.6, 'fill': 'currentColor', 'stroke-width': 1 }),\r\n h('circle', { 'cx': 3, 'cy': 12, 'r': 0.6, 'fill': 'currentColor', 'stroke-width': 1 }),\r\n])\r\nexport const IconOrderedList = icon('IconOrderedList', () => [p('M6.75 4h6.75M6.75 8h6.75M6.75 12h6.75M2.5 3l1-.5v3M2.5 5.5h2M2.5 9.25c0-.5.45-.9 1-.9s1 .35 1 .85c0 .9-2 1.3-2 2.3h2')])\r\nexport const IconTaskList = icon('IconTaskList', () => [p('M8 4.25h5.5M8 11.75h5.5M2.5 4.25l1.25 1.25L6 3.25'), h('rect', { x: 2.5, y: 9.75, width: 3.5, height: 3.5, rx: 0.75 })])\r\nexport const IconQuote = icon('IconQuote', () => [p('M3 4v8M6 5.5h7M6 8h7M6 10.5h4.5')])\r\nexport const IconCodeBlock = icon('IconCodeBlock', () => [h('rect', { x: 2, y: 2.75, width: 12, height: 10.5, rx: 2 }), p('M6.25 6.5 4.75 8l1.5 1.5M9.75 6.5l1.5 1.5-1.5 1.5')])\r\nexport const IconDivider = icon('IconDivider', () => [p('M2.5 8h11M5 4.5h6M5 11.5h6')])\r\nexport const IconUndo = icon('IconUndo', () => [p('M5.5 3.5 2.75 6.25 5.5 9M3 6.25h6.25a3.75 3.75 0 0 1 0 7.5H7')])\r\nexport const IconRedo = icon('IconRedo', () => [p('M10.5 3.5l2.75 2.75L10.5 9M13 6.25H6.75a3.75 3.75 0 0 0 0 7.5H9')])\r\nexport const IconText = icon('IconText', () => [p('M3.5 4V3h9v1M8 3v10M6.25 13h3.5')])\r\nexport const IconHeading1 = icon('IconHeading1', () => [p('M2.5 3.5v9M8 3.5v9M2.5 8H8M11 6.25l1.75-1.25v7.5')])\r\nexport const IconHeading2 = icon('IconHeading2', () => [p('M2.5 3.5v9M7.5 3.5v9M2.5 8h5M10.25 6.5c0-.95.75-1.6 1.6-1.6s1.65.6 1.65 1.55c0 1.95-3.25 2.8-3.25 5.05h3.3')])\r\nexport const IconHeading3 = icon('IconHeading3', () => [p('M2.5 3.5v9M7.5 3.5v9M2.5 8h5M10.25 5.25h3.25L11.75 7.7c1 0 1.75.7 1.75 1.75 0 1.2-.9 1.95-1.9 1.95-.7 0-1.2-.3-1.5-.8')])\r\nexport const IconChevronDown = icon('IconChevronDown', () => [p('M4.5 6.25 8 9.75l3.5-3.5')])\r\nexport const IconCornerDownLeft = icon('IconCornerDownLeft', () => [p('M12.5 3.5v4.25a2 2 0 0 1-2 2h-7M6 7l-2.5 2.75L6 12.5')])\r\n","import type { Editor, Range } from '@tiptap/core'\r\nimport type { Component } from 'vue'\r\nimport {\r\n IconBulletList,\r\n IconCodeBlock,\r\n IconDivider,\r\n IconHeading1,\r\n IconHeading2,\r\n IconHeading3,\r\n IconOrderedList,\r\n IconQuote,\r\n IconTaskList,\r\n IconText,\r\n} from './icons'\r\n\r\n/** A block the user can turn the current line into, from the toolbar or the slash menu. */\r\nexport interface BlockCommand {\r\n id: string\r\n label: string\r\n description: string\r\n icon: Component\r\n /** The Markdown shortcut that does the same thing, shown as a hint. */\r\n markdown?: string\r\n keywords: string[]\r\n /** Whether the selection is currently this kind of block. */\r\n isActive?: (editor: Editor) => boolean\r\n /** Only offered as a \"turn into\" target, not as an insertable block. */\r\n convertible: boolean\r\n run: (editor: Editor, range?: Range) => void\r\n}\r\n\r\n// Start from the slash-command range when there is one, so the `/query` text is removed.\r\nconst chain = (editor: Editor, range?: Range) => (range ? editor.chain().focus().deleteRange(range) : editor.chain().focus())\r\n\r\nexport const blockCommands: BlockCommand[] = [\r\n {\r\n id: 'text',\r\n label: 'Text',\r\n description: 'Plain paragraph',\r\n icon: IconText,\r\n keywords: ['paragraph', 'p', 'body'],\r\n convertible: true,\r\n isActive: editor => editor.isActive('paragraph') && !editor.isActive('bulletList') && !editor.isActive('orderedList') && !editor.isActive('taskList') && !editor.isActive('blockquote'),\r\n run: (editor, range) => chain(editor, range).setParagraph().run(),\r\n },\r\n {\r\n id: 'h1',\r\n label: 'Heading 1',\r\n description: 'Large section title',\r\n icon: IconHeading1,\r\n markdown: '#',\r\n keywords: ['title', 'h1', 'big'],\r\n convertible: true,\r\n isActive: editor => editor.isActive('heading', { level: 1 }),\r\n run: (editor, range) => chain(editor, range).setHeading({ level: 1 }).run(),\r\n },\r\n {\r\n id: 'h2',\r\n label: 'Heading 2',\r\n description: 'Medium section title',\r\n icon: IconHeading2,\r\n markdown: '##',\r\n keywords: ['subtitle', 'h2'],\r\n convertible: true,\r\n isActive: editor => editor.isActive('heading', { level: 2 }),\r\n run: (editor, range) => chain(editor, range).setHeading({ level: 2 }).run(),\r\n },\r\n {\r\n id: 'h3',\r\n label: 'Heading 3',\r\n description: 'Small section title',\r\n icon: IconHeading3,\r\n markdown: '###',\r\n keywords: ['h3'],\r\n convertible: true,\r\n isActive: editor => editor.isActive('heading', { level: 3 }),\r\n run: (editor, range) => chain(editor, range).setHeading({ level: 3 }).run(),\r\n },\r\n {\r\n id: 'bullet',\r\n label: 'Bulleted list',\r\n description: 'A simple list',\r\n icon: IconBulletList,\r\n markdown: '-',\r\n keywords: ['ul', 'unordered', 'bullets'],\r\n convertible: true,\r\n isActive: editor => editor.isActive('bulletList'),\r\n run: (editor, range) => chain(editor, range).toggleBulletList().run(),\r\n },\r\n {\r\n id: 'ordered',\r\n label: 'Numbered list',\r\n description: 'A list with numbers',\r\n icon: IconOrderedList,\r\n markdown: '1.',\r\n keywords: ['ol', 'ordered', 'numbers'],\r\n convertible: true,\r\n isActive: editor => editor.isActive('orderedList'),\r\n run: (editor, range) => chain(editor, range).toggleOrderedList().run(),\r\n },\r\n {\r\n id: 'task',\r\n label: 'To-do list',\r\n description: 'Checkboxes you can tick off',\r\n icon: IconTaskList,\r\n markdown: '[ ]',\r\n keywords: ['todo', 'task', 'checkbox', 'checklist'],\r\n convertible: true,\r\n isActive: editor => editor.isActive('taskList'),\r\n run: (editor, range) => chain(editor, range).toggleTaskList().run(),\r\n },\r\n {\r\n id: 'quote',\r\n label: 'Quote',\r\n description: 'Set a passage apart',\r\n icon: IconQuote,\r\n markdown: '>',\r\n keywords: ['blockquote', 'citation'],\r\n convertible: true,\r\n isActive: editor => editor.isActive('blockquote'),\r\n run: (editor, range) => chain(editor, range).toggleBlockquote().run(),\r\n },\r\n {\r\n id: 'code',\r\n label: 'Code block',\r\n description: 'Monospaced, whitespace kept',\r\n icon: IconCodeBlock,\r\n markdown: '```',\r\n keywords: ['pre', 'snippet', 'code'],\r\n convertible: true,\r\n isActive: editor => editor.isActive('codeBlock'),\r\n run: (editor, range) => chain(editor, range).toggleCodeBlock().run(),\r\n },\r\n {\r\n id: 'divider',\r\n label: 'Divider',\r\n description: 'A line between sections',\r\n icon: IconDivider,\r\n markdown: '---',\r\n keywords: ['hr', 'rule', 'separator', 'line'],\r\n convertible: false,\r\n run: (editor, range) => chain(editor, range).setHorizontalRule().run(),\r\n },\r\n]\r\n","<script setup lang=\"ts\">\r\n/** Internal: a toolbar / bubble-menu button with a tooltip that shows its shortcut. */\r\nimport JKbd from '../components/kbd/JKbd.vue'\r\nimport JTooltip from '../components/tooltip/JTooltip.vue'\r\n\r\ndefineProps<{\r\n label: string\r\n /** Hotkey syntax, e.g. `mod+b`. */\r\n shortcut?: string\r\n /** Toggle state; renders `aria-pressed`. Leave undefined for plain actions. */\r\n active?: boolean\r\n disabled?: boolean\r\n side?: 'top' | 'bottom'\r\n}>()\r\n\r\nconst emit = defineEmits<{ click: [event: MouseEvent] }>()\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <JTooltip :side=\"side ?? 'top'\" :delay=\"500\" :disabled=\"disabled\">\r\n <button\r\n type=\"button\"\r\n class=\"j-editor-button j-focusable\"\r\n :class=\"{ 'is-active': active }\"\r\n :aria-label=\"label\"\r\n :aria-pressed=\"active === undefined ? undefined : active\"\r\n :disabled=\"disabled\"\r\n data-editor-control\r\n @pointerdown.prevent\r\n @click=\"emit('click', $event)\"\r\n >\r\n <slot />\r\n </button>\r\n <template #content>\r\n {{ label }}<JKbd v-if=\"shortcut\" :keys=\"shortcut\" size=\"sm\" class=\"j-editor-button__keys\" />\r\n </template>\r\n </JTooltip>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-editor-button {\r\n display: inline-grid;\r\n flex: none;\r\n place-items: center;\r\n width: var(--juxt-control-sm);\r\n height: var(--juxt-control-sm);\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-secondary);\r\n cursor: pointer;\r\n -webkit-tap-highlight-color: transparent;\r\n transition-property: background-color, color, outline-color, outline-offset, transform;\r\n}\r\n\r\n.j-editor-button > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-editor-button:hover:not(:disabled) {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-editor-button:active:not(:disabled) {\r\n transform: scale(0.94);\r\n transition-duration: var(--juxt-duration-instant);\r\n}\r\n\r\n.j-editor-button.is-active {\r\n background: var(--juxt-surface-active);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-editor-button:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-editor-button__keys {\r\n margin-left: var(--juxt-space-1-5);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-editor-button:active:not(:disabled) {\r\n transform: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\n/** Internal: a toolbar / bubble-menu button with a tooltip that shows its shortcut. */\r\nimport JKbd from '../components/kbd/JKbd.vue'\r\nimport JTooltip from '../components/tooltip/JTooltip.vue'\r\n\r\ndefineProps<{\r\n label: string\r\n /** Hotkey syntax, e.g. `mod+b`. */\r\n shortcut?: string\r\n /** Toggle state; renders `aria-pressed`. Leave undefined for plain actions. */\r\n active?: boolean\r\n disabled?: boolean\r\n side?: 'top' | 'bottom'\r\n}>()\r\n\r\nconst emit = defineEmits<{ click: [event: MouseEvent] }>()\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <JTooltip :side=\"side ?? 'top'\" :delay=\"500\" :disabled=\"disabled\">\r\n <button\r\n type=\"button\"\r\n class=\"j-editor-button j-focusable\"\r\n :class=\"{ 'is-active': active }\"\r\n :aria-label=\"label\"\r\n :aria-pressed=\"active === undefined ? undefined : active\"\r\n :disabled=\"disabled\"\r\n data-editor-control\r\n @pointerdown.prevent\r\n @click=\"emit('click', $event)\"\r\n >\r\n <slot />\r\n </button>\r\n <template #content>\r\n {{ label }}<JKbd v-if=\"shortcut\" :keys=\"shortcut\" size=\"sm\" class=\"j-editor-button__keys\" />\r\n </template>\r\n </JTooltip>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-editor-button {\r\n display: inline-grid;\r\n flex: none;\r\n place-items: center;\r\n width: var(--juxt-control-sm);\r\n height: var(--juxt-control-sm);\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-secondary);\r\n cursor: pointer;\r\n -webkit-tap-highlight-color: transparent;\r\n transition-property: background-color, color, outline-color, outline-offset, transform;\r\n}\r\n\r\n.j-editor-button > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-editor-button:hover:not(:disabled) {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-editor-button:active:not(:disabled) {\r\n transform: scale(0.94);\r\n transition-duration: var(--juxt-duration-instant);\r\n}\r\n\r\n.j-editor-button.is-active {\r\n background: var(--juxt-surface-active);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-editor-button:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-editor-button__keys {\r\n margin-left: var(--juxt-space-1-5);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-editor-button:active:not(:disabled) {\r\n transform: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","import type { Editor, Range } from '@tiptap/core'\r\nimport type { BlockCommand } from './commands'\r\nimport { Extension } from '@tiptap/core'\r\nimport { PluginKey } from '@tiptap/pm/state'\r\nimport Suggestion from '@tiptap/suggestion'\r\nimport { matchText } from '../components/command-palette/search'\r\nimport { blockCommands } from './commands'\r\n\r\n/** Reactive state the editor component renders the slash menu from. */\r\nexport interface SlashState {\r\n open: boolean\r\n query: string\r\n items: BlockCommand[]\r\n index: number\r\n rect: DOMRect | null\r\n select: ((item: BlockCommand) => void) | null\r\n}\r\n\r\nexport function filterCommands(query: string): BlockCommand[] {\r\n const q = query.trim()\r\n if (!q) return blockCommands\r\n return blockCommands\r\n .map((command) => {\r\n const score = Math.max(\r\n matchText(q, command.label).score,\r\n ...command.keywords.map(keyword => matchText(q, keyword).score * 0.9),\r\n )\r\n return { command, score }\r\n })\r\n .filter(entry => entry.score > 0)\r\n .sort((a, b) => b.score - a.score)\r\n .map(entry => entry.command)\r\n}\r\n\r\nexport const slashPluginKey = new PluginKey('juxtSlash')\r\n\r\n/**\r\n * Typing `/` opens a menu of blocks at the caret. The menu itself is rendered\r\n * by JEditor from `state`; this extension only feeds it and handles keys.\r\n */\r\nexport function createSlashCommands(state: SlashState) {\r\n return Extension.create({\r\n name: 'juxtSlashCommands',\r\n addProseMirrorPlugins() {\r\n return [\r\n Suggestion<BlockCommand>({\r\n editor: this.editor,\r\n pluginKey: slashPluginKey,\r\n char: '/',\r\n // Only in plain paragraphs: not in code, headings or mid-word.\r\n allow: ({ state: editorState, range }) => {\r\n const $from = editorState.doc.resolve(range.from)\r\n return $from.parent.type.name === 'paragraph' && !$from.parent.type.spec.code\r\n },\r\n items: ({ query }) => filterCommands(query),\r\n command: ({ editor, range, props }: { editor: Editor, range: Range, props: BlockCommand }) => props.run(editor, range),\r\n render: () => {\r\n let select: ((item: BlockCommand) => void) | null = null\r\n return {\r\n onStart: (props) => {\r\n select = item => props.command(item)\r\n Object.assign(state, {\r\n open: true,\r\n query: props.query,\r\n items: props.items,\r\n index: 0,\r\n rect: props.clientRect?.() ?? null,\r\n select,\r\n })\r\n },\r\n onUpdate: (props) => {\r\n select = item => props.command(item)\r\n // A new query puts the best match back under the cursor.\r\n const index = props.query !== state.query ? 0 : Math.min(state.index, Math.max(0, props.items.length - 1))\r\n Object.assign(state, {\r\n query: props.query,\r\n items: props.items,\r\n index,\r\n rect: props.clientRect?.() ?? null,\r\n select,\r\n })\r\n },\r\n onKeyDown: ({ event }) => {\r\n if (!state.open) return false\r\n const count = state.items.length\r\n if (event.key === 'ArrowDown') {\r\n if (count) state.index = (state.index + 1) % count\r\n return true\r\n }\r\n if (event.key === 'ArrowUp') {\r\n if (count) state.index = (state.index - 1 + count) % count\r\n return true\r\n }\r\n if (event.key === 'Enter' || event.key === 'Tab') {\r\n const item = state.items[state.index]\r\n if (!item) return false\r\n select?.(item)\r\n return true\r\n }\r\n if (event.key === 'Escape') {\r\n state.open = false\r\n return true\r\n }\r\n return false\r\n },\r\n onExit: () => {\r\n Object.assign(state, { open: false, items: [], query: '', rect: null, select: null })\r\n },\r\n }\r\n },\r\n }),\r\n ]\r\n },\r\n })\r\n}\r\n","<script setup lang=\"ts\">\r\nimport type { Editor, JSONContent } from '@tiptap/core'\r\nimport type { FieldProps } from '../components/field/useField'\r\nimport type { BlockCommand } from './commands'\r\nimport type { SlashState } from './slash'\r\nimport { Extension } from '@tiptap/core'\r\nimport { Highlight } from '@tiptap/extension-highlight'\r\nimport { TaskItem, TaskList } from '@tiptap/extension-list'\r\nimport { Typography } from '@tiptap/extension-typography'\r\nimport { CharacterCount, Placeholder } from '@tiptap/extensions'\r\nimport { StarterKit } from '@tiptap/starter-kit'\r\nimport { EditorContent, useEditor } from '@tiptap/vue-3'\r\nimport { BubbleMenu } from '@tiptap/vue-3/menus'\r\nimport { computed, nextTick, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue'\r\nimport JDropdownMenu from '../components/dropdown-menu/JDropdownMenu.vue'\r\nimport JDropdownMenuItem from '../components/dropdown-menu/JDropdownMenuItem.vue'\r\nimport JFieldShell from '../components/field/JFieldShell.vue'\r\nimport { useField } from '../components/field/useField'\r\nimport JPortal from '../components/primitives/JPortal.vue'\r\nimport { useFloating } from '../composables/useFloating'\r\nimport { useLayer } from '../composables/useLayer'\r\nimport { blockCommands } from './commands'\r\nimport {\r\n IconBold,\r\n IconChevronDown,\r\n IconCode,\r\n IconCornerDownLeft,\r\n IconExternal,\r\n IconHighlight,\r\n IconItalic,\r\n IconLink,\r\n IconRedo,\r\n IconStrike,\r\n IconUnderline,\r\n IconUndo,\r\n IconUnlink,\r\n} from './icons'\r\nimport JEditorButton from './JEditorButton.vue'\r\nimport { createSlashCommands } from './slash'\r\n\r\nexport type EditorFormat = 'html' | 'json'\r\n\r\nexport interface EditorProps extends FieldProps {\r\n placeholder?: string\r\n /** Shape of `v-model`: an HTML string, or Tiptap/ProseMirror JSON. */\r\n format?: EditorFormat\r\n /** `full` shows every tool, `minimal` just the inline marks and lists, `false` hides the toolbar. */\r\n toolbar?: 'full' | 'minimal' | false\r\n /** Formatting menu that floats above selected text. */\r\n bubbleMenu?: boolean\r\n /** Type `/` for a menu of blocks. */\r\n slashCommands?: boolean\r\n /** Maximum number of characters. Shown in the footer and enforced while typing. */\r\n limit?: number\r\n /** Show word and character counts. */\r\n showCount?: boolean\r\n /** Height of the writing area before it grows. Any CSS length. */\r\n minHeight?: string\r\n /** Height after which the writing area scrolls. Any CSS length. */\r\n maxHeight?: string\r\n readonly?: boolean\r\n autofocus?: boolean | 'start' | 'end'\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<EditorProps>(), {\r\n placeholder: 'Write something, or type / for blocks…',\r\n format: 'html',\r\n toolbar: 'full',\r\n bubbleMenu: true,\r\n slashCommands: true,\r\n showCount: false,\r\n minHeight: '10rem',\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<string | JSONContent | null>({ default: '' })\r\n\r\nconst emit = defineEmits<{\r\n /** The editor instance is ready, for advanced use. */\r\n ready: [editor: Editor]\r\n focus: []\r\n blur: []\r\n}>()\r\n\r\ndefineSlots<{\r\n /** Extra controls at the end of the toolbar. */\r\n 'toolbar-end'?: (props: { editor: Editor }) => unknown\r\n 'label'?: () => unknown\r\n}>()\r\n\r\nconst field = useField(props)\r\nconst root = ref<HTMLElement | null>(null)\r\nconst slashId = `${field.id.value}-blocks`\r\n\r\n// --- Slash menu state -----------------------------------------------------\r\nconst slash = reactive<SlashState>({ open: false, query: '', items: [], index: 0, rect: null, select: null })\r\n\r\n// --- Editor ----------------------------------------------------------------\r\nlet lastEmitted: string | null = null\r\nconst serialize = (value: unknown) => (typeof value === 'string' ? value : JSON.stringify(value ?? ''))\r\n\r\nfunction contentFromModel() {\r\n const value = model.value\r\n if (!value) return ''\r\n return value\r\n}\r\n\r\nconst LinkShortcut = Extension.create({\r\n name: 'juxtLinkShortcut',\r\n addKeyboardShortcuts() {\r\n return {\r\n 'Mod-k': () => {\r\n openLink()\r\n return true\r\n },\r\n }\r\n },\r\n})\r\n\r\nconst editor = useEditor({\r\n content: contentFromModel(),\r\n editable: !props.disabled && !props.readonly,\r\n autofocus: props.autofocus ?? false,\r\n extensions: [\r\n StarterKit.configure({\r\n heading: { levels: [1, 2, 3] },\r\n link: {\r\n openOnClick: false,\r\n autolink: true,\r\n defaultProtocol: 'https',\r\n HTMLAttributes: { rel: 'noopener noreferrer nofollow', target: null },\r\n },\r\n codeBlock: { HTMLAttributes: { spellcheck: 'false' } },\r\n dropcursor: { color: 'var(--juxt-accent)', width: 2 },\r\n }),\r\n TaskList,\r\n TaskItem.configure({ nested: true }),\r\n Highlight,\r\n Typography,\r\n Placeholder.configure({\r\n // Empty document: the placeholder. Empty heading: its level. Empty line\r\n // under the caret: a quiet hint that blocks are a slash away.\r\n placeholder: ({ editor: instance, node }) => {\r\n if (node.type.name === 'heading') return `Heading ${node.attrs.level}`\r\n if (instance.isEmpty) return props.placeholder\r\n return props.slashCommands ? 'Type / for blocks' : ''\r\n },\r\n }),\r\n CharacterCount.configure({ limit: props.limit ?? null }),\r\n LinkShortcut,\r\n ...(props.slashCommands ? [createSlashCommands(slash)] : []),\r\n ],\r\n editorProps: {\r\n attributes: editorAttributes(),\r\n },\r\n onCreate: ({ editor: instance }) => emit('ready', instance),\r\n onFocus: () => emit('focus'),\r\n onBlur: () => emit('blur'),\r\n onUpdate: ({ editor: instance }) => {\r\n const value = props.format === 'json' ? instance.getJSON() : instance.isEmpty ? '' : instance.getHTML()\r\n lastEmitted = serialize(value)\r\n model.value = value\r\n },\r\n})\r\n\r\nfunction editorAttributes(): Record<string, string> {\r\n const attrs: Record<string, string> = {\r\n 'role': 'textbox',\r\n 'aria-multiline': 'true',\r\n 'class': 'j-editor__prose',\r\n }\r\n if (props.label || props.id) attrs['aria-labelledby'] = field.labelId.value\r\n if (field.describedBy.value) attrs['aria-describedby'] = field.describedBy.value\r\n if (field.invalid.value) attrs['aria-invalid'] = 'true'\r\n if (props.required) attrs['aria-required'] = 'true'\r\n if (props.disabled || props.readonly) attrs['aria-readonly'] = 'true'\r\n if (slash.open) {\r\n attrs['aria-controls'] = slashId\r\n attrs['aria-expanded'] = 'true'\r\n const active = slash.items[slash.index]\r\n if (active) attrs['aria-activedescendant'] = `${slashId}-${active.id}`\r\n }\r\n return attrs\r\n}\r\n\r\n// Keep ARIA on the editable element in sync with the field and the slash menu.\r\nwatch(\r\n [() => field.describedBy.value, () => field.invalid.value, () => props.disabled, () => props.readonly, () => slash.open, () => slash.index, () => slash.items],\r\n () => editor.value?.setOptions({ editorProps: { attributes: editorAttributes() } }),\r\n)\r\n\r\n// Outside changes to v-model replace the content; our own echoes don't.\r\nwatch(model, (value) => {\r\n const instance = editor.value\r\n if (!instance || serialize(value) === lastEmitted) return\r\n instance.commands.setContent(value || '', { emitUpdate: false })\r\n lastEmitted = serialize(value)\r\n})\r\n\r\nwatch(() => [props.disabled, props.readonly], () => editor.value?.setEditable(!props.disabled && !props.readonly))\r\n\r\n// --- Toolbar state -----------------------------------------------------------\r\nconst currentBlock = computed<BlockCommand>(() => {\r\n const instance = editor.value\r\n const fallback = blockCommands[0]!\r\n if (!instance) return fallback\r\n return blockCommands.find(command => command.convertible && command.id !== 'text' && command.isActive?.(instance)) ?? fallback\r\n})\r\n\r\nconst can = computed(() => ({\r\n undo: !!editor.value?.can().undo(),\r\n redo: !!editor.value?.can().redo(),\r\n}))\r\n\r\nconst editable = computed(() => !!editor.value && !props.disabled && !props.readonly)\r\n\r\nfunction run(fn: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>) {\r\n const instance = editor.value\r\n if (!instance) return\r\n fn(instance.chain().focus()).run()\r\n}\r\n\r\n// Arrow keys move along the toolbar; Tab moves past it (roving tabindex).\r\nfunction onToolbarKeydown(event: KeyboardEvent) {\r\n if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft' && event.key !== 'Home' && event.key !== 'End') return\r\n const toolbar = event.currentTarget as HTMLElement\r\n const items = Array.from(toolbar.querySelectorAll<HTMLElement>('[data-editor-control]:not(:disabled)'))\r\n const index = items.indexOf(document.activeElement as HTMLElement)\r\n if (index === -1) return\r\n event.preventDefault()\r\n let next = index\r\n if (event.key === 'ArrowRight') next = (index + 1) % items.length\r\n else if (event.key === 'ArrowLeft') next = (index - 1 + items.length) % items.length\r\n else if (event.key === 'Home') next = 0\r\n else next = items.length - 1\r\n items.forEach((item, i) => (item.tabIndex = i === next ? 0 : -1))\r\n items[next]!.focus()\r\n}\r\n\r\nfunction initRovingTabindex(el: unknown) {\r\n const toolbar = el as HTMLElement | null\r\n if (!toolbar) return\r\n nextTick(() => {\r\n const items = Array.from(toolbar.querySelectorAll<HTMLElement>('[data-editor-control]'))\r\n if (items.some(item => item.tabIndex === 0)) return\r\n items.forEach((item, i) => (item.tabIndex = i === 0 ? 0 : -1))\r\n })\r\n}\r\n\r\n// --- Floating panels positioned at the text -------------------------------\r\nfunction virtualAt(getRect: () => DOMRect | null) {\r\n return {\r\n getBoundingClientRect: () => getRect() ?? new DOMRect(),\r\n get contextElement() {\r\n return editor.value?.view.dom\r\n },\r\n } as unknown as HTMLElement\r\n}\r\n\r\n// Slash menu\r\nconst slashEl = ref<HTMLElement | null>(null)\r\nconst slashRef = shallowRef<HTMLElement | null>(virtualAt(() => slash.rect))\r\nconst { styles: slashStyles, update: updateSlash } = useFloating(slashRef, slashEl, { placement: 'bottom-start', offset: 6 })\r\nwatch(() => slash.rect, () => nextTick(updateSlash))\r\nwatch(() => slash.index, () => nextTick(() => document.getElementById(`${slashId}-${slash.items[slash.index]?.id}`)?.scrollIntoView({ block: 'nearest' })))\r\n\r\n// Link editor\r\nconst link = reactive({ open: false, url: '', rect: null as DOMRect | null, active: false })\r\nconst linkEl = ref<HTMLElement | null>(null)\r\nconst linkInput = ref<HTMLInputElement | null>(null)\r\nconst linkRef = shallowRef<HTMLElement | null>(virtualAt(() => link.rect))\r\nconst { styles: linkStyles, update: updateLink } = useFloating(linkRef, linkEl, { placement: 'bottom-start', offset: 8 })\r\n\r\nfunction selectionRect(instance: Editor): DOMRect {\r\n const { from, to } = instance.state.selection\r\n const start = instance.view.coordsAtPos(from)\r\n const end = instance.view.coordsAtPos(to)\r\n const left = Math.min(start.left, end.left)\r\n const right = Math.max(start.right, end.right)\r\n return new DOMRect(left, start.top, Math.max(1, right - left), end.bottom - start.top)\r\n}\r\n\r\nfunction openLink() {\r\n const instance = editor.value\r\n if (!instance || !editable.value) return\r\n if (instance.isActive('link')) instance.chain().extendMarkRange('link').run()\r\n link.active = instance.isActive('link')\r\n link.url = (instance.getAttributes('link').href as string | undefined) ?? ''\r\n link.rect = selectionRect(instance)\r\n link.open = true\r\n nextTick(() => {\r\n updateLink()\r\n linkInput.value?.focus()\r\n linkInput.value?.select()\r\n })\r\n}\r\n\r\nfunction closeLink(refocus = true) {\r\n link.open = false\r\n if (refocus) editor.value?.commands.focus()\r\n}\r\n\r\nfunction normalizeUrl(raw: string): string | null {\r\n const value = raw.trim()\r\n if (!value) return null\r\n if (/^(?:javascript|data|vbscript):/i.test(value)) return null\r\n if (/^(?:https?:|mailto:|tel:|\\/|#)/i.test(value)) return value\r\n if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value)) return `mailto:${value}`\r\n return `https://${value}`\r\n}\r\n\r\nfunction applyLink() {\r\n const instance = editor.value\r\n if (!instance) return\r\n const href = normalizeUrl(link.url)\r\n if (!href) {\r\n removeLink()\r\n return\r\n }\r\n if (instance.state.selection.empty && !instance.isActive('link')) {\r\n instance.chain().focus().insertContent({ type: 'text', text: link.url.trim(), marks: [{ type: 'link', attrs: { href } }] }).run()\r\n } else {\r\n instance.chain().focus().extendMarkRange('link').setLink({ href }).run()\r\n }\r\n closeLink(false)\r\n}\r\n\r\nfunction removeLink() {\r\n editor.value?.chain().focus().extendMarkRange('link').unsetLink().run()\r\n closeLink(false)\r\n}\r\n\r\nuseLayer({\r\n active: computed(() => link.open),\r\n elements: () => [linkEl.value],\r\n onEscape: () => closeLink(),\r\n onPointerDownOutside: () => closeLink(false),\r\n})\r\n\r\n// --- Bubble menu -------------------------------------------------------------\r\nfunction shouldShowBubble({ editor: instance, from, to }: { editor: Editor, from: number, to: number }) {\r\n if (!props.bubbleMenu || !instance.isEditable || link.open || slash.open) return false\r\n if (from === to || instance.isActive('codeBlock')) return false\r\n return instance.state.doc.textBetween(from, to).trim().length > 0\r\n}\r\n\r\n// --- Counts ------------------------------------------------------------------\r\nconst counts = computed(() => {\r\n const storage = editor.value?.storage.characterCount as { characters: () => number, words: () => number } | undefined\r\n return { characters: storage?.characters() ?? 0, words: storage?.words() ?? 0 }\r\n})\r\nconst nearLimit = computed(() => !!props.limit && counts.value.characters >= props.limit * 0.9)\r\nconst atLimit = computed(() => !!props.limit && counts.value.characters >= props.limit)\r\n\r\nfunction focusEditor(event: MouseEvent) {\r\n // Clicking the empty space under the text puts the caret at the end.\r\n if (event.target === event.currentTarget && editable.value) editor.value?.commands.focus('end')\r\n}\r\n\r\nonBeforeUnmount(() => editor.value?.destroy())\r\n\r\ndefineExpose({\r\n /** The underlying Tiptap editor. */\r\n editor,\r\n focus: (position?: 'start' | 'end') => editor.value?.commands.focus(position ?? null),\r\n clear: () => editor.value?.commands.clearContent(true),\r\n})\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n :label-for=\"null\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n\r\n <div\r\n ref=\"root\"\r\n class=\"j-editor\"\r\n :class=\"{ 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }\"\r\n :style=\"{ '--j-editor-min': minHeight, '--j-editor-max': maxHeight ?? 'none' }\"\r\n >\r\n <!-- Toolbar -->\r\n <div\r\n v-if=\"toolbar\"\r\n :ref=\"initRovingTabindex\"\r\n role=\"toolbar\"\r\n aria-label=\"Formatting\"\r\n class=\"j-editor__toolbar\"\r\n @keydown=\"onToolbarKeydown\"\r\n >\r\n <template v-if=\"toolbar === 'full'\">\r\n <JDropdownMenu :disabled=\"!editable\">\r\n <template #trigger>\r\n <button\r\n type=\"button\"\r\n class=\"j-editor__block j-focusable\"\r\n :disabled=\"!editable\"\r\n aria-label=\"Block type\"\r\n data-editor-control\r\n @pointerdown.prevent\r\n >\r\n <component :is=\"currentBlock.icon\" />\r\n <span class=\"j-editor__block-label\">{{ currentBlock.label }}</span>\r\n <IconChevronDown class=\"j-editor__block-chevron\" />\r\n </button>\r\n </template>\r\n <JDropdownMenuItem\r\n v-for=\"command in blockCommands.filter(c => c.convertible)\"\r\n :key=\"command.id\"\r\n :checked=\"command.id === currentBlock.id\"\r\n @select=\"editor && command.run(editor)\"\r\n >\r\n <template #leading>\r\n <component :is=\"command.icon\" />\r\n </template>\r\n {{ command.label }}\r\n </JDropdownMenuItem>\r\n </JDropdownMenu>\r\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\r\n </template>\r\n\r\n <JEditorButton label=\"Bold\" shortcut=\"mod+b\" :active=\"!!editor?.isActive('bold')\" :disabled=\"!editable\" @click=\"run(c => c.toggleBold())\">\r\n <IconBold />\r\n </JEditorButton>\r\n <JEditorButton label=\"Italic\" shortcut=\"mod+i\" :active=\"!!editor?.isActive('italic')\" :disabled=\"!editable\" @click=\"run(c => c.toggleItalic())\">\r\n <IconItalic />\r\n </JEditorButton>\r\n <JEditorButton label=\"Underline\" shortcut=\"mod+u\" :active=\"!!editor?.isActive('underline')\" :disabled=\"!editable\" @click=\"run(c => c.toggleUnderline())\">\r\n <IconUnderline />\r\n </JEditorButton>\r\n <JEditorButton v-if=\"toolbar === 'full'\" label=\"Strikethrough\" shortcut=\"mod+shift+s\" :active=\"!!editor?.isActive('strike')\" :disabled=\"!editable\" @click=\"run(c => c.toggleStrike())\">\r\n <IconStrike />\r\n </JEditorButton>\r\n <JEditorButton v-if=\"toolbar === 'full'\" label=\"Inline code\" shortcut=\"mod+e\" :active=\"!!editor?.isActive('code')\" :disabled=\"!editable\" @click=\"run(c => c.toggleCode())\">\r\n <IconCode />\r\n </JEditorButton>\r\n <JEditorButton label=\"Link\" shortcut=\"mod+k\" :active=\"!!editor?.isActive('link')\" :disabled=\"!editable\" @click=\"openLink\">\r\n <IconLink />\r\n </JEditorButton>\r\n\r\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\r\n\r\n <JEditorButton\r\n v-for=\"command in blockCommands.filter(c => (toolbar === 'full' ? ['bullet', 'ordered', 'task', 'quote', 'code', 'divider'] : ['bullet', 'ordered']).includes(c.id))\"\r\n :key=\"command.id\"\r\n :label=\"command.label\"\r\n :active=\"command.isActive ? !!(editor && command.isActive(editor)) : undefined\"\r\n :disabled=\"!editable\"\r\n @click=\"editor && command.run(editor)\"\r\n >\r\n <component :is=\"command.icon\" />\r\n </JEditorButton>\r\n\r\n <span class=\"j-editor__spacer\" />\r\n\r\n <slot v-if=\"editor\" name=\"toolbar-end\" :editor=\"editor\" />\r\n <JEditorButton label=\"Undo\" shortcut=\"mod+z\" :disabled=\"!editable || !can.undo\" @click=\"run(c => c.undo())\">\r\n <IconUndo />\r\n </JEditorButton>\r\n <JEditorButton label=\"Redo\" shortcut=\"mod+shift+z\" :disabled=\"!editable || !can.redo\" @click=\"run(c => c.redo())\">\r\n <IconRedo />\r\n </JEditorButton>\r\n </div>\r\n\r\n <!-- Writing area -->\r\n <div class=\"j-editor__content\" @click=\"focusEditor\">\r\n <EditorContent v-if=\"editor\" :editor=\"editor\" />\r\n <p v-else class=\"j-editor__prose j-editor__ssr\" aria-hidden=\"true\">\r\n {{ placeholder }}\r\n </p>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div v-if=\"showCount || limit\" class=\"j-editor__footer\" aria-live=\"polite\">\r\n <span>{{ counts.words }} {{ counts.words === 1 ? 'word' : 'words' }}</span>\r\n <span :class=\"{ 'is-near': nearLimit, 'is-at': atLimit }\">\r\n {{ counts.characters }}<template v-if=\"limit\"> / {{ limit }}</template> characters\r\n </span>\r\n </div>\r\n </div>\r\n\r\n <!-- Selection toolbar -->\r\n <BubbleMenu\r\n v-if=\"editor && bubbleMenu\"\r\n :editor=\"editor\"\r\n :should-show=\"shouldShowBubble\"\r\n :options=\"{ placement: 'top', offset: 8 }\"\r\n class=\"j-editor__bubble\"\r\n >\r\n <JEditorButton label=\"Bold\" shortcut=\"mod+b\" side=\"bottom\" :active=\"editor.isActive('bold')\" @click=\"run(c => c.toggleBold())\">\r\n <IconBold />\r\n </JEditorButton>\r\n <JEditorButton label=\"Italic\" shortcut=\"mod+i\" side=\"bottom\" :active=\"editor.isActive('italic')\" @click=\"run(c => c.toggleItalic())\">\r\n <IconItalic />\r\n </JEditorButton>\r\n <JEditorButton label=\"Underline\" shortcut=\"mod+u\" side=\"bottom\" :active=\"editor.isActive('underline')\" @click=\"run(c => c.toggleUnderline())\">\r\n <IconUnderline />\r\n </JEditorButton>\r\n <JEditorButton label=\"Strikethrough\" shortcut=\"mod+shift+s\" side=\"bottom\" :active=\"editor.isActive('strike')\" @click=\"run(c => c.toggleStrike())\">\r\n <IconStrike />\r\n </JEditorButton>\r\n <JEditorButton label=\"Inline code\" shortcut=\"mod+e\" side=\"bottom\" :active=\"editor.isActive('code')\" @click=\"run(c => c.toggleCode())\">\r\n <IconCode />\r\n </JEditorButton>\r\n <JEditorButton label=\"Highlight\" shortcut=\"mod+shift+h\" side=\"bottom\" :active=\"editor.isActive('highlight')\" @click=\"run(c => c.toggleHighlight())\">\r\n <IconHighlight />\r\n </JEditorButton>\r\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\r\n <JEditorButton label=\"Link\" shortcut=\"mod+k\" side=\"bottom\" :active=\"editor.isActive('link')\" @click=\"openLink\">\r\n <IconLink />\r\n </JEditorButton>\r\n </BubbleMenu>\r\n\r\n <JPortal>\r\n <!-- Slash menu -->\r\n <Transition name=\"j-pop\">\r\n <div\r\n v-if=\"slash.open\"\r\n :id=\"slashId\"\r\n ref=\"slashEl\"\r\n role=\"listbox\"\r\n aria-label=\"Blocks\"\r\n class=\"j-editor__slash j-popover-surface\"\r\n :style=\"slashStyles\"\r\n @pointerdown.prevent\r\n >\r\n <template v-if=\"slash.items.length\">\r\n <div class=\"j-editor__slash-label\" role=\"presentation\">\r\n Blocks\r\n </div>\r\n <div\r\n v-for=\"(item, i) in slash.items\"\r\n :id=\"`${slashId}-${item.id}`\"\r\n :key=\"item.id\"\r\n role=\"option\"\r\n class=\"j-editor__slash-item\"\r\n :class=\"{ 'is-active': i === slash.index }\"\r\n :aria-selected=\"i === slash.index\"\r\n @pointermove=\"slash.index = i\"\r\n @click=\"slash.select?.(item)\"\r\n >\r\n <span class=\"j-editor__slash-icon\"><component :is=\"item.icon\" /></span>\r\n <span class=\"j-editor__slash-text\">\r\n <span class=\"j-editor__slash-title\">{{ item.label }}</span>\r\n <span class=\"j-editor__slash-description\">{{ item.description }}</span>\r\n </span>\r\n <kbd v-if=\"item.markdown\" class=\"j-editor__slash-md\">{{ item.markdown }}</kbd>\r\n </div>\r\n </template>\r\n <div v-else class=\"j-editor__slash-empty\">\r\n No blocks match “{{ slash.query }}”\r\n </div>\r\n </div>\r\n </Transition>\r\n\r\n <!-- Link editor -->\r\n <Transition name=\"j-pop\">\r\n <form\r\n v-if=\"link.open\"\r\n ref=\"linkEl\"\r\n class=\"j-editor__link j-popover-surface\"\r\n :style=\"linkStyles\"\r\n @submit.prevent=\"applyLink\"\r\n >\r\n <IconLink class=\"j-editor__link-icon\" />\r\n <input\r\n ref=\"linkInput\"\r\n v-model=\"link.url\"\r\n class=\"j-editor__link-input\"\r\n type=\"text\"\r\n inputmode=\"url\"\r\n placeholder=\"Paste or type a link\"\r\n aria-label=\"Link address\"\r\n autocomplete=\"off\"\r\n spellcheck=\"false\"\r\n />\r\n <button type=\"submit\" class=\"j-editor-button j-focusable\" aria-label=\"Apply link\">\r\n <IconCornerDownLeft />\r\n </button>\r\n <a\r\n v-if=\"link.active && normalizeUrl(link.url)\"\r\n :href=\"normalizeUrl(link.url)!\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n class=\"j-editor-button j-focusable\"\r\n aria-label=\"Open link in a new tab\"\r\n >\r\n <IconExternal />\r\n </a>\r\n <button v-if=\"link.active\" type=\"button\" class=\"j-editor-button j-focusable\" aria-label=\"Remove link\" @click=\"removeLink\">\r\n <IconUnlink />\r\n </button>\r\n </form>\r\n </Transition>\r\n </JPortal>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/* Frame: the same border, focus halo and invalid states as Input. */\r\n.j-editor {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n transition:\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n box-shadow var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.dark .j-editor,\r\n[data-theme='dark'] .j-editor {\r\n background: var(--juxt-surface-sunken);\r\n}\r\n\r\n.j-editor:hover:not(.is-disabled, :focus-within) {\r\n border-color: var(--juxt-border-strong);\r\n}\r\n\r\n.j-editor:has(.ProseMirror-focused) {\r\n border-color: var(--juxt-accent);\r\n box-shadow: 0 0 0 3px var(--juxt-ring-soft);\r\n}\r\n\r\n.j-editor.is-invalid {\r\n border-color: var(--juxt-danger-border);\r\n}\r\n\r\n.j-editor.is-invalid:has(.ProseMirror-focused) {\r\n border-color: var(--juxt-danger);\r\n box-shadow: 0 0 0 3px var(--juxt-danger-ring);\r\n}\r\n\r\n.j-editor.is-disabled {\r\n background: var(--juxt-surface-sunken);\r\n box-shadow: none;\r\n opacity: 0.7;\r\n}\r\n\r\n/* Toolbar: stays reachable while you scroll a long document. */\r\n.j-editor__toolbar {\r\n position: sticky;\r\n top: var(--j-editor-sticky-top, 0);\r\n z-index: 1;\r\n display: flex;\r\n align-items: center;\r\n gap: 2px;\r\n padding: var(--juxt-space-1);\r\n overflow-x: auto;\r\n border-bottom: 1px solid var(--juxt-border-subtle);\r\n border-radius: var(--juxt-radius-md) var(--juxt-radius-md) 0 0;\r\n background: inherit;\r\n scrollbar-width: none;\r\n}\r\n\r\n.j-editor__toolbar::-webkit-scrollbar {\r\n display: none;\r\n}\r\n\r\n.dark .j-editor__toolbar,\r\n[data-theme='dark'] .j-editor__toolbar {\r\n border-bottom-color: var(--juxt-border);\r\n}\r\n\r\n.j-editor__divider {\r\n flex: none;\r\n width: 1px;\r\n height: 1rem;\r\n margin: 0 var(--juxt-space-1);\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-editor__spacer {\r\n flex: 1;\r\n min-width: var(--juxt-space-2);\r\n}\r\n\r\n.j-editor__block {\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-1-5);\r\n height: var(--juxt-control-sm);\r\n padding: 0 var(--juxt-space-2);\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n cursor: pointer;\r\n transition-property: background-color, outline-color, outline-offset;\r\n}\r\n\r\n.j-editor__block:hover:not(:disabled) {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-editor__block:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-editor__block > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-editor__block-label {\r\n min-width: 5.5rem;\r\n text-align: left;\r\n}\r\n\r\n.j-editor__block .j-editor__block-chevron {\r\n width: 0.75rem;\r\n height: 0.75rem;\r\n}\r\n\r\n/* Writing area */\r\n.j-editor__content {\r\n flex: 1;\r\n min-height: var(--j-editor-min);\r\n max-height: var(--j-editor-max);\r\n padding: var(--juxt-space-3) var(--juxt-space-4);\r\n overflow-y: auto;\r\n cursor: text;\r\n}\r\n\r\n.j-editor.is-disabled .j-editor__content,\r\n.j-editor.is-readonly .j-editor__content {\r\n cursor: default;\r\n}\r\n\r\n.j-editor__ssr {\r\n margin: 0;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-editor__footer {\r\n display: flex;\r\n justify-content: space-between;\r\n gap: var(--juxt-space-3);\r\n padding: var(--juxt-space-1-5) var(--juxt-space-4);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.dark .j-editor__footer,\r\n[data-theme='dark'] .j-editor__footer {\r\n border-top-color: var(--juxt-border);\r\n}\r\n\r\n.j-editor__footer .is-near {\r\n color: var(--juxt-warning-text);\r\n}\r\n\r\n.j-editor__footer .is-at {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n/* ---------------------------------------------------------------------------\r\n * Document typography. Deliberately close to the docs prose: calm, readable,\r\n * a clear hierarchy without shouting.\r\n * ------------------------------------------------------------------------ */\r\n.j-editor__prose {\r\n min-height: calc(var(--j-editor-min) - var(--juxt-space-6));\r\n color: var(--juxt-fg);\r\n font-size: 0.9375rem;\r\n line-height: 1.7;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: pre-wrap;\r\n word-wrap: break-word;\r\n outline: none;\r\n font-variant-ligatures: none;\r\n font-feature-settings: 'liga' 0;\r\n}\r\n\r\n.j-editor__prose > * + * {\r\n margin-top: 0.75em;\r\n}\r\n\r\n.j-editor__prose p {\r\n margin: 0;\r\n}\r\n\r\n.j-editor__prose :is(h1, h2, h3) {\r\n margin: 1.4em 0 0;\r\n color: var(--juxt-fg);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: 1.3;\r\n letter-spacing: var(--juxt-tracking-tighter);\r\n}\r\n\r\n.j-editor__prose > :is(h1, h2, h3):first-child {\r\n margin-top: 0;\r\n}\r\n\r\n.j-editor__prose h1 {\r\n font-size: 1.625rem;\r\n}\r\n\r\n.j-editor__prose h2 {\r\n font-size: 1.3125rem;\r\n}\r\n\r\n.j-editor__prose h3 {\r\n font-size: 1.0625rem;\r\n}\r\n\r\n.j-editor__prose :is(ul, ol) {\r\n margin-bottom: 0;\r\n padding-left: 1.375em;\r\n}\r\n\r\n.j-editor__prose li + li {\r\n margin-top: 0.25em;\r\n}\r\n\r\n.j-editor__prose li > p {\r\n margin: 0;\r\n}\r\n\r\n.j-editor__prose li::marker {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-editor__prose ul ul {\r\n list-style-type: circle;\r\n}\r\n\r\n/* To-do items look and feel like JCheckbox. */\r\n.j-editor__prose ul[data-type='taskList'] {\r\n padding-left: 0.125em;\r\n list-style: none;\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] li {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 0.625em;\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] li > label {\r\n display: inline-flex;\r\n flex: none;\r\n margin-top: 0.2em;\r\n user-select: none;\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] li > div {\r\n flex: 1;\r\n min-width: 0;\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox'] {\r\n display: grid;\r\n place-content: center;\r\n width: 1rem;\r\n height: 1rem;\r\n margin: 0;\r\n border: 1px solid var(--juxt-border-strong);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface);\r\n cursor: pointer;\r\n appearance: none;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']::after {\r\n content: '';\r\n width: 0.5rem;\r\n height: 0.28rem;\r\n margin-top: -0.1rem;\r\n border-bottom: 2px solid var(--juxt-accent-fg);\r\n border-left: 2px solid var(--juxt-accent-fg);\r\n opacity: 0;\r\n transform: rotate(-45deg) scale(0.6);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:checked {\r\n border-color: var(--juxt-accent);\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:checked::after {\r\n opacity: 1;\r\n transform: rotate(-45deg);\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:focus-visible {\r\n outline: 2px solid var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-editor__prose li[data-checked='true'] > div {\r\n color: var(--juxt-fg-muted);\r\n text-decoration: line-through;\r\n text-decoration-color: var(--juxt-border-strong);\r\n}\r\n\r\n.j-editor__prose blockquote {\r\n margin-left: 0;\r\n margin-right: 0;\r\n padding-left: 1em;\r\n border-left: 2px solid var(--juxt-border-strong);\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-editor__prose a {\r\n color: var(--juxt-fg);\r\n text-decoration: underline;\r\n text-decoration-color: var(--juxt-border-strong);\r\n text-underline-offset: 3px;\r\n cursor: text;\r\n transition: text-decoration-color var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-editor__prose a:hover {\r\n text-decoration-color: var(--juxt-accent);\r\n}\r\n\r\n.j-editor__prose code {\r\n padding: 0.1em 0.3em;\r\n border: 1px solid var(--juxt-border-subtle);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface-sunken);\r\n font-family: var(--juxt-font-mono);\r\n font-size: 0.85em;\r\n}\r\n\r\n.dark .j-editor__prose code,\r\n[data-theme='dark'] .j-editor__prose code {\r\n border-color: var(--juxt-border);\r\n background: var(--juxt-surface-raised);\r\n}\r\n\r\n.j-editor__prose pre {\r\n padding: 0.875em 1em;\r\n overflow-x: auto;\r\n border: 1px solid var(--juxt-border-subtle);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-sunken);\r\n font-family: var(--juxt-font-mono);\r\n font-size: 0.8125rem;\r\n line-height: 1.65;\r\n white-space: pre;\r\n}\r\n\r\n.dark .j-editor__prose pre,\r\n[data-theme='dark'] .j-editor__prose pre {\r\n border-color: var(--juxt-border);\r\n background: var(--juxt-bg);\r\n}\r\n\r\n.j-editor__prose pre code {\r\n padding: 0;\r\n border: 0;\r\n background: none;\r\n font-size: inherit;\r\n}\r\n\r\n.j-editor__prose hr {\r\n height: 1px;\r\n margin: 1.5em 0;\r\n border: 0;\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-editor__prose hr.ProseMirror-selectednode {\r\n outline: 2px solid var(--juxt-ring);\r\n outline-offset: 4px;\r\n}\r\n\r\n.j-editor__prose mark {\r\n padding: 0.05em 0.1em;\r\n border-radius: 2px;\r\n background: var(--juxt-warning-soft);\r\n color: inherit;\r\n}\r\n\r\n.j-editor__prose ::selection {\r\n background: var(--juxt-accent-soft);\r\n}\r\n\r\n/* Placeholder: on an empty document, and on each empty heading. */\r\n.j-editor__prose .is-empty::before {\r\n content: attr(data-placeholder);\r\n float: left;\r\n height: 0;\r\n color: var(--juxt-fg-muted);\r\n pointer-events: none;\r\n}\r\n\r\n/* The per-line hint is quieter than the document placeholder. */\r\n.j-editor__prose .is-empty:not(.is-editor-empty):not(h1, h2, h3)::before {\r\n color: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-editor__prose .ProseMirror-gapcursor {\r\n display: none;\r\n position: relative;\r\n pointer-events: none;\r\n}\r\n\r\n.j-editor__prose.ProseMirror-focused .ProseMirror-gapcursor {\r\n display: block;\r\n}\r\n\r\n.j-editor__prose .ProseMirror-gapcursor::after {\r\n content: '';\r\n position: absolute;\r\n top: -2px;\r\n display: block;\r\n width: 20px;\r\n border-top: 1px solid var(--juxt-fg);\r\n animation: j-editor-cursor 1.1s steps(2, start) infinite;\r\n}\r\n\r\n@keyframes j-editor-cursor {\r\n to {\r\n visibility: hidden;\r\n }\r\n}\r\n\r\n/* Selection toolbar */\r\n.j-editor__bubble {\r\n z-index: var(--juxt-z-popover);\r\n display: flex;\r\n align-items: center;\r\n gap: 2px;\r\n padding: 3px;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-md);\r\n animation: j-editor-bubble-in var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n@keyframes j-editor-bubble-in {\r\n from {\r\n opacity: 0;\r\n transform: translateY(calc(var(--juxt-motion-shift) * 0.5)) scale(var(--juxt-motion-scale));\r\n }\r\n}\r\n\r\n/* Slash menu */\r\n.j-editor__slash {\r\n z-index: var(--juxt-z-popover);\r\n width: 17rem;\r\n max-height: min(20rem, var(--j-available-height, 20rem));\r\n padding: var(--juxt-space-1);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n}\r\n\r\n.j-editor__slash-label {\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2) var(--juxt-space-1);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n.j-editor__slash-item {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2-5);\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-sm);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-editor__slash-item.is-active {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-editor__slash-item.is-active::before {\r\n content: '';\r\n position: absolute;\r\n top: 0.625rem;\r\n bottom: 0.625rem;\r\n left: 0;\r\n width: 2px;\r\n border-radius: 0 2px 2px 0;\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-editor__slash-icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 2rem;\r\n height: 2rem;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-surface);\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-editor__slash-icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-editor__slash-text {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n min-width: 0;\r\n}\r\n\r\n.j-editor__slash-title {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n.j-editor__slash-description {\r\n overflow: hidden;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-editor__slash-md {\r\n color: var(--juxt-fg-muted);\r\n font-family: var(--juxt-font-mono);\r\n font-size: var(--juxt-text-2xs);\r\n}\r\n\r\n.j-editor__slash-empty {\r\n padding: var(--juxt-space-3) var(--juxt-space-2);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n}\r\n\r\n/* Link editor */\r\n.j-editor__link {\r\n z-index: var(--juxt-z-popover);\r\n display: flex;\r\n align-items: center;\r\n gap: 2px;\r\n width: min(22rem, calc(100vw - 16px));\r\n padding: 3px 3px 3px var(--juxt-space-2-5);\r\n}\r\n\r\n.j-editor__link-icon {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-editor__link-input {\r\n flex: 1;\r\n min-width: 0;\r\n height: var(--juxt-control-sm);\r\n padding: 0 var(--juxt-space-1-5);\r\n border: 0;\r\n outline: none;\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-sm);\r\n}\r\n\r\n.j-editor__link-input::placeholder {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-editor__bubble {\r\n animation: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Editor, JSONContent } from '@tiptap/core'\r\nimport type { FieldProps } from '../components/field/useField'\r\nimport type { BlockCommand } from './commands'\r\nimport type { SlashState } from './slash'\r\nimport { Extension } from '@tiptap/core'\r\nimport { Highlight } from '@tiptap/extension-highlight'\r\nimport { TaskItem, TaskList } from '@tiptap/extension-list'\r\nimport { Typography } from '@tiptap/extension-typography'\r\nimport { CharacterCount, Placeholder } from '@tiptap/extensions'\r\nimport { StarterKit } from '@tiptap/starter-kit'\r\nimport { EditorContent, useEditor } from '@tiptap/vue-3'\r\nimport { BubbleMenu } from '@tiptap/vue-3/menus'\r\nimport { computed, nextTick, onBeforeUnmount, reactive, ref, shallowRef, watch } from 'vue'\r\nimport JDropdownMenu from '../components/dropdown-menu/JDropdownMenu.vue'\r\nimport JDropdownMenuItem from '../components/dropdown-menu/JDropdownMenuItem.vue'\r\nimport JFieldShell from '../components/field/JFieldShell.vue'\r\nimport { useField } from '../components/field/useField'\r\nimport JPortal from '../components/primitives/JPortal.vue'\r\nimport { useFloating } from '../composables/useFloating'\r\nimport { useLayer } from '../composables/useLayer'\r\nimport { blockCommands } from './commands'\r\nimport {\r\n IconBold,\r\n IconChevronDown,\r\n IconCode,\r\n IconCornerDownLeft,\r\n IconExternal,\r\n IconHighlight,\r\n IconItalic,\r\n IconLink,\r\n IconRedo,\r\n IconStrike,\r\n IconUnderline,\r\n IconUndo,\r\n IconUnlink,\r\n} from './icons'\r\nimport JEditorButton from './JEditorButton.vue'\r\nimport { createSlashCommands } from './slash'\r\n\r\nexport type EditorFormat = 'html' | 'json'\r\n\r\nexport interface EditorProps extends FieldProps {\r\n placeholder?: string\r\n /** Shape of `v-model`: an HTML string, or Tiptap/ProseMirror JSON. */\r\n format?: EditorFormat\r\n /** `full` shows every tool, `minimal` just the inline marks and lists, `false` hides the toolbar. */\r\n toolbar?: 'full' | 'minimal' | false\r\n /** Formatting menu that floats above selected text. */\r\n bubbleMenu?: boolean\r\n /** Type `/` for a menu of blocks. */\r\n slashCommands?: boolean\r\n /** Maximum number of characters. Shown in the footer and enforced while typing. */\r\n limit?: number\r\n /** Show word and character counts. */\r\n showCount?: boolean\r\n /** Height of the writing area before it grows. Any CSS length. */\r\n minHeight?: string\r\n /** Height after which the writing area scrolls. Any CSS length. */\r\n maxHeight?: string\r\n readonly?: boolean\r\n autofocus?: boolean | 'start' | 'end'\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<EditorProps>(), {\r\n placeholder: 'Write something, or type / for blocks…',\r\n format: 'html',\r\n toolbar: 'full',\r\n bubbleMenu: true,\r\n slashCommands: true,\r\n showCount: false,\r\n minHeight: '10rem',\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<string | JSONContent | null>({ default: '' })\r\n\r\nconst emit = defineEmits<{\r\n /** The editor instance is ready, for advanced use. */\r\n ready: [editor: Editor]\r\n focus: []\r\n blur: []\r\n}>()\r\n\r\ndefineSlots<{\r\n /** Extra controls at the end of the toolbar. */\r\n 'toolbar-end'?: (props: { editor: Editor }) => unknown\r\n 'label'?: () => unknown\r\n}>()\r\n\r\nconst field = useField(props)\r\nconst root = ref<HTMLElement | null>(null)\r\nconst slashId = `${field.id.value}-blocks`\r\n\r\n// --- Slash menu state -----------------------------------------------------\r\nconst slash = reactive<SlashState>({ open: false, query: '', items: [], index: 0, rect: null, select: null })\r\n\r\n// --- Editor ----------------------------------------------------------------\r\nlet lastEmitted: string | null = null\r\nconst serialize = (value: unknown) => (typeof value === 'string' ? value : JSON.stringify(value ?? ''))\r\n\r\nfunction contentFromModel() {\r\n const value = model.value\r\n if (!value) return ''\r\n return value\r\n}\r\n\r\nconst LinkShortcut = Extension.create({\r\n name: 'juxtLinkShortcut',\r\n addKeyboardShortcuts() {\r\n return {\r\n 'Mod-k': () => {\r\n openLink()\r\n return true\r\n },\r\n }\r\n },\r\n})\r\n\r\nconst editor = useEditor({\r\n content: contentFromModel(),\r\n editable: !props.disabled && !props.readonly,\r\n autofocus: props.autofocus ?? false,\r\n extensions: [\r\n StarterKit.configure({\r\n heading: { levels: [1, 2, 3] },\r\n link: {\r\n openOnClick: false,\r\n autolink: true,\r\n defaultProtocol: 'https',\r\n HTMLAttributes: { rel: 'noopener noreferrer nofollow', target: null },\r\n },\r\n codeBlock: { HTMLAttributes: { spellcheck: 'false' } },\r\n dropcursor: { color: 'var(--juxt-accent)', width: 2 },\r\n }),\r\n TaskList,\r\n TaskItem.configure({ nested: true }),\r\n Highlight,\r\n Typography,\r\n Placeholder.configure({\r\n // Empty document: the placeholder. Empty heading: its level. Empty line\r\n // under the caret: a quiet hint that blocks are a slash away.\r\n placeholder: ({ editor: instance, node }) => {\r\n if (node.type.name === 'heading') return `Heading ${node.attrs.level}`\r\n if (instance.isEmpty) return props.placeholder\r\n return props.slashCommands ? 'Type / for blocks' : ''\r\n },\r\n }),\r\n CharacterCount.configure({ limit: props.limit ?? null }),\r\n LinkShortcut,\r\n ...(props.slashCommands ? [createSlashCommands(slash)] : []),\r\n ],\r\n editorProps: {\r\n attributes: editorAttributes(),\r\n },\r\n onCreate: ({ editor: instance }) => emit('ready', instance),\r\n onFocus: () => emit('focus'),\r\n onBlur: () => emit('blur'),\r\n onUpdate: ({ editor: instance }) => {\r\n const value = props.format === 'json' ? instance.getJSON() : instance.isEmpty ? '' : instance.getHTML()\r\n lastEmitted = serialize(value)\r\n model.value = value\r\n },\r\n})\r\n\r\nfunction editorAttributes(): Record<string, string> {\r\n const attrs: Record<string, string> = {\r\n 'role': 'textbox',\r\n 'aria-multiline': 'true',\r\n 'class': 'j-editor__prose',\r\n }\r\n if (props.label || props.id) attrs['aria-labelledby'] = field.labelId.value\r\n if (field.describedBy.value) attrs['aria-describedby'] = field.describedBy.value\r\n if (field.invalid.value) attrs['aria-invalid'] = 'true'\r\n if (props.required) attrs['aria-required'] = 'true'\r\n if (props.disabled || props.readonly) attrs['aria-readonly'] = 'true'\r\n if (slash.open) {\r\n attrs['aria-controls'] = slashId\r\n attrs['aria-expanded'] = 'true'\r\n const active = slash.items[slash.index]\r\n if (active) attrs['aria-activedescendant'] = `${slashId}-${active.id}`\r\n }\r\n return attrs\r\n}\r\n\r\n// Keep ARIA on the editable element in sync with the field and the slash menu.\r\nwatch(\r\n [() => field.describedBy.value, () => field.invalid.value, () => props.disabled, () => props.readonly, () => slash.open, () => slash.index, () => slash.items],\r\n () => editor.value?.setOptions({ editorProps: { attributes: editorAttributes() } }),\r\n)\r\n\r\n// Outside changes to v-model replace the content; our own echoes don't.\r\nwatch(model, (value) => {\r\n const instance = editor.value\r\n if (!instance || serialize(value) === lastEmitted) return\r\n instance.commands.setContent(value || '', { emitUpdate: false })\r\n lastEmitted = serialize(value)\r\n})\r\n\r\nwatch(() => [props.disabled, props.readonly], () => editor.value?.setEditable(!props.disabled && !props.readonly))\r\n\r\n// --- Toolbar state -----------------------------------------------------------\r\nconst currentBlock = computed<BlockCommand>(() => {\r\n const instance = editor.value\r\n const fallback = blockCommands[0]!\r\n if (!instance) return fallback\r\n return blockCommands.find(command => command.convertible && command.id !== 'text' && command.isActive?.(instance)) ?? fallback\r\n})\r\n\r\nconst can = computed(() => ({\r\n undo: !!editor.value?.can().undo(),\r\n redo: !!editor.value?.can().redo(),\r\n}))\r\n\r\nconst editable = computed(() => !!editor.value && !props.disabled && !props.readonly)\r\n\r\nfunction run(fn: (chain: ReturnType<Editor['chain']>) => ReturnType<Editor['chain']>) {\r\n const instance = editor.value\r\n if (!instance) return\r\n fn(instance.chain().focus()).run()\r\n}\r\n\r\n// Arrow keys move along the toolbar; Tab moves past it (roving tabindex).\r\nfunction onToolbarKeydown(event: KeyboardEvent) {\r\n if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft' && event.key !== 'Home' && event.key !== 'End') return\r\n const toolbar = event.currentTarget as HTMLElement\r\n const items = Array.from(toolbar.querySelectorAll<HTMLElement>('[data-editor-control]:not(:disabled)'))\r\n const index = items.indexOf(document.activeElement as HTMLElement)\r\n if (index === -1) return\r\n event.preventDefault()\r\n let next = index\r\n if (event.key === 'ArrowRight') next = (index + 1) % items.length\r\n else if (event.key === 'ArrowLeft') next = (index - 1 + items.length) % items.length\r\n else if (event.key === 'Home') next = 0\r\n else next = items.length - 1\r\n items.forEach((item, i) => (item.tabIndex = i === next ? 0 : -1))\r\n items[next]!.focus()\r\n}\r\n\r\nfunction initRovingTabindex(el: unknown) {\r\n const toolbar = el as HTMLElement | null\r\n if (!toolbar) return\r\n nextTick(() => {\r\n const items = Array.from(toolbar.querySelectorAll<HTMLElement>('[data-editor-control]'))\r\n if (items.some(item => item.tabIndex === 0)) return\r\n items.forEach((item, i) => (item.tabIndex = i === 0 ? 0 : -1))\r\n })\r\n}\r\n\r\n// --- Floating panels positioned at the text -------------------------------\r\nfunction virtualAt(getRect: () => DOMRect | null) {\r\n return {\r\n getBoundingClientRect: () => getRect() ?? new DOMRect(),\r\n get contextElement() {\r\n return editor.value?.view.dom\r\n },\r\n } as unknown as HTMLElement\r\n}\r\n\r\n// Slash menu\r\nconst slashEl = ref<HTMLElement | null>(null)\r\nconst slashRef = shallowRef<HTMLElement | null>(virtualAt(() => slash.rect))\r\nconst { styles: slashStyles, update: updateSlash } = useFloating(slashRef, slashEl, { placement: 'bottom-start', offset: 6 })\r\nwatch(() => slash.rect, () => nextTick(updateSlash))\r\nwatch(() => slash.index, () => nextTick(() => document.getElementById(`${slashId}-${slash.items[slash.index]?.id}`)?.scrollIntoView({ block: 'nearest' })))\r\n\r\n// Link editor\r\nconst link = reactive({ open: false, url: '', rect: null as DOMRect | null, active: false })\r\nconst linkEl = ref<HTMLElement | null>(null)\r\nconst linkInput = ref<HTMLInputElement | null>(null)\r\nconst linkRef = shallowRef<HTMLElement | null>(virtualAt(() => link.rect))\r\nconst { styles: linkStyles, update: updateLink } = useFloating(linkRef, linkEl, { placement: 'bottom-start', offset: 8 })\r\n\r\nfunction selectionRect(instance: Editor): DOMRect {\r\n const { from, to } = instance.state.selection\r\n const start = instance.view.coordsAtPos(from)\r\n const end = instance.view.coordsAtPos(to)\r\n const left = Math.min(start.left, end.left)\r\n const right = Math.max(start.right, end.right)\r\n return new DOMRect(left, start.top, Math.max(1, right - left), end.bottom - start.top)\r\n}\r\n\r\nfunction openLink() {\r\n const instance = editor.value\r\n if (!instance || !editable.value) return\r\n if (instance.isActive('link')) instance.chain().extendMarkRange('link').run()\r\n link.active = instance.isActive('link')\r\n link.url = (instance.getAttributes('link').href as string | undefined) ?? ''\r\n link.rect = selectionRect(instance)\r\n link.open = true\r\n nextTick(() => {\r\n updateLink()\r\n linkInput.value?.focus()\r\n linkInput.value?.select()\r\n })\r\n}\r\n\r\nfunction closeLink(refocus = true) {\r\n link.open = false\r\n if (refocus) editor.value?.commands.focus()\r\n}\r\n\r\nfunction normalizeUrl(raw: string): string | null {\r\n const value = raw.trim()\r\n if (!value) return null\r\n if (/^(?:javascript|data|vbscript):/i.test(value)) return null\r\n if (/^(?:https?:|mailto:|tel:|\\/|#)/i.test(value)) return value\r\n if (/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value)) return `mailto:${value}`\r\n return `https://${value}`\r\n}\r\n\r\nfunction applyLink() {\r\n const instance = editor.value\r\n if (!instance) return\r\n const href = normalizeUrl(link.url)\r\n if (!href) {\r\n removeLink()\r\n return\r\n }\r\n if (instance.state.selection.empty && !instance.isActive('link')) {\r\n instance.chain().focus().insertContent({ type: 'text', text: link.url.trim(), marks: [{ type: 'link', attrs: { href } }] }).run()\r\n } else {\r\n instance.chain().focus().extendMarkRange('link').setLink({ href }).run()\r\n }\r\n closeLink(false)\r\n}\r\n\r\nfunction removeLink() {\r\n editor.value?.chain().focus().extendMarkRange('link').unsetLink().run()\r\n closeLink(false)\r\n}\r\n\r\nuseLayer({\r\n active: computed(() => link.open),\r\n elements: () => [linkEl.value],\r\n onEscape: () => closeLink(),\r\n onPointerDownOutside: () => closeLink(false),\r\n})\r\n\r\n// --- Bubble menu -------------------------------------------------------------\r\nfunction shouldShowBubble({ editor: instance, from, to }: { editor: Editor, from: number, to: number }) {\r\n if (!props.bubbleMenu || !instance.isEditable || link.open || slash.open) return false\r\n if (from === to || instance.isActive('codeBlock')) return false\r\n return instance.state.doc.textBetween(from, to).trim().length > 0\r\n}\r\n\r\n// --- Counts ------------------------------------------------------------------\r\nconst counts = computed(() => {\r\n const storage = editor.value?.storage.characterCount as { characters: () => number, words: () => number } | undefined\r\n return { characters: storage?.characters() ?? 0, words: storage?.words() ?? 0 }\r\n})\r\nconst nearLimit = computed(() => !!props.limit && counts.value.characters >= props.limit * 0.9)\r\nconst atLimit = computed(() => !!props.limit && counts.value.characters >= props.limit)\r\n\r\nfunction focusEditor(event: MouseEvent) {\r\n // Clicking the empty space under the text puts the caret at the end.\r\n if (event.target === event.currentTarget && editable.value) editor.value?.commands.focus('end')\r\n}\r\n\r\nonBeforeUnmount(() => editor.value?.destroy())\r\n\r\ndefineExpose({\r\n /** The underlying Tiptap editor. */\r\n editor,\r\n focus: (position?: 'start' | 'end') => editor.value?.commands.focus(position ?? null),\r\n clear: () => editor.value?.commands.clearContent(true),\r\n})\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n :label-for=\"null\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n\r\n <div\r\n ref=\"root\"\r\n class=\"j-editor\"\r\n :class=\"{ 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }\"\r\n :style=\"{ '--j-editor-min': minHeight, '--j-editor-max': maxHeight ?? 'none' }\"\r\n >\r\n <!-- Toolbar -->\r\n <div\r\n v-if=\"toolbar\"\r\n :ref=\"initRovingTabindex\"\r\n role=\"toolbar\"\r\n aria-label=\"Formatting\"\r\n class=\"j-editor__toolbar\"\r\n @keydown=\"onToolbarKeydown\"\r\n >\r\n <template v-if=\"toolbar === 'full'\">\r\n <JDropdownMenu :disabled=\"!editable\">\r\n <template #trigger>\r\n <button\r\n type=\"button\"\r\n class=\"j-editor__block j-focusable\"\r\n :disabled=\"!editable\"\r\n aria-label=\"Block type\"\r\n data-editor-control\r\n @pointerdown.prevent\r\n >\r\n <component :is=\"currentBlock.icon\" />\r\n <span class=\"j-editor__block-label\">{{ currentBlock.label }}</span>\r\n <IconChevronDown class=\"j-editor__block-chevron\" />\r\n </button>\r\n </template>\r\n <JDropdownMenuItem\r\n v-for=\"command in blockCommands.filter(c => c.convertible)\"\r\n :key=\"command.id\"\r\n :checked=\"command.id === currentBlock.id\"\r\n @select=\"editor && command.run(editor)\"\r\n >\r\n <template #leading>\r\n <component :is=\"command.icon\" />\r\n </template>\r\n {{ command.label }}\r\n </JDropdownMenuItem>\r\n </JDropdownMenu>\r\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\r\n </template>\r\n\r\n <JEditorButton label=\"Bold\" shortcut=\"mod+b\" :active=\"!!editor?.isActive('bold')\" :disabled=\"!editable\" @click=\"run(c => c.toggleBold())\">\r\n <IconBold />\r\n </JEditorButton>\r\n <JEditorButton label=\"Italic\" shortcut=\"mod+i\" :active=\"!!editor?.isActive('italic')\" :disabled=\"!editable\" @click=\"run(c => c.toggleItalic())\">\r\n <IconItalic />\r\n </JEditorButton>\r\n <JEditorButton label=\"Underline\" shortcut=\"mod+u\" :active=\"!!editor?.isActive('underline')\" :disabled=\"!editable\" @click=\"run(c => c.toggleUnderline())\">\r\n <IconUnderline />\r\n </JEditorButton>\r\n <JEditorButton v-if=\"toolbar === 'full'\" label=\"Strikethrough\" shortcut=\"mod+shift+s\" :active=\"!!editor?.isActive('strike')\" :disabled=\"!editable\" @click=\"run(c => c.toggleStrike())\">\r\n <IconStrike />\r\n </JEditorButton>\r\n <JEditorButton v-if=\"toolbar === 'full'\" label=\"Inline code\" shortcut=\"mod+e\" :active=\"!!editor?.isActive('code')\" :disabled=\"!editable\" @click=\"run(c => c.toggleCode())\">\r\n <IconCode />\r\n </JEditorButton>\r\n <JEditorButton label=\"Link\" shortcut=\"mod+k\" :active=\"!!editor?.isActive('link')\" :disabled=\"!editable\" @click=\"openLink\">\r\n <IconLink />\r\n </JEditorButton>\r\n\r\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\r\n\r\n <JEditorButton\r\n v-for=\"command in blockCommands.filter(c => (toolbar === 'full' ? ['bullet', 'ordered', 'task', 'quote', 'code', 'divider'] : ['bullet', 'ordered']).includes(c.id))\"\r\n :key=\"command.id\"\r\n :label=\"command.label\"\r\n :active=\"command.isActive ? !!(editor && command.isActive(editor)) : undefined\"\r\n :disabled=\"!editable\"\r\n @click=\"editor && command.run(editor)\"\r\n >\r\n <component :is=\"command.icon\" />\r\n </JEditorButton>\r\n\r\n <span class=\"j-editor__spacer\" />\r\n\r\n <slot v-if=\"editor\" name=\"toolbar-end\" :editor=\"editor\" />\r\n <JEditorButton label=\"Undo\" shortcut=\"mod+z\" :disabled=\"!editable || !can.undo\" @click=\"run(c => c.undo())\">\r\n <IconUndo />\r\n </JEditorButton>\r\n <JEditorButton label=\"Redo\" shortcut=\"mod+shift+z\" :disabled=\"!editable || !can.redo\" @click=\"run(c => c.redo())\">\r\n <IconRedo />\r\n </JEditorButton>\r\n </div>\r\n\r\n <!-- Writing area -->\r\n <div class=\"j-editor__content\" @click=\"focusEditor\">\r\n <EditorContent v-if=\"editor\" :editor=\"editor\" />\r\n <p v-else class=\"j-editor__prose j-editor__ssr\" aria-hidden=\"true\">\r\n {{ placeholder }}\r\n </p>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div v-if=\"showCount || limit\" class=\"j-editor__footer\" aria-live=\"polite\">\r\n <span>{{ counts.words }} {{ counts.words === 1 ? 'word' : 'words' }}</span>\r\n <span :class=\"{ 'is-near': nearLimit, 'is-at': atLimit }\">\r\n {{ counts.characters }}<template v-if=\"limit\"> / {{ limit }}</template> characters\r\n </span>\r\n </div>\r\n </div>\r\n\r\n <!-- Selection toolbar -->\r\n <BubbleMenu\r\n v-if=\"editor && bubbleMenu\"\r\n :editor=\"editor\"\r\n :should-show=\"shouldShowBubble\"\r\n :options=\"{ placement: 'top', offset: 8 }\"\r\n class=\"j-editor__bubble\"\r\n >\r\n <JEditorButton label=\"Bold\" shortcut=\"mod+b\" side=\"bottom\" :active=\"editor.isActive('bold')\" @click=\"run(c => c.toggleBold())\">\r\n <IconBold />\r\n </JEditorButton>\r\n <JEditorButton label=\"Italic\" shortcut=\"mod+i\" side=\"bottom\" :active=\"editor.isActive('italic')\" @click=\"run(c => c.toggleItalic())\">\r\n <IconItalic />\r\n </JEditorButton>\r\n <JEditorButton label=\"Underline\" shortcut=\"mod+u\" side=\"bottom\" :active=\"editor.isActive('underline')\" @click=\"run(c => c.toggleUnderline())\">\r\n <IconUnderline />\r\n </JEditorButton>\r\n <JEditorButton label=\"Strikethrough\" shortcut=\"mod+shift+s\" side=\"bottom\" :active=\"editor.isActive('strike')\" @click=\"run(c => c.toggleStrike())\">\r\n <IconStrike />\r\n </JEditorButton>\r\n <JEditorButton label=\"Inline code\" shortcut=\"mod+e\" side=\"bottom\" :active=\"editor.isActive('code')\" @click=\"run(c => c.toggleCode())\">\r\n <IconCode />\r\n </JEditorButton>\r\n <JEditorButton label=\"Highlight\" shortcut=\"mod+shift+h\" side=\"bottom\" :active=\"editor.isActive('highlight')\" @click=\"run(c => c.toggleHighlight())\">\r\n <IconHighlight />\r\n </JEditorButton>\r\n <span class=\"j-editor__divider\" aria-hidden=\"true\" />\r\n <JEditorButton label=\"Link\" shortcut=\"mod+k\" side=\"bottom\" :active=\"editor.isActive('link')\" @click=\"openLink\">\r\n <IconLink />\r\n </JEditorButton>\r\n </BubbleMenu>\r\n\r\n <JPortal>\r\n <!-- Slash menu -->\r\n <Transition name=\"j-pop\">\r\n <div\r\n v-if=\"slash.open\"\r\n :id=\"slashId\"\r\n ref=\"slashEl\"\r\n role=\"listbox\"\r\n aria-label=\"Blocks\"\r\n class=\"j-editor__slash j-popover-surface\"\r\n :style=\"slashStyles\"\r\n @pointerdown.prevent\r\n >\r\n <template v-if=\"slash.items.length\">\r\n <div class=\"j-editor__slash-label\" role=\"presentation\">\r\n Blocks\r\n </div>\r\n <div\r\n v-for=\"(item, i) in slash.items\"\r\n :id=\"`${slashId}-${item.id}`\"\r\n :key=\"item.id\"\r\n role=\"option\"\r\n class=\"j-editor__slash-item\"\r\n :class=\"{ 'is-active': i === slash.index }\"\r\n :aria-selected=\"i === slash.index\"\r\n @pointermove=\"slash.index = i\"\r\n @click=\"slash.select?.(item)\"\r\n >\r\n <span class=\"j-editor__slash-icon\"><component :is=\"item.icon\" /></span>\r\n <span class=\"j-editor__slash-text\">\r\n <span class=\"j-editor__slash-title\">{{ item.label }}</span>\r\n <span class=\"j-editor__slash-description\">{{ item.description }}</span>\r\n </span>\r\n <kbd v-if=\"item.markdown\" class=\"j-editor__slash-md\">{{ item.markdown }}</kbd>\r\n </div>\r\n </template>\r\n <div v-else class=\"j-editor__slash-empty\">\r\n No blocks match “{{ slash.query }}”\r\n </div>\r\n </div>\r\n </Transition>\r\n\r\n <!-- Link editor -->\r\n <Transition name=\"j-pop\">\r\n <form\r\n v-if=\"link.open\"\r\n ref=\"linkEl\"\r\n class=\"j-editor__link j-popover-surface\"\r\n :style=\"linkStyles\"\r\n @submit.prevent=\"applyLink\"\r\n >\r\n <IconLink class=\"j-editor__link-icon\" />\r\n <input\r\n ref=\"linkInput\"\r\n v-model=\"link.url\"\r\n class=\"j-editor__link-input\"\r\n type=\"text\"\r\n inputmode=\"url\"\r\n placeholder=\"Paste or type a link\"\r\n aria-label=\"Link address\"\r\n autocomplete=\"off\"\r\n spellcheck=\"false\"\r\n />\r\n <button type=\"submit\" class=\"j-editor-button j-focusable\" aria-label=\"Apply link\">\r\n <IconCornerDownLeft />\r\n </button>\r\n <a\r\n v-if=\"link.active && normalizeUrl(link.url)\"\r\n :href=\"normalizeUrl(link.url)!\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n class=\"j-editor-button j-focusable\"\r\n aria-label=\"Open link in a new tab\"\r\n >\r\n <IconExternal />\r\n </a>\r\n <button v-if=\"link.active\" type=\"button\" class=\"j-editor-button j-focusable\" aria-label=\"Remove link\" @click=\"removeLink\">\r\n <IconUnlink />\r\n </button>\r\n </form>\r\n </Transition>\r\n </JPortal>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/* Frame: the same border, focus halo and invalid states as Input. */\r\n.j-editor {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n transition:\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n box-shadow var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.dark .j-editor,\r\n[data-theme='dark'] .j-editor {\r\n background: var(--juxt-surface-sunken);\r\n}\r\n\r\n.j-editor:hover:not(.is-disabled, :focus-within) {\r\n border-color: var(--juxt-border-strong);\r\n}\r\n\r\n.j-editor:has(.ProseMirror-focused) {\r\n border-color: var(--juxt-accent);\r\n box-shadow: 0 0 0 3px var(--juxt-ring-soft);\r\n}\r\n\r\n.j-editor.is-invalid {\r\n border-color: var(--juxt-danger-border);\r\n}\r\n\r\n.j-editor.is-invalid:has(.ProseMirror-focused) {\r\n border-color: var(--juxt-danger);\r\n box-shadow: 0 0 0 3px var(--juxt-danger-ring);\r\n}\r\n\r\n.j-editor.is-disabled {\r\n background: var(--juxt-surface-sunken);\r\n box-shadow: none;\r\n opacity: 0.7;\r\n}\r\n\r\n/* Toolbar: stays reachable while you scroll a long document. */\r\n.j-editor__toolbar {\r\n position: sticky;\r\n top: var(--j-editor-sticky-top, 0);\r\n z-index: 1;\r\n display: flex;\r\n align-items: center;\r\n gap: 2px;\r\n padding: var(--juxt-space-1);\r\n overflow-x: auto;\r\n border-bottom: 1px solid var(--juxt-border-subtle);\r\n border-radius: var(--juxt-radius-md) var(--juxt-radius-md) 0 0;\r\n background: inherit;\r\n scrollbar-width: none;\r\n}\r\n\r\n.j-editor__toolbar::-webkit-scrollbar {\r\n display: none;\r\n}\r\n\r\n.dark .j-editor__toolbar,\r\n[data-theme='dark'] .j-editor__toolbar {\r\n border-bottom-color: var(--juxt-border);\r\n}\r\n\r\n.j-editor__divider {\r\n flex: none;\r\n width: 1px;\r\n height: 1rem;\r\n margin: 0 var(--juxt-space-1);\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-editor__spacer {\r\n flex: 1;\r\n min-width: var(--juxt-space-2);\r\n}\r\n\r\n.j-editor__block {\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-1-5);\r\n height: var(--juxt-control-sm);\r\n padding: 0 var(--juxt-space-2);\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n cursor: pointer;\r\n transition-property: background-color, outline-color, outline-offset;\r\n}\r\n\r\n.j-editor__block:hover:not(:disabled) {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-editor__block:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-editor__block > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-editor__block-label {\r\n min-width: 5.5rem;\r\n text-align: left;\r\n}\r\n\r\n.j-editor__block .j-editor__block-chevron {\r\n width: 0.75rem;\r\n height: 0.75rem;\r\n}\r\n\r\n/* Writing area */\r\n.j-editor__content {\r\n flex: 1;\r\n min-height: var(--j-editor-min);\r\n max-height: var(--j-editor-max);\r\n padding: var(--juxt-space-3) var(--juxt-space-4);\r\n overflow-y: auto;\r\n cursor: text;\r\n}\r\n\r\n.j-editor.is-disabled .j-editor__content,\r\n.j-editor.is-readonly .j-editor__content {\r\n cursor: default;\r\n}\r\n\r\n.j-editor__ssr {\r\n margin: 0;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-editor__footer {\r\n display: flex;\r\n justify-content: space-between;\r\n gap: var(--juxt-space-3);\r\n padding: var(--juxt-space-1-5) var(--juxt-space-4);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.dark .j-editor__footer,\r\n[data-theme='dark'] .j-editor__footer {\r\n border-top-color: var(--juxt-border);\r\n}\r\n\r\n.j-editor__footer .is-near {\r\n color: var(--juxt-warning-text);\r\n}\r\n\r\n.j-editor__footer .is-at {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n/* ---------------------------------------------------------------------------\r\n * Document typography. Deliberately close to the docs prose: calm, readable,\r\n * a clear hierarchy without shouting.\r\n * ------------------------------------------------------------------------ */\r\n.j-editor__prose {\r\n min-height: calc(var(--j-editor-min) - var(--juxt-space-6));\r\n color: var(--juxt-fg);\r\n font-size: 0.9375rem;\r\n line-height: 1.7;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: pre-wrap;\r\n word-wrap: break-word;\r\n outline: none;\r\n font-variant-ligatures: none;\r\n font-feature-settings: 'liga' 0;\r\n}\r\n\r\n.j-editor__prose > * + * {\r\n margin-top: 0.75em;\r\n}\r\n\r\n.j-editor__prose p {\r\n margin: 0;\r\n}\r\n\r\n.j-editor__prose :is(h1, h2, h3) {\r\n margin: 1.4em 0 0;\r\n color: var(--juxt-fg);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: 1.3;\r\n letter-spacing: var(--juxt-tracking-tighter);\r\n}\r\n\r\n.j-editor__prose > :is(h1, h2, h3):first-child {\r\n margin-top: 0;\r\n}\r\n\r\n.j-editor__prose h1 {\r\n font-size: 1.625rem;\r\n}\r\n\r\n.j-editor__prose h2 {\r\n font-size: 1.3125rem;\r\n}\r\n\r\n.j-editor__prose h3 {\r\n font-size: 1.0625rem;\r\n}\r\n\r\n.j-editor__prose :is(ul, ol) {\r\n margin-bottom: 0;\r\n padding-left: 1.375em;\r\n}\r\n\r\n.j-editor__prose li + li {\r\n margin-top: 0.25em;\r\n}\r\n\r\n.j-editor__prose li > p {\r\n margin: 0;\r\n}\r\n\r\n.j-editor__prose li::marker {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-editor__prose ul ul {\r\n list-style-type: circle;\r\n}\r\n\r\n/* To-do items look and feel like JCheckbox. */\r\n.j-editor__prose ul[data-type='taskList'] {\r\n padding-left: 0.125em;\r\n list-style: none;\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] li {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 0.625em;\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] li > label {\r\n display: inline-flex;\r\n flex: none;\r\n margin-top: 0.2em;\r\n user-select: none;\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] li > div {\r\n flex: 1;\r\n min-width: 0;\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox'] {\r\n display: grid;\r\n place-content: center;\r\n width: 1rem;\r\n height: 1rem;\r\n margin: 0;\r\n border: 1px solid var(--juxt-border-strong);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface);\r\n cursor: pointer;\r\n appearance: none;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']::after {\r\n content: '';\r\n width: 0.5rem;\r\n height: 0.28rem;\r\n margin-top: -0.1rem;\r\n border-bottom: 2px solid var(--juxt-accent-fg);\r\n border-left: 2px solid var(--juxt-accent-fg);\r\n opacity: 0;\r\n transform: rotate(-45deg) scale(0.6);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:checked {\r\n border-color: var(--juxt-accent);\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:checked::after {\r\n opacity: 1;\r\n transform: rotate(-45deg);\r\n}\r\n\r\n.j-editor__prose ul[data-type='taskList'] input[type='checkbox']:focus-visible {\r\n outline: 2px solid var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-editor__prose li[data-checked='true'] > div {\r\n color: var(--juxt-fg-muted);\r\n text-decoration: line-through;\r\n text-decoration-color: var(--juxt-border-strong);\r\n}\r\n\r\n.j-editor__prose blockquote {\r\n margin-left: 0;\r\n margin-right: 0;\r\n padding-left: 1em;\r\n border-left: 2px solid var(--juxt-border-strong);\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-editor__prose a {\r\n color: var(--juxt-fg);\r\n text-decoration: underline;\r\n text-decoration-color: var(--juxt-border-strong);\r\n text-underline-offset: 3px;\r\n cursor: text;\r\n transition: text-decoration-color var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-editor__prose a:hover {\r\n text-decoration-color: var(--juxt-accent);\r\n}\r\n\r\n.j-editor__prose code {\r\n padding: 0.1em 0.3em;\r\n border: 1px solid var(--juxt-border-subtle);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface-sunken);\r\n font-family: var(--juxt-font-mono);\r\n font-size: 0.85em;\r\n}\r\n\r\n.dark .j-editor__prose code,\r\n[data-theme='dark'] .j-editor__prose code {\r\n border-color: var(--juxt-border);\r\n background: var(--juxt-surface-raised);\r\n}\r\n\r\n.j-editor__prose pre {\r\n padding: 0.875em 1em;\r\n overflow-x: auto;\r\n border: 1px solid var(--juxt-border-subtle);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-sunken);\r\n font-family: var(--juxt-font-mono);\r\n font-size: 0.8125rem;\r\n line-height: 1.65;\r\n white-space: pre;\r\n}\r\n\r\n.dark .j-editor__prose pre,\r\n[data-theme='dark'] .j-editor__prose pre {\r\n border-color: var(--juxt-border);\r\n background: var(--juxt-bg);\r\n}\r\n\r\n.j-editor__prose pre code {\r\n padding: 0;\r\n border: 0;\r\n background: none;\r\n font-size: inherit;\r\n}\r\n\r\n.j-editor__prose hr {\r\n height: 1px;\r\n margin: 1.5em 0;\r\n border: 0;\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-editor__prose hr.ProseMirror-selectednode {\r\n outline: 2px solid var(--juxt-ring);\r\n outline-offset: 4px;\r\n}\r\n\r\n.j-editor__prose mark {\r\n padding: 0.05em 0.1em;\r\n border-radius: 2px;\r\n background: var(--juxt-warning-soft);\r\n color: inherit;\r\n}\r\n\r\n.j-editor__prose ::selection {\r\n background: var(--juxt-accent-soft);\r\n}\r\n\r\n/* Placeholder: on an empty document, and on each empty heading. */\r\n.j-editor__prose .is-empty::before {\r\n content: attr(data-placeholder);\r\n float: left;\r\n height: 0;\r\n color: var(--juxt-fg-muted);\r\n pointer-events: none;\r\n}\r\n\r\n/* The per-line hint is quieter than the document placeholder. */\r\n.j-editor__prose .is-empty:not(.is-editor-empty):not(h1, h2, h3)::before {\r\n color: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-editor__prose .ProseMirror-gapcursor {\r\n display: none;\r\n position: relative;\r\n pointer-events: none;\r\n}\r\n\r\n.j-editor__prose.ProseMirror-focused .ProseMirror-gapcursor {\r\n display: block;\r\n}\r\n\r\n.j-editor__prose .ProseMirror-gapcursor::after {\r\n content: '';\r\n position: absolute;\r\n top: -2px;\r\n display: block;\r\n width: 20px;\r\n border-top: 1px solid var(--juxt-fg);\r\n animation: j-editor-cursor 1.1s steps(2, start) infinite;\r\n}\r\n\r\n@keyframes j-editor-cursor {\r\n to {\r\n visibility: hidden;\r\n }\r\n}\r\n\r\n/* Selection toolbar */\r\n.j-editor__bubble {\r\n z-index: var(--juxt-z-popover);\r\n display: flex;\r\n align-items: center;\r\n gap: 2px;\r\n padding: 3px;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-md);\r\n animation: j-editor-bubble-in var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n@keyframes j-editor-bubble-in {\r\n from {\r\n opacity: 0;\r\n transform: translateY(calc(var(--juxt-motion-shift) * 0.5)) scale(var(--juxt-motion-scale));\r\n }\r\n}\r\n\r\n/* Slash menu */\r\n.j-editor__slash {\r\n z-index: var(--juxt-z-popover);\r\n width: 17rem;\r\n max-height: min(20rem, var(--j-available-height, 20rem));\r\n padding: var(--juxt-space-1);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n}\r\n\r\n.j-editor__slash-label {\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2) var(--juxt-space-1);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n.j-editor__slash-item {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2-5);\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-sm);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-editor__slash-item.is-active {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-editor__slash-item.is-active::before {\r\n content: '';\r\n position: absolute;\r\n top: 0.625rem;\r\n bottom: 0.625rem;\r\n left: 0;\r\n width: 2px;\r\n border-radius: 0 2px 2px 0;\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-editor__slash-icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 2rem;\r\n height: 2rem;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-surface);\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-editor__slash-icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-editor__slash-text {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n min-width: 0;\r\n}\r\n\r\n.j-editor__slash-title {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n.j-editor__slash-description {\r\n overflow: hidden;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-editor__slash-md {\r\n color: var(--juxt-fg-muted);\r\n font-family: var(--juxt-font-mono);\r\n font-size: var(--juxt-text-2xs);\r\n}\r\n\r\n.j-editor__slash-empty {\r\n padding: var(--juxt-space-3) var(--juxt-space-2);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n}\r\n\r\n/* Link editor */\r\n.j-editor__link {\r\n z-index: var(--juxt-z-popover);\r\n display: flex;\r\n align-items: center;\r\n gap: 2px;\r\n width: min(22rem, calc(100vw - 16px));\r\n padding: 3px 3px 3px var(--juxt-space-2-5);\r\n}\r\n\r\n.j-editor__link-icon {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-editor__link-input {\r\n flex: 1;\r\n min-width: 0;\r\n height: var(--juxt-control-sm);\r\n padding: 0 var(--juxt-space-1-5);\r\n border: 0;\r\n outline: none;\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-sm);\r\n}\r\n\r\n.j-editor__link-input::placeholder {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-editor__bubble {\r\n animation: none;\r\n }\r\n}\r\n}\r\n</style>\r\n"],"mappings":"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"}
|