nuvra 0.1.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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/dist/components/document-editor.vue.d.ts +82 -0
  4. package/dist/components/editor-bubble-menus.vue.d.ts +14 -0
  5. package/dist/components/editor-canvas.vue.d.ts +30 -0
  6. package/dist/components/editor-color-picker.vue.d.ts +15 -0
  7. package/dist/components/editor-find-bar.vue.d.ts +24 -0
  8. package/dist/components/editor-object-overlay.vue.d.ts +10 -0
  9. package/dist/components/editor-page-setup.vue.d.ts +12 -0
  10. package/dist/components/editor-status-bar.vue.d.ts +36 -0
  11. package/dist/components/editor-table-picker.vue.d.ts +16 -0
  12. package/dist/components/editor-toolbar.vue.d.ts +38 -0
  13. package/dist/core/engine/blocks.d.ts +37 -0
  14. package/dist/core/engine/clipboard.d.ts +23 -0
  15. package/dist/core/engine/dom.d.ts +64 -0
  16. package/dist/core/engine/editing.d.ts +44 -0
  17. package/dist/core/engine/engine.d.ts +319 -0
  18. package/dist/core/engine/history.d.ts +43 -0
  19. package/dist/core/engine/input-rules.d.ts +17 -0
  20. package/dist/core/engine/keymap.d.ts +7 -0
  21. package/dist/core/engine/lists.d.ts +29 -0
  22. package/dist/core/engine/marks.d.ts +50 -0
  23. package/dist/core/engine/schema.d.ts +37 -0
  24. package/dist/core/engine/search.d.ts +65 -0
  25. package/dist/core/engine/selection.d.ts +30 -0
  26. package/dist/core/engine/tables.d.ts +59 -0
  27. package/dist/core/export.d.ts +21 -0
  28. package/dist/core/labels.d.ts +150 -0
  29. package/dist/core/page.d.ts +109 -0
  30. package/dist/core/pagination.d.ts +23 -0
  31. package/dist/core/types.d.ts +3 -0
  32. package/dist/core/ui-state.d.ts +79 -0
  33. package/dist/editor.vue.d.ts +50 -0
  34. package/dist/export-cyOnlakM.js +59 -0
  35. package/dist/export-cyOnlakM.js.map +1 -0
  36. package/dist/index.d.ts +11 -0
  37. package/dist/index.js +4900 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/page-CEEleKNI.js +93 -0
  40. package/dist/page-CEEleKNI.js.map +1 -0
  41. package/dist/style.css +2 -0
  42. package/package.json +72 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["$attrs"],"sources":["../src/core/engine/dom.ts","../src/core/engine/schema.ts","../src/core/engine/blocks.ts","../src/core/engine/lists.ts","../src/core/engine/marks.ts","../src/core/ui-state.ts","../src/core/engine/clipboard.ts","../src/core/engine/selection.ts","../src/core/engine/editing.ts","../src/core/engine/history.ts","../src/core/engine/input-rules.ts","../src/core/engine/keymap.ts","../src/core/engine/search.ts","../src/core/engine/tables.ts","../src/core/engine/engine.ts","../src/core/labels.ts","../src/core/pagination.ts","../src/components/editor-bubble-menus.vue","../src/components/editor-bubble-menus.vue","../src/components/editor-object-overlay.vue","../src/components/editor-object-overlay.vue","../src/components/editor-canvas.vue","../src/components/editor-canvas.vue","../src/components/editor-find-bar.vue","../src/components/editor-find-bar.vue","../src/components/editor-status-bar.vue","../src/components/editor-status-bar.vue","../src/components/editor-color-picker.vue","../src/components/editor-color-picker.vue","../src/components/editor-page-setup.vue","../src/components/editor-page-setup.vue","../src/components/editor-table-picker.vue","../src/components/editor-table-picker.vue","../src/components/editor-toolbar.vue","../src/components/editor-toolbar.vue","../src/components/document-editor.vue","../src/components/document-editor.vue","../src/editor.vue","../src/editor.vue"],"sourcesContent":["/**\n * Framework-free DOM helpers shared by every part of the editing engine: node type guards, lookups that never leave\n * the editor root, element factories and the small structural fixes (caret placeholders, mark merging) that keep the\n * editable markup predictable.\n */\n\n/** Tags of blocks that hold editable inline text. */\nconst TEXT_BLOCK_TAGS = new Set(['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'PRE']);\n\n/** Tags of ordered and unordered lists. */\nconst LIST_TAGS = new Set(['UL', 'OL']);\n\n/** Tags of inline formatting elements (marks). */\nconst MARK_TAGS = new Set(['STRONG', 'EM', 'U', 'S', 'CODE', 'SUB', 'SUP', 'A', 'MARK', 'SPAN']);\n\n/** Selector matching every mark element, used to scan a subtree for formatting. */\nconst MARK_SELECTOR = 'strong, em, u, s, code, sub, sup, a, mark, span';\n\n/** Selector for blocks without editable text inside: rules, images and page breaks. */\nexport const ATOM_SELECTOR = 'hr, img, div[data-type=\"page-break\"]';\n\n/** Selector for blocks that hold editable inline text. */\nexport const TEXT_BLOCK_SELECTOR = 'p, h1, h2, h3, h4, h5, h6, pre';\n\n/** Marks the extra `<br>` that keeps an empty or break-terminated line visible; it is never serialised. */\nexport const TRAILING_BREAK = 'data-doc-trailing';\n\n/** Inline content a user can see besides text: images and real (non-placeholder) line breaks. */\nconst VISIBLE_INLINE_SELECTOR = `img, br:not([${TRAILING_BREAK}])`;\n\n/** Whether the node is an element. */\nexport const isElement = (node: Node | null | undefined): node is HTMLElement => node?.nodeType === Node.ELEMENT_NODE;\n\n/** Whether the node is a text node. */\nexport const isText = (node: Node | null | undefined): node is Text => node?.nodeType === Node.TEXT_NODE;\n\n/** Whether the node is a paragraph, heading or code block. */\nexport const isTextBlock = (node: Node | null | undefined): node is HTMLElement =>\n isElement(node) && TEXT_BLOCK_TAGS.has(node.tagName);\n\n/** Whether the node is an ordered or unordered list. */\nexport const isList = (node: Node | null | undefined): node is HTMLElement =>\n isElement(node) && LIST_TAGS.has(node.tagName);\n\n/** Whether the node is an inline formatting element. */\nconst isMark = (node: Node | null | undefined): node is HTMLElement => isElement(node) && MARK_TAGS.has(node.tagName);\n\n/** Whether the node is a block without editable text (rule, image, page break). */\nexport const isAtom = (node: Node | null | undefined): node is HTMLElement =>\n isElement(node) && node.matches(ATOM_SELECTOR);\n\n/** Whether the node is a `<br>`, placeholder or real. */\nexport const isBreak = (node: Node | null | undefined): node is HTMLBRElement =>\n isElement(node) && node.tagName === 'BR';\n\n/** Whether the node is the editor's placeholder `<br>` rather than a line break typed by the user. */\nexport const isTrailingBreak = (node: Node | null | undefined): boolean =>\n isBreak(node) && node.hasAttribute(TRAILING_BREAK);\n\n/** Nearest ancestor-or-self matching the predicate, never leaving `root` (which itself is never returned). */\nexport const closestWithin = (\n node: Node | null | undefined,\n root: HTMLElement,\n predicate: (element: HTMLElement) => boolean\n): HTMLElement | null => {\n let current: Node | null | undefined = node;\n while (current && current !== root) {\n if (isElement(current) && predicate(current)) return current;\n current = current.parentNode;\n }\n return null;\n};\n\n/** Nearest ancestor-or-self inside `root` whose tag is `tags` or one of `tags`. */\nexport const closestTag = (\n node: Node | null | undefined,\n root: HTMLElement,\n tags: string | ReadonlySet<string>\n): HTMLElement | null =>\n closestWithin(node, root, element =>\n typeof tags === 'string' ? element.tagName === tags : tags.has(element.tagName)\n );\n\n/** The paragraph, heading or code block containing `node`. */\nexport const closestTextBlock = (node: Node | null | undefined, root: HTMLElement): HTMLElement | null =>\n closestWithin(node, root, element => TEXT_BLOCK_TAGS.has(element.tagName));\n\n/** Creates an element with attributes and children in one call. */\nexport const createElement = <K extends keyof HTMLElementTagNameMap>(\n tag: K,\n attributes: Record<string, string> = {},\n children: Array<Node | string> = []\n): HTMLElementTagNameMap[K] => {\n const element = document.createElement(tag);\n for (const [name, value] of Object.entries(attributes)) element.setAttribute(name, value);\n element.append(...children);\n return element;\n};\n\n/** Creates the placeholder `<br>` that gives empty lines their height. */\nexport const createTrailingBreak = (): HTMLBRElement => createElement('br', { [TRAILING_BREAK]: '' });\n\n/** Creates a paragraph with the given inline content and the placeholder break it needs. */\nexport const createParagraph = (children: Node[] = []): HTMLParagraphElement => {\n const paragraph = createElement('p', {}, children);\n syncTrailingBreak(paragraph);\n return paragraph;\n};\n\n/** Whether the element contains text, an image or a real line break. */\nexport const hasVisibleContent = (element: Element): boolean =>\n (element.textContent ?? '') !== '' || element.querySelector(VISIBLE_INLINE_SELECTOR) !== null;\n\n/** Whether a text block has no text and no image, holding at most one break. */\nexport const isEmptyTextBlock = (block: Element): boolean =>\n (block.textContent ?? '') === '' && block.querySelector('img') === null && block.querySelectorAll('br').length <= 1;\n\n/** The last child (or the last one before `before`) that is not an empty text node. */\nconst lastMeaningfulChild = (element: Node, before?: Node | null): Node | null => {\n let node = before === undefined ? element.lastChild : (before?.previousSibling ?? null);\n while (node && isText(node) && node.data === '') node = node.previousSibling;\n return node;\n};\n\n/**\n * Keeps exactly one placeholder `<br>` where the browser needs it: in empty blocks and after a user line break at the\n * end of a block. Leaves the DOM untouched when nothing has to change, so the caret is not disturbed while typing.\n */\nexport const syncTrailingBreak = (block: HTMLElement): void => {\n if (block.tagName === 'PRE') return;\n if (isEmptyTextBlock(block)) {\n const only = block.firstChild;\n const single = block.childNodes.length === 1;\n if (single && isBreak(only)) {\n only.setAttribute(TRAILING_BREAK, '');\n return;\n }\n if (single && isTrailingBreak(only)) return;\n // Empty marks stay so a pending bold or colour survives; only the breaks are rebuilt.\n for (const br of Array.from(block.querySelectorAll('br'))) br.remove();\n block.append(createTrailingBreak());\n return;\n }\n const last = lastMeaningfulChild(block);\n const trailing = isBreak(last) && last.hasAttribute(TRAILING_BREAK) ? last : null;\n const beforeTrailing = trailing ? lastMeaningfulChild(block, trailing) : last;\n const needed = beforeTrailing === null || isBreak(beforeTrailing);\n if (needed && !trailing) block.append(createTrailingBreak());\n if (!needed && trailing) trailing.remove();\n};\n\n/** Replaces an element with its own children. */\nexport const unwrap = (element: Element): void => {\n element.replaceWith(...Array.from(element.childNodes));\n};\n\n/** Replaces the element with one of another tag, keeping attributes and children. */\nexport const renameElement = <T extends HTMLElement = HTMLElement>(element: HTMLElement, tag: string): T => {\n const renamed = document.createElement(tag);\n for (const attribute of Array.from(element.attributes)) renamed.setAttribute(attribute.name, attribute.value);\n renamed.append(...Array.from(element.childNodes));\n element.replaceWith(renamed);\n return renamed as T;\n};\n\n/** Whether two elements carry exactly the same attributes and values. */\nconst sameAttributes = (a: Element, b: Element): boolean =>\n a.attributes.length === b.attributes.length &&\n Array.from(a.attributes).every(attribute => b.getAttribute(attribute.name) === attribute.value);\n\n/** Removes marks that no longer contain anything the user can see or place a caret into. */\nexport const removeEmptyMarks = (scope: HTMLElement): void => {\n // Innermost marks come last in document order, so walking backwards empties nested marks bottom-up.\n const marks = Array.from(scope.querySelectorAll<HTMLElement>(MARK_SELECTOR));\n for (let index = marks.length - 1; index >= 0; index -= 1) {\n const mark = marks[index];\n if (mark && !hasVisibleContent(mark)) mark.remove();\n }\n};\n\n/** Joins neighbouring marks with identical tag and attributes, then merges adjacent text nodes. */\nexport const mergeAdjacentMarks = (scope: HTMLElement): void => {\n /** Merges equal siblings among the children of `parent`, then descends into every child element. */\n const visit = (parent: Node) => {\n let child = parent.firstChild;\n while (child) {\n const next = child.nextSibling;\n if (isMark(child) && isMark(next) && child.tagName === next.tagName && sameAttributes(child, next)) {\n child.append(...Array.from(next.childNodes));\n next.remove();\n continue;\n }\n if (isElement(child)) visit(child);\n child = next;\n }\n };\n visit(scope);\n scope.normalize();\n};\n\n/** All paragraphs, headings and code blocks inside `scope`, in document order. */\nexport const textBlocksWithin = (scope: ParentNode): HTMLElement[] =>\n Array.from(scope.querySelectorAll<HTMLElement>(TEXT_BLOCK_SELECTOR));\n\n/** Text blocks touched by the range, in document order. */\nexport const textBlocksInRange = (root: HTMLElement, range: Range): HTMLElement[] => {\n const startBlock = closestTextBlock(range.startContainer, root);\n if (range.collapsed || startBlock === closestTextBlock(range.endContainer, root)) {\n return startBlock ? [startBlock] : [];\n }\n return textBlocksWithin(root).filter(block => range.intersectsNode(block));\n};\n\n/** Range from the start of `block` to a point (`toEnd` false) or from a point to the end of `block` (`toEnd` true). */\nconst rangeBetween = (block: HTMLElement, container: Node, offset: number, toEnd: boolean): Range => {\n const range = document.createRange();\n range.selectNodeContents(block);\n if (toEnd) range.setStart(container, offset);\n else range.setEnd(container, offset);\n return range;\n};\n\n/** Whether a range covers no text, image or real line break. */\nconst isVisuallyEmpty = (range: Range): boolean =>\n range.toString() === '' && range.cloneContents().querySelector(VISIBLE_INLINE_SELECTOR) === null;\n\n/** Whether nothing visible precedes the point inside its block. */\nexport const isAtBlockStart = (block: HTMLElement, container: Node, offset: number): boolean =>\n isVisuallyEmpty(rangeBetween(block, container, offset, false));\n\n/** Whether nothing visible follows the point inside its block. */\nexport const isAtBlockEnd = (block: HTMLElement, container: Node, offset: number): boolean =>\n isVisuallyEmpty(rangeBetween(block, container, offset, true));\n\n/** Escapes regular-expression syntax so user text can be searched literally. */\nexport const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n","/**\n * The document model of the editor: which HTML is allowed, how untrusted HTML (pasted from Word, Google Docs or the\n * web, or loaded from the server) is converted into it, how the live DOM is kept well-formed, and how the editable\n * DOM is turned back into clean HTML.\n */\nimport {\n TRAILING_BREAK,\n createElement,\n createParagraph,\n createTrailingBreak,\n hasVisibleContent,\n isBreak,\n isElement,\n isList,\n isText,\n isTrailingBreak,\n mergeAdjacentMarks,\n removeEmptyMarks,\n syncTrailingBreak,\n unwrap\n} from './dom';\n\n/** Attribute pagination writes on blocks it moved to the next sheet; editor-only. */\nexport const GAP_ATTRIBUTE = 'data-doc-gap';\n\n/** Class marking table cells in a drag selection; editor-only. */\nexport const SELECTED_CELL_CLASS = 'doc-cell-selected';\n\n/** Width of one paragraph indentation step. */\nconst INDENT_STEP_PX = 48;\n\n/** Deepest paragraph indentation. */\nconst MAX_INDENT = 10;\n\n/** CSS pixels per inch, the base of every absolute unit. */\nconst PX_PER_INCH = 96;\n\n/** Centimetres per inch. */\nconst CM_PER_INCH = 2.54;\n\n/** Millimetres per inch. */\nconst MM_PER_INCH = 25.4;\n\n/** Points per CSS pixel. */\nconst PT_PER_PX = 0.75;\n\n/** Font sizes are rounded to half points. */\nconst HALF_POINTS_PER_POINT = 2;\n\n/** Highest code point of the control characters and space that browsers ignore inside URL schemes. */\nconst LAST_IGNORED_URL_CODE = 32;\n\n/** The DEL control character, also ignored inside URL schemes. */\nconst DELETE_CODE = 127;\n\n/** Tags that are blocks in the editor model; every other element is inline content. */\nconst BLOCK_TAGS = new Set([\n 'P',\n 'H1',\n 'H2',\n 'H3',\n 'H4',\n 'H5',\n 'H6',\n 'PRE',\n 'BLOCKQUOTE',\n 'UL',\n 'OL',\n 'TABLE',\n 'HR',\n 'IMG',\n 'DIV'\n]);\n\n/** Elements removed together with their content: scripts, embeds, media and form controls. */\nconst DROPPED_TAGS = new Set([\n 'SCRIPT',\n 'STYLE',\n 'TITLE',\n 'META',\n 'LINK',\n 'BASE',\n 'HEAD',\n 'IFRAME',\n 'FRAME',\n 'OBJECT',\n 'EMBED',\n 'NOSCRIPT',\n 'TEMPLATE',\n 'SVG',\n 'MATH',\n 'CANVAS',\n 'VIDEO',\n 'AUDIO',\n 'SOURCE',\n 'SELECT',\n 'TEXTAREA',\n 'BUTTON',\n 'INPUT',\n 'FORM'\n]);\n\n/** Text-holding elements without an editor equivalent, mapped to the block they become. */\nconst TEXT_LIKE_TAGS: Record<string, string> = { DT: 'p', DD: 'p', ADDRESS: 'p', FIGCAPTION: 'p' };\n\n/** Table and list parts that are unwrapped when they appear outside their proper parent. */\nconst STRAY_WRAPPERS = new Set(['LI', 'TR', 'TD', 'TH', 'TBODY', 'THEAD', 'TFOOT', 'COLGROUP', 'COL']);\n\n/** A URL that starts with a scheme; any scheme outside the allow lists is rejected. */\nconst UNSAFE_SCHEME = /^[a-z][a-z0-9+.-]*:/i;\n\n/** Background colours treated as \"no highlight\" when converting pasted inline styles. */\nconst NO_BACKGROUND = /^(transparent|initial|inherit|white|#fff(fff)?|rgba?\\(255, 255, 255(, 1)?\\))$/i;\n\n/** Whether the node is a block in the editor model. */\nexport const isBlockNode = (node: Node | null | undefined): node is HTMLElement =>\n isElement(node) && BLOCK_TAGS.has(node.tagName);\n\n/** Rounds an indentation level into the supported range; non-numbers become 0. */\nconst clampIndent = (value: number): number =>\n Number.isFinite(value) ? Math.min(MAX_INDENT, Math.max(0, Math.round(value))) : 0;\n\n/** Sets a paragraph's indentation level as `data-indent` plus the matching left margin, removing both at level 0. */\nexport const setIndent = (block: HTMLElement, indent: number): void => {\n const value = clampIndent(indent);\n if (value) {\n block.dataset.indent = String(value);\n block.style.marginLeft = `${value * INDENT_STEP_PX}px`;\n } else {\n delete block.dataset.indent;\n block.style.removeProperty('margin-left');\n }\n if (!block.getAttribute('style')) block.removeAttribute('style');\n};\n\n/**\n * Returns the trimmed URL when it is safe to put in a link (`http(s)`, `mailto`, `tel` or relative) or an image\n * (`http(s)`, `data:image`, `blob` or relative), otherwise `null`.\n */\nexport const sanitizeUrl = (value: string | null, image = false): string | null => {\n const url = value?.trim();\n if (!url) return null;\n // Browsers ignore control characters and spaces inside a scheme (\"java\\tscript:\"), so they are removed first.\n const compact = Array.from(url)\n .filter(character => {\n const code = character.charCodeAt(0);\n return code > LAST_IGNORED_URL_CODE && code !== DELETE_CODE;\n })\n .join('')\n .toLowerCase();\n const allowed = image ? /^(https?:|data:image\\/|blob:)/ : /^(https?:|mailto:|tel:)/;\n return allowed.test(compact) || !UNSAFE_SCHEME.test(compact) ? url : null;\n};\n\n/** Converts a CSS length in px, pt, cm, mm or in to pixels; unknown units give 0. */\nconst toPixels = (value: string): number => {\n const number = Number.parseFloat(value);\n if (!Number.isFinite(number)) return 0;\n if (value.endsWith('pt')) return number / PT_PER_PX;\n if (value.endsWith('cm')) return (number * PX_PER_INCH) / CM_PER_INCH;\n if (value.endsWith('mm')) return (number * PX_PER_INCH) / MM_PER_INCH;\n if (value.endsWith('in')) return number * PX_PER_INCH;\n return /^[\\d.]+(px)?$/.test(value.trim()) ? number : 0;\n};\n\n/** Converts a pt or px font size into points rounded to half a point; other units give `null`. */\nconst toPoints = (value: string): string | null => {\n const number = Number.parseFloat(value);\n if (!Number.isFinite(number) || number <= 0) return null;\n const points = value.endsWith('pt') ? number : value.endsWith('px') ? number * PT_PER_PX : null;\n if (points === null) return null;\n return `${Math.round(points * HALF_POINTS_PER_POINT) / HALF_POINTS_PER_POINT}pt`;\n};\n\n/** Wraps nodes in a new element, or returns nothing when there are no nodes to wrap. */\nconst wrap = (tag: string, nodes: Node[], attributes: Record<string, string> = {}): Node[] =>\n nodes.length ? [createElement(tag as 'span', attributes, nodes)] : [];\n\n/** Copies the paragraph formatting the editor supports: alignment, line height, indentation and direction. */\nconst copyBlockFormat = (source: HTMLElement, target: HTMLElement): void => {\n const align = source.style.textAlign || source.getAttribute('align') || '';\n if (['center', 'right', 'justify'].includes(align)) target.style.textAlign = align;\n const lineHeight = source.style.lineHeight;\n if (lineHeight && lineHeight !== 'normal' && !lineHeight.endsWith('%')) target.style.lineHeight = lineHeight;\n const indent = source.dataset.indent\n ? Number.parseInt(source.dataset.indent, 10)\n : toPixels(source.style.marginLeft) / INDENT_STEP_PX;\n if (clampIndent(indent)) setIndent(target, indent);\n const direction = source.getAttribute('dir');\n if (direction === 'ltr' || direction === 'rtl') target.setAttribute('dir', direction);\n};\n\n/** Turns presentational CSS (Word, Google Docs) into the editor's own marks around the converted content. */\nconst applyInlineStyles = (source: HTMLElement, content: Node[]): Node[] => {\n let nodes = content;\n if (!nodes.length) return nodes;\n const { style } = source;\n const weight = style.fontWeight;\n if (weight === 'bold' || Number(weight) >= 600) nodes = wrap('strong', nodes);\n if (style.fontStyle === 'italic') nodes = wrap('em', nodes);\n const decoration = `${style.textDecorationLine} ${style.textDecoration}`;\n if (decoration.includes('underline')) nodes = wrap('u', nodes);\n if (decoration.includes('line-through')) nodes = wrap('s', nodes);\n if (style.verticalAlign === 'super') nodes = wrap('sup', nodes);\n if (style.verticalAlign === 'sub') nodes = wrap('sub', nodes);\n\n const spanStyles: string[] = [];\n const color = style.color || source.getAttribute('color');\n if (color && !/^(inherit|initial|windowtext)$/i.test(color)) spanStyles.push(`color: ${color}`);\n const family = style.fontFamily || source.getAttribute('face');\n if (family && !/^(inherit|initial)$/i.test(family)) spanStyles.push(`font-family: ${family}`);\n const size = toPoints(style.fontSize);\n if (size) spanStyles.push(`font-size: ${size}`);\n if (spanStyles.length) nodes = wrap('span', nodes, { style: spanStyles.join('; ') });\n\n const background = style.backgroundColor;\n if (background && !NO_BACKGROUND.test(background)) {\n nodes = wrap('mark', nodes, { 'data-color': background, style: `background-color: ${background}; color: inherit` });\n }\n return nodes;\n};\n\n/** Converts every child of `source`; `preformatted` keeps whitespace as typed. */\nconst convertChildren = (source: Node, preformatted = false): Node[] =>\n Array.from(source.childNodes).flatMap(child => convertNode(child, preformatted));\n\n/** Rebuilds an image with a safe `src`, its size, alt text and alignment; unsafe images are dropped. */\nconst convertImage = (source: HTMLElement): Node[] => {\n const src = sanitizeUrl(source.getAttribute('src'), true);\n if (!src) return [];\n const image = createElement('img', { src });\n for (const name of ['alt', 'title']) {\n const value = source.getAttribute(name);\n if (value) image.setAttribute(name, value);\n }\n for (const name of ['width', 'height'] as const) {\n const value = Number.parseInt(source.getAttribute(name) ?? source.style[name] ?? '', 10);\n if (value > 0) image.setAttribute(name, String(value));\n }\n const align = source.getAttribute('data-align');\n image.setAttribute('data-align', align === 'left' || align === 'right' ? align : 'center');\n return [image];\n};\n\n/** Rebuilds a table row by row, keeping cell spans, header cells and pixel column widths. */\nconst convertTable = (source: HTMLTableElement): HTMLElement => {\n const table = createElement('table');\n const widths = Array.from(source.querySelectorAll<HTMLElement>(':scope > colgroup > col, :scope > col')).map(col =>\n toPixels(col.style.width || col.getAttribute('width') || '')\n );\n if (widths.length && widths.every(width => width > 0)) {\n const cols = widths.map(width => createElement('col', { style: `width: ${Math.round(width)}px` }));\n table.append(createElement('colgroup', {}, cols));\n table.style.width = `${Math.round(widths.reduce((sum, width) => sum + width, 0))}px`;\n }\n const body = createElement('tbody');\n for (const row of Array.from(source.rows)) {\n const tr = createElement('tr');\n for (const cell of Array.from(row.cells)) {\n const next = createElement(cell.tagName === 'TH' ? 'th' : 'td', {}, convertChildren(cell));\n if (cell.colSpan > 1) next.setAttribute('colspan', String(cell.colSpan));\n if (cell.rowSpan > 1) next.setAttribute('rowspan', String(cell.rowSpan));\n tr.append(next);\n }\n body.append(tr);\n }\n table.append(body);\n return table;\n};\n\n/** Converts a task list item, taking its content from the `<div>` body and skipping the checkbox label. */\nconst convertTaskItem = (source: HTMLElement): HTMLElement => {\n const body = source.querySelector(':scope > div');\n const nodes = body\n ? convertChildren(body)\n : Array.from(source.childNodes)\n .filter(node => !(isElement(node) && node.tagName === 'LABEL'))\n .flatMap(node => convertNode(node, false));\n const checked = String(source.getAttribute('data-checked') === 'true');\n return createElement('li', { 'data-type': 'taskItem', 'data-checked': checked }, nodes);\n};\n\n/**\n * Converts one untrusted node into editor nodes. Supported elements are rebuilt from scratch (so no foreign\n * attribute or event handler survives), unsupported ones are replaced by their converted content.\n */\nconst convertNode = (source: Node, preformatted: boolean): Node[] => {\n if (isText(source)) {\n const data = preformatted ? source.data : source.data.replace(/[\\t\\n\\r ]+/g, ' ');\n return data ? [document.createTextNode(data)] : [];\n }\n if (!isElement(source)) return [];\n const tag = source.tagName.toUpperCase();\n if (DROPPED_TAGS.has(tag)) return [];\n const content = () => convertChildren(source);\n\n switch (tag) {\n case 'P':\n case 'H1':\n case 'H2':\n case 'H3':\n case 'H4':\n case 'H5':\n case 'H6':\n case 'DT':\n case 'DD':\n case 'ADDRESS':\n case 'FIGCAPTION': {\n const block = createElement((TEXT_LIKE_TAGS[tag] ?? tag.toLowerCase()) as 'p', {}, content());\n copyBlockFormat(source, block);\n return [block];\n }\n case 'DIV': {\n if (source.getAttribute('data-type') === 'page-break') {\n return [createElement('div', { 'data-type': 'page-break', class: 'doc-page-break' })];\n }\n const nodes = content();\n if (nodes.some(isBlockNode)) return nodes;\n const block = createElement('p', {}, nodes);\n copyBlockFormat(source, block);\n return nodes.length ? [block] : [];\n }\n case 'BLOCKQUOTE':\n return [createElement('blockquote', {}, content())];\n case 'PRE':\n return [createElement('pre', {}, [createElement('code', {}, [source.textContent ?? ''])])];\n case 'UL':\n case 'OL': {\n const task = source.getAttribute('data-type') === 'taskList';\n const list = createElement(tag === 'OL' ? 'ol' : 'ul', task ? { 'data-type': 'taskList' } : {}, content());\n const start = Number.parseInt(source.getAttribute('start') ?? '', 10);\n if (tag === 'OL' && start > 1) list.setAttribute('start', String(start));\n return [list];\n }\n case 'LI':\n return [\n source.getAttribute('data-type') === 'taskItem' ? convertTaskItem(source) : createElement('li', {}, content())\n ];\n case 'TABLE':\n return [convertTable(source as HTMLTableElement)];\n case 'HR':\n return [createElement('hr')];\n case 'IMG':\n return convertImage(source);\n case 'BR':\n return [createElement('br')];\n case 'A': {\n const href = sanitizeUrl(source.getAttribute('href'));\n const nodes = applyInlineStyles(source, content());\n if (!href) return nodes;\n const attributes: Record<string, string> = { href, rel: 'noopener noreferrer nofollow' };\n if (source.getAttribute('target') === '_blank') attributes.target = '_blank';\n return wrap('a', nodes, attributes);\n }\n case 'STRONG':\n case 'B':\n // Google Docs wraps whole documents in <b style=\"font-weight: normal\">.\n return /^(normal|400)$/.test(source.style.fontWeight)\n ? applyInlineStyles(source, content())\n : wrap('strong', applyInlineStyles(source, content()));\n case 'EM':\n case 'I':\n case 'CITE':\n case 'DFN':\n case 'VAR':\n return wrap('em', applyInlineStyles(source, content()));\n case 'U':\n case 'INS':\n return wrap('u', applyInlineStyles(source, content()));\n case 'S':\n case 'STRIKE':\n case 'DEL':\n return wrap('s', applyInlineStyles(source, content()));\n case 'CODE':\n case 'KBD':\n case 'SAMP':\n case 'TT':\n return wrap('code', [document.createTextNode(source.textContent ?? '')]);\n case 'SUB':\n case 'SUP':\n return wrap(tag.toLowerCase(), applyInlineStyles(source, content()));\n case 'MARK': {\n const color = source.getAttribute('data-color') || source.style.backgroundColor;\n const attributes: Record<string, string> = color\n ? { 'data-color': color, style: `background-color: ${color}; color: inherit` }\n : {};\n return wrap('mark', content(), attributes);\n }\n default:\n return applyInlineStyles(source, content());\n }\n};\n\n// ---------------------------------------------------------------------------------------------------------------\n// Structure normalisation\n\n/** The element holding a list item's blocks: the `<div>` of a task item, the `<li>` itself otherwise. */\nexport const listItemBody = (item: HTMLElement): HTMLElement =>\n item.getAttribute('data-type') === 'taskItem' ? (item.querySelector<HTMLElement>(':scope > div') ?? item) : item;\n\n/**\n * Gives a list item the task structure (non-editable checkbox label plus `<div>` body) with the checkbox mirroring\n * `data-checked`, moves any stray content into the body and returns the body.\n */\nexport const ensureTaskItem = (item: HTMLElement): HTMLElement => {\n const checked = item.getAttribute('data-checked') === 'true';\n item.setAttribute('data-type', 'taskItem');\n item.setAttribute('data-checked', String(checked));\n let label = item.querySelector<HTMLElement>(':scope > label');\n if (!label) {\n label = createElement('label', {}, [createElement('input', { type: 'checkbox' }), createElement('span')]);\n item.prepend(label);\n }\n label.setAttribute('contenteditable', 'false');\n const input = label.querySelector('input') ?? label.appendChild(createElement('input', { type: 'checkbox' }));\n input.checked = checked;\n input.toggleAttribute('checked', checked);\n let body = item.querySelector<HTMLElement>(':scope > div');\n if (!body) {\n body = createElement('div');\n item.append(body);\n }\n for (const node of Array.from(item.childNodes)) if (node !== label && node !== body) body.append(node);\n return body;\n};\n\n/** Strips the task structure from a list item that now belongs to a bullet or numbered list. */\nconst toPlainListItem = (item: HTMLElement): void => {\n item.removeAttribute('data-type');\n item.removeAttribute('data-checked');\n item.querySelector(':scope > label')?.remove();\n const body = item.querySelector(':scope > div');\n if (body) unwrap(body);\n};\n\n/** Makes every child of a list an item of the list's kind; nested lists join the previous item. */\nconst normalizeList = (list: HTMLElement): void => {\n const task = list.getAttribute('data-type') === 'taskList';\n for (const child of Array.from(list.childNodes)) {\n if (isElement(child) && child.tagName === 'LI') continue;\n if (isText(child) && child.data.trim() === '') {\n child.remove();\n } else if (isList(child) && isElement(child.previousSibling) && child.previousSibling.tagName === 'LI') {\n listItemBody(child.previousSibling).append(child);\n } else {\n const item = createElement('li');\n child.replaceWith(item);\n item.append(child);\n }\n }\n for (const item of Array.from(list.children) as HTMLElement[]) {\n if (task) ensureTaskItem(item);\n else toPlainListItem(item);\n normalizeContainer(listItemBody(item));\n }\n if (!list.children.length) list.remove();\n};\n\n/** Collects all rows into a single `<tbody>`, drops non-cell children and removes tables left without rows. */\nconst normalizeTable = (table: HTMLElement): void => {\n const rows = Array.from(\n table.querySelectorAll<HTMLElement>(':scope > thead > tr, :scope > tbody > tr, :scope > tfoot > tr, :scope > tr')\n );\n const colgroup = table.querySelector(':scope > colgroup');\n const body = createElement('tbody', {}, rows);\n table.replaceChildren(...(colgroup ? [colgroup] : []), body);\n for (const row of rows) {\n for (const child of Array.from(row.childNodes)) {\n if (!(isElement(child) && (child.tagName === 'TD' || child.tagName === 'TH'))) child.remove();\n }\n if (!row.children.length) row.remove();\n for (const cell of Array.from(row.children) as HTMLElement[]) normalizeContainer(cell);\n }\n if (!body.children.length) table.remove();\n};\n\n/**\n * Keeps a code block as `<pre><code>text</code></pre>` and adds a placeholder break when it is empty or ends with a\n * newline, so the last line stays visible.\n */\nconst normalizePre = (pre: HTMLElement): void => {\n const code = pre.firstElementChild;\n const valid =\n pre.childNodes.length === 1 &&\n code?.tagName === 'CODE' &&\n Array.from(code.childNodes).every(node => isText(node) || isTrailingBreak(node));\n const text = pre.textContent ?? '';\n const target = valid && code ? (code as HTMLElement) : createElement('code', {}, [text]);\n if (!valid) pre.replaceChildren(target);\n const trailing = Array.from(target.childNodes).find(isTrailingBreak);\n const needsBreak = text === '' || text.endsWith('\\n');\n if (needsBreak && !trailing) target.append(createTrailingBreak());\n if (!needsBreak && trailing) trailing.remove();\n};\n\n/** Moves an image out of a paragraph, splitting the paragraph around it; returns the right-hand part. */\nconst splitAroundImage = (block: HTMLElement, image: HTMLElement): HTMLElement => {\n const range = document.createRange();\n range.setStartBefore(image);\n range.setEnd(block, block.childNodes.length);\n const tail = range.extractContents();\n tail.querySelector('img')?.remove();\n const right = block.cloneNode(false) as HTMLElement;\n right.append(tail);\n block.after(image, right);\n for (const part of [block, right]) if (!hasVisibleContent(part)) part.remove();\n return right;\n};\n\n/**\n * Keeps a paragraph or heading inline-only: nested text blocks are unwrapped, other blocks and images move out,\n * empty marks are removed, equal marks merged and the placeholder break synchronised.\n */\nconst normalizeTextBlock = (block: HTMLElement): void => {\n for (const child of Array.from(block.children) as HTMLElement[]) {\n if (!isBlockNode(child) || child.tagName === 'IMG') continue;\n if (/^(P|H[1-6])$/.test(child.tagName)) unwrap(child);\n else block.after(child);\n }\n const image = block.querySelector<HTMLElement>('img');\n if (image) {\n const right = splitAroundImage(block, image);\n if (right.isConnected) normalizeTextBlock(right);\n if (!block.isConnected) return;\n }\n removeEmptyMarks(block);\n mergeAdjacentMarks(block);\n syncTrailingBreak(block);\n};\n\n/** Normalises one block according to its kind. */\nconst normalizeBlock = (block: HTMLElement): void => {\n switch (block.tagName) {\n case 'PRE':\n normalizePre(block);\n break;\n case 'BLOCKQUOTE':\n normalizeContainer(block);\n break;\n case 'UL':\n case 'OL':\n normalizeList(block);\n break;\n case 'TABLE':\n normalizeTable(block);\n break;\n case 'IMG':\n case 'HR':\n break;\n case 'DIV':\n block.replaceChildren();\n block.className = 'doc-page-break';\n break;\n default:\n normalizeTextBlock(block);\n }\n};\n\n/** Whether a direct child of a block container is a wrapper that must be unwrapped. */\nconst isStrayWrapper = (node: Node): node is HTMLElement =>\n isElement(node) &&\n (STRAY_WRAPPERS.has(node.tagName) || (node.tagName === 'DIV' && node.getAttribute('data-type') !== 'page-break'));\n\n/**\n * Makes a block container (document, quote, list item body, table cell) hold only well-formed blocks: stray wrappers\n * are unwrapped, runs of inline content become paragraphs and every block is normalised. The document root also\n * always ends with a paragraph, so the caret can be placed after tables, images and rules.\n */\nexport const normalizeContainer = (container: ParentNode & Node, isRoot = false): void => {\n let unwrapped = true;\n while (unwrapped) {\n unwrapped = false;\n for (const child of Array.from(container.childNodes)) {\n if (!isStrayWrapper(child)) continue;\n unwrap(child);\n unwrapped = true;\n }\n }\n\n let run: Node[] = [];\n /** Wraps the pending inline run into a paragraph before `before`, or drops it when it shows nothing. */\n const flush = (before: Node | null) => {\n if (!run.length) return;\n const visible = run.some(node =>\n isText(node) ? node.data.trim() !== '' : isElement(node) && (isBreak(node) || hasVisibleContent(node))\n );\n if (visible) container.insertBefore(createParagraph(run), before);\n else for (const node of run) node.parentNode?.removeChild(node);\n run = [];\n };\n\n for (const child of Array.from(container.childNodes)) {\n if (isBlockNode(child)) {\n flush(child);\n normalizeBlock(child);\n } else {\n run.push(child);\n }\n }\n flush(null);\n\n // Paragraphs created from runs may still hold images or nested blocks.\n for (const child of Array.from(container.childNodes)) {\n if (isElement(child) && child.tagName === 'P' && child.querySelector('img, p, ul, ol, table')) {\n normalizeTextBlock(child);\n }\n }\n if (!container.firstChild) container.appendChild(createParagraph());\n if (isRoot && !(isElement(container.lastChild) && container.lastChild.tagName === 'P')) {\n container.appendChild(createParagraph());\n }\n};\n\n/** Parses untrusted HTML without executing it and returns editor-ready blocks. */\nexport const sanitizeHtml = (html: string): DocumentFragment => {\n const parsed = new DOMParser().parseFromString(html, 'text/html');\n const fragment = document.createDocumentFragment();\n fragment.append(...convertChildren(parsed.body));\n normalizeContainer(fragment);\n return fragment;\n};\n\n/**\n * Removes editor-only markup (pagination gaps, cell selection). With `forExport` it also drops caret placeholders\n * and editing attributes, which history snapshots keep so the restored DOM is immediately editable.\n */\nexport const cleanEditorArtifacts = (scope: Element | DocumentFragment, forExport: boolean): void => {\n for (const element of Array.from(scope.querySelectorAll<HTMLElement>(`[${GAP_ATTRIBUTE}]`))) {\n element.style.removeProperty('margin-top');\n element.removeAttribute(GAP_ATTRIBUTE);\n if (!element.getAttribute('style')) element.removeAttribute('style');\n }\n for (const element of Array.from(scope.querySelectorAll(`.${SELECTED_CELL_CLASS}`))) {\n element.classList.remove(SELECTED_CELL_CLASS);\n if (!element.classList.length) element.removeAttribute('class');\n }\n if (!forExport) return;\n for (const br of Array.from(scope.querySelectorAll(`br[${TRAILING_BREAK}]`))) br.remove();\n for (const label of Array.from(scope.querySelectorAll('label[contenteditable]'))) {\n label.removeAttribute('contenteditable');\n }\n};\n\n/** Clean HTML of the editor content, as stored in the model and exported. */\nexport const serialize = (root: HTMLElement): string => {\n const clone = root.cloneNode(true) as HTMLElement;\n cleanEditorArtifacts(clone, true);\n return clone.innerHTML;\n};\n\n/** Whether the document is a single unformatted, empty paragraph. */\nexport const isEmptyDocument = (root: HTMLElement): boolean => {\n const only = root.firstElementChild;\n return (\n root.childElementCount === 1 &&\n only?.tagName === 'P' &&\n !only.hasAttribute('style') &&\n (only.textContent ?? '') === '' &&\n only.querySelector(`img, br:not([${TRAILING_BREAK}])`) === null\n );\n};\n","import {\n closestTag,\n closestTextBlock,\n closestWithin,\n createElement,\n createParagraph,\n createTrailingBreak,\n isAtBlockEnd,\n isAtBlockStart,\n isEmptyTextBlock,\n isTextBlock,\n removeEmptyMarks,\n renameElement,\n syncTrailingBreak,\n textBlocksInRange,\n unwrap\n} from './dom';\nimport { setIndent } from './schema';\n\n/** Heading element tags. */\nexport type HeadingTag = 'H1' | 'H2' | 'H3' | 'H4' | 'H5' | 'H6';\n\n/** Paragraph alignment; `left` is the default and is stored as no style. */\nexport type TextAlign = 'left' | 'center' | 'right' | 'justify';\n\n/** Paragraph direction; `auto` is stored as no `dir` attribute. */\nexport type TextDirection = 'auto' | 'ltr' | 'rtl';\n\n/** Elements whose children are blocks rather than inline content. */\nconst CONTAINER_TAGS = new Set(['BLOCKQUOTE', 'TD', 'TH', 'LI']);\n\n/** The element whose children are blocks: the document, a quote, a list item body or a table cell. */\nexport const containerOf = (node: Node, root: HTMLElement): HTMLElement =>\n closestWithin(\n node.parentNode,\n root,\n element =>\n CONTAINER_TAGS.has(element.tagName) ||\n (element.tagName === 'DIV' && element.parentElement?.getAttribute('data-type') === 'taskItem')\n ) ?? root;\n\n/** The direct child of `container` that contains `node` (or `node` itself when it is a direct child). */\nexport const childOf = (container: Node, node: Node): Node => {\n let current = node;\n while (current.parentNode && current.parentNode !== container) current = current.parentNode;\n return current;\n};\n\n/** Drops an empty `style` attribute left behind after removing the last inline property. */\nconst removeEmptyStyle = (element: HTMLElement) => {\n if (!element.getAttribute('style')) element.removeAttribute('style');\n};\n\n/** Replaces a code block with one block of `tag` per line of its text. */\nconst codeBlockToBlocks = (pre: HTMLElement, tag: string) => {\n const lines = (pre.textContent ?? '').replace(/\\n$/, '').split('\\n');\n const blocks = lines.map(line => {\n const block = document.createElement(tag.toLowerCase());\n if (line) block.append(line);\n syncTrailingBreak(block);\n return block;\n });\n pre.replaceWith(...blocks);\n};\n\n/** Turns every selected text block into a paragraph or heading, keeping its alignment and indentation. */\nexport const setBlockType = (root: HTMLElement, range: Range, tag: 'P' | HeadingTag): void => {\n for (const block of textBlocksInRange(root, range)) {\n if (block.tagName === tag) continue;\n if (block.tagName === 'PRE') codeBlockToBlocks(block, tag);\n else syncTrailingBreak(renameElement(block, tag.toLowerCase()));\n }\n};\n\n/** Converts the selected blocks to code blocks, or back to paragraphs when all of them already are. */\nexport const toggleCodeBlock = (root: HTMLElement, range: Range): void => {\n const blocks = textBlocksInRange(root, range);\n if (blocks.length && blocks.every(block => block.tagName === 'PRE')) {\n setBlockType(root, range, 'P');\n return;\n }\n for (const block of blocks) {\n if (block.tagName === 'PRE') continue;\n const text = block.textContent ?? '';\n const code = createElement('code', {}, text ? [text] : [createTrailingBreak()]);\n block.replaceWith(createElement('pre', {}, [code]));\n }\n};\n\n/**\n * Unwraps the quotes around the selection when every selected block is quoted; otherwise wraps the selected\n * blocks, at the level of their closest common container, in a new quote.\n */\nexport const toggleBlockquote = (root: HTMLElement, range: Range): void => {\n const blocks = textBlocksInRange(root, range);\n const first = blocks[0];\n const last = blocks.at(-1);\n if (!first || !last) return;\n\n const quotes = blocks.map(block => closestTag(block, root, 'BLOCKQUOTE'));\n if (quotes.every(Boolean)) {\n for (const quote of new Set(quotes)) if (quote) unwrap(quote);\n return;\n }\n\n let container = containerOf(first, root);\n while (container !== root && !container.contains(last)) container = containerOf(container, root);\n const start = childOf(container, first);\n const end = childOf(container, last);\n const quote = createElement('blockquote');\n start.parentNode?.insertBefore(quote, start);\n let node: Node | null = start;\n while (node) {\n const next: Node | null = node === end ? null : node.nextSibling;\n quote.append(node);\n node = next;\n }\n};\n\n/** Aligns the selected paragraphs and headings; code blocks keep their alignment. */\nexport const setTextAlign = (root: HTMLElement, range: Range, align: TextAlign): void => {\n for (const block of textBlocksInRange(root, range)) {\n if (block.tagName === 'PRE') continue;\n if (align === 'left') block.style.removeProperty('text-align');\n else block.style.textAlign = align;\n removeEmptyStyle(block);\n }\n};\n\n/** Sets the line height of the selected paragraphs and headings, or resets it with `null`. */\nexport const setLineHeight = (root: HTMLElement, range: Range, lineHeight: string | null): void => {\n for (const block of textBlocksInRange(root, range)) {\n if (block.tagName === 'PRE') continue;\n if (lineHeight) block.style.lineHeight = lineHeight;\n else block.style.removeProperty('line-height');\n removeEmptyStyle(block);\n }\n};\n\n/** Sets the writing direction of the selected blocks; `auto` lets each paragraph follow its own text. */\nexport const setTextDirection = (root: HTMLElement, range: Range, direction: TextDirection): void => {\n for (const block of textBlocksInRange(root, range)) {\n if (direction === 'auto') block.removeAttribute('dir');\n else block.setAttribute('dir', direction);\n }\n};\n\n/** Reads the indentation step stored on a block. */\nconst indentOf = (block: HTMLElement) => Number.parseInt(block.dataset.indent ?? '0', 10) || 0;\n\n/**\n * Moves the selected paragraphs outside lists by whole indentation steps.\n * @returns whether any block's indentation changed.\n */\nexport const changeIndent = (root: HTMLElement, range: Range, delta: number): boolean => {\n let changed = false;\n for (const block of textBlocksInRange(root, range)) {\n if (block.tagName === 'PRE' || closestTag(block, root, 'LI')) continue;\n const current = indentOf(block);\n setIndent(block, current + delta);\n changed ||= indentOf(block) !== current;\n }\n return changed;\n};\n\n/** Splits a text block at a point; the new right-hand block keeps the tag and paragraph formatting. */\nexport const splitBlock = (block: HTMLElement, container: Node, offset: number): HTMLElement => {\n const tail = document.createRange();\n tail.setStart(container, offset);\n tail.setEnd(block, block.childNodes.length);\n const right = block.cloneNode(false) as HTMLElement;\n right.append(tail.extractContents());\n block.after(right);\n for (const part of [block, right]) {\n removeEmptyMarks(part);\n syncTrailingBreak(part);\n }\n return right;\n};\n\n/**\n * Places a block element (rule, page break, image, table) at the caret and returns the text block that should\n * receive the caret afterwards.\n */\nexport const insertBlockAtCaret = (root: HTMLElement, range: Range, element: HTMLElement): HTMLElement => {\n const block = closestTextBlock(range.startContainer, root);\n if (!block) {\n const paragraph = createParagraph();\n root.append(element, paragraph);\n return paragraph;\n }\n if (isEmptyTextBlock(block) || isAtBlockStart(block, range.startContainer, range.startOffset)) {\n block.before(element);\n return block;\n }\n if (isAtBlockEnd(block, range.endContainer, range.endOffset)) {\n block.after(element);\n const next = element.nextElementSibling;\n if (isTextBlock(next)) return next;\n const paragraph = createParagraph();\n element.after(paragraph);\n return paragraph;\n }\n const right = splitBlock(block, range.startContainer, range.startOffset);\n block.after(element);\n return right;\n};\n","import { childOf, containerOf } from './blocks';\nimport { closestTag, createElement, isList, renameElement, textBlocksInRange, unwrap } from './dom';\nimport { ensureTaskItem, listItemBody } from './schema';\n\n/** The three list types; the names match the toolbar state keys. */\nexport type ListKind = 'bulletList' | 'orderedList' | 'taskList';\n\n/** Kind of a `<ul>`/`<ol>` element; task lists are `<ul data-type=\"taskList\">`. */\nexport const listKind = (list: Element): ListKind =>\n list.tagName === 'OL' ? 'orderedList' : list.getAttribute('data-type') === 'taskList' ? 'taskList' : 'bulletList';\n\n/** List item containing `node`. */\nexport const closestListItem = (node: Node, root: HTMLElement): HTMLElement | null => closestTag(node, root, 'LI');\n\n/** Kind of the list the node is in, or `null` outside lists. */\nexport const activeListKind = (root: HTMLElement, node: Node): ListKind | null => {\n const list = closestListItem(node, root)?.parentElement;\n return list && isList(list) ? listKind(list) : null;\n};\n\n/** Creates an empty list element of the given kind. */\nconst createList = (kind: ListKind): HTMLElement =>\n kind === 'orderedList'\n ? createElement('ol')\n : createElement('ul', kind === 'taskList' ? { 'data-type': 'taskList' } : {});\n\n/** Creates a list item holding `content`; task items get their checkbox and body wrapper. */\nconst createItem = (kind: ListKind, content: Node[]): HTMLElement => {\n const item = createElement('li');\n if (kind === 'taskList') {\n item.setAttribute('data-checked', 'false');\n ensureTaskItem(item).append(...content);\n } else {\n item.append(...content);\n }\n return item;\n};\n\n/** Converts an item between the task-item structure and a plain list item. */\nconst convertItem = (item: HTMLElement, kind: ListKind) => {\n const isTask = item.getAttribute('data-type') === 'taskItem';\n if (kind === 'taskList' && !isTask) ensureTaskItem(item);\n if (kind === 'taskList' || !isTask) return;\n item.removeAttribute('data-type');\n item.removeAttribute('data-checked');\n item.querySelector(':scope > label')?.remove();\n const body = item.querySelector(':scope > div');\n if (body) unwrap(body);\n};\n\n/** Changes a list (and all its items) to another kind, returning the possibly renamed list element. */\nconst convertList = (list: HTMLElement, kind: ListKind): HTMLElement => {\n if (listKind(list) === kind) return list;\n const target = kind === 'orderedList' ? 'OL' : 'UL';\n const converted = list.tagName === target ? list : renameElement(list, target.toLowerCase());\n converted.removeAttribute('start');\n if (kind === 'taskList') converted.setAttribute('data-type', 'taskList');\n else converted.removeAttribute('data-type');\n for (const item of Array.from(converted.children) as HTMLElement[]) convertItem(item, kind);\n return converted;\n};\n\n/** Joins the list with same-kind neighbours, as office editors do. */\nconst joinNeighbours = (list: HTMLElement) => {\n const previous = list.previousElementSibling;\n let target = list;\n if (previous && isList(previous) && listKind(previous) === listKind(list)) {\n previous.append(...Array.from(list.children));\n list.remove();\n target = previous as HTMLElement;\n }\n const next = target.nextElementSibling;\n if (next && isList(next) && listKind(next) === listKind(target)) {\n target.append(...Array.from(next.children));\n next.remove();\n }\n};\n\n/**\n * Moves an item one level up. From a nested list it becomes an item of the outer list, taking the following\n * siblings as its own sub-list; from a top-level list its content becomes plain blocks between two list halves.\n */\nexport const liftListItem = (root: HTMLElement, item: HTMLElement): void => {\n const list = item.parentElement;\n if (!list || !isList(list)) return;\n const kind = listKind(list);\n const following: Element[] = [];\n for (let sibling = item.nextElementSibling; sibling; sibling = sibling.nextElementSibling) following.push(sibling);\n\n const container = containerOf(list, root);\n const outerItem =\n container.tagName === 'LI' ? container : container.tagName === 'DIV' ? container.parentElement : null;\n\n if (outerItem?.tagName === 'LI') {\n if (following.length) {\n const rest = createList(kind);\n rest.append(...following);\n listItemBody(item).append(rest);\n }\n outerItem.after(item);\n const outerList = outerItem.parentElement;\n if (outerList) convertItem(item, listKind(outerList));\n } else {\n if (following.length) {\n const rest = list.cloneNode(false) as HTMLElement;\n rest.removeAttribute('start');\n rest.append(...following);\n list.after(rest);\n }\n list.after(...Array.from(listItemBody(item).childNodes));\n item.remove();\n }\n if (!list.children.length) list.remove();\n};\n\n/**\n * Nests an item under its previous sibling, reusing a trailing sub-list of the same kind.\n * @returns whether the item could be nested (the first item cannot).\n */\nconst sinkListItem = (item: HTMLElement): boolean => {\n const previous = item.previousElementSibling as HTMLElement | null;\n const list = item.parentElement;\n if (!previous || !list) return false;\n const body = listItemBody(previous);\n let nested = body.lastElementChild as HTMLElement | null;\n if (!(nested && isList(nested) && listKind(nested) === listKind(list))) {\n nested = createList(listKind(list));\n body.append(nested);\n }\n nested.append(item);\n return true;\n};\n\n/** Distinct list items that contain selected text blocks, in document order. */\nconst selectedItems = (root: HTMLElement, range: Range): HTMLElement[] => {\n const items = textBlocksInRange(root, range).map(block => closestListItem(block, root));\n return [...new Set(items.filter((item): item is HTMLElement => item !== null))];\n};\n\n/**\n * Tab / Shift+Tab inside lists: nests or lifts every selected item.\n * @returns whether any item changed level.\n */\nexport const changeListIndent = (root: HTMLElement, range: Range, delta: 1 | -1): boolean => {\n const items = selectedItems(root, range);\n if (!items.length) return false;\n if (delta > 0) return items.map(item => sinkListItem(item)).some(Boolean);\n // Lifting from the last item keeps the earlier items' positions valid.\n for (const item of items.reverse()) liftListItem(root, item);\n return true;\n};\n\n/** Lifts the block out of every list it is nested in (\"clear formatting\"). */\nexport const liftOutOfLists = (root: HTMLElement, block: HTMLElement): void => {\n for (let item = closestListItem(block, root); item; item = closestListItem(block, root)) liftListItem(root, item);\n};\n\n/**\n * Toolbar list button: lifts the selection out when it is already in lists of this kind, converts lists of other\n * kinds, and otherwise wraps the selected blocks in a new list joined with same-kind neighbours.\n */\nexport const toggleList = (root: HTMLElement, range: Range, kind: ListKind): void => {\n const blocks = textBlocksInRange(root, range);\n const first = blocks[0];\n const last = blocks.at(-1);\n if (!first || !last) return;\n const items = blocks.map(block => closestListItem(block, root));\n\n if (items.every(Boolean)) {\n const lists = [...new Set(items.map(item => item?.parentElement as HTMLElement))];\n if (lists.every(list => listKind(list) === kind)) {\n for (const item of [...new Set(items)].reverse()) if (item) liftListItem(root, item);\n } else {\n for (const list of lists) joinNeighbours(convertList(list, kind));\n }\n return;\n }\n\n let container = containerOf(first, root);\n while (container !== root && !container.contains(last)) container = containerOf(container, root);\n const start = childOf(container, first);\n const end = childOf(container, last);\n const nodes: Node[] = [];\n for (let node: Node | null = start; node; node = node === end ? null : node.nextSibling) nodes.push(node);\n\n const list = createList(kind);\n start.parentNode?.insertBefore(list, start);\n for (const node of nodes) {\n if (isList(node)) {\n const converted = convertList(node as HTMLElement, kind);\n list.append(...Array.from(converted.children));\n converted.remove();\n } else {\n list.append(createItem(kind, [node]));\n }\n }\n joinNeighbours(list);\n};\n\n/** Moves `rightBlock` and everything after it in the item body into a new item placed after `item`. */\nexport const splitListItem = (item: HTMLElement, rightBlock: HTMLElement): HTMLElement => {\n const list = item.parentElement as HTMLElement;\n const body = listItemBody(item);\n const moving: Node[] = [];\n for (let node: Node | null = childOf(body, rightBlock); node; node = node.nextSibling) moving.push(node);\n const next = createItem(listKind(list), moving);\n item.after(next);\n return next;\n};\n\n/** Flips a task item's checked state in both the `data-checked` attribute and its checkbox. */\nexport const toggleTaskItem = (item: HTMLElement): void => {\n const checked = item.getAttribute('data-checked') !== 'true';\n item.setAttribute('data-checked', String(checked));\n const input = item.querySelector<HTMLInputElement>(':scope > label input');\n if (!input) return;\n input.checked = checked;\n input.toggleAttribute('checked', checked);\n};\n","import {\n closestTextBlock,\n closestWithin,\n isElement,\n isText,\n mergeAdjacentMarks,\n removeEmptyMarks,\n syncTrailingBreak,\n unwrap\n} from './dom';\n\n/** Toggleable inline formatting, each rendered as its own element. */\nexport type MarkName = 'bold' | 'italic' | 'underline' | 'strike' | 'code' | 'subscript' | 'superscript';\n\n/** Inline formatting stored as CSS properties on a `<span>`. */\nexport type StyleName = 'color' | 'fontFamily' | 'fontSize';\n\n/** Formatting chosen while the caret is collapsed; applied to the next typed text. */\nexport interface PendingFormat {\n /** Marks forced on (`true`) or off (`false`); missing marks follow the surrounding text. */\n marks: Partial<Record<MarkName, boolean>>;\n /** Span styles to apply; `null` removes the style, missing styles follow the surrounding text. */\n styles: Partial<Record<StyleName, string | null>>;\n /** Highlight colour; `null` removes the highlight, `undefined` follows the surrounding text. */\n highlight?: string | null;\n}\n\n/** Colour used when a highlight is applied without choosing one, e.g. by `==text==` or Mod+Shift+H. */\nexport const DEFAULT_HIGHLIGHT_COLOR = '#fef08a';\n\n/** Element tag of every mark, in the nesting order used when marks are created. */\nconst MARK_TAG: Record<MarkName, string> = {\n bold: 'STRONG',\n italic: 'EM',\n underline: 'U',\n strike: 'S',\n code: 'CODE',\n subscript: 'SUB',\n superscript: 'SUP'\n};\n\n/** CSS property written for every span style. */\nconst STYLE_PROPERTY: Record<StyleName, string> = { color: 'color', fontFamily: 'font-family', fontSize: 'font-size' };\n\n/** Every element that carries inline formatting; removed by \"clear formatting\". */\nconst FORMATTING_TAGS = new Set(['STRONG', 'EM', 'U', 'S', 'CODE', 'SUB', 'SUP', 'SPAN', 'MARK', 'A']);\n\n/** `rel` written on links so opened pages cannot reach back into the application. */\nconst LINK_REL = 'noopener noreferrer nofollow';\n\n/** Nearest ancestor matching the predicate without leaving the text block that contains `node`. */\nconst ancestorWithin = (node: Node, root: HTMLElement, predicate: (element: HTMLElement) => boolean) =>\n closestWithin(node, closestTextBlock(node, root) ?? root, predicate);\n\n/** Nearest mark element with the given tag around `node`. */\nconst markAncestor = (node: Node, root: HTMLElement, tag: string) =>\n ancestorWithin(node, root, element => element.tagName === tag);\n\n/** Nearest `<span>` around `node` that sets the given CSS property. */\nconst styleAncestor = (node: Node, root: HTMLElement, property: string) =>\n ancestorWithin(node, root, element => element.tagName === 'SPAN' && element.style.getPropertyValue(property) !== '');\n\n/** Nearest `<mark>` highlight around `node`. */\nconst highlightAncestor = (node: Node, root: HTMLElement) =>\n ancestorWithin(node, root, element => element.tagName === 'MARK');\n\n/** Link containing `node`; links may span several blocks' worth of inline markup, so the search stops at `root`. */\nexport const linkAncestor = (node: Node, root: HTMLElement): HTMLAnchorElement | null =>\n closestWithin(node, root, element => element.tagName === 'A') as HTMLAnchorElement | null;\n\n/** Splits text nodes at the range edges so every selected character sits in a fully selected text node. */\nconst splitRangeBoundaries = (range: Range): void => {\n const { endContainer, endOffset } = range;\n if (isText(endContainer) && endOffset > 0 && endOffset < endContainer.length) endContainer.splitText(endOffset);\n const { startContainer, startOffset } = range;\n if (isText(startContainer) && startOffset > 0 && startOffset < startContainer.length) {\n startContainer.splitText(startOffset);\n }\n};\n\n/** Text nodes with at least one selected character, skipping code blocks and text outside blocks. */\nexport const selectedTextNodes = (root: HTMLElement, range: Range): Text[] => {\n if (range.collapsed) return [];\n const ancestor = range.commonAncestorContainer;\n const scope = isText(ancestor) ? (ancestor.parentNode ?? root) : ancestor;\n const nodes: Text[] = [];\n const walker = document.createTreeWalker(scope, NodeFilter.SHOW_TEXT);\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n const text = node as Text;\n if (!range.intersectsNode(text)) continue;\n const block = closestTextBlock(text, root);\n if (!block || block.tagName === 'PRE') continue;\n const from = text === range.startContainer ? range.startOffset : 0;\n const to = text === range.endContainer ? range.endOffset : text.length;\n if (to > from) nodes.push(text);\n }\n return nodes;\n};\n\n/** Splits the parent of `child` into up to three shallow copies so `child` ends up alone in the middle one. */\nconst splitAroundChild = (child: Node) => {\n const parent = child.parentNode as HTMLElement;\n if (child.previousSibling) {\n const before = parent.cloneNode(false) as HTMLElement;\n while (parent.firstChild && parent.firstChild !== child) before.append(parent.firstChild);\n parent.before(before);\n }\n if (child.nextSibling) {\n const after = parent.cloneNode(false) as HTMLElement;\n while (child.nextSibling) after.append(child.nextSibling);\n parent.after(after);\n }\n};\n\n/** Splits `ancestor` and every element below it so that `ancestor` wraps exactly `node`. */\nconst isolate = (node: Node, ancestor: HTMLElement): HTMLElement => {\n let current: Node = node;\n while (current !== ancestor && current.parentNode) {\n splitAroundChild(current);\n current = current.parentNode;\n }\n return ancestor;\n};\n\n/** Wraps a text node in a new element with the given tag and returns that element. */\nconst wrapText = (node: Text, tag: string): HTMLElement => {\n const element = document.createElement(tag);\n node.before(element);\n element.append(node);\n return element;\n};\n\n/** Writes the colour of a `<mark>` in the attribute and inline-style form the sanitiser also produces. */\nconst setHighlightAttributes = (mark: HTMLElement, color: string) => {\n mark.setAttribute('data-color', color);\n mark.setAttribute('style', `background-color: ${color}; color: inherit`);\n};\n\n/** Removes empty marks, merges identical neighbours and fixes caret placeholders in the blocks that were edited. */\nconst tidyBlocks = (root: HTMLElement, nodes: Node[]) => {\n const blocks = new Set(nodes.map(node => closestTextBlock(node, root)));\n for (const block of blocks) {\n if (!block?.isConnected) continue;\n removeEmptyMarks(block);\n mergeAdjacentMarks(block);\n syncTrailingBreak(block);\n }\n};\n\n/**\n * Runs `apply` for every selected text node after splitting the range edges, then tidies the touched blocks.\n * @returns whether any text was selected.\n */\nconst eachSelectedText = (root: HTMLElement, range: Range, apply: (node: Text) => void) => {\n splitRangeBoundaries(range);\n const nodes = selectedTextNodes(root, range);\n for (const node of nodes) apply(node);\n tidyBlocks(root, nodes);\n return nodes.length > 0;\n};\n\n/** Whether the mark applies at the caret, or to every selected character of a non-collapsed range. */\nexport const isMarkActive = (root: HTMLElement, range: Range, mark: MarkName): boolean => {\n const tag = MARK_TAG[mark];\n if (range.collapsed) return markAncestor(range.startContainer, root, tag) !== null;\n const nodes = selectedTextNodes(root, range);\n return nodes.length > 0 && nodes.every(node => markAncestor(node, root, tag) !== null);\n};\n\n/**\n * Removes the mark when the whole selection already has it, otherwise adds it to every selected character.\n * Subscript and superscript exclude each other.\n */\nexport const toggleMark = (root: HTMLElement, range: Range, mark: MarkName): void => {\n const tag = MARK_TAG[mark];\n splitRangeBoundaries(range);\n const nodes = selectedTextNodes(root, range);\n const active = nodes.length > 0 && nodes.every(node => markAncestor(node, root, tag) !== null);\n const opposite = mark === 'subscript' ? MARK_TAG.superscript : mark === 'superscript' ? MARK_TAG.subscript : null;\n for (const node of nodes) {\n const existing = markAncestor(node, root, tag);\n if (active) {\n if (existing) unwrap(isolate(node, existing));\n continue;\n }\n const excluded = opposite ? markAncestor(node, root, opposite) : null;\n if (excluded) unwrap(isolate(node, excluded));\n if (!existing) wrapText(node, tag);\n }\n tidyBlocks(root, nodes);\n};\n\n/** Sets (or with `null` removes) a span style on the selected text, reusing spans that already carry it. */\nexport const setTextStyle = (root: HTMLElement, range: Range, name: StyleName, value: string | null): void => {\n const property = STYLE_PROPERTY[name];\n eachSelectedText(root, range, node => {\n const existing = styleAncestor(node, root, property);\n if (existing) {\n const span = isolate(node, existing);\n if (value) span.style.setProperty(property, value);\n else span.style.removeProperty(property);\n if (!span.getAttribute('style')) unwrap(span);\n } else if (value) {\n wrapText(node, 'span').style.setProperty(property, value);\n }\n });\n};\n\n/** Highlights the selected text with a colour, or removes the highlight with `null`. */\nexport const setHighlight = (root: HTMLElement, range: Range, color: string | null): void => {\n eachSelectedText(root, range, node => {\n const existing = highlightAncestor(node, root);\n if (existing) {\n const mark = isolate(node, existing);\n if (color) setHighlightAttributes(mark, color);\n else unwrap(mark);\n } else if (color) {\n setHighlightAttributes(wrapText(node, 'mark'), color);\n }\n });\n};\n\n/**\n * Turns the selected text into a link, replacing any link it was part of.\n * @returns whether any text was selected.\n */\nexport const setLink = (root: HTMLElement, range: Range, href: string, target: string | null): boolean =>\n eachSelectedText(root, range, node => {\n const existing = linkAncestor(node, root);\n if (existing) unwrap(isolate(node, existing));\n const link = wrapText(node, 'a');\n link.setAttribute('href', href);\n link.setAttribute('rel', LINK_REL);\n if (target) link.setAttribute('target', target);\n });\n\n/** Removes links from the selected text, keeping the text itself. */\nexport const unsetLink = (root: HTMLElement, range: Range): void => {\n eachSelectedText(root, range, node => {\n const existing = linkAncestor(node, root);\n if (existing) unwrap(isolate(node, existing));\n });\n};\n\n/** Removes every mark, span style, highlight and link from the selected text. */\nexport const clearMarks = (root: HTMLElement, range: Range): void => {\n const isFormatting = (element: HTMLElement) => FORMATTING_TAGS.has(element.tagName);\n eachSelectedText(root, range, node => {\n let mark = ancestorWithin(node, root, isFormatting);\n while (mark) {\n unwrap(isolate(node, mark));\n mark = ancestorWithin(node, root, isFormatting);\n }\n });\n};\n\n/** Which marks apply to `node`. */\nexport const readMarks = (root: HTMLElement, node: Node): Record<MarkName, boolean> => {\n const result = {} as Record<MarkName, boolean>;\n for (const [name, tag] of Object.entries(MARK_TAG) as Array<[MarkName, string]>) {\n result[name] = markAncestor(node, root, tag) !== null;\n }\n return result;\n};\n\n/** Value of a span style at `node`, or an empty string when none is set. */\nexport const readStyle = (root: HTMLElement, node: Node, name: StyleName): string =>\n styleAncestor(node, root, STYLE_PROPERTY[name])?.style.getPropertyValue(STYLE_PROPERTY[name]) ?? '';\n\n/** Highlight colour at `node`, the default colour for a `<mark>` without one, or an empty string. */\nexport const readHighlight = (root: HTMLElement, node: Node): string => {\n const mark = highlightAncestor(node, root);\n if (!mark) return '';\n return mark.getAttribute('data-color') || mark.style.backgroundColor || DEFAULT_HIGHLIGHT_COLOR;\n};\n\n/**\n * Inserts text at a collapsed caret with formatting that differs from its surroundings: the inline elements\n * around the caret are split and the text is wrapped in exactly the requested marks.\n */\nexport const insertFormattedText = (root: HTMLElement, range: Range, text: string, format: PendingFormat): void => {\n const block = closestTextBlock(range.startContainer, root);\n if (!block) return;\n const anchor = range.startContainer;\n const marks = { ...readMarks(root, anchor), ...format.marks };\n const styles: Record<StyleName, string> = {\n color: readStyle(root, anchor, 'color'),\n fontFamily: readStyle(root, anchor, 'fontFamily'),\n fontSize: readStyle(root, anchor, 'fontSize')\n };\n for (const [name, value] of Object.entries(format.styles) as Array<[StyleName, string | null]>) {\n styles[name] = value ?? '';\n }\n const highlight = format.highlight === undefined ? readHighlight(root, anchor) : (format.highlight ?? '');\n const link = linkAncestor(anchor, root);\n\n // Everything after the caret moves to the end of the block, splitting the marks around the caret.\n const tail = document.createRange();\n tail.setStart(range.startContainer, range.startOffset);\n tail.setEnd(block, block.childNodes.length);\n const right = tail.extractContents();\n const reference = right.firstChild;\n block.append(right);\n\n let node: Node = document.createTextNode(text);\n const wrapNode = (element: HTMLElement) => {\n element.append(node);\n node = element;\n };\n for (const [name, tag] of Object.entries(MARK_TAG) as Array<[MarkName, string]>) {\n if (marks[name]) wrapNode(document.createElement(tag));\n }\n const css = (Object.keys(STYLE_PROPERTY) as StyleName[])\n .filter(name => styles[name])\n .map(name => `${STYLE_PROPERTY[name]}: ${styles[name]}`);\n if (css.length) {\n const span = document.createElement('span');\n span.setAttribute('style', css.join('; '));\n wrapNode(span);\n }\n if (highlight) {\n const mark = document.createElement('mark');\n setHighlightAttributes(mark, highlight);\n wrapNode(mark);\n }\n if (link && isElement(reference) && link.contains(anchor)) wrapNode(link.cloneNode(false) as HTMLElement);\n\n block.insertBefore(node, reference && reference.parentNode === block ? reference : null);\n removeEmptyMarks(block);\n mergeAdjacentMarks(block);\n syncTrailingBreak(block);\n};\n","import type { TextAlign, TextDirection } from './engine/blocks';\nimport { closestTag, closestTextBlock } from './engine/dom';\nimport { activeListKind } from './engine/lists';\nimport {\n type MarkName,\n type PendingFormat,\n isMarkActive,\n linkAncestor,\n readHighlight,\n readStyle,\n selectedTextNodes\n} from './engine/marks';\n\n/**\n * Flat snapshot of everything the toolbar renders. Components receive this instead of the engine so they\n * re-render only when a visible value changes, not on every keystroke.\n */\nexport interface EditorUiState {\n /** Heading level of the current block, `0` for a paragraph. */\n headingLevel: number;\n /** Font family at the selection without quotes, empty for the document default. */\n fontFamily: string;\n /** Font size at the selection (e.g. `14pt`), empty for the document default. */\n fontSize: string;\n /** Line spacing of the current paragraph, empty for the default. */\n lineHeight: string;\n /** Text color at the selection, empty for the default. */\n color: string;\n /** Highlight color at the selection, empty when not highlighted. */\n highlight: string;\n /** Alignment of the current paragraph. */\n align: TextAlign;\n /** Writing direction of the current paragraph. */\n direction: TextDirection;\n /** Whether the selection is bold. */\n bold: boolean;\n /** Whether the selection is italic. */\n italic: boolean;\n /** Whether the selection is underlined. */\n underline: boolean;\n /** Whether the selection is struck through. */\n strike: boolean;\n /** Whether the selection is inline code. */\n code: boolean;\n /** Whether the selection is subscript. */\n subscript: boolean;\n /** Whether the selection is superscript. */\n superscript: boolean;\n /** Whether the selection is inside a link. */\n link: boolean;\n /** Whether the selection is in a bulleted list. */\n bulletList: boolean;\n /** Whether the selection is in a numbered list. */\n orderedList: boolean;\n /** Whether the selection is in a task list. */\n taskList: boolean;\n /** Whether the selection is inside a quote. */\n blockquote: boolean;\n /** Whether the selection is inside a code block. */\n codeBlock: boolean;\n /** Whether there is an undo step. */\n canUndo: boolean;\n /** Whether there is a redo step. */\n canRedo: boolean;\n}\n\n/** Everything {@link readUiState} needs to know about the editor. */\ninterface UiStateSource {\n /** Editable document root. */\n root: HTMLElement;\n /** Current or last selection, `null` when the document never had one. */\n range: Range | null;\n /** Formatting chosen at a collapsed caret for the next typed text. */\n pending: PendingFormat | null;\n /** Whether there is an undo step. */\n canUndo: boolean;\n /** Whether there is a redo step. */\n canRedo: boolean;\n}\n\n/** State shown before the editor exists or while nothing is selected. */\nexport const EMPTY_UI_STATE: EditorUiState = {\n headingLevel: 0,\n fontFamily: '',\n fontSize: '',\n lineHeight: '',\n color: '',\n highlight: '',\n align: 'left',\n direction: 'auto',\n bold: false,\n italic: false,\n underline: false,\n strike: false,\n code: false,\n subscript: false,\n superscript: false,\n link: false,\n bulletList: false,\n orderedList: false,\n taskList: false,\n blockquote: false,\n codeBlock: false,\n canUndo: false,\n canRedo: false\n};\n\n/** Marks reported to the toolbar. */\nconst MARKS: MarkName[] = ['bold', 'italic', 'underline', 'strike', 'code', 'subscript', 'superscript'];\n\n/** Font families compare without quotes: browsers re-serialise `'Times New Roman'` as `\"Times New Roman\"`. */\nexport const normalizeFontFamily = (value: string): string =>\n value\n .replace(/[\"']/g, '')\n .replace(/\\s*,\\s*/g, ', ')\n .trim();\n\n/** Alignment of a text block; left is the default and is never stored. */\nconst readAlignment = (block: HTMLElement | null): TextAlign => {\n const value = block?.style.textAlign ?? '';\n return value === 'center' || value === 'right' || value === 'justify' ? value : 'left';\n};\n\n/** Explicit direction of a text block; blocks without `dir` follow their own text. */\nconst readDirection = (block: HTMLElement | null): TextDirection => {\n const value = block?.getAttribute('dir');\n return value === 'rtl' || value === 'ltr' ? value : 'auto';\n};\n\n/**\n * Reads the toolbar state at a selection. Marks must cover the whole selection to count as active; styles and\n * block properties come from its first text. At a collapsed caret pending formatting overrides the document.\n */\nexport const readUiState = ({ root, range, pending, canUndo, canRedo }: UiStateSource): EditorUiState => {\n if (!range) return { ...EMPTY_UI_STATE, canUndo, canRedo };\n const anchor = range.collapsed ? range.startContainer : (selectedTextNodes(root, range)[0] ?? range.startContainer);\n const block = closestTextBlock(anchor, root);\n const marks = Object.fromEntries(MARKS.map(mark => [mark, isMarkActive(root, range, mark)])) as Record<\n MarkName,\n boolean\n >;\n const styles = {\n color: readStyle(root, anchor, 'color'),\n fontFamily: normalizeFontFamily(readStyle(root, anchor, 'fontFamily')),\n fontSize: readStyle(root, anchor, 'fontSize')\n };\n let highlight = readHighlight(root, anchor);\n\n if (range.collapsed && pending) {\n Object.assign(marks, pending.marks);\n for (const [name, value] of Object.entries(pending.styles)) {\n styles[name as keyof typeof styles] = name === 'fontFamily' ? normalizeFontFamily(value ?? '') : (value ?? '');\n }\n if (pending.highlight !== undefined) highlight = pending.highlight ?? '';\n }\n\n const listKind = activeListKind(root, anchor);\n const headingLevel = block && /^H[1-6]$/.test(block.tagName) ? Number(block.tagName[1]) : 0;\n\n return {\n ...marks,\n ...styles,\n headingLevel,\n highlight,\n lineHeight: block?.style.lineHeight ?? '',\n align: readAlignment(block),\n direction: readDirection(block),\n link: linkAncestor(anchor, root) !== null,\n bulletList: listKind === 'bulletList',\n orderedList: listKind === 'orderedList',\n taskList: listKind === 'taskList',\n blockquote: closestTag(anchor, root, 'BLOCKQUOTE') !== null,\n codeBlock: block?.tagName === 'PRE',\n canUndo,\n canRedo\n };\n};\n\n/** Shallow comparison used to skip toolbar re-renders when nothing visible changed. */\nexport const isSameUiState = (a: EditorUiState, b: EditorUiState): boolean =>\n (Object.keys(a) as Array<keyof EditorUiState>).every(key => a[key] === b[key]);\n","/** Reading and writing clipboard and drag-and-drop data in the formats the editor understands. */\nimport { createParagraph, textBlocksInRange } from './dom';\nimport { cleanEditorArtifacts, sanitizeHtml } from './schema';\n\n/** The parts of a clipboard or drag payload the editor uses. */\nexport interface TransferContent {\n /** Rich HTML, empty when the source offered none. */\n html: string;\n /** Plain text, empty when the source offered none. */\n text: string;\n /** Files, e.g. pasted screenshots or dropped images. */\n files: File[];\n}\n\n/** MIME types the editor accepts as images. */\nconst IMAGE_MIME = /^image\\//;\n\n/** A payload consisting of exactly one web address. */\nconst SINGLE_URL = /^(?:https?:\\/\\/|www\\.)\\S+$/i;\n\n/** Extracts HTML, text and files from a clipboard or drag payload. */\nexport const readTransfer = (data: DataTransfer | null): TransferContent => ({\n html: data?.getData('text/html') ?? '',\n text: data?.getData('text/plain') ?? '',\n files: Array.from(data?.files ?? [])\n});\n\n/** The image files of a payload. */\nexport const imageFiles = (content: TransferContent): File[] =>\n content.files.filter(file => IMAGE_MIME.test(file.type));\n\n/** The URL when the plain text is a single web address (`www.` addresses get `https://`), otherwise `null`. */\nexport const singleUrl = (content: TransferContent): string | null => {\n const text = content.text.trim();\n if (!SINGLE_URL.test(text)) return null;\n return text.startsWith('www.') ? `https://${text}` : text;\n};\n\n/** Converts plain text into paragraphs, one per line; a final line break does not add an empty paragraph. */\nexport const textToFragment = (text: string): DocumentFragment => {\n const fragment = document.createDocumentFragment();\n const lines = text.replace(/\\r\\n?/g, '\\n').split('\\n');\n if (lines.length > 1 && lines.at(-1) === '') lines.pop();\n for (const line of lines) fragment.append(createParagraph(line ? [document.createTextNode(line)] : []));\n return fragment;\n};\n\n/** Converts a payload into editor blocks, preferring sanitised HTML over plain text; `null` when it has neither. */\nexport const transferToFragment = (content: TransferContent): DocumentFragment | null => {\n if (content.html) return sanitizeHtml(content.html);\n return content.text ? textToFragment(content.text) : null;\n};\n\n/** Plain text of a range with a line break between blocks, which `Range.toString()` loses. */\nconst rangeText = (root: HTMLElement, range: Range): string => {\n const blocks = textBlocksInRange(root, range);\n if (blocks.length < 2) return range.toString();\n return blocks\n .map(block => {\n const part = document.createRange();\n part.selectNodeContents(block);\n if (block.contains(range.startContainer)) part.setStart(range.startContainer, range.startOffset);\n if (block.contains(range.endContainer)) part.setEnd(range.endContainer, range.endOffset);\n return part.toString();\n })\n .join('\\n');\n};\n\n/** Puts clean HTML and plain text of the selection on a clipboard or drag payload for copy, cut and drag. */\nexport const writeTransfer = (data: DataTransfer, root: HTMLElement, range: Range): void => {\n const container = document.createElement('div');\n container.append(range.cloneContents());\n cleanEditorArtifacts(container, true);\n data.setData('text/html', container.innerHTML);\n data.setData('text/plain', rangeText(root, range));\n};\n\n/** `Document` with both caret-from-point APIs, which not every browser (or TypeScript lib) provides. */\ntype CaretDocument = Document & {\n /** Standard API (Firefox, recent Chromium). */\n caretPositionFromPoint?: (x: number, y: number) => { offsetNode: Node; offset: number } | null;\n /** Legacy WebKit/Chromium API. */\n caretRangeFromPoint?: (x: number, y: number) => Range | null;\n};\n\n/** Collapsed range at the text position under the pointer, used to place dropped content and canvas clicks. */\nexport const rangeFromPoint = (x: number, y: number): Range | null => {\n const doc = document as CaretDocument;\n if (!doc.caretPositionFromPoint) return doc.caretRangeFromPoint?.(x, y) ?? null;\n const position = doc.caretPositionFromPoint(x, y);\n if (!position) return null;\n const range = document.createRange();\n range.setStart(position.offsetNode, position.offset);\n range.collapse(true);\n return range;\n};\n","import { TRAILING_BREAK, closestTextBlock, isAtom, isBreak, isElement, isText, isTrailingBreak } from './dom';\n\n/**\n * Selection positions expressed as character offsets through the document. Unlike DOM ranges they stay valid when\n * the markup around the text is rebuilt (marks merged, blocks renamed, history restored).\n */\nexport interface TextBookmark {\n /** Offset where the selection starts (for a saved selection: where the user started selecting). */\n anchor: number;\n /** Offset where the selection ends (for a saved selection: where the caret currently is). */\n focus: number;\n}\n\n/** A caret-bearing node with its position in the flattened document. */\ninterface Leaf {\n /** Text node, real line break, trailing placeholder break or atom block. */\n node: Node;\n /** Offset of the leaf's first position. */\n start: number;\n /** Number of positions the leaf occupies: text length, 1 for breaks and atoms, 0 for placeholders. */\n size: number;\n /** Text block containing the leaf, used to insert one position between blocks. */\n block: HTMLElement | null;\n}\n\n/** A DOM boundary point: a node and an offset inside it. */\ninterface BoundaryPoint {\n /** Container node of the boundary. */\n node: Node;\n /** Character offset for text nodes, child index for elements. */\n offset: number;\n}\n\n/** Index of a node among its parent's child nodes, or -1 when it has no parent. */\nexport const indexOf = (node: Node): number => Array.prototype.indexOf.call(node.parentNode?.childNodes ?? [], node);\n\n/** Positions a node occupies, or `null` when the node cannot hold the caret (for example text between blocks). */\nconst leafSize = (node: Node, root: HTMLElement): number | null => {\n if (isText(node)) return closestTextBlock(node, root) ? node.data.length : null;\n if (isBreak(node)) return node.hasAttribute(TRAILING_BREAK) ? 0 : 1;\n return isAtom(node) ? 1 : null;\n};\n\n/** Lists every caret-bearing node with its offset; entering a new text block adds one position, like a line break. */\nconst collectLeaves = (root: HTMLElement): Leaf[] => {\n const leaves: Leaf[] = [];\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);\n let position = 0;\n let previousBlock: HTMLElement | null | undefined;\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n const size = leafSize(node, root);\n if (size === null) continue;\n const block = closestTextBlock(node, root);\n if (previousBlock !== undefined && block !== previousBlock) position += 1;\n previousBlock = block;\n leaves.push({ node, start: position, size, block });\n position += size;\n }\n return leaves;\n};\n\n/** Converts a DOM boundary point into a document offset. */\nconst positionOf = (root: HTMLElement, leaves: Leaf[], container: Node, offset: number): number => {\n if (isText(container)) {\n const leaf = leaves.find(candidate => candidate.node === container);\n if (leaf) return leaf.start + Math.min(offset, leaf.size);\n }\n const point = document.createRange();\n point.setStart(container, offset);\n const pointBlock = closestTextBlock(container, root);\n let previous: Leaf | null = null;\n for (const leaf of leaves) {\n if (point.comparePoint(leaf.node, 0) > 0) {\n if (!previous || leaf.block === pointBlock) return leaf.start;\n return previous.start + previous.size;\n }\n previous = leaf;\n }\n return previous ? previous.start + previous.size : 0;\n};\n\n/** Converts a document offset back into a DOM boundary point, clamping offsets past the end to the last leaf. */\nconst resolvePosition = (root: HTMLElement, leaves: Leaf[], position: number): BoundaryPoint => {\n for (const leaf of leaves) {\n if (position > leaf.start + leaf.size) continue;\n const target = Math.max(position, leaf.start);\n if (isText(leaf.node)) return { node: leaf.node, offset: target - leaf.start };\n const parent = leaf.node.parentNode ?? root;\n const index = indexOf(leaf.node);\n const before = target === leaf.start || isTrailingBreak(leaf.node);\n return { node: parent, offset: before ? index : index + 1 };\n }\n const last = leaves.at(-1);\n if (!last) return { node: root, offset: 0 };\n if (isText(last.node)) return { node: last.node, offset: last.size };\n return { node: last.node.parentNode ?? root, offset: indexOf(last.node) + (isTrailingBreak(last.node) ? 0 : 1) };\n};\n\n/** The current selection range when it lies entirely inside `root`, otherwise `null`. */\nexport const getRangeWithin = (root: HTMLElement): Range | null => {\n const selection = document.getSelection();\n if (!selection || selection.rangeCount === 0) return null;\n const range = selection.getRangeAt(0);\n return root.contains(range.startContainer) && root.contains(range.endContainer) ? range : null;\n};\n\n/** Replaces the document selection with `range`. */\nexport const selectRange = (range: Range): void => {\n const selection = document.getSelection();\n if (!selection) return;\n selection.removeAllRanges();\n selection.addRange(range);\n};\n\n/** Collapses the selection to a boundary point and returns the created range. */\nconst setCaret = (node: Node, offset: number): Range => {\n const range = document.createRange();\n range.setStart(node, offset);\n range.collapse(true);\n selectRange(range);\n return range;\n};\n\n/** All text nodes inside `element`, in document order. */\nconst textNodes = (element: Node): Text[] => {\n const nodes: Text[] = [];\n const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);\n for (let node = walker.nextNode(); node; node = walker.nextNode()) nodes.push(node as Text);\n return nodes;\n};\n\n/** Places the caret before the first character of `element`. */\nexport const placeCaretAtStart = (element: HTMLElement): Range => {\n const first = textNodes(element)[0];\n return first ? setCaret(first, 0) : setCaret(element, 0);\n};\n\n/** Places the caret after the last character of `element`, in front of its placeholder break if it has one. */\nexport const placeCaretAtEnd = (element: HTMLElement): Range => {\n const last = textNodes(element).at(-1);\n if (last) return setCaret(last, last.data.length);\n const trailing = Array.from(element.childNodes).find(isTrailingBreak);\n return setCaret(element, trailing ? indexOf(trailing) : element.childNodes.length);\n};\n\n/** Stores a range as document offsets; `anchor` is the start and `focus` the end. */\nexport const rangeToBookmark = (root: HTMLElement, range: Range): TextBookmark => {\n const leaves = collectLeaves(root);\n const anchor = positionOf(root, leaves, range.startContainer, range.startOffset);\n const focus = range.collapsed ? anchor : positionOf(root, leaves, range.endContainer, range.endOffset);\n return { anchor, focus };\n};\n\n/** Rebuilds a forward DOM range from a bookmark. */\nexport const bookmarkToRange = (root: HTMLElement, bookmark: TextBookmark): Range => {\n const leaves = collectLeaves(root);\n const from = resolvePosition(root, leaves, Math.min(bookmark.anchor, bookmark.focus));\n const to = resolvePosition(root, leaves, Math.max(bookmark.anchor, bookmark.focus));\n const range = document.createRange();\n range.setStart(from.node, from.offset);\n range.setEnd(to.node, to.offset);\n return range;\n};\n\n/** Stores the current selection, keeping its direction, or returns `null` when it is outside `root`. */\nexport const saveBookmark = (root: HTMLElement): TextBookmark | null => {\n const selection = document.getSelection();\n const { anchorNode, focusNode } = selection ?? {};\n if (!selection || !anchorNode || !focusNode || !root.contains(anchorNode) || !root.contains(focusNode)) return null;\n const leaves = collectLeaves(root);\n return {\n anchor: positionOf(root, leaves, anchorNode, selection.anchorOffset),\n focus: positionOf(root, leaves, focusNode, selection.focusOffset)\n };\n};\n\n/** Restores a selection saved with {@link saveBookmark}, including its direction. */\nexport const restoreBookmark = (root: HTMLElement, bookmark: TextBookmark): void => {\n const leaves = collectLeaves(root);\n const anchor = resolvePosition(root, leaves, bookmark.anchor);\n const focus = resolvePosition(root, leaves, bookmark.focus);\n document.getSelection()?.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);\n};\n\n/** Scrolls the caret line into view inside the nearest scroll container. */\nexport const scrollSelectionIntoView = (root: HTMLElement): void => {\n const range = getRangeWithin(root);\n if (!range) return;\n const target = isElement(range.startContainer) ? range.startContainer : range.startContainer.parentElement;\n target?.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n};\n","import { childOf, containerOf, splitBlock } from './blocks';\nimport {\n ATOM_SELECTOR,\n TEXT_BLOCK_SELECTOR,\n closestTag,\n closestTextBlock,\n createParagraph,\n createTrailingBreak,\n isAtBlockEnd,\n isAtBlockStart,\n isAtom,\n isElement,\n isEmptyTextBlock,\n isText,\n isTextBlock,\n isTrailingBreak,\n mergeAdjacentMarks,\n removeEmptyMarks,\n renameElement,\n syncTrailingBreak,\n textBlocksInRange,\n textBlocksWithin\n} from './dom';\nimport { closestListItem, liftListItem, splitListItem } from './lists';\nimport { listItemBody } from './schema';\nimport { indexOf } from './selection';\n\n/** A collapsed DOM position returned by edits so the engine can restore the caret. */\nexport interface Caret {\n /** Container node of the position. */\n node: Node;\n /** Offset inside `node`: a character index for text, a child index for elements. */\n offset: number;\n}\n\n/** Table elements that must never be removed as \"empty\" while cleaning up after a merge. */\nconst STRUCTURAL_TAGS = new Set(['TD', 'TH', 'TR', 'TBODY', 'TABLE']);\n/** Table cell tags; edits never merge content across cells. */\nconst CELL_TAGS = new Set(['TD', 'TH']);\n/** Headings are followed by body text when Enter is pressed at their end. */\nconst HEADING_TAG = /^H[1-6]$/;\n\n/** Table cell containing `node`, or `null` outside tables. */\nconst closestCell = (node: Node, root: HTMLElement) => closestTag(node, root, CELL_TAGS);\n\n/** Caret before the first character of a block (or inside it when it has no text). */\nexport const startCaret = (block: HTMLElement): Caret => {\n const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT);\n const first = walker.nextNode();\n return first ? { node: first, offset: 0 } : { node: block, offset: 0 };\n};\n\n/** Caret after the last character of a block, before its caret placeholder when it has no text. */\nexport const endCaret = (block: HTMLElement): Caret => {\n const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT);\n let last: Text | null = null;\n for (let node = walker.nextNode(); node; node = walker.nextNode()) last = node as Text;\n if (last) return { node: last, offset: last.length };\n const scope = block.tagName === 'PRE' ? (block.querySelector('code') ?? block) : block;\n const trailing = Array.from(scope.childNodes).find(isTrailingBreak);\n return { node: scope, offset: trailing ? indexOf(trailing) : scope.childNodes.length };\n};\n\n/** Removes elements left without any content, stopping at tables so their structure survives. */\nconst pruneEmptyAncestors = (node: Node | null, root: HTMLElement) => {\n let current = node;\n while (\n current &&\n current !== root &&\n isElement(current) &&\n !STRUCTURAL_TAGS.has(current.tagName) &&\n !current.querySelector(`${TEXT_BLOCK_SELECTOR}, ${ATOM_SELECTOR}, table`)\n ) {\n const parent: Node | null = current.parentNode;\n current.remove();\n current = parent;\n }\n};\n\n/** Normalises the inline content of a block after an edit. */\nconst tidy = (block: HTMLElement) => {\n removeEmptyMarks(block);\n mergeAdjacentMarks(block);\n syncTrailingBreak(block);\n};\n\n/** Appends the content of `source` to `target`, removes `source`, and returns the caret at the join. */\nconst mergeBlocks = (target: HTMLElement, source: HTMLElement, root: HTMLElement): Caret => {\n const caret = endCaret(target);\n if (target.tagName === 'PRE' || source.tagName === 'PRE') {\n // Code blocks hold plain text, so only the text of the other block is carried over.\n const text = document.createTextNode(source.textContent ?? '');\n const scope = target.tagName === 'PRE' ? (target.querySelector('code') ?? target) : target;\n const trailing = Array.from(scope.childNodes).find(isTrailingBreak) ?? null;\n scope.insertBefore(text, trailing);\n } else {\n for (const trailing of Array.from(target.childNodes).filter(isTrailingBreak)) trailing.remove();\n target.append(...Array.from(source.childNodes).filter(node => !isTrailingBreak(node)));\n }\n const parent = source.parentNode;\n source.remove();\n pruneEmptyAncestors(parent, root);\n if (target.tagName !== 'PRE') {\n removeEmptyMarks(target);\n syncTrailingBreak(target);\n }\n return caret;\n};\n\n/** Deletes the selected content. The caret should be restored to the range start by the caller. */\nexport const deleteRange = (root: HTMLElement, range: Range): void => {\n if (range.collapsed) return;\n const startCell = closestCell(range.startContainer, root);\n const endCell = closestCell(range.endContainer, root);\n if (startCell !== endCell) {\n // Across table cells only the text is cleared; rows and columns are removed with table commands.\n for (const block of textBlocksInRange(root, range)) {\n const part = document.createRange();\n part.selectNodeContents(block);\n if (block.contains(range.startContainer)) part.setStart(range.startContainer, range.startOffset);\n if (block.contains(range.endContainer)) part.setEnd(range.endContainer, range.endOffset);\n part.deleteContents();\n tidy(block);\n }\n return;\n }\n const startBlock = closestTextBlock(range.startContainer, root);\n const endBlock = closestTextBlock(range.endContainer, root);\n range.deleteContents();\n if (startBlock?.isConnected && endBlock?.isConnected && startBlock !== endBlock) {\n mergeBlocks(startBlock, endBlock, root);\n }\n if (startBlock?.isConnected) tidy(startBlock);\n};\n\n/** Moves a block out of its quote, splitting the quote when blocks follow it. */\nconst liftFromQuote = (block: HTMLElement) => {\n const quote = block.parentElement;\n if (quote?.tagName !== 'BLOCKQUOTE') return;\n const following: Node[] = [];\n for (let node = block.nextSibling; node; node = node.nextSibling) following.push(node);\n quote.after(block);\n if (following.length) {\n const rest = quote.cloneNode(false) as HTMLElement;\n rest.append(...following);\n block.after(rest);\n }\n if (!quote.childElementCount) quote.remove();\n};\n\n/** Text block before `block` in document order, at any nesting level. */\nconst previousTextBlock = (root: HTMLElement, block: HTMLElement) => {\n const blocks = textBlocksWithin(root);\n return blocks[blocks.indexOf(block) - 1] ?? null;\n};\n\n/** Text block after `block` in document order, at any nesting level. */\nconst nextTextBlock = (root: HTMLElement, block: HTMLElement) => {\n const blocks = textBlocksWithin(root);\n return blocks[blocks.indexOf(block) + 1] ?? null;\n};\n\n/**\n * Backspace at the very start of a text block: lifts it out of a list or quote, deletes a preceding atom,\n * moves into a preceding table, or merges it into the previous text block of the same cell.\n * @returns the new caret, or `null` when nothing applies.\n */\nexport const joinBackward = (root: HTMLElement, block: HTMLElement): Caret | null => {\n const item = closestListItem(block, root);\n if (item && listItemBody(item).firstElementChild === block) {\n liftListItem(root, item);\n return startCaret(block);\n }\n if (block.parentElement?.tagName === 'BLOCKQUOTE' && block.parentElement.firstElementChild === block) {\n liftFromQuote(block);\n return startCaret(block);\n }\n\n const container = containerOf(block, root);\n const previousSibling = (childOf(container, block) as Element).previousElementSibling;\n if (isAtom(previousSibling)) {\n previousSibling.remove();\n return startCaret(block);\n }\n if (previousSibling?.tagName === 'TABLE') {\n if (isEmptyTextBlock(block) && container.childElementCount > 1) block.remove();\n const lastCell = previousSibling.querySelector('tr:last-child > :last-child');\n const target = lastCell ? Array.from(lastCell.querySelectorAll<HTMLElement>(TEXT_BLOCK_SELECTOR)).at(-1) : null;\n return target ? endCaret(target) : null;\n }\n\n const previous = previousTextBlock(root, block);\n if (!previous || closestCell(previous, root) !== closestCell(block, root)) return null;\n return mergeBlocks(previous, block, root);\n};\n\n/**\n * Delete at the very end of a text block: deletes a following atom or merges the next text block of the same\n * cell into this one.\n * @returns the new caret, or `null` when nothing applies.\n */\nexport const joinForward = (root: HTMLElement, block: HTMLElement): Caret | null => {\n const container = containerOf(block, root);\n const top = childOf(container, block) as Element;\n const nextSibling = top === block ? block.nextElementSibling : null;\n if (isAtom(nextSibling)) {\n nextSibling.remove();\n return endCaret(block);\n }\n if (nextSibling?.tagName === 'TABLE') return null;\n\n const next = nextTextBlock(root, block);\n if (!next || closestCell(next, root) !== closestCell(block, root)) return null;\n const nextItem = closestListItem(next, root);\n const caret = mergeBlocks(block, next, root);\n // The rest of a list item that lost its first paragraph moves up into the current item.\n if (nextItem?.isConnected && closestListItem(block, root) !== nextItem) {\n const currentItem = closestListItem(block, root);\n if (currentItem) {\n listItemBody(currentItem).append(...Array.from(listItemBody(nextItem).childNodes));\n nextItem.remove();\n }\n }\n return caret;\n};\n\n/** Inserts a newline into a code block, adding the placeholder a trailing newline needs to be visible. */\nconst insertNewline = (block: HTMLElement, range: Range): Caret => {\n const caret = insertTextAtCaret(block, range, '\\n');\n const code = block.querySelector('code') ?? block;\n if ((block.textContent ?? '').endsWith('\\n') && !Array.from(code.childNodes).some(isTrailingBreak)) {\n code.append(createTrailingBreak());\n }\n return caret;\n};\n\n/**\n * Enter at a collapsed range: a newline in code blocks, leaving an empty list item or quote, or splitting the\n * block (and its list item). A new line after a heading becomes a paragraph.\n */\nexport const splitAtCaret = (root: HTMLElement, range: Range): Caret | null => {\n const block = closestTextBlock(range.startContainer, root);\n if (!block) return null;\n if (block.tagName === 'PRE') return insertNewline(block, range);\n\n const item = closestListItem(block, root);\n if (item && isEmptyTextBlock(block) && listItemBody(item).childElementCount === 1) {\n liftListItem(root, item);\n return startCaret(block);\n }\n if (block.parentElement?.tagName === 'BLOCKQUOTE' && isEmptyTextBlock(block)) {\n liftFromQuote(block);\n return startCaret(block);\n }\n\n const atEnd = isAtBlockEnd(block, range.startContainer, range.startOffset);\n let right = splitBlock(block, range.startContainer, range.startOffset);\n if (atEnd && HEADING_TAG.test(block.tagName)) right = renameElement(right, 'p');\n if (item && block.parentElement === listItemBody(item)) splitListItem(item, right);\n return startCaret(right);\n};\n\n/** Shift+Enter: a `<br>` line break, or a newline inside code blocks. */\nexport const insertLineBreak = (root: HTMLElement, range: Range): Caret | null => {\n const block = closestTextBlock(range.startContainer, root);\n if (!block) return null;\n if (block.tagName === 'PRE') return insertNewline(block, range);\n const br = document.createElement('br');\n range.insertNode(br);\n syncTrailingBreak(block);\n return { node: br.parentNode ?? block, offset: indexOf(br) + 1 };\n};\n\n/** Inserts plain text at a collapsed range, creating a paragraph if the caret sits between blocks. */\nexport const insertTextAtCaret = (root: HTMLElement, range: Range, text: string): Caret => {\n let { startContainer: container, startOffset: offset } = range;\n if (!closestTextBlock(container, root) && !isText(container)) {\n const paragraph = createParagraph();\n container.insertBefore(paragraph, container.childNodes[offset] ?? null);\n container = paragraph;\n offset = 0;\n }\n if (isText(container)) {\n container.insertData(offset, text);\n return { node: container, offset: offset + text.length };\n }\n // Joining a neighbouring text node keeps the markup free of fragmented text nodes.\n const before = container.childNodes[offset - 1];\n if (isText(before)) {\n before.appendData(text);\n return { node: before, offset: before.length };\n }\n const after = container.childNodes[offset] ?? null;\n if (isText(after)) {\n after.insertData(0, text);\n return { node: after, offset: text.length };\n }\n const node = document.createTextNode(text);\n container.insertBefore(node, after);\n if (isTextBlock(container)) syncTrailingBreak(container);\n return { node, offset: text.length };\n};\n\n/**\n * Pastes sanitised blocks at a collapsed range. A single paragraph flows into the current line; several blocks\n * split the current block, with the first and last pasted paragraphs merging into its halves as in Word.\n * @returns the caret after the inserted content.\n */\nexport const insertFragment = (root: HTMLElement, range: Range, fragment: DocumentFragment): Caret | null => {\n const nodes = Array.from(fragment.childNodes);\n const first = nodes[0];\n const last = nodes.at(-1);\n if (!first || !last) return null;\n const block = closestTextBlock(range.startContainer, root);\n\n if (!block) {\n root.append(fragment);\n return isTextBlock(last) ? endCaret(last) : null;\n }\n if (block.tagName === 'PRE') {\n const text = nodes.map(node => node.textContent ?? '').join('\\n');\n return insertTextAtCaret(root, range, text);\n }\n\n if (nodes.length === 1 && isElement(first) && first.tagName === 'P') {\n const inline = Array.from(first.childNodes).filter(node => !isTrailingBreak(node));\n const tail = inline.at(-1);\n if (!tail) return { node: range.startContainer, offset: range.startOffset };\n const piece = document.createDocumentFragment();\n piece.append(...inline);\n range.insertNode(piece);\n syncTrailingBreak(block);\n return isText(tail)\n ? { node: tail, offset: tail.length }\n : { node: tail.parentNode ?? block, offset: indexOf(tail) + 1 };\n }\n\n const right = splitBlock(block, range.startContainer, range.startOffset);\n block.after(...nodes);\n if (isElement(first) && first.tagName === 'P' && !isEmptyTextBlock(block)) mergeBlocks(block, first, root);\n let caret: Caret;\n if (isTextBlock(last) && last.isConnected && last.tagName !== 'PRE') {\n if (isEmptyTextBlock(right)) {\n // Pasting at the end of a line must not leave an empty paragraph behind.\n caret = endCaret(last);\n right.remove();\n } else {\n caret = mergeBlocks(last, right, root);\n }\n } else {\n caret = startCaret(right);\n }\n if (block.isConnected && isEmptyTextBlock(block) && block.nextElementSibling) block.remove();\n return caret;\n};\n\n/** Whether the range is collapsed before any visible content of the block. */\nexport const isCaretAtBlockStart = (block: HTMLElement, range: Range): boolean =>\n range.collapsed && isAtBlockStart(block, range.startContainer, range.startOffset);\n\n/** Whether the range is collapsed after all visible content of the block. */\nexport const isCaretAtBlockEnd = (block: HTMLElement, range: Range): boolean =>\n range.collapsed && isAtBlockEnd(block, range.startContainer, range.startOffset);\n","import type { TextBookmark } from './selection';\n\n/** The document and selection at one point in time. */\nexport interface Snapshot {\n /** outerHTML of each top-level block; unchanged blocks share the same string with earlier snapshots. */\n blocks: string[];\n /** Selection to restore together with the blocks, or `null` when the editor had no selection. */\n bookmark: TextBookmark | null;\n}\n\n/** How a change was made: consecutive typing is grouped into one undo step, commands never are. */\ntype ChangeKind = 'typing' | 'command';\n\n/** Keystrokes closer together than this become one undo step. */\nconst TYPING_GROUP_MS = 800;\n\n/** Undo steps kept by default; the oldest ones are dropped beyond this. */\nconst DEFAULT_LIMIT = 200;\n\n/** Undo/redo stacks of document snapshots with typing grouping and memory sharing between snapshots. */\nexport class EditorHistory {\n /** States before each change, newest last. */\n private readonly undoStack: Snapshot[] = [];\n /** States replaced by undo, newest last; cleared by any new change. */\n private readonly redoStack: Snapshot[] = [];\n /** Kind of the last recorded change, `null` when the next keystroke must start a new step. */\n private lastKind: ChangeKind | null = null;\n /** Time of the last recorded change, used to group typing. */\n private lastTime = 0;\n\n /** @param limit Maximum number of steps kept on each stack. */\n constructor(private readonly limit = DEFAULT_LIMIT) {}\n\n /** Whether there is a step to undo. */\n get canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n /** Whether there is an undone step to redo. */\n get canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n /** Records the state before a change; `take` runs only when a new undo step actually starts. */\n record(take: () => Snapshot, kind: ChangeKind): void {\n const now = Date.now();\n const grouped = kind === 'typing' && this.lastKind === 'typing' && now - this.lastTime < TYPING_GROUP_MS;\n this.lastKind = kind;\n this.lastTime = now;\n if (grouped) return;\n this.push(this.undoStack, this.share(take()));\n this.redoStack.length = 0;\n }\n\n /** Starts a new undo step for the next keystroke, e.g. after the caret was moved. */\n breakGroup(): void {\n this.lastKind = null;\n }\n\n /** Moves one step back: stores `current` for redo and returns the state to restore, or `null` when empty. */\n undo(current: Snapshot): Snapshot | null {\n const previous = this.undoStack.pop();\n if (!previous) return null;\n this.push(this.redoStack, this.share(current));\n this.lastKind = null;\n return previous;\n }\n\n /** Moves one step forward: stores `current` for undo and returns the state to restore, or `null` when empty. */\n redo(current: Snapshot): Snapshot | null {\n const next = this.redoStack.pop();\n if (!next) return null;\n this.push(this.undoStack, this.share(current));\n this.lastKind = null;\n return next;\n }\n\n /** Forgets every step, e.g. after the content was replaced from outside. */\n clear(): void {\n this.undoStack.length = 0;\n this.redoStack.length = 0;\n this.lastKind = null;\n }\n\n /** Adds a snapshot to a stack and drops the oldest one when the limit is exceeded. */\n private push(stack: Snapshot[], snapshot: Snapshot): void {\n stack.push(snapshot);\n if (stack.length > this.limit) stack.shift();\n }\n\n /** Reuses identical block strings from the latest snapshot so large documents do not multiply in memory. */\n private share(snapshot: Snapshot): Snapshot {\n const reference = this.undoStack.at(-1) ?? this.redoStack.at(-1);\n if (!reference) return snapshot;\n const pool = new Map(reference.blocks.map(block => [block, block]));\n return { ...snapshot, blocks: snapshot.blocks.map(block => pool.get(block) ?? block) };\n }\n}\n","import { toggleBlockquote, toggleCodeBlock } from './blocks';\nimport { closestTag, closestTextBlock, createElement, isText, renameElement, syncTrailingBreak } from './dom';\nimport { type Caret, startCaret } from './editing';\nimport { type ListKind, activeListKind, closestListItem, toggleList, toggleTaskItem } from './lists';\nimport {\n DEFAULT_HIGHLIGHT_COLOR,\n type MarkName,\n type PendingFormat,\n linkAncestor,\n setHighlight,\n setLink,\n toggleMark\n} from './marks';\n\n/** What an applied rule leaves behind for the engine. */\ninterface InputRuleResult {\n /** New caret position, or `null` to restore the caret by text position. */\n caret: Caret | null;\n /** Formatting for the next typed characters, so text after `**bold**` is not bold. */\n pending?: PendingFormat;\n}\n\n/** A matched rule; calling it performs the document change. */\ntype InputRule = () => InputRuleResult;\n\n/** Block-level shortcuts typed at the start of a paragraph. */\ntype BlockRuleKind = ListKind | 'heading' | 'blockquote' | 'codeBlock' | 'rule';\n\n/** Characters in text before the caret, with the text nodes they came from. */\ninterface TextBefore {\n /** Text from the block start to the caret, non-breaking spaces normalised to spaces. */\n text: string;\n /** Every text node with the offset of its first character within `text`. */\n segments: Array<{ node: Text; start: number }>;\n}\n\n/** Browsers insert a non-breaking space for a trailing space; rules treat both alike. */\nconst NBSP = String.fromCharCode(160);\n\n/** Characters that can complete a block rule (`# `, `---`, ```` ``` ````). */\nconst BLOCK_RULE_TRIGGERS = new Set([' ', '`', '-']);\n/** Characters that can close an inline mark rule; typed input that is a substring of these also qualifies. */\nconst MARK_RULE_TRIGGERS = '*_~`=';\n\n/** Automatic replacements for typed character sequences. */\nconst TYPOGRAPHY: Array<[RegExp, string]> = [\n [/--$/, '—'],\n [/\\.\\.\\.$/, '…'],\n [/<-$/, '←'],\n [/->$/, '→'],\n [/\\(c\\)$/i, '©'],\n [/\\(r\\)$/i, '®'],\n [/\\(tm\\)$/i, '™'],\n [/\\(sm\\)$/i, '℠'],\n [/\\+\\/-$/, '±'],\n [/!=$/, '≠'],\n [/<<$/, '«'],\n [/>>$/, '»'],\n [/(?<=\\d ?)[x*](?= ?\\d$)/, '×']\n];\n\n/** Markdown-style inline marks, checked in this order so `**` wins over `*`. */\nconst INLINE_MARKS: Array<{ pattern: RegExp; mark: MarkName | 'highlight' }> = [\n { pattern: /(\\*\\*|__)([^*_\\s](?:[^*_]*[^*_\\s])?)\\1$/, mark: 'bold' },\n { pattern: /~~([^~\\s](?:[^~]*[^~\\s])?)~~$/, mark: 'strike' },\n { pattern: /==([^=\\s](?:[^=]*[^=\\s])?)==$/, mark: 'highlight' },\n { pattern: /`([^`]+)`$/, mark: 'code' },\n { pattern: /(?<![*_\\w])(\\*|_)([^*_\\s](?:[^*_]*[^*_\\s])?)\\1$/, mark: 'italic' }\n];\n\n/** Delimiters of inline rules whose pattern has no delimiter capture group. */\nconst FIXED_DELIMITERS: Partial<Record<MarkName | 'highlight', string>> = { highlight: '==', strike: '~~', code: '`' };\n\n/** Markdown-style block shortcuts; the whole text before the caret must match. */\nconst BLOCK_RULES: Array<{ pattern: RegExp; kind: BlockRuleKind }> = [\n { pattern: /^(#{1,6}) $/, kind: 'heading' },\n { pattern: /^\\[( |x)\\] $/i, kind: 'taskList' },\n { pattern: /^[-+*] $/, kind: 'bulletList' },\n { pattern: /^(\\d+)\\. $/, kind: 'orderedList' },\n { pattern: /^> $/, kind: 'blockquote' },\n { pattern: /^```$/, kind: 'codeBlock' },\n { pattern: /^(?:---|—-|___ |\\*\\*\\* )$/, kind: 'rule' }\n];\n\n/** URL followed by the space that was just typed, not already part of a link. */\nconst AUTOLINK = /(?:^|\\s)((?:https?:\\/\\/|www\\.)[^\\s]+[^\\s.,;:!?)])\\s$/;\n/** Characters after which a typed quote opens rather than closes. */\nconst QUOTE_OPENERS = /[\\s([{«]/;\n\n/** Collects the text between the start of `block` and the caret. */\nconst readTextBefore = (block: HTMLElement, range: Range): TextBefore => {\n const before = document.createRange();\n before.setStart(block, 0);\n before.setEnd(range.startContainer, range.startOffset);\n const segments: TextBefore['segments'] = [];\n let text = '';\n const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT);\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (!isText(node) || !before.intersectsNode(node)) continue;\n const data = node === range.startContainer ? node.data.slice(0, range.startOffset) : node.data;\n if (before.comparePoint(node, 0) > 0) break;\n segments.push({ node, start: text.length });\n text += data.replaceAll(NBSP, ' ');\n }\n return { text, segments };\n};\n\n/** DOM range for character offsets `from`..`to` of a `TextBefore`, or `null` when they fall outside it. */\nconst rangeOf = ({ segments }: TextBefore, from: number, to: number): Range | null => {\n const start = segments.find(segment => from >= segment.start && from <= segment.start + segment.node.length);\n const end = [...segments].reverse().find(segment => to >= segment.start && to <= segment.start + segment.node.length);\n if (!start || !end) return null;\n const range = document.createRange();\n range.setStart(start.node, from - start.start);\n range.setEnd(end.node, to - end.start);\n return range;\n};\n\n/** Replaces characters `from`..`to` with `replacement` and returns the caret after it. */\nconst replaceText = (before: TextBefore, from: number, to: number, replacement: string): Caret | null => {\n const range = rangeOf(before, from, to);\n if (!range) return null;\n range.deleteContents();\n const node = document.createTextNode(replacement);\n range.insertNode(node);\n return { node, offset: replacement.length };\n};\n\n/** Matches a block shortcut such as `# `, `- `, `1. `, `[ ] `, `> `, ```` ``` ```` or `---`. */\nconst blockRule = (root: HTMLElement, block: HTMLElement, before: TextBefore): InputRule | null => {\n if (block.tagName === 'PRE') return null;\n for (const { pattern, kind } of BLOCK_RULES) {\n const match = before.text.match(pattern);\n if (!match) continue;\n const isListRule = kind === 'bulletList' || kind === 'orderedList' || kind === 'taskList';\n if (kind === 'heading' && (block.tagName !== 'P' || closestListItem(block, root) !== null)) return null;\n if (isListRule && activeListKind(root, block) === kind) return null;\n\n return () => {\n rangeOf(before, 0, before.text.length)?.deleteContents();\n syncTrailingBreak(block);\n const blockRange = document.createRange();\n blockRange.selectNodeContents(block);\n // Commands may replace the block; its position lets the caret find the new element.\n const parent = block.parentNode;\n const index = parent ? Array.prototype.indexOf.call(parent.childNodes, block) : -1;\n let target = block;\n\n if (kind === 'heading') {\n target = renameElement(block, `h${match[1]?.length ?? 1}`);\n } else if (kind === 'blockquote') {\n toggleBlockquote(root, blockRange);\n } else if (kind === 'codeBlock') {\n toggleCodeBlock(root, blockRange);\n const pre = parent?.childNodes[index];\n const code = pre instanceof HTMLElement ? (pre.querySelector<HTMLElement>('code') ?? pre) : null;\n return { caret: code ? startCaret(code) : null };\n } else if (kind === 'rule') {\n block.before(createElement('hr'));\n } else {\n toggleList(root, blockRange, kind);\n const item = closestListItem(block, root);\n if (kind === 'taskList' && match[1]?.toLowerCase() === 'x' && item) toggleTaskItem(item);\n const start = Number.parseInt(match[1] ?? '', 10);\n if (kind === 'orderedList' && start > 1) item?.parentElement?.setAttribute('start', String(start));\n }\n return { caret: target.isConnected ? startCaret(target) : null };\n };\n }\n return null;\n};\n\n/** Matches a closed inline mark such as `**bold**`, `*italic*`, `~~strike~~`, `` `code` `` or `==highlight==`. */\nconst inlineMarkRule = (root: HTMLElement, before: TextBefore): InputRule | null => {\n for (const { pattern, mark } of INLINE_MARKS) {\n const match = before.text.match(pattern);\n if (!match || match.index === undefined) continue;\n const hasDelimiterGroup = match.length === 3;\n const delimiter = hasDelimiterGroup ? (match[1] ?? '') : (FIXED_DELIMITERS[mark] ?? '');\n const inner = (hasDelimiterGroup ? match[2] : match[1]) ?? '';\n const start = match.index;\n const innerStart = start + delimiter.length;\n const innerEnd = innerStart + inner.length;\n\n return () => {\n // The closing delimiter goes first so the opening delimiter's offsets stay valid.\n rangeOf(before, innerEnd, before.text.length)?.deleteContents();\n rangeOf(before, start, innerStart)?.deleteContents();\n const block = closestTextBlock(before.segments[0]?.node, root);\n const last = before.segments.at(-1)?.node;\n if (!block || !last) return { caret: null };\n const caretRange = document.createRange();\n caretRange.setStart(last, last.length);\n const updated = readTextBefore(block, caretRange);\n const target = rangeOf(updated, updated.text.length - inner.length, updated.text.length);\n if (!target) return { caret: null };\n\n if (mark === 'highlight') setHighlight(root, target, DEFAULT_HIGHLIGHT_COLOR);\n else toggleMark(root, target, mark);\n const pending: PendingFormat =\n mark === 'highlight' ? { marks: {}, styles: {}, highlight: null } : { marks: { [mark]: false }, styles: {} };\n // The live range followed the text through splitting and merging, so its end is right after the word.\n return { caret: { node: target.endContainer, offset: target.endOffset }, pending };\n };\n }\n return null;\n};\n\n/** Matches smart quotes and the typography replacements. */\nconst typographyRule = (before: TextBefore, typed: string): InputRule | null => {\n const { text } = before;\n if (typed === '\"' || typed === \"'\") {\n const previous = text.slice(-2, -1);\n const opening = previous === '' || QUOTE_OPENERS.test(previous);\n const replacement = typed === '\"' ? (opening ? '“' : '”') : opening ? '‘' : '’';\n return () => ({ caret: replaceText(before, text.length - 1, text.length, replacement) });\n }\n for (const [pattern, replacement] of TYPOGRAPHY) {\n const match = text.match(pattern);\n if (!match || match.index === undefined) continue;\n const from = match.index;\n const to = from + match[0].length;\n return () => ({ caret: replaceText(before, from, to, replacement) });\n }\n return null;\n};\n\n/** Matches a URL completed by a typed space and turns it into a link (`www.` addresses get `https://`). */\nconst autolinkRule = (root: HTMLElement, before: TextBefore, range: Range): InputRule | null => {\n const match = before.text.match(AUTOLINK);\n if (!match?.[1] || linkAncestor(range.startContainer, root)) return null;\n const url = match[1];\n const from = before.text.length - 1 - url.length;\n return () => {\n const target = rangeOf(before, from, from + url.length);\n if (target) setLink(root, target, url.startsWith('www.') ? `https://${url}` : url, null);\n return { caret: null };\n };\n};\n\n/**\n * Looks for a rule completed by the character that was just typed. The returned function performs the change,\n * so the caller can record an undo step first. Rules never apply inside code.\n */\nexport const matchInputRule = (root: HTMLElement, range: Range, typed: string): InputRule | null => {\n const block = closestTextBlock(range.startContainer, root);\n if (!block || !range.collapsed || closestTag(range.startContainer, root, 'CODE')) return null;\n const before = readTextBefore(block, range);\n if (!before.text) return null;\n\n const rule =\n (BLOCK_RULE_TRIGGERS.has(typed) ? blockRule(root, block, before) : null) ??\n (MARK_RULE_TRIGGERS.includes(typed) ? inlineMarkRule(root, before) : null) ??\n (typed === ' ' ? autolinkRule(root, before, range) : null);\n return rule ?? typographyRule(before, typed);\n};\n","/** Editor commands reachable through keyboard shortcuts. */\nexport type KeyCommand =\n | 'undo'\n | 'redo'\n | 'bold'\n | 'italic'\n | 'underline'\n | 'strike'\n | 'code'\n | 'subscript'\n | 'superscript'\n | 'highlight'\n | 'paragraph'\n | 'heading1'\n | 'heading2'\n | 'heading3'\n | 'heading4'\n | 'heading5'\n | 'heading6'\n | 'alignLeft'\n | 'alignCenter'\n | 'alignRight'\n | 'alignJustify'\n | 'bulletList'\n | 'orderedList'\n | 'taskList'\n | 'blockquote'\n | 'codeBlock'\n | 'pageBreak';\n\n/** Apple platforms use Command instead of Control as the shortcut modifier. */\nconst IS_MAC = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.userAgent);\n\n/** Shortcuts pressed with Mod+Shift, keyed by physical key code. */\nconst SHIFT_COMMANDS: Record<string, KeyCommand> = {\n KeyZ: 'redo',\n KeyS: 'strike',\n KeyL: 'alignLeft',\n KeyE: 'alignCenter',\n KeyR: 'alignRight',\n KeyJ: 'alignJustify',\n Digit7: 'orderedList',\n Digit8: 'bulletList',\n Digit9: 'taskList',\n KeyB: 'blockquote',\n KeyH: 'highlight'\n};\n\n/** Shortcuts pressed with Mod alone, keyed by physical key code. */\nconst PLAIN_COMMANDS: Record<string, KeyCommand> = {\n KeyZ: 'undo',\n KeyY: 'redo',\n KeyB: 'bold',\n KeyI: 'italic',\n KeyU: 'underline',\n KeyE: 'code',\n Comma: 'subscript',\n Period: 'superscript',\n Enter: 'pageBreak',\n NumpadEnter: 'pageBreak'\n};\n\n/** Key code with Mod+Alt that turns the block into a paragraph. */\nconst PARAGRAPH_KEY = 'Digit0';\n\n/** Key code with Mod+Alt that toggles a code block. */\nconst CODE_BLOCK_KEY = 'KeyC';\n\n/** Mod+Alt+1…6 set the heading level. */\nconst HEADING_KEY = /^Digit([1-6])$/;\n\n/**\n * Returns the editor command for a key press, or `null` when it is not a shortcut. Physical key codes are used so\n * shortcuts keep working with Cyrillic and other keyboard layouts; AltGr combinations are left to text input.\n */\nexport const matchKeyCommand = (event: KeyboardEvent): KeyCommand | null => {\n const mod = IS_MAC ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;\n if (!mod || event.getModifierState?.('AltGraph')) return null;\n const { code, shiftKey, altKey } = event;\n if (!altKey) return (shiftKey ? SHIFT_COMMANDS[code] : PLAIN_COMMANDS[code]) ?? null;\n if (shiftKey) return null;\n if (code === PARAGRAPH_KEY) return 'paragraph';\n if (code === CODE_BLOCK_KEY) return 'codeBlock';\n const level = HEADING_KEY.exec(code)?.[1];\n return level ? (`heading${level}` as KeyCommand) : null;\n};\n","import { escapeRegExp, isBreak, isText, textBlocksWithin } from './dom';\n\n/** Matching options of the find bar. */\ninterface SearchOptions {\n /** Match letter case exactly. */\n caseSensitive: boolean;\n /** Only match whole words. */\n wholeWord: boolean;\n}\n\n/** Match counter shown in the find bar. */\nexport interface SearchState {\n /** Number of matches in the document. */\n total: number;\n /** 1-based index of the current match, 0 when there is none. */\n current: number;\n}\n\n/** Text node of a block with the offset of its first character within the block text. */\ninterface Segment {\n node: Text;\n start: number;\n}\n\n/** Names used by `::highlight()` rules in editor-ui.css. */\nconst MATCH_HIGHLIGHT = 'doc-search';\nconst CURRENT_HIGHLIGHT = 'doc-search-current';\n/** Letters, digits and the apostrophes used in Uzbek words count as part of a word. */\nconst WORD_BEFORE = String.raw`(?<![\\p{L}\\p{N}_'‘’ʻ])`;\nconst WORD_AFTER = String.raw`(?![\\p{L}\\p{N}_'‘’ʻ])`;\n\n/** Whether the browser supports the CSS Custom Highlight API; without it matches are still navigable. */\nconst supportsHighlights = () => typeof CSS !== 'undefined' && 'highlights' in CSS && typeof Highlight !== 'undefined';\n\n/** DOM range for block-text offsets `from`..`to`, or `null` when they do not map onto text nodes. */\nconst rangeFor = (segments: Segment[], from: number, to: number): Range | null => {\n const start = segments.find(segment => from >= segment.start && from < segment.start + segment.node.length);\n const end = segments.find(segment => to > segment.start && to <= segment.start + segment.node.length);\n if (!start || !end) return null;\n const range = document.createRange();\n range.setStart(start.node, from - start.start);\n range.setEnd(end.node, to - end.start);\n return range;\n};\n\n/** Find and replace without touching the document markup: matches are painted with CSS highlights. */\nexport class SearchController {\n /** Current search term; empty when the search is inactive. */\n private term = '';\n /** Current matching options. */\n private options: SearchOptions = { caseSensitive: false, wholeWord: false };\n /** Live ranges of all matches in document order. */\n private results: Range[] = [];\n /** Index of the current match, -1 when there is none. */\n private index = -1;\n\n /**\n * @param root the editable document element.\n * @param onChange called with the match counter whenever it may have changed.\n */\n constructor(\n private readonly root: HTMLElement,\n private readonly onChange: (state: SearchState) => void\n ) {}\n\n /** Current match counter. */\n get state(): SearchState {\n return { total: this.results.length, current: this.index + 1 };\n }\n\n /** Whether a search term is set, so the results must follow document edits. */\n get active(): boolean {\n return this.term !== '';\n }\n\n /** Starts a new search; options not passed keep their previous value. */\n setQuery(term: string, options: Partial<SearchOptions> = {}): void {\n this.term = term;\n this.options = { ...this.options, ...options };\n this.refresh(true);\n }\n\n /** Re-runs the search, e.g. after the document changed, keeping the current match position unless `reset`. */\n refresh(reset = false): void {\n this.results = this.term ? this.find() : [];\n if (reset || this.index < 0) this.index = this.results.length ? 0 : -1;\n else this.index = Math.min(this.index, this.results.length - 1);\n this.paint();\n this.onChange(this.state);\n }\n\n /** Moves to the next match, wrapping around, and scrolls it into view. */\n next(): void {\n if (!this.results.length) return;\n this.index = (this.index + 1) % this.results.length;\n this.reveal();\n }\n\n /** Moves to the previous match, wrapping around, and scrolls it into view. */\n previous(): void {\n if (!this.results.length) return;\n this.index = (this.index - 1 + this.results.length) % this.results.length;\n this.reveal();\n }\n\n /**\n * Replaces the current match; the caller records history and tidies the markup.\n * @returns whether a match was replaced.\n */\n replaceCurrent(replacement: string): boolean {\n const range = this.results[this.index];\n if (!range || range.collapsed) return false;\n this.replaceRange(range, replacement);\n this.refresh();\n return true;\n }\n\n /**\n * Replaces every match, last first so earlier ranges stay valid.\n * @returns the number of replaced matches.\n */\n replaceAll(replacement: string): number {\n const ranges = [...this.results].reverse();\n for (const range of ranges) this.replaceRange(range, replacement);\n this.refresh(true);\n return ranges.length;\n }\n\n /** Ends the search and removes the highlights. */\n clear(): void {\n this.term = '';\n this.results = [];\n this.index = -1;\n if (supportsHighlights()) {\n CSS.highlights.delete(MATCH_HIGHLIGHT);\n CSS.highlights.delete(CURRENT_HIGHLIGHT);\n }\n this.onChange(this.state);\n }\n\n /** Finds all matches block by block; line breaks count as newlines so matches never span them. */\n private find(): Range[] {\n const body = escapeRegExp(this.term);\n const pattern = this.options.wholeWord ? `${WORD_BEFORE}${body}${WORD_AFTER}` : body;\n const regex = new RegExp(pattern, this.options.caseSensitive ? 'gu' : 'giu');\n const ranges: Range[] = [];\n for (const block of textBlocksWithin(this.root)) {\n const segments: Segment[] = [];\n let text = '';\n const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (isText(node)) {\n segments.push({ node, start: text.length });\n text += node.data;\n } else if (isBreak(node)) {\n text += '\\n';\n }\n }\n if (!text) continue;\n for (const match of text.matchAll(regex)) {\n if (!match[0]) continue;\n const range = rangeFor(segments, match.index, match.index + match[0].length);\n if (range) ranges.push(range);\n }\n }\n return ranges;\n }\n\n /** Paints all matches and, separately, the current one. */\n private paint() {\n if (!supportsHighlights()) return;\n const current = this.results[this.index];\n const matches = new Highlight();\n for (const range of this.results) if (range !== current) matches.add(range);\n CSS.highlights.set(MATCH_HIGHLIGHT, matches);\n if (current) CSS.highlights.set(CURRENT_HIGHLIGHT, new Highlight(current));\n else CSS.highlights.delete(CURRENT_HIGHLIGHT);\n }\n\n /** Repaints, scrolls the current match into the middle of the view and reports the counter. */\n private reveal() {\n this.paint();\n const range = this.results[this.index];\n range?.startContainer.parentElement?.scrollIntoView({ block: 'center', inline: 'nearest' });\n this.onChange(this.state);\n }\n\n /** Replaces the content of one match with plain text (an empty replacement deletes the match). */\n private replaceRange(range: Range, replacement: string) {\n range.deleteContents();\n if (replacement) range.insertNode(document.createTextNode(replacement));\n }\n}\n","import { createElement, createParagraph, renameElement } from './dom';\nimport { SELECTED_CELL_CLASS } from './schema';\n\n/** A `<td>` or `<th>` element. */\nexport type TableCell = HTMLTableCellElement;\n\n/** Zero-based grid slot of a cell's top-left corner. */\ninterface CellPosition {\n row: number;\n col: number;\n}\n\n/** Grid model of a table that resolves `rowspan` and `colspan`. */\ninterface TableMap {\n /** Rows in document order. */\n rows: HTMLTableRowElement[];\n /** grid[row][column] is the cell covering that slot, including slots covered by spans. */\n grid: Array<Array<TableCell | undefined>>;\n /** Top-left slot of every cell. */\n positions: Map<TableCell, CellPosition>;\n /** Number of columns in the widest row. */\n width: number;\n}\n\n/** Rectangular block of grid slots, inclusive on every side. */\nexport interface CellRect {\n /** Table the rectangle belongs to. */\n table: HTMLTableElement;\n /** First row index. */\n top: number;\n /** First column index. */\n left: number;\n /** Last row index. */\n bottom: number;\n /** Last column index. */\n right: number;\n}\n\n/** Narrowest column a user can resize to, in CSS pixels. */\nconst MIN_COLUMN_WIDTH = 40;\n/** Width given to a column inserted into a table whose columns already have pixel widths. */\nconst NEW_COLUMN_WIDTH = 80;\n\n/** Reads a span attribute, treating missing or invalid values as 1. */\nconst spanOf = (value: number) => Math.max(1, value);\n\n/** Rows of the table's own body (normalised tables always have exactly one `<tbody>`). */\nconst rowsOf = (table: HTMLTableElement): HTMLTableRowElement[] =>\n Array.from(table.querySelectorAll<HTMLTableRowElement>(':scope > tbody > tr'));\n\n/** Builds the grid model of a table. */\nconst buildTableMap = (table: HTMLTableElement): TableMap => {\n const rows = rowsOf(table);\n const grid: Array<Array<TableCell | undefined>> = rows.map(() => []);\n const positions = new Map<TableCell, CellPosition>();\n rows.forEach((row, rowIndex) => {\n const slots = grid[rowIndex] ?? [];\n let col = 0;\n for (const cell of Array.from(row.cells)) {\n while (slots[col]) col += 1;\n positions.set(cell, { row: rowIndex, col });\n for (let rowOffset = 0; rowOffset < spanOf(cell.rowSpan) && rowIndex + rowOffset < rows.length; rowOffset += 1) {\n const target = grid[rowIndex + rowOffset];\n for (let colOffset = 0; colOffset < spanOf(cell.colSpan); colOffset += 1) {\n if (target) target[col + colOffset] = cell;\n }\n }\n col += spanOf(cell.colSpan);\n }\n });\n return { rows, grid, positions, width: Math.max(0, ...grid.map(slots => slots.length)) };\n};\n\n/** Writes a span attribute, omitting it when it is 1. */\nconst setSpan = (cell: TableCell, name: 'colspan' | 'rowspan', value: number) => {\n if (value > 1) cell.setAttribute(name, String(value));\n else cell.removeAttribute(name);\n};\n\n/** Creates an empty body or header cell containing one paragraph. */\nconst createCell = (tag: string): TableCell => createElement(tag === 'TH' ? 'th' : 'td', {}, [createParagraph()]);\n\n/** Whether every cell in the row is a header cell. */\nconst isHeaderRow = (map: TableMap, row: number) =>\n (map.grid[row] ?? []).every(cell => cell === undefined || cell.tagName === 'TH');\n\n/** Creates an empty table; with `withHeaderRow` the first row uses header cells. */\nexport const createTable = (rows: number, cols: number, withHeaderRow: boolean): HTMLTableElement => {\n const body = createElement('tbody');\n for (let row = 0; row < rows; row += 1) {\n const tr = createElement('tr');\n for (let col = 0; col < cols; col += 1) tr.append(createCell(withHeaderRow && row === 0 ? 'TH' : 'TD'));\n body.append(tr);\n }\n return createElement('table', {}, [body]);\n};\n\n/** The first cell that starts in `row` at or after `fromCol`, used as the insertion reference. */\nconst cellStartingAfter = (map: TableMap, row: number, fromCol: number, exclude?: TableCell): TableCell | null => {\n const tr = map.rows[row];\n for (let col = fromCol; col < map.width; col += 1) {\n const cell = map.grid[row]?.[col];\n if (cell && cell !== exclude && cell.parentElement === tr && map.positions.get(cell)?.col === col) return cell;\n }\n return null;\n};\n\n/**\n * Inserts a row above or below the cell's row span. Cells spanning across the insertion line grow instead of\n * getting a new cell; header columns stay header cells.\n */\nexport const addRow = (cell: TableCell, side: 'before' | 'after'): void => {\n const table = cell.closest('table');\n if (!table) return;\n const map = buildTableMap(table);\n const position = map.positions.get(cell);\n if (!position) return;\n\n const index = side === 'before' ? position.row : position.row + spanOf(cell.rowSpan);\n const tr = createElement('tr');\n const extended = new Set<TableCell>();\n for (let col = 0; col < map.width; col += 1) {\n const above = map.grid[index - 1]?.[col];\n const below = map.grid[index]?.[col];\n if (above && above === below) {\n if (!extended.has(above)) setSpan(above, 'rowspan', above.rowSpan + 1);\n extended.add(above);\n continue;\n }\n const reference = map.grid[position.row]?.[col];\n const headerColumn = reference?.tagName === 'TH' && !isHeaderRow(map, position.row);\n tr.append(createCell(headerColumn ? 'TH' : 'TD'));\n }\n\n const anchorRow = map.rows[side === 'before' ? position.row : index - 1];\n if (side === 'before') anchorRow?.before(tr);\n else anchorRow?.after(tr);\n};\n\n/** Removes one grid row, shrinking cells that span it and moving cells that start in it down one row. */\nconst deleteGridRow = (table: HTMLTableElement, rowIndex: number) => {\n const map = buildTableMap(table);\n const row = map.rows[rowIndex];\n if (!row) return;\n const handled = new Set<TableCell>();\n for (let col = 0; col < map.width; col += 1) {\n const cell = map.grid[rowIndex]?.[col];\n if (!cell || handled.has(cell)) continue;\n handled.add(cell);\n if (cell.rowSpan <= 1) continue;\n const position = map.positions.get(cell);\n setSpan(cell, 'rowspan', cell.rowSpan - 1);\n if (position?.row === rowIndex) {\n const nextRow = map.rows[rowIndex + 1];\n const reference = cellStartingAfter(map, rowIndex + 1, position.col + spanOf(cell.colSpan));\n nextRow?.insertBefore(cell, reference);\n }\n }\n row.remove();\n};\n\n/** Deletes every row the cell spans; an emptied table is removed. */\nexport const deleteRow = (cell: TableCell): void => {\n const table = cell.closest('table');\n const position = table ? buildTableMap(table).positions.get(cell) : undefined;\n if (!table || !position) return;\n for (let row = position.row + spanOf(cell.rowSpan) - 1; row >= position.row; row -= 1) deleteGridRow(table, row);\n if (!rowsOf(table).length) table.remove();\n};\n\n/**\n * Inserts a column left or right of the cell's column span. Cells spanning across the insertion line grow; an\n * existing `<colgroup>` gets a matching `<col>`.\n */\nexport const addColumn = (cell: TableCell, side: 'before' | 'after'): void => {\n const table = cell.closest('table');\n if (!table) return;\n const map = buildTableMap(table);\n const position = map.positions.get(cell);\n if (!position) return;\n\n const index = side === 'before' ? position.col : position.col + spanOf(cell.colSpan);\n const extended = new Set<TableCell>();\n map.rows.forEach((row, rowIndex) => {\n const left = map.grid[rowIndex]?.[index - 1];\n const right = map.grid[rowIndex]?.[index];\n if (left && left === right) {\n if (!extended.has(left)) setSpan(left, 'colspan', left.colSpan + 1);\n extended.add(left);\n return;\n }\n const reference = cellStartingAfter(map, rowIndex, index);\n row.insertBefore(createCell(isHeaderRow(map, rowIndex) ? 'TH' : 'TD'), reference);\n });\n\n const cols = table.querySelectorAll(':scope > colgroup > col');\n if (!cols.length) return;\n const col = createElement('col', { style: `width: ${NEW_COLUMN_WIDTH}px` });\n table.querySelector(':scope > colgroup')?.insertBefore(col, cols[index] ?? null);\n};\n\n/** Removes one grid column, shrinking spanning cells, its `<col>` and rows left without cells. */\nconst deleteGridColumn = (table: HTMLTableElement, colIndex: number) => {\n const map = buildTableMap(table);\n const handled = new Set<TableCell>();\n map.rows.forEach((_, rowIndex) => {\n const cell = map.grid[rowIndex]?.[colIndex];\n if (!cell || handled.has(cell)) return;\n handled.add(cell);\n if (cell.colSpan > 1) setSpan(cell, 'colspan', cell.colSpan - 1);\n else cell.remove();\n });\n table.querySelectorAll(':scope > colgroup > col')[colIndex]?.remove();\n for (const row of rowsOf(table)) if (!row.cells.length) row.remove();\n};\n\n/** Deletes every column the cell spans; an emptied table is removed. */\nexport const deleteColumn = (cell: TableCell): void => {\n const table = cell.closest('table');\n const position = table ? buildTableMap(table).positions.get(cell) : undefined;\n if (!table || !position) return;\n for (let col = position.col + spanOf(cell.colSpan) - 1; col >= position.col; col -= 1) {\n deleteGridColumn(table, col);\n }\n if (!table.querySelector('td, th')) table.remove();\n};\n\n/** Smallest rectangle containing both cells, grown until no span crosses its edge. */\nexport const rectBetween = (from: TableCell, to: TableCell): CellRect | null => {\n const table = from.closest('table');\n if (!table || to.closest('table') !== table) return null;\n const map = buildTableMap(table);\n const a = map.positions.get(from);\n const b = map.positions.get(to);\n if (!a || !b) return null;\n\n const rect = {\n table,\n top: Math.min(a.row, b.row),\n left: Math.min(a.col, b.col),\n bottom: Math.max(a.row + from.rowSpan - 1, b.row + to.rowSpan - 1),\n right: Math.max(a.col + from.colSpan - 1, b.col + to.colSpan - 1)\n };\n let grown = true;\n while (grown) {\n grown = false;\n for (let row = rect.top; row <= rect.bottom; row += 1) {\n for (let col = rect.left; col <= rect.right; col += 1) {\n const cell = map.grid[row]?.[col];\n const position = cell ? map.positions.get(cell) : undefined;\n if (!cell || !position) continue;\n const bottom = position.row + spanOf(cell.rowSpan) - 1;\n const right = position.col + spanOf(cell.colSpan) - 1;\n if (position.row < rect.top || position.col < rect.left || bottom > rect.bottom || right > rect.right) {\n rect.top = Math.min(rect.top, position.row);\n rect.left = Math.min(rect.left, position.col);\n rect.bottom = Math.max(rect.bottom, bottom);\n rect.right = Math.max(rect.right, right);\n grown = true;\n }\n }\n }\n }\n return rect;\n};\n\n/** Distinct cells covering the rectangle, in grid order. */\nexport const cellsInRect = (rect: CellRect): TableCell[] => {\n const map = buildTableMap(rect.table);\n const cells = new Set<TableCell>();\n for (let row = rect.top; row <= rect.bottom; row += 1) {\n for (let col = rect.left; col <= rect.right; col += 1) {\n const cell = map.grid[row]?.[col];\n if (cell) cells.add(cell);\n }\n }\n return [...cells];\n};\n\n/** Whether a cell selection covers more than one cell and can therefore be merged. */\nexport const canMergeRect = (rect: CellRect | null): boolean => rect !== null && cellsInRect(rect).length > 1;\n\n/**\n * Merges the rectangle into its top-left cell, moving the non-empty content of the other cells into it and\n * removing rows that end up without cells.\n * @returns the merged cell, or `null` when there was nothing to merge.\n */\nexport const mergeCells = (rect: CellRect): TableCell | null => {\n const [target, ...others] = cellsInRect(rect);\n if (!target || !others.length) return null;\n for (const cell of others) {\n const blocks = Array.from(cell.childNodes).filter(\n node => !(node instanceof HTMLElement && node.tagName === 'P' && (node.textContent ?? '') === '')\n );\n target.append(...blocks);\n cell.remove();\n }\n setSpan(target, 'colspan', rect.right - rect.left + 1);\n setSpan(target, 'rowspan', rect.bottom - rect.top + 1);\n for (const row of rowsOf(rect.table)) {\n if (row.cells.length) continue;\n const map = buildTableMap(rect.table);\n const index = map.rows.indexOf(row);\n for (const cell of new Set(map.grid[index] ?? [])) if (cell) setSpan(cell, 'rowspan', cell.rowSpan - 1);\n row.remove();\n }\n return target;\n};\n\n/** Whether the cell spans several rows or columns and can therefore be split. */\nexport const canSplitCell = (cell: TableCell | null): boolean =>\n cell !== null && (cell.colSpan > 1 || cell.rowSpan > 1);\n\n/** Splits a spanning cell back into single cells; the new cells are empty and use the same cell type. */\nexport const splitCell = (cell: TableCell): void => {\n const table = cell.closest('table');\n if (!table || !canSplitCell(cell)) return;\n const map = buildTableMap(table);\n const position = map.positions.get(cell);\n if (!position) return;\n const rows = spanOf(cell.rowSpan);\n const cols = spanOf(cell.colSpan);\n setSpan(cell, 'colspan', 1);\n setSpan(cell, 'rowspan', 1);\n for (let row = position.row; row < position.row + rows; row += 1) {\n const tr = map.rows[row];\n const reference = cellStartingAfter(map, row, position.col + cols, cell);\n for (let col = position.col; col < position.col + cols; col += 1) {\n if (row === position.row && col === position.col) continue;\n tr?.insertBefore(createCell(cell.tagName), reference);\n }\n }\n};\n\n/** Turns the first row into header cells, or back into body cells when it already is a header row. */\nexport const toggleHeaderRow = (table: HTMLTableElement): void => {\n const firstRow = rowsOf(table)[0];\n if (!firstRow) return;\n const cells = Array.from(firstRow.cells);\n const header = cells.every(cell => cell.tagName === 'TH');\n for (const cell of cells) renameElement(cell, header ? 'td' : 'th');\n};\n\n/** All cells of the table's own rows in document order. */\nconst cellsOf = (table: HTMLTableElement) =>\n Array.from(table.querySelectorAll<TableCell>(':scope > tbody > tr > td, :scope > tbody > tr > th'));\n\n/** Tab order through cells; moving past the last cell appends a row. */\nexport const siblingCell = (cell: TableCell, direction: 1 | -1): TableCell | null => {\n const table = cell.closest('table');\n if (!table) return null;\n const cells = cellsOf(table);\n const next = cells[cells.indexOf(cell) + direction];\n if (next || direction < 0) return next ?? null;\n const lastRowCell = rowsOf(table).at(-1)?.cells[0];\n if (!lastRowCell) return null;\n addRow(lastRowCell, 'after');\n return rowsOf(table).at(-1)?.cells[0] ?? null;\n};\n\n/** Width of the first unspanned cell in a column, or `fallback` when every cell there spans columns. */\nconst renderedColumnWidth = (map: TableMap, index: number, fallback: number) => {\n const cell = map.grid.map(slots => slots[index]).find(candidate => candidate && candidate.colSpan === 1);\n return cell?.offsetWidth || fallback;\n};\n\n/** Makes sure a <colgroup> with pixel widths matches the columns, seeded from the rendered layout. */\nconst ensureColumnWidths = (table: HTMLTableElement): HTMLElement[] => {\n const map = buildTableMap(table);\n const existing = table.querySelector(':scope > colgroup');\n if (existing && existing.children.length === map.width) return Array.from(existing.children) as HTMLElement[];\n const fallback = table.offsetWidth / Math.max(1, map.width);\n const widths = Array.from({ length: map.width }, (_, col) => renderedColumnWidth(map, col, fallback));\n const colgroup = createElement(\n 'colgroup',\n {},\n widths.map(width => createElement('col', { style: `width: ${Math.round(width)}px` }))\n );\n existing?.remove();\n table.prepend(colgroup);\n return Array.from(colgroup.children) as HTMLElement[];\n};\n\n/** Current width of a column, read without touching the markup. */\nexport const columnWidth = (table: HTMLTableElement, index: number): number => {\n const col = table.querySelectorAll<HTMLElement>(':scope > colgroup > col')[index];\n const declared = col ? Number.parseFloat(col.style.width) : 0;\n if (declared) return declared;\n const map = buildTableMap(table);\n const cell = map.grid.map(slots => slots[index]).find(candidate => candidate && candidate.colSpan === 1);\n return cell?.offsetWidth ?? table.offsetWidth / Math.max(1, map.width);\n};\n\n/** Sets a column's pixel width (clamped to the minimum) and makes the table as wide as its columns. */\nexport const setColumnWidth = (table: HTMLTableElement, index: number, width: number): void => {\n const cols = ensureColumnWidths(table);\n const col = cols[index];\n if (!col) return;\n col.style.width = `${Math.max(MIN_COLUMN_WIDTH, Math.round(width))}px`;\n const total = cols.reduce((sum, item) => sum + (Number.parseFloat(item.style.width) || 0), 0);\n table.style.width = `${total}px`;\n};\n\n/** Column whose right border is within `tolerance` of `clientX`, or null when the pointer is not near a border. */\nexport const columnBorderAt = (\n table: HTMLTableElement,\n cell: TableCell,\n clientX: number,\n tolerance: number\n): number | null => {\n const rect = cell.getBoundingClientRect();\n const position = buildTableMap(table).positions.get(cell);\n if (!position) return null;\n if (Math.abs(clientX - rect.right) <= tolerance) return position.col + spanOf(cell.colSpan) - 1;\n if (Math.abs(clientX - rect.left) <= tolerance && position.col > 0) return position.col - 1;\n return null;\n};\n\n/** Replaces the highlighted cell selection inside `scope` with `cells` (an empty list clears it). */\nexport const markSelectedCells = (scope: HTMLElement, cells: TableCell[]): void => {\n for (const cell of Array.from(scope.querySelectorAll(`.${SELECTED_CELL_CLASS}`))) {\n cell.classList.remove(SELECTED_CELL_CLASS);\n if (!cell.classList.length) cell.removeAttribute('class');\n }\n for (const cell of cells) cell.classList.add(SELECTED_CELL_CLASS);\n};\n","import { type EditorUiState, readUiState } from '../ui-state';\nimport {\n type HeadingTag,\n type TextAlign,\n type TextDirection,\n changeIndent,\n insertBlockAtCaret,\n setBlockType,\n setLineHeight,\n setTextAlign,\n setTextDirection,\n toggleBlockquote,\n toggleCodeBlock\n} from './blocks';\nimport {\n type TransferContent,\n imageFiles,\n rangeFromPoint,\n readTransfer,\n singleUrl,\n textToFragment,\n transferToFragment,\n writeTransfer\n} from './clipboard';\nimport {\n closestTag,\n closestTextBlock,\n createElement,\n createParagraph,\n isEmptyTextBlock,\n isTextBlock,\n syncTrailingBreak,\n textBlocksInRange,\n textBlocksWithin,\n unwrap\n} from './dom';\nimport {\n type Caret,\n deleteRange,\n endCaret,\n insertFragment,\n insertLineBreak,\n insertTextAtCaret,\n isCaretAtBlockEnd,\n isCaretAtBlockStart,\n joinBackward,\n joinForward,\n splitAtCaret,\n startCaret\n} from './editing';\nimport { EditorHistory, type Snapshot } from './history';\nimport { matchInputRule } from './input-rules';\nimport { type KeyCommand, matchKeyCommand } from './keymap';\nimport { type ListKind, changeListIndent, closestListItem, liftOutOfLists, toggleList } from './lists';\nimport {\n type MarkName,\n type PendingFormat,\n type StyleName,\n clearMarks,\n insertFormattedText,\n linkAncestor,\n readHighlight,\n readMarks,\n readStyle,\n setHighlight,\n setLink,\n setTextStyle,\n toggleMark,\n unsetLink\n} from './marks';\nimport {\n cleanEditorArtifacts,\n isBlockNode,\n isEmptyDocument,\n normalizeContainer,\n sanitizeHtml,\n sanitizeUrl,\n serialize\n} from './schema';\nimport { SearchController, type SearchState } from './search';\nimport {\n type TextBookmark,\n bookmarkToRange,\n getRangeWithin,\n placeCaretAtEnd,\n placeCaretAtStart,\n rangeToBookmark,\n restoreBookmark,\n saveBookmark,\n scrollSelectionIntoView,\n selectRange\n} from './selection';\nimport {\n type CellRect,\n type TableCell,\n addColumn,\n addRow,\n canMergeRect,\n canSplitCell,\n cellsInRect,\n createTable,\n deleteColumn,\n deleteRow,\n markSelectedCells,\n mergeCells,\n rectBetween,\n setColumnWidth,\n siblingCell,\n splitCell,\n toggleHeaderRow\n} from './tables';\n\n/** Settings the host component passes when it creates the engine. */\ninterface DocumentEngineOptions {\n /** Initial HTML; it is sanitised before it reaches the DOM. */\n content: string;\n /** Whether the user may change the document. */\n editable: boolean;\n /** Maximum number of characters in the document, or `0` for no limit. */\n maxLength: number;\n /** Returns the placeholder shown while the document is empty; called on every refresh so it follows the locale. */\n placeholder: () => string;\n /** Receives image files pasted or dropped into the document; the selection is already at the target. */\n onImageFiles: (files: File[]) => void;\n}\n\n/** Events emitted by the engine, mapped to their payloads. */\ninterface EngineEvents {\n /** The document content changed. */\n update: undefined;\n /** The selection, pending formatting or anything else shown by the toolbar may have changed. */\n selection: undefined;\n /** The editable root received focus. */\n focus: undefined;\n /** The editable root lost focus. */\n blur: undefined;\n /** An IME composition started (`true`) or ended (`false`). */\n composition: boolean;\n /** The find results or the current match changed. */\n search: SearchState;\n}\n\n/** Table operations offered by the table menu. */\nexport type TableCommand =\n | 'addRowBefore'\n | 'addRowAfter'\n | 'deleteRow'\n | 'addColumnBefore'\n | 'addColumnAfter'\n | 'deleteColumn'\n | 'mergeCells'\n | 'splitCell'\n | 'toggleHeaderRow'\n | 'deleteTable';\n\n/** Image attributes that can change after insertion; `null` or an empty value removes the attribute. */\ninterface ImageAttributes {\n /** Horizontal placement, stored as `data-align`. */\n align?: 'left' | 'center' | 'right';\n /** Alternative text for screen readers. */\n alt?: string | null;\n /** Rendered width in CSS pixels. */\n width?: number | null;\n /** Rendered height in CSS pixels. */\n height?: number | null;\n}\n\n/**\n * Result of an edit callback: the new caret, a text bookmark, `undefined` to keep the previous selection, or `null`\n * when nothing changed and no undo step may be recorded.\n */\ntype EditResult = Caret | TextBookmark | undefined | null;\n\n/** Callback registered with {@link DocumentEngine.on}. */\ntype Listener<T> = (payload: T) => void;\n\n/** Table cell tags, used to find the cell around a node. */\nconst CELL_TAGS = new Set(['TD', 'TH']);\n/** Blocks that can hold the caret, used when moving between table cells. */\nconst TEXT_BLOCK_QUERY = 'p, h1, h2, h3, h4, h5, h6, pre';\n/** A selectionchange arriving this soon after an edit was caused by the edit, not by the user. */\nconst INTERNAL_SELECTION_MS = 150;\n/** Highlight colour applied by the keyboard shortcut. */\nconst DEFAULT_HIGHLIGHT_COLOR = '#fef08a';\n/** `MouseEvent.button` value of the primary button. */\nconst PRIMARY_BUTTON = 0;\n/** `MouseEvent.buttons` bit that is set while the primary button is held. */\nconst PRIMARY_BUTTON_MASK = 1;\n/** Inline styles carried over to a new empty line after Enter. */\nconst CARRIED_STYLES: StyleName[] = ['color', 'fontFamily', 'fontSize'];\n\n/** Tells a caret apart from a text bookmark. */\nconst isCaret = (value: Caret | TextBookmark): value is Caret => 'node' in value;\n\n/**\n * Framework-free rich text engine on top of a `contenteditable` element. It owns the browser events, keeps the\n * document well-formed, records undo history and exposes every editing command the toolbar and menus need.\n */\nexport class DocumentEngine {\n /** Undo and redo stacks of document snapshots. */\n readonly history = new EditorHistory();\n /** Find and replace over the document text. */\n readonly search: SearchController;\n\n /** Registered listeners per event. */\n private readonly listeners = new Map<keyof EngineEvents, Set<Listener<never>>>();\n /** Removes every DOM listener added by the engine. */\n private readonly disposers: Array<() => void> = [];\n /** Whether the user may change the document. */\n private editable: boolean;\n /** Whether an IME composition is in progress. */\n private composingText = false;\n /** Last selection inside the document, used when focus is in a toolbar popover. */\n private lastRange: Range | null = null;\n /** Formatting chosen at a collapsed caret, applied to the next typed text. */\n private pending: PendingFormat | null = null;\n /** Time until which selection changes are attributed to the engine itself. */\n private internalSelectionUntil = 0;\n /** Cell where a mouse drag for a cell selection started. */\n private cellAnchor: TableCell | null = null;\n /** Rectangle of selected table cells, when several cells are selected. */\n private cellRect: CellRect | null = null;\n /** Selection being dragged, so a drop inside the document can move instead of copy. */\n private dragRange: Range | null = null;\n /** Image selected by a click, shown with resize handles. */\n private selectedImageElement: HTMLImageElement | null = null;\n\n /**\n * Takes over `root` as the editable document, loads the initial content and starts listening to its events.\n *\n * @param root Element that becomes the editable document.\n * @param options Initial content, limits and callbacks.\n */\n constructor(\n readonly root: HTMLElement,\n private readonly options: DocumentEngineOptions\n ) {\n this.editable = options.editable;\n this.search = new SearchController(root, state => this.emit('search', state));\n root.classList.add('doc-content');\n root.setAttribute('role', 'textbox');\n root.setAttribute('aria-multiline', 'true');\n root.spellcheck = true;\n root.contentEditable = String(options.editable);\n this.setContent(options.content);\n\n this.listen(root, 'beforeinput', this.onBeforeInput);\n this.listen(root, 'input', this.onInput);\n this.listen(root, 'keydown', this.onKeyDown);\n this.listen(root, 'paste', this.onPaste);\n this.listen(root, 'copy', this.onCopy);\n this.listen(root, 'cut', this.onCut);\n this.listen(root, 'dragstart', this.onDragStart);\n this.listen(root, 'dragend', this.onDragEnd);\n this.listen(root, 'dragover', this.onDragOver);\n this.listen(root, 'drop', this.onDrop);\n this.listen(root, 'mousedown', this.onMouseDown);\n this.listen(root, 'mousemove', this.onMouseMove);\n this.listen(root, 'click', this.onClick);\n this.listen(root, 'compositionstart', this.onCompositionStart);\n this.listen(root, 'compositionend', this.onCompositionEnd);\n this.listen(root, 'focus', () => this.emit('focus', undefined));\n this.listen(root, 'blur', () => this.emit('blur', undefined));\n this.listen(document, 'selectionchange', this.onSelectionChange);\n this.listen(document, 'mouseup', () => {\n this.cellAnchor = null;\n });\n }\n\n // -------------------------------------------------------------------------------------------------------------\n // Events and lifecycle\n\n /**\n * Subscribes to an engine event.\n *\n * @returns Function that removes the listener again.\n */\n on<K extends keyof EngineEvents>(event: K, listener: Listener<EngineEvents[K]>): () => void {\n const set = this.listeners.get(event) ?? new Set();\n set.add(listener as Listener<never>);\n this.listeners.set(event, set);\n return () => set.delete(listener as Listener<never>);\n }\n\n /** Removes all DOM listeners, event subscriptions and search highlights. The root element is left in place. */\n destroy(): void {\n for (const dispose of this.disposers) dispose();\n this.disposers.length = 0;\n this.listeners.clear();\n this.search.clear();\n }\n\n /** Calls every listener of an event with its payload. */\n private emit<K extends keyof EngineEvents>(event: K, payload: EngineEvents[K]) {\n for (const listener of this.listeners.get(event) ?? []) (listener as Listener<EngineEvents[K]>)(payload);\n }\n\n /** Adds a DOM listener that {@link destroy} removes again. */\n private listen(target: EventTarget, type: string, handler: (event: never) => void) {\n const listener = handler as unknown as EventListener;\n target.addEventListener(type, listener);\n this.disposers.push(() => target.removeEventListener(type, listener));\n }\n\n // -------------------------------------------------------------------------------------------------------------\n // Content and state\n\n /** Whether the document holds nothing but one empty paragraph. */\n get isEmpty(): boolean {\n return isEmptyDocument(this.root);\n }\n\n /** Whether the user may change the document. */\n get isEditable(): boolean {\n return this.editable;\n }\n\n /** Whether an IME composition is in progress; layout work should wait until it ends. */\n get composing(): boolean {\n return this.composingText;\n }\n\n /** Image currently selected by a click, if it is still in the document. */\n get selectedImage(): HTMLImageElement | null {\n return this.selectedImageElement?.isConnected ? this.selectedImageElement : null;\n }\n\n /** Whether several table cells are selected with the mouse. */\n get hasCellSelection(): boolean {\n return this.cellRect !== null;\n }\n\n /** Clean HTML of the document, without caret placeholders or other editor-only markup. */\n getHTML(): string {\n return serialize(this.root);\n }\n\n /**\n * Replaces the whole document with sanitised HTML.\n *\n * @param html New content; scripts, handlers and unsupported markup are dropped.\n * @param options.addToHistory Record the replacement as an undo step instead of clearing the history.\n * @param options.emitUpdate Emit `update` so the host saves the new content.\n */\n setContent(html: string, { addToHistory = false, emitUpdate = false } = {}): void {\n const before = addToHistory ? this.snapshot() : null;\n this.root.replaceChildren(sanitizeHtml(html));\n normalizeContainer(this.root, true);\n if (before) this.history.record(() => before, 'command');\n else this.history.clear();\n this.pending = null;\n this.selectImage(null);\n this.clearCellSelection();\n this.refresh(emitUpdate);\n }\n\n /** Switches between editing and read-only mode. */\n setEditable(editable: boolean): void {\n this.editable = editable;\n this.root.contentEditable = String(editable);\n this.emit('selection', undefined);\n }\n\n /** Word and character counts of the document text. */\n getStats(): { words: number; characters: number } {\n const texts = textBlocksWithin(this.root).map(block => block.textContent ?? '');\n return {\n characters: texts.reduce((sum, text) => sum + text.length, 0),\n words: texts.join(' ').split(/\\s+/).filter(Boolean).length\n };\n }\n\n /** Snapshot of the formatting at the selection, for the toolbar. */\n getState(): EditorUiState {\n return readUiState({\n root: this.root,\n range: this.currentRange(),\n pending: this.pending,\n canUndo: this.editable && this.history.canUndo,\n canRedo: this.editable && this.history.canRedo\n });\n }\n\n /** Plain text of the current selection. */\n getSelectedText(): string {\n return this.currentRange()?.toString() ?? '';\n }\n\n /**\n * Focuses the document.\n *\n * @param position Place the caret at the start or end; without it the last selection is restored.\n */\n focus(position?: 'start' | 'end'): void {\n this.expectInternalSelection();\n this.root.focus({ preventScroll: true });\n const blocks = textBlocksWithin(this.root);\n const first = blocks[0];\n const last = blocks.at(-1);\n if (position === 'start' && first) placeCaretAtStart(first);\n else if (position === 'end' && last) placeCaretAtEnd(last);\n else if (!getRangeWithin(this.root) && this.lastRange?.startContainer.isConnected) selectRange(this.lastRange);\n }\n\n // -------------------------------------------------------------------------------------------------------------\n // Edit pipeline\n\n /** Marks the next selection changes as caused by the engine, so they keep pending formatting and typing groups. */\n private expectInternalSelection() {\n this.internalSelectionUntil = performance.now() + INTERNAL_SELECTION_MS;\n }\n\n /** Live selection inside the document, or a copy of the last one while focus is elsewhere (e.g. in a popover). */\n private currentRange(): Range | null {\n const live = getRangeWithin(this.root);\n if (live) return live;\n const last = this.lastRange;\n return last && this.root.contains(last.startContainer) && this.root.contains(last.endContainer)\n ? last.cloneRange()\n : null;\n }\n\n /** Captures the document markup and selection for the undo history. */\n private snapshot(bookmark: TextBookmark | null = saveBookmark(this.root)): Snapshot {\n const clone = this.root.cloneNode(true) as HTMLElement;\n cleanEditorArtifacts(clone, false);\n return { blocks: Array.from(clone.children, child => child.outerHTML), bookmark };\n }\n\n /** Updates the placeholder state and search results, then notifies listeners. */\n private refresh(emitUpdate = true) {\n this.root.classList.toggle('is-empty', this.isEmpty);\n this.root.style.setProperty('--doc-placeholder', JSON.stringify(this.options.placeholder()));\n if (this.search.active) this.search.refresh();\n if (emitUpdate) this.emit('update', undefined);\n this.emit('selection', undefined);\n }\n\n /**\n * Runs one edit as an undo step, then normalises the document and restores the selection by text position.\n *\n * @param edit Changes the document at the given range and describes the resulting selection.\n * @param kind `typing` edits join the running typing group instead of always starting a new undo step.\n * @returns Whether the edit ran and changed something.\n */\n private exec(edit: (range: Range) => EditResult, kind: 'command' | 'typing' = 'command'): boolean {\n if (!this.editable) return false;\n const range = this.currentRange();\n if (!range) return false;\n const bookmark = rangeToBookmark(this.root, range);\n const before = kind === 'command' ? this.snapshot(bookmark) : null;\n if (kind === 'typing') this.history.record(() => this.snapshot(bookmark), 'typing');\n const result = edit(range);\n if (result === null) return false;\n if (before) this.history.record(() => before, 'command');\n this.finishEdit(result ?? bookmark);\n return true;\n }\n\n /** Normalises the document after an edit, restores the selection and notifies listeners. */\n private finishEdit(selection: Caret | TextBookmark) {\n let bookmark: TextBookmark | null = null;\n if (!isCaret(selection)) bookmark = selection;\n else if (selection.node.isConnected && this.root.contains(selection.node)) {\n const caret = document.createRange();\n caret.setStart(selection.node, selection.offset);\n bookmark = rangeToBookmark(this.root, caret);\n }\n normalizeContainer(this.root, true);\n this.expectInternalSelection();\n if (document.activeElement !== this.root) this.root.focus({ preventScroll: true });\n if (bookmark) restoreBookmark(this.root, bookmark);\n this.lastRange = getRangeWithin(this.root)?.cloneRange() ?? this.lastRange;\n this.refresh();\n scrollSelectionIntoView(this.root);\n }\n\n /** Records an undo step for a change that does not depend on the selection (checkboxes, image attributes). */\n private mutate(change: () => void) {\n if (!this.editable) return;\n const before = this.snapshot();\n change();\n this.history.record(() => before, 'command');\n this.refresh();\n }\n\n /** Deletes the selected content, if any, and returns a collapsed range where it started. */\n private collapsedAfterDelete(range: Range): Range {\n if (range.collapsed) return range;\n const { anchor } = rangeToBookmark(this.root, range);\n deleteRange(this.root, range);\n normalizeContainer(this.root, true);\n return bookmarkToRange(this.root, { anchor, focus: anchor });\n }\n\n /** Deletes the selection as one undo step and leaves a collapsed caret where it started. */\n private deleteSelection() {\n this.exec(target => {\n const { anchor } = rangeToBookmark(this.root, target);\n deleteRange(this.root, target);\n return { anchor, focus: anchor };\n });\n }\n\n /** Number of characters in the document text. */\n private characterCount(): number {\n return textBlocksWithin(this.root).reduce((sum, block) => sum + (block.textContent?.length ?? 0), 0);\n }\n\n /** Whether adding `extra` characters would exceed the configured maximum length. */\n private exceedsLimit(extra: number) {\n return this.options.maxLength > 0 && this.characterCount() + extra > this.options.maxLength;\n }\n\n // -------------------------------------------------------------------------------------------------------------\n // Browser events\n\n /** Remembers the selection; a selection moved by the user drops pending formatting and ends the typing group. */\n private onSelectionChange = () => {\n const range = getRangeWithin(this.root);\n if (!range) return;\n this.lastRange = range.cloneRange();\n if (performance.now() > this.internalSelectionUntil) {\n this.pending = null;\n this.history.breakGroup();\n }\n this.emit('selection', undefined);\n };\n\n /** Decides for every input whether the browser may apply it natively or the engine performs it instead. */\n private onBeforeInput = (event: InputEvent) => {\n if (!this.editable) {\n event.preventDefault();\n return;\n }\n const type = event.inputType;\n if (type === 'historyUndo' || type === 'historyRedo') {\n event.preventDefault();\n if (type === 'historyUndo') this.undo();\n else this.redo();\n return;\n }\n // Native formatting is replaced by the engine's commands; paste and drop have their own handlers.\n if (type.startsWith('format') || type === 'insertFromPaste' || type === 'insertFromDrop') {\n event.preventDefault();\n return;\n }\n if (this.cellRect && (type.startsWith('delete') || type.startsWith('insert'))) {\n event.preventDefault();\n if (type.startsWith('delete')) this.clearSelectedCells();\n return;\n }\n const range = getRangeWithin(this.root);\n if (!range || this.composingText || type === 'insertCompositionText') return;\n this.expectInternalSelection();\n\n if (type === 'insertParagraph' || type === 'insertLineBreak') {\n event.preventDefault();\n this.splitLine(type === 'insertLineBreak');\n } else if (type === 'insertText' || type === 'insertReplacementText') {\n this.handleTextInput(event, range);\n } else if (type.startsWith('delete')) {\n this.handleDeleteInput(event, range);\n }\n };\n\n /**\n * Lets the browser type plain characters itself (fast and IME friendly) and takes over when a selection has to be\n * replaced, pending formatting applies or the caret sits outside a text block.\n */\n private handleTextInput(event: InputEvent, range: Range) {\n const text = event.data ?? event.dataTransfer?.getData('text/plain') ?? '';\n if (this.exceedsLimit(text.length)) {\n event.preventDefault();\n return;\n }\n const block = closestTextBlock(range.startContainer, this.root);\n if (event.inputType === 'insertText' && (!range.collapsed || this.pending || !block)) {\n event.preventDefault();\n this.insertText(text);\n return;\n }\n this.history.record(() => this.snapshot(), 'typing');\n }\n\n /** Deletes selections and joins blocks at their edges itself; single characters are deleted natively. */\n private handleDeleteInput(event: InputEvent, range: Range) {\n if (!range.collapsed) {\n event.preventDefault();\n this.deleteSelection();\n return;\n }\n const block = closestTextBlock(range.startContainer, this.root);\n const backward = event.inputType.includes('Backward');\n if (block && (backward ? isCaretAtBlockStart(block, range) : isCaretAtBlockEnd(block, range))) {\n event.preventDefault();\n this.exec(() => (backward ? joinBackward(this.root, block) : joinForward(this.root, block)));\n return;\n }\n this.history.record(() => this.snapshot(), 'typing');\n }\n\n /** Repairs the structure after a native edit and applies Markdown and typography rules to typed text. */\n private onInput = (event: Event) => {\n this.expectInternalSelection();\n this.repairStructure();\n const { inputType, data } = event as InputEvent;\n if (!this.composingText && inputType === 'insertText' && data) this.applyInputRule(data);\n this.refresh();\n };\n\n /** Browsers occasionally leave text outside paragraphs or a <div> after native edits; fix it in place. */\n private repairStructure() {\n const broken =\n !this.root.firstChild ||\n Array.from(this.root.childNodes).some(\n node => !isBlockNode(node) || (node.tagName === 'DIV' && node.getAttribute('data-type') !== 'page-break')\n );\n if (broken) {\n const bookmark = saveBookmark(this.root);\n normalizeContainer(this.root, true);\n if (bookmark) restoreBookmark(this.root, bookmark);\n return;\n }\n const block = closestTextBlock(getRangeWithin(this.root)?.startContainer, this.root);\n if (block && block.tagName !== 'PRE') syncTrailingBreak(block);\n }\n\n /** Applies the input rule completed by the typed character as its own undo step. */\n private applyInputRule(typed: string) {\n const range = getRangeWithin(this.root);\n const rule = range ? matchInputRule(this.root, range, typed) : null;\n if (!rule) return;\n let pending: PendingFormat | undefined;\n this.exec(() => {\n const result = rule();\n pending = result.pending;\n return result.caret ?? undefined;\n });\n if (pending) this.pending = pending;\n }\n\n /** Runs keyboard shortcuts and handles Delete, Tab and Escape for images, cell selections, tables and lists. */\n private onKeyDown = (event: KeyboardEvent) => {\n if (event.isComposing || this.composingText) return;\n const command = matchKeyCommand(event);\n if (command) {\n event.preventDefault();\n this.runKeyCommand(command);\n return;\n }\n if (!this.editable) return;\n const deleting = event.key === 'Backspace' || event.key === 'Delete';\n if (deleting && this.selectedImage) {\n event.preventDefault();\n this.removeImage(this.selectedImage);\n } else if (deleting && this.cellRect) {\n event.preventDefault();\n this.clearSelectedCells();\n } else if (event.key === 'Tab') {\n this.handleTab(event);\n } else if (event.key === 'Escape') {\n this.clearCellSelection();\n this.selectImage(null);\n }\n };\n\n /**\n * Tab moves between table cells (appending a row after the last one), nests list items, indents a paragraph at its\n * start and types a tab stop inside a line. Shift+Tab reverses each of these.\n */\n private handleTab(event: KeyboardEvent) {\n const range = getRangeWithin(this.root);\n if (!range) return;\n const direction = event.shiftKey ? -1 : 1;\n const cell = closestTag(range.startContainer, this.root, CELL_TAGS) as TableCell | null;\n if (cell) {\n event.preventDefault();\n this.moveToSiblingCell(cell, direction);\n return;\n }\n if (closestListItem(range.startContainer, this.root)) {\n event.preventDefault();\n this.exec(target => (changeListIndent(this.root, target, direction) ? undefined : null));\n return;\n }\n const block = closestTextBlock(range.startContainer, this.root);\n if (!block || block.tagName === 'PRE') return;\n event.preventDefault();\n // Word behaviour: Tab at the start of a paragraph indents it, inside a line it types a tab stop.\n if (direction > 0 && range.collapsed && !isCaretAtBlockStart(block, range)) {\n this.insertText('\\t');\n return;\n }\n this.exec(target => (changeIndent(this.root, target, direction) ? undefined : null));\n }\n\n /** Moves the caret to the next or previous cell; moving past the last cell appends a row as an undo step. */\n private moveToSiblingCell(cell: TableCell, direction: 1 | -1) {\n const lastCell = cell.closest('table')?.querySelector('tr:last-child > :last-child');\n const staysInsideTable = direction > 0 && !lastCell?.isSameNode(cell);\n if (staysInsideTable) {\n const block = siblingCell(cell, direction)?.querySelector<HTMLElement>(TEXT_BLOCK_QUERY);\n if (block) {\n this.expectInternalSelection();\n placeCaretAtEnd(block);\n }\n return;\n }\n this.exec(() => {\n const block = siblingCell(cell, direction)?.querySelector<HTMLElement>(TEXT_BLOCK_QUERY);\n return block ? endCaret(block) : null;\n });\n }\n\n /** Runs the command bound to a keyboard shortcut. */\n private runKeyCommand(command: KeyCommand) {\n const heading = /^heading([1-6])$/.exec(command)?.[1];\n if (heading) {\n this.setBlockType(`H${heading}` as HeadingTag);\n return;\n }\n const actions: Partial<Record<KeyCommand, () => void>> = {\n undo: () => this.undo(),\n redo: () => this.redo(),\n highlight: () => this.setHighlight(this.getState().highlight ? null : DEFAULT_HIGHLIGHT_COLOR),\n paragraph: () => this.setBlockType('P'),\n alignLeft: () => this.setTextAlign('left'),\n alignCenter: () => this.setTextAlign('center'),\n alignRight: () => this.setTextAlign('right'),\n alignJustify: () => this.setTextAlign('justify'),\n bulletList: () => this.toggleList('bulletList'),\n orderedList: () => this.toggleList('orderedList'),\n taskList: () => this.toggleList('taskList'),\n blockquote: () => this.toggleBlockquote(),\n codeBlock: () => this.toggleCodeBlock(),\n pageBreak: () => this.insertPageBreak()\n };\n const action = actions[command];\n if (action) action();\n else this.toggleMark(command as MarkName);\n }\n\n /** Pastes sanitised content; lone images go to the host for upload and a URL over a selection becomes a link. */\n private onPaste = (event: ClipboardEvent) => {\n event.preventDefault();\n if (!this.editable) return;\n const content = readTransfer(event.clipboardData);\n const images = imageFiles(content);\n if (images.length && !content.html && !content.text) {\n this.options.onImageFiles(images);\n return;\n }\n const url = singleUrl(content);\n const range = this.currentRange();\n if (url && range && !range.collapsed && !closestTag(range.startContainer, this.root, 'PRE')) {\n this.setLink(url, null);\n return;\n }\n this.insertTransfer(content);\n };\n\n /** Inserts clipboard content at the selection; inside a code block only its plain text is used. */\n private insertTransfer(content: TransferContent) {\n const range = this.currentRange();\n const inCode = range ? closestTextBlock(range.startContainer, this.root)?.tagName === 'PRE' : false;\n const fragment = inCode ? textToFragment(content.text) : transferToFragment(content);\n if (!fragment || this.exceedsLimit(fragment.textContent?.length ?? 0)) return;\n this.exec(target => insertFragment(this.root, this.collapsedAfterDelete(target), fragment) ?? undefined);\n }\n\n /** Puts clean HTML and plain text of the selection on the clipboard. */\n private onCopy = (event: ClipboardEvent) => {\n const range = getRangeWithin(this.root);\n if (!range || range.collapsed || !event.clipboardData) return;\n event.preventDefault();\n writeTransfer(event.clipboardData, this.root, range);\n };\n\n /** Copies the selection like {@link onCopy} and deletes it as an undo step. */\n private onCut = (event: ClipboardEvent) => {\n const range = getRangeWithin(this.root);\n if (!range || range.collapsed || !event.clipboardData) return;\n event.preventDefault();\n writeTransfer(event.clipboardData, this.root, range);\n this.deleteSelection();\n };\n\n /** Starts dragging the selection with clean clipboard data and remembers it for a move on drop. */\n private onDragStart = (event: DragEvent) => {\n const range = getRangeWithin(this.root);\n if (!range || range.collapsed || !event.dataTransfer) return;\n this.dragRange = range.cloneRange();\n writeTransfer(event.dataTransfer, this.root, range);\n event.dataTransfer.effectAllowed = 'copyMove';\n };\n\n /** Forgets the dragged selection once the drag ends anywhere. */\n private onDragEnd = () => {\n this.dragRange = null;\n };\n\n /** Allows dropping into an editable document. */\n private onDragOver = (event: DragEvent) => {\n if (this.editable) event.preventDefault();\n };\n\n /**\n * Inserts dropped content at the pointer. A selection dragged within the document is moved (copied with Ctrl) in a\n * single undo step; dropped image files go to the host for upload.\n */\n private onDrop = (event: DragEvent) => {\n event.preventDefault();\n const source = this.dragRange;\n this.dragRange = null;\n if (!this.editable) return;\n const point = rangeFromPoint(event.clientX, event.clientY);\n if (!point || !this.root.contains(point.startContainer)) return;\n const content = readTransfer(event.dataTransfer);\n const images = imageFiles(content);\n this.expectInternalSelection();\n this.root.focus({ preventScroll: true });\n if (images.length) {\n selectRange(point);\n this.options.onImageFiles(images);\n return;\n }\n const fragment = transferToFragment(content);\n if (!fragment) return;\n const before = this.snapshot();\n let insertAt = rangeToBookmark(this.root, point).anchor;\n if (source && !source.collapsed && !event.ctrlKey && this.root.contains(source.startContainer)) {\n const moved = rangeToBookmark(this.root, source);\n if (insertAt > moved.anchor && insertAt < moved.focus) return;\n deleteRange(this.root, source);\n normalizeContainer(this.root, true);\n if (insertAt >= moved.focus) insertAt -= moved.focus - moved.anchor;\n }\n const target = bookmarkToRange(this.root, { anchor: insertAt, focus: insertAt });\n const caret = insertFragment(this.root, target, fragment);\n this.history.record(() => before, 'command');\n this.finishEdit(caret ?? { anchor: insertAt, focus: insertAt });\n };\n\n /** Selects a clicked image, clears a previous cell selection and remembers the cell where a drag may start. */\n private onMouseDown = (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n this.clearCellSelection();\n if (target.tagName === 'IMG' && this.root.contains(target)) {\n this.selectImage(target as HTMLImageElement);\n const range = document.createRange();\n range.selectNode(target);\n this.expectInternalSelection();\n selectRange(range);\n } else {\n this.selectImage(null);\n }\n this.cellAnchor =\n event.button === PRIMARY_BUTTON ? (closestTag(target, this.root, CELL_TAGS) as TableCell | null) : null;\n };\n\n /** Extends a rectangular cell selection while the mouse is dragged across table cells. */\n private onMouseMove = (event: MouseEvent) => {\n if (!this.cellAnchor || !(event.buttons & PRIMARY_BUTTON_MASK)) return;\n const cell = closestTag(event.target as Node, this.root, CELL_TAGS) as TableCell | null;\n if (!cell || cell === this.cellAnchor) return;\n const rect = rectBetween(this.cellAnchor, cell);\n if (!rect) return;\n this.cellRect = rect;\n markSelectedCells(this.root, cellsInRect(rect));\n this.root.classList.add('has-cell-selection');\n this.emit('selection', undefined);\n };\n\n /** Toggles task list checkboxes as undo steps and opens links on Ctrl/Cmd+click. */\n private onClick = (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (target instanceof HTMLInputElement && target.type === 'checkbox') {\n const item = target.closest('li[data-type=\"taskItem\"]');\n if (!this.editable || !item || !this.root.contains(item)) {\n event.preventDefault();\n return;\n }\n // The browser has already toggled the box; mirror it into the document.\n const checked = target.checked;\n this.mutate(() => {\n item.setAttribute('data-checked', String(checked));\n target.toggleAttribute('checked', checked);\n });\n return;\n }\n const link = target.closest('a');\n if (link && this.root.contains(link) && (event.ctrlKey || event.metaKey)) {\n event.preventDefault();\n window.open(link.href, '_blank', 'noopener,noreferrer');\n }\n };\n\n /** Deletes a selection before an IME replaces it and pauses layout work while composing. */\n private onCompositionStart = () => {\n const range = getRangeWithin(this.root);\n if (range && !range.collapsed) this.deleteSelection();\n else this.history.record(() => this.snapshot(), 'typing');\n this.composingText = true;\n this.emit('composition', true);\n };\n\n /** Repairs the structure once the IME has committed its text. */\n private onCompositionEnd = () => {\n this.composingText = false;\n this.repairStructure();\n this.refresh();\n this.emit('composition', false);\n };\n\n // -------------------------------------------------------------------------------------------------------------\n // History\n\n /** Reverts the last undo step. */\n undo(): void {\n if (!this.editable) return;\n const previous = this.history.undo(this.snapshot());\n if (previous) this.restoreSnapshot(previous);\n }\n\n /** Re-applies the last reverted undo step. */\n redo(): void {\n if (!this.editable) return;\n const next = this.history.redo(this.snapshot());\n if (next) this.restoreSnapshot(next);\n }\n\n /** Puts a history snapshot back into the document and restores its selection. */\n private restoreSnapshot(snapshot: Snapshot) {\n this.root.innerHTML = snapshot.blocks.join('');\n this.pending = null;\n this.selectImage(null);\n this.clearCellSelection();\n this.expectInternalSelection();\n this.root.focus({ preventScroll: true });\n if (snapshot.bookmark) restoreBookmark(this.root, snapshot.bookmark);\n this.refresh();\n scrollSelectionIntoView(this.root);\n }\n\n // -------------------------------------------------------------------------------------------------------------\n // Text and inline formatting\n\n /** Inserts plain text at the selection, replacing selected content and applying pending formatting. */\n insertText(text: string): void {\n if (this.exceedsLimit(text.length)) return;\n const pending = this.pending;\n this.exec(range => {\n const target = this.collapsedAfterDelete(range);\n if (pending && closestTextBlock(target.startContainer, this.root)) {\n const { anchor } = rangeToBookmark(this.root, target);\n insertFormattedText(this.root, target, text, pending);\n return { anchor: anchor + text.length, focus: anchor + text.length };\n }\n return insertTextAtCaret(this.root, target, text);\n }, 'typing');\n this.pending = null;\n }\n\n /** Formatting at the start of a range, or `null` when the text there is unformatted. */\n private captureFormat(range: Range): PendingFormat | null {\n const node = range.startContainer;\n const marks = Object.fromEntries(Object.entries(readMarks(this.root, node)).filter(([, active]) => active));\n const styles = Object.fromEntries(\n CARRIED_STYLES.map(name => [name, readStyle(this.root, node, name)]).filter(([, value]) => value)\n );\n const highlight = readHighlight(this.root, node);\n if (!Object.keys(marks).length && !Object.keys(styles).length && !highlight) return null;\n return { marks, styles, ...(highlight ? { highlight } : {}) };\n }\n\n /**\n * Enter splits the block (or leaves an empty list item or quote), Shift+Enter inserts a line break. An empty new\n * line keeps the formatting of the text before it, as in office editors.\n */\n private splitLine(lineBreak: boolean) {\n const range = this.currentRange();\n const format = range ? (this.pending ?? this.captureFormat(range)) : null;\n this.exec(target => {\n const collapsed = this.collapsedAfterDelete(target);\n return (lineBreak ? insertLineBreak : splitAtCaret)(this.root, collapsed);\n });\n const block = closestTextBlock(getRangeWithin(this.root)?.startContainer, this.root);\n if (format && block && isEmptyTextBlock(block)) this.pending = format;\n }\n\n /** Toggles bold, italic and the other marks; at a collapsed caret it applies to the next typed text. */\n toggleMark(mark: MarkName): void {\n const range = this.currentRange();\n if (!range || !this.editable) return;\n if (range.collapsed) {\n const active = this.getState()[mark];\n this.setPending({ marks: { ...this.pending?.marks, [mark]: !active } });\n return;\n }\n this.exec(target => {\n toggleMark(this.root, target, mark);\n });\n }\n\n /** Sets or removes (`null`) text colour, font family or font size; at a collapsed caret for the next text. */\n setTextStyle(name: StyleName, value: string | null): void {\n const range = this.currentRange();\n if (!range || !this.editable) return;\n if (range.collapsed) {\n this.setPending({ styles: { ...this.pending?.styles, [name]: value } });\n return;\n }\n this.exec(target => {\n setTextStyle(this.root, target, name, value);\n });\n }\n\n /** Sets or removes (`null`) the highlight colour; at a collapsed caret for the next text. */\n setHighlight(color: string | null): void {\n const range = this.currentRange();\n if (!range || !this.editable) return;\n if (range.collapsed) {\n this.setPending({ highlight: color });\n return;\n }\n this.exec(target => {\n setHighlight(this.root, target, color);\n });\n }\n\n /** Merges a formatting change into the pending format and keeps focus in the document. */\n private setPending(change: Partial<PendingFormat>) {\n this.pending = { marks: {}, styles: {}, ...this.pending, ...change };\n this.focus();\n this.emit('selection', undefined);\n }\n\n /** Removes marks from the selection and turns its blocks into plain paragraphs outside lists and quotes. */\n clearFormatting(): void {\n this.exec(target => {\n const blocks = textBlocksInRange(this.root, target);\n if (!target.collapsed) clearMarks(this.root, target);\n for (const block of blocks) {\n if (!block.isConnected) continue;\n liftOutOfLists(this.root, block);\n const quote = closestTag(block, this.root, 'BLOCKQUOTE');\n if (quote) unwrap(quote);\n block.removeAttribute('style');\n block.removeAttribute('data-indent');\n const blockRange = document.createRange();\n blockRange.selectNodeContents(block);\n setBlockType(this.root, blockRange, 'P');\n }\n });\n }\n\n // -------------------------------------------------------------------------------------------------------------\n // Blocks and paragraphs\n\n /** Turns the selected blocks into paragraphs or headings. */\n setBlockType(tag: 'P' | HeadingTag): void {\n this.exec(target => {\n setBlockType(this.root, target, tag);\n });\n }\n\n /** Wraps the selected blocks in a quote, or unwraps them when they are all quoted already. */\n toggleBlockquote(): void {\n this.exec(target => {\n toggleBlockquote(this.root, target);\n });\n }\n\n /** Turns the selected blocks into code blocks, or back into paragraphs. */\n toggleCodeBlock(): void {\n this.exec(target => {\n toggleCodeBlock(this.root, target);\n });\n }\n\n /** Aligns the selected paragraphs. */\n setTextAlign(align: TextAlign): void {\n this.exec(target => {\n setTextAlign(this.root, target, align);\n });\n }\n\n /** Sets or removes (`null`) the line spacing of the selected paragraphs. */\n setLineHeight(lineHeight: string | null): void {\n this.exec(target => {\n setLineHeight(this.root, target, lineHeight);\n });\n }\n\n /** Sets the writing direction of the selected paragraphs. */\n setTextDirection(direction: TextDirection): void {\n this.exec(target => {\n setTextDirection(this.root, target, direction);\n });\n }\n\n /** Turns the selected blocks into a list of the given kind, converts another list kind, or lifts them out. */\n toggleList(kind: ListKind): void {\n this.exec(target => {\n toggleList(this.root, target, kind);\n });\n }\n\n /** List items are nested or lifted; other paragraphs get indentation steps. */\n indent(direction: 1 | -1): void {\n this.exec(target => {\n const inList = closestListItem(target.startContainer, this.root) !== null;\n const changed = inList\n ? changeListIndent(this.root, target, direction)\n : changeIndent(this.root, target, direction);\n return changed ? undefined : null;\n });\n }\n\n /**\n * Inserts a block element at the caret and places the caret after it, or inside it when `caretInside` finds a\n * target (e.g. the first cell of a new table).\n */\n private insertBlock(element: HTMLElement, caretInside?: (element: HTMLElement) => HTMLElement | null) {\n this.exec(range => {\n const next = insertBlockAtCaret(this.root, this.collapsedAfterDelete(range), element);\n const inside = caretInside?.(element);\n return startCaret(inside ?? next);\n });\n }\n\n /** Inserts a horizontal rule. */\n insertHorizontalRule(): void {\n this.insertBlock(createElement('hr'));\n }\n\n /** Inserts a manual page break. */\n insertPageBreak(): void {\n this.insertBlock(createElement('div', { 'data-type': 'page-break', class: 'doc-page-break' }));\n }\n\n // -------------------------------------------------------------------------------------------------------------\n // Links\n\n /** Link that contains the start of the selection. */\n getActiveLink(): HTMLAnchorElement | null {\n const range = this.currentRange();\n return range ? linkAncestor(range.startContainer, this.root) : null;\n }\n\n /**\n * Links the selection, edits the link under a collapsed caret, or inserts a new linked text. Unsafe URLs are ignored.\n *\n * @param href Link address.\n * @param target `_blank` to open in a new tab, `null` for the same tab.\n * @param text Label inserted when nothing is selected; defaults to the URL.\n */\n setLink(href: string, target: string | null, text?: string): void {\n const url = sanitizeUrl(href);\n if (!url) return;\n this.exec(range => {\n const existing = linkAncestor(range.startContainer, this.root);\n if (range.collapsed && existing) range.selectNodeContents(existing);\n if (!range.collapsed) {\n setLink(this.root, range, url, target);\n return undefined;\n }\n const label = text || url;\n const caret = insertTextAtCaret(this.root, range, label);\n const linkRange = document.createRange();\n linkRange.setStart(caret.node, caret.offset - label.length);\n linkRange.setEnd(caret.node, caret.offset);\n setLink(this.root, linkRange, url, target);\n return { node: linkRange.endContainer, offset: linkRange.endOffset };\n });\n }\n\n /** Removes links from the selection, or the whole link under a collapsed caret. */\n unsetLink(): void {\n this.exec(range => {\n const existing = linkAncestor(range.startContainer, this.root);\n if (range.collapsed && existing) range.selectNodeContents(existing);\n unsetLink(this.root, range);\n });\n }\n\n // -------------------------------------------------------------------------------------------------------------\n // Images\n\n /** Inserts a centred image at the caret; unsafe sources are ignored. */\n insertImage(src: string, alt = ''): void {\n const url = sanitizeUrl(src, true);\n if (url) this.insertBlock(createElement('img', { src: url, alt, 'data-align': 'center' }));\n }\n\n /** Selects an image for the resize handles and image menu, or clears the selection with `null`. */\n selectImage(image: HTMLImageElement | null): void {\n if (this.selectedImageElement === image) return;\n this.selectedImageElement = image;\n this.emit('selection', undefined);\n }\n\n /** Changes image attributes as an undo step. */\n updateImage(image: HTMLImageElement, attributes: ImageAttributes): void {\n this.mutate(() => {\n for (const [name, value] of Object.entries(attributes)) {\n const attribute = name === 'align' ? 'data-align' : name;\n if (value === null || value === undefined || value === '') image.removeAttribute(attribute);\n else image.setAttribute(attribute, String(value));\n }\n });\n }\n\n /** Deletes an image and places the caret in the neighbouring text. */\n removeImage(image: HTMLImageElement): void {\n this.selectImage(null);\n this.exec(() => {\n const next = image.nextElementSibling;\n const previous = image.previousElementSibling;\n image.remove();\n if (isTextBlock(next)) return startCaret(next);\n return isTextBlock(previous) ? endCaret(previous) : undefined;\n });\n }\n\n // -------------------------------------------------------------------------------------------------------------\n // Tables\n\n /** Table cell that contains the start of the selection. */\n getActiveCell(): TableCell | null {\n const range = this.currentRange();\n return range ? (closestTag(range.startContainer, this.root, CELL_TAGS) as TableCell | null) : null;\n }\n\n /** Whether the selected cells form a rectangle that can be merged. */\n canMergeCells(): boolean {\n return canMergeRect(this.cellRect);\n }\n\n /** Whether the active cell spans several rows or columns. */\n canSplitCell(): boolean {\n return canSplitCell(this.getActiveCell());\n }\n\n /** Sets a column width in pixels as an undo step, e.g. after dragging a column border. */\n resizeColumn(table: HTMLTableElement, index: number, width: number): void {\n this.mutate(() => setColumnWidth(table, index, width));\n }\n\n /** Inserts a table and places the caret in its first cell. */\n insertTable(rows: number, cols: number, withHeaderRow: boolean): void {\n this.insertBlock(createTable(rows, cols, withHeaderRow), table => table.querySelector<HTMLElement>('p'));\n }\n\n /** Runs a table operation on the active cell or the selected cells. */\n tableCommand(command: TableCommand): void {\n const cell = this.getActiveCell() ?? (this.cellRect ? cellsInRect(this.cellRect)[0] : null);\n const table = cell?.closest('table');\n if (!cell || !table) return;\n const rect = this.cellRect;\n this.clearCellSelection();\n this.exec(() => {\n switch (command) {\n case 'addRowBefore':\n case 'addRowAfter':\n addRow(cell, command === 'addRowBefore' ? 'before' : 'after');\n return undefined;\n case 'addColumnBefore':\n case 'addColumnAfter':\n addColumn(cell, command === 'addColumnBefore' ? 'before' : 'after');\n return undefined;\n case 'deleteRow':\n deleteRow(cell);\n return undefined;\n case 'deleteColumn':\n deleteColumn(cell);\n return undefined;\n case 'mergeCells': {\n const merged = rect ? mergeCells(rect) : null;\n const block = merged?.querySelector<HTMLElement>(TEXT_BLOCK_QUERY);\n return block ? endCaret(block) : null;\n }\n case 'splitCell':\n if (!canSplitCell(cell)) return null;\n splitCell(cell);\n return undefined;\n case 'toggleHeaderRow':\n toggleHeaderRow(table);\n return undefined;\n default: {\n // deleteTable\n const next = table.nextElementSibling;\n table.remove();\n return isTextBlock(next) ? startCaret(next) : undefined;\n }\n }\n });\n }\n\n /** Ends a rectangular cell selection. */\n private clearCellSelection() {\n if (!this.cellRect) return;\n this.cellRect = null;\n markSelectedCells(this.root, []);\n this.root.classList.remove('has-cell-selection');\n this.emit('selection', undefined);\n }\n\n /** Empties every selected cell as one undo step. */\n private clearSelectedCells() {\n const rect = this.cellRect;\n if (!rect) return;\n this.mutate(() => {\n for (const cell of cellsInRect(rect)) cell.replaceChildren(createParagraph());\n });\n }\n\n // -------------------------------------------------------------------------------------------------------------\n // Find and replace\n\n /** Replaces the current search match as an undo step. */\n replaceMatch(replacement: string): void {\n this.mutate(() => {\n this.search.replaceCurrent(replacement);\n normalizeContainer(this.root, true);\n });\n }\n\n /** Replaces every search match as one undo step. */\n replaceAllMatches(replacement: string): void {\n this.mutate(() => {\n this.search.replaceAll(replacement);\n normalizeContainer(this.root, true);\n });\n }\n}\n","/** Built-in Uzbek texts, used for every key the registered translator does not translate. */\nconst MESSAGES = {\n 'editor.align': 'Tekislash',\n 'editor.align.center': 'Markazga',\n 'editor.align.justify': 'Ikki tomonga',\n 'editor.align.left': 'Chapga',\n 'editor.align.right': 'O‘ngga',\n 'editor.apply': 'Qo‘llash',\n 'editor.bold': 'Qalin',\n 'editor.bulletList': 'Belgili ro‘yxat',\n 'editor.clearFormatting': 'Formatni tozalash',\n 'editor.close': 'Yopish',\n 'editor.codeBlock': 'Kod bloki',\n 'editor.colors.automatic': 'Avtomatik',\n 'editor.colors.custom': 'Boshqa rang…',\n 'editor.colors.none': 'Rangsiz',\n 'editor.defaultFont': 'Standart shrift',\n 'editor.defaultLineHeight': 'Standart',\n 'editor.defaultSize': 'Standart o‘lcham',\n 'editor.delete': 'O‘chirish',\n 'editor.direction.auto': 'Avtomatik yo‘nalish',\n 'editor.direction.ltr': 'Chapdan o‘ngga',\n 'editor.direction.rtl': 'O‘ngdan chapga',\n 'editor.document': 'Hujjat',\n 'editor.editLink': 'Havolani tahrirlash',\n 'editor.enterFullscreen': 'To‘liq ekran',\n 'editor.exitFullscreen': 'To‘liq ekrandan chiqish',\n 'editor.exportHtml': 'HTML sifatida yuklab olish',\n 'editor.exportWord': 'Word (.doc) sifatida yuklab olish',\n 'editor.fitWidth': 'Kenglikka moslash',\n 'editor.fontFamily': 'Shrift',\n 'editor.fontSize': 'Shrift o‘lchami',\n 'editor.heading': 'Sarlavha {level}',\n 'editor.highlight': 'Belgilash rangi',\n 'editor.horizontalRule': 'Gorizontal chiziq',\n 'editor.image': 'Rasm',\n 'editor.imageAlt': 'Muqobil matn (alt)',\n 'editor.imageReadError': 'Rasmni o‘qib bo‘lmadi',\n 'editor.imageSizeError': 'Rasm hajmi {size} MB dan oshmasligi kerak',\n 'editor.imageTypeError': 'Faqat rasm fayllarini qo‘shish mumkin',\n 'editor.imageUploadError': 'Rasmni yuklab bo‘lmadi',\n 'editor.indent': 'Chekinishni oshirish',\n 'editor.inlineCode': 'Kod',\n 'editor.insert': 'Qo‘shish',\n 'editor.insertDate': 'Bugungi sana',\n 'editor.insertMore': 'Boshqa elementlar',\n 'editor.italic': 'Kursiv',\n 'editor.lineHeight': 'Qator oralig‘i',\n 'editor.link': 'Havola',\n 'editor.linkNewTab': 'Yangi oynada ochish',\n 'editor.linkText': 'Matn',\n 'editor.linkUrl': 'Manzil (URL)',\n 'editor.more': 'Yana',\n 'editor.moreFormatting': 'Boshqa formatlar',\n 'editor.or': 'yoki havola orqali',\n 'editor.orderedList': 'Raqamli ro‘yxat',\n 'editor.outdent': 'Chekinishni kamaytirish',\n 'editor.page.customMargins': 'Aniq qiymatlar (mm)',\n 'editor.page.landscape': 'Albom',\n 'editor.page.marginBottom': 'Pastki',\n 'editor.page.marginLeft': 'Chap',\n 'editor.page.marginRight': 'O‘ng',\n 'editor.page.marginTop': 'Yuqori',\n 'editor.page.margins': 'Hoshiyalar',\n 'editor.page.margins.moderate': 'O‘rtacha',\n 'editor.page.margins.narrow': 'Tor',\n 'editor.page.margins.normal': 'Oddiy',\n 'editor.page.margins.official': 'Rasmiy hujjat',\n 'editor.page.margins.wide': 'Keng',\n 'editor.page.orientation': 'Yo‘nalish',\n 'editor.page.portrait': 'Kitob',\n 'editor.page.setup': 'Sahifa sozlamalari',\n 'editor.page.size': 'Qog‘oz o‘lchami',\n 'editor.pageBreak': 'Sahifa uzilishi',\n 'editor.paragraph': 'Oddiy matn',\n 'editor.placeholder': 'Hujjat matnini kiriting…',\n 'editor.print': 'Chop etish / PDF',\n 'editor.quote': 'Iqtibos',\n 'editor.redo': 'Qaytarish',\n 'editor.replace.all': 'Barchasini',\n 'editor.replace.one': 'Almashtirish',\n 'editor.replace.placeholder': 'Almashtirish',\n 'editor.replace.toggle': 'Almashtirishni ko‘rsatish',\n 'editor.search.caseSensitive': 'Katta-kichik harfni farqlash',\n 'editor.search.next': 'Keyingisi',\n 'editor.search.placeholder': 'Qidirish',\n 'editor.search.previous': 'Oldingisi',\n 'editor.search.title': 'Qidirish va almashtirish',\n 'editor.search.wholeWord': 'Butun so‘z',\n 'editor.source': 'HTML kod',\n 'editor.specialCharacters': 'Maxsus belgilar',\n 'editor.status.characters': '{count} belgi',\n 'editor.status.page': 'Sahifa {current} / {total}',\n 'editor.status.words': '{count} so‘z',\n 'editor.strike': 'Ustidan chizilgan',\n 'editor.subscript': 'Pastki indeks',\n 'editor.superscript': 'Yuqori indeks',\n 'editor.table.addColumnAfter': 'O‘ngga ustun qo‘shish',\n 'editor.table.addColumnBefore': 'Chapga ustun qo‘shish',\n 'editor.table.addRowAfter': 'Pastga qator qo‘shish',\n 'editor.table.addRowBefore': 'Yuqoriga qator qo‘shish',\n 'editor.table.delete': 'Jadvalni o‘chirish',\n 'editor.table.deleteColumn': 'Ustunni o‘chirish',\n 'editor.table.deleteRow': 'Qatorni o‘chirish',\n 'editor.table.headerRow': 'Sarlavha qatori',\n 'editor.table.insert': 'Jadval qo‘shish',\n 'editor.table.mergeCells': 'Kataklarni birlashtirish',\n 'editor.table.pickSize': 'O‘lchamni tanlang',\n 'editor.table.splitCell': 'Katakni ajratish',\n 'editor.table.title': 'Jadval',\n 'editor.taskList': 'Vazifalar ro‘yxati',\n 'editor.textColor': 'Matn rangi',\n 'editor.textDirection': 'Matn yo‘nalishi',\n 'editor.textStyle': 'Matn uslubi',\n 'editor.toolbar': 'Muharrir asboblari',\n 'editor.underline': 'Tagiga chizilgan',\n 'editor.undo': 'Bekor qilish',\n 'editor.unlink': 'Havolani olib tashlash',\n 'editor.uploadImage': 'Kompyuterdan yuklash',\n 'editor.uploading': 'Rasm yuklanmoqda…',\n 'editor.view.page': 'Sahifa ko‘rinishi',\n 'editor.view.web': 'Veb ko‘rinish',\n 'editor.zoom': 'Masshtab',\n 'editor.zoomIn': 'Kattalashtirish',\n 'editor.zoomOut': 'Kichraytirish',\n 'editor.zoomReset': '100% ga qaytarish'\n} as const;\n\n/** Every label key the editor uses. */\nexport type EditorLabelKey = keyof typeof MESSAGES;\n\n/** Built-in label texts by key; `{name}` marks a placeholder filled in at translation time. */\nexport const editorMessages: Readonly<Record<EditorLabelKey, string>> = MESSAGES;\n\n/**\n * Translates an editor label into the app's language; returning `undefined` keeps the built-in text.\n *\n * @param named Values for `{name}` placeholders in the message.\n */\nexport type EditorTranslator = (key: EditorLabelKey, named?: Record<string, unknown>) => string | undefined;\n\n/** Translator registered by the app, if any. */\nlet translator: EditorTranslator | undefined;\n\n/** Registers the translator used for every editor label; calling it without arguments restores the built-in texts. */\nexport const setEditorTranslator = (next?: EditorTranslator): void => {\n translator = next;\n};\n\n/** Whether shortcuts should be shown with Apple modifier symbols. */\nconst IS_MAC = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.userAgent);\n/** `{name}` placeholders inside built-in messages. */\nconst PLACEHOLDER_PATTERN = /\\{(\\w+)\\}/g;\n\n/**\n * Translates an editor label, falling back to the built-in Uzbek text while the key has no translation.\n *\n * @param named Values for `{name}` placeholders in the message.\n */\nexport const t = (key: EditorLabelKey, named?: Record<string, unknown>): string => {\n const translated = translator?.(key, named);\n if (translated !== undefined) return translated;\n const message: string = MESSAGES[key];\n return named ? message.replace(PLACEHOLDER_PATTERN, (_, name: string) => String(named[name] ?? '')) : message;\n};\n\n/** Formats a shortcut written as `Mod+Shift+K` for the current platform (`Ctrl+Shift+K` or `⌘⇧K`). */\nexport const formatShortcut = (shortcut: string): string =>\n IS_MAC\n ? shortcut\n .replace(/Mod\\+/g, '⌘')\n .replace(/Shift\\+/g, '⇧')\n .replace(/Alt\\+/g, '⌥')\n : shortcut.replace(/Mod/g, 'Ctrl');\n\n/** Label followed by its keyboard shortcut in parentheses, for button tooltips. */\nexport const withShortcut = (key: EditorLabelKey, shortcut: string): string =>\n `${t(key)} (${formatShortcut(shortcut)})`;\n","import { GAP_ATTRIBUTE } from './engine/schema';\nimport type { PageMetrics } from './page';\n\n/** Handle returned by {@link createPagination}. */\nexport interface PaginationController {\n /** Sets the page geometry and re-runs the layout; pass `null` to switch pagination off (web view). */\n setMetrics: (metrics: PageMetrics | null) => void;\n /** Requests a layout pass on the next animation frame, e.g. after the document changed. */\n schedule: () => void;\n /** Stops observing the document and cancels a pending layout pass. */\n destroy: () => void;\n}\n\n/** Callbacks the pagination needs from its host. */\ninterface PaginationOptions {\n /** Receives the number of sheets whenever it changes. */\n onPageCount: (count: number) => void;\n /** Whether an IME composition is in progress, during which blocks must not move. */\n isComposing: () => boolean;\n}\n\n/** Rounding tolerance so a block sitting exactly on the page top is never pushed again. */\nconst EPSILON = 1;\n/** Page count reported when pagination is off. */\nconst SINGLE_PAGE = 1;\n\n/** Top margin that pagination previously applied to a block, in pixels. */\nconst readGap = (element: HTMLElement) => Number.parseFloat(element.getAttribute(GAP_ATTRIBUTE) ?? '') || 0;\n\n/** Applies or removes the top margin that moves a block to the next sheet, touching the DOM only on change. */\nconst writeGap = (element: HTMLElement, gap: number) => {\n const rounded = Math.max(0, Math.round(gap));\n if (rounded === readGap(element)) return;\n if (rounded) {\n element.setAttribute(GAP_ATTRIBUTE, String(rounded));\n element.style.marginTop = `${rounded}px`;\n return;\n }\n element.removeAttribute(GAP_ATTRIBUTE);\n element.style.removeProperty('margin-top');\n if (!element.getAttribute('style')) element.removeAttribute('style');\n};\n\n/** Computed bottom margin of a block in pixels, `0` when there is no block. */\nconst marginBottom = (element: HTMLElement | undefined) =>\n element ? Number.parseFloat(getComputedStyle(element).marginBottom) || 0 : 0;\n\n/**\n * Lays top-level blocks out on sheets: a block that would cross the bottom margin, or follows a page break,\n * gets a top margin that moves it to the next sheet. Blocks taller than a page are not split.\n * All layout reads happen before the writes, so a pass costs a single reflow.\n *\n * @returns Number of sheets the document needs.\n */\nconst paginate = (root: HTMLElement, page: PageMetrics): number => {\n const period = page.height + page.gap;\n const contentStart = (index: number) => Math.round(index * period + page.marginTop);\n const contentEnd = (index: number) => Math.round(index * period + page.height - page.marginBottom);\n const children = Array.from(root.children) as HTMLElement[];\n\n let appliedShift = 0;\n const measured = children.map((element, index) => {\n const gap = readGap(element);\n if (gap) {\n // A top margin collapses with the previous block's bottom margin; only the excess moved the block.\n const previousMargin = index > 0 ? marginBottom(children[index - 1]) : 0;\n appliedShift += index > 0 ? Math.max(gap, previousMargin) - previousMargin : gap;\n }\n return { element, top: element.offsetTop - appliedShift, height: element.offsetHeight };\n });\n\n const gaps: number[] = [];\n let added = 0;\n let pageIndex = 0;\n let breakBefore = false;\n measured.forEach(({ element, top: naturalTop, height }, index) => {\n let top = naturalTop + added;\n if (top > contentStart(pageIndex) + EPSILON && (breakBefore || top + height > contentEnd(pageIndex) + EPSILON)) {\n pageIndex += 1;\n }\n const start = contentStart(pageIndex);\n let gap = 0;\n if (top < start - EPSILON) {\n const shift = start - top;\n gap = index > 0 ? shift + marginBottom(children[index - 1]) : shift;\n added += shift;\n top = start;\n }\n gaps.push(gap);\n while (top + height > contentEnd(pageIndex) + EPSILON) pageIndex += 1;\n breakBefore = element.getAttribute('data-type') === 'page-break';\n });\n\n children.forEach((element, index) => {\n writeGap(element, gaps[index] ?? 0);\n });\n return pageIndex + 1;\n};\n\n/** Removes all page gaps, used when switching to the continuous web view. */\nconst clearGaps = (root: HTMLElement) => {\n for (const element of Array.from(root.querySelectorAll<HTMLElement>(`:scope > [${GAP_ATTRIBUTE}]`))) {\n writeGap(element, 0);\n }\n return SINGLE_PAGE;\n};\n\n/**\n * Keeps the document laid out on sheets. Layout passes are batched to one per animation frame and re-run when the\n * document resizes, images or fonts load, or the host schedules one after an edit.\n */\nexport const createPagination = (root: HTMLElement, options: PaginationOptions): PaginationController => {\n let metrics: PageMetrics | null = null;\n let frame = 0;\n let pageCount = SINGLE_PAGE;\n\n /** Runs one layout pass and reports a changed page count. */\n const measure = () => {\n frame = 0;\n // Moving blocks mid-composition disturbs IME and mobile keyboards; the engine re-schedules afterwards.\n if (!root.isConnected || options.isComposing()) return;\n const count = metrics ? paginate(root, metrics) : clearGaps(root);\n if (count !== pageCount) {\n pageCount = count;\n options.onPageCount(count);\n }\n };\n\n /** Requests a layout pass unless one is already waiting for the next frame. */\n const schedule = () => {\n if (!frame) frame = requestAnimationFrame(measure);\n };\n\n const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(schedule);\n observer?.observe(root);\n // Images and web fonts change block heights without any edit.\n root.addEventListener('load', schedule, true);\n void document.fonts?.ready.then(schedule);\n\n return {\n setMetrics: next => {\n metrics = next;\n schedule();\n },\n schedule,\n destroy: () => {\n observer?.disconnect();\n root.removeEventListener('load', schedule, true);\n if (frame) cancelAnimationFrame(frame);\n frame = 0;\n }\n };\n};\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';\nimport {\n BetweenHorizontalEnd,\n BetweenHorizontalStart,\n BetweenVerticalEnd,\n BetweenVerticalStart,\n Columns2,\n ExternalLink,\n Grid2x2X,\n PanelTop,\n Pencil,\n Rows2,\n TableCellsMerge,\n TableCellsSplit,\n TextAlignCenter,\n TextAlignEnd,\n TextAlignStart,\n TextCursorInput,\n Trash2,\n Unlink\n} from '@lucide/vue';\nimport { ElButton, ElInput, type InputInstance, useZIndex } from 'element-plus';\n\nimport type { DocumentEngine, TableCommand } from '../core/engine/engine';\nimport { t } from '../core/labels';\n\n/**\n * Floating context toolbars: link actions under a link, image alignment and alt text above a selected image, and\n * row/column/cell actions above the active table. Rendered in `body` and positioned against the canvas viewport.\n */\ndefineOptions({ name: 'EditorBubbleMenus' });\n\ninterface Props {\n /** Engine whose selection decides which menu is shown. */\n engine: DocumentEngine;\n /** Canvas scroll container; menus follow its scrolling and hide when their target leaves it. */\n scrollTarget: HTMLElement;\n}\n\nconst props = defineProps<Props>();\n\ninterface Emits {\n /** The user asked to edit the link under the caret; the toolbar opens its link form. */\n editLink: [];\n}\n\nconst emit = defineEmits<Emits>();\n\ntype MenuKind = 'link' | 'image' | 'table';\ntype ImageAlign = 'left' | 'center' | 'right';\n\n/** Menu to show and the element it is attached to. */\ninterface ResolvedMenu {\n kind: MenuKind;\n /** Element the menu is positioned against. */\n reference: HTMLElement;\n /** Side of the reference the menu prefers. */\n placement: 'above' | 'below';\n}\n\nconst IMAGE_ALIGNMENTS = [\n { value: 'left', icon: TextAlignStart, label: 'editor.align.left' },\n { value: 'center', icon: TextAlignCenter, label: 'editor.align.center' },\n { value: 'right', icon: TextAlignEnd, label: 'editor.align.right' }\n] as const;\n\n/** Table actions in visual groups; `danger` actions are highlighted in red on hover. */\nconst TABLE_GROUPS = [\n [\n { command: 'addRowBefore', icon: BetweenHorizontalStart, label: 'editor.table.addRowBefore' },\n { command: 'addRowAfter', icon: BetweenHorizontalEnd, label: 'editor.table.addRowAfter' },\n { command: 'deleteRow', icon: Rows2, label: 'editor.table.deleteRow', danger: true }\n ],\n [\n { command: 'addColumnBefore', icon: BetweenVerticalStart, label: 'editor.table.addColumnBefore' },\n { command: 'addColumnAfter', icon: BetweenVerticalEnd, label: 'editor.table.addColumnAfter' },\n { command: 'deleteColumn', icon: Columns2, label: 'editor.table.deleteColumn', danger: true }\n ],\n [\n { command: 'mergeCells', icon: TableCellsMerge, label: 'editor.table.mergeCells' },\n { command: 'splitCell', icon: TableCellsSplit, label: 'editor.table.splitCell' },\n { command: 'toggleHeaderRow', icon: PanelTop, label: 'editor.table.headerRow' }\n ],\n [{ command: 'deleteTable', icon: Grid2x2X, label: 'editor.table.delete', danger: true }]\n] as const;\n\n/** Distance between a menu and its reference or the canvas edges, in pixels. */\nconst MENU_GAP = 8;\n\n/** Kind of the visible menu, or `null` when none is shown. */\nconst kind = ref<MenuKind | null>(null);\n/** Viewport coordinates of the menu. */\nconst position = ref({ left: 0, top: 0 });\n/** Whether the reference has scrolled out of the canvas. */\nconst hidden = ref(false);\nconst menuRef = ref<HTMLElement>();\nconst altInput = ref<InputInstance>();\nconst linkHref = ref('');\nconst imageAlign = ref<ImageAlign>('center');\nconst imageAlt = ref('');\n/** Whether the image menu shows the alt text form instead of its buttons. */\nconst altEditing = ref(false);\nconst canMerge = ref(false);\nconst canSplit = ref(false);\n/** Stacking order taken from Element Plus whenever a menu appears. */\nconst zIndex = ref<number>();\n\nconst { nextZIndex } = useZIndex();\n\n/** The menu currently shown with its reference element. */\nlet current: ResolvedMenu | null = null;\nlet updateFrame = 0;\n/** Cleanup callbacks for DOM listeners and engine subscriptions. */\nconst disposers: Array<() => void> = [];\n\n/** Accessible name of the visible menu. */\nconst menuLabel = computed(() => {\n if (kind.value === 'link') return t('editor.link');\n if (kind.value === 'image') return t('editor.image');\n return t('editor.table.title');\n});\n\n/** Whether keyboard focus is inside the editable document. */\nconst isEditorFocused = () => document.activeElement === props.engine.root;\n\n/** Decides which menu the selection calls for: a selected image, a link under a collapsed caret, or a table. */\nconst resolveMenu = (): ResolvedMenu | null => {\n const { engine } = props;\n if (!engine.isEditable) return null;\n const image = engine.selectedImage;\n if (image) return { kind: 'image', reference: image, placement: 'above' };\n if (!isEditorFocused() && !engine.hasCellSelection) return null;\n const link = engine.getActiveLink();\n if (link && (document.getSelection()?.isCollapsed ?? true)) {\n return { kind: 'link', reference: link, placement: 'below' };\n }\n const table = engine.getActiveCell()?.closest('table');\n return table ? { kind: 'table', reference: table, placement: 'above' } : null;\n};\n\n/** Positions the menu next to its reference, kept inside the canvas and flipped when there is no room. */\nconst positionMenu = () => {\n const menu = menuRef.value;\n const reference = current?.reference;\n if (!menu || !reference?.isConnected) return;\n const anchor = reference.getBoundingClientRect();\n const bounds = props.scrollTarget.getBoundingClientRect();\n hidden.value = anchor.bottom < bounds.top || anchor.top > bounds.bottom;\n const { offsetWidth: width, offsetHeight: height } = menu;\n const below = current?.placement === 'below';\n let top = below ? anchor.bottom + MENU_GAP : anchor.top - height - MENU_GAP;\n if (below && top + height > bounds.bottom - MENU_GAP) top = anchor.top - height - MENU_GAP;\n // A menu above a tall table stays visible at the top of the canvas while the table is on screen.\n if (!below && top < bounds.top + MENU_GAP) top = bounds.top + MENU_GAP;\n const preferredLeft = below ? anchor.left : anchor.left + anchor.width / 2 - width / 2;\n const left = Math.min(\n Math.max(preferredLeft, bounds.left + MENU_GAP),\n Math.max(bounds.left + MENU_GAP, bounds.right - width - MENU_GAP)\n );\n position.value = { left, top };\n};\n\n/** Copies the values a menu displays from its reference element and the engine. */\nconst refreshValues = ({ kind: menu, reference }: ResolvedMenu) => {\n if (menu === 'link') linkHref.value = reference.getAttribute('href') ?? '';\n if (menu === 'image') {\n const align = reference.getAttribute('data-align');\n imageAlign.value = align === 'left' || align === 'right' ? align : 'center';\n if (!altEditing.value) imageAlt.value = reference.getAttribute('alt') ?? '';\n }\n if (menu === 'table') {\n canMerge.value = props.engine.canMergeCells();\n canSplit.value = props.engine.canSplitCell();\n }\n};\n\n/** Re-evaluates which menu to show, refreshes its values and repositions it. */\nconst update = () => {\n updateFrame = 0;\n // While the user types into the menu itself (alt text), keep it open where it is.\n if (menuRef.value?.contains(document.activeElement)) {\n positionMenu();\n return;\n }\n const resolved = resolveMenu();\n current = resolved;\n if (resolved?.kind !== 'image') altEditing.value = false;\n // A fresh layer whenever a menu appears keeps it above dialogs and the fullscreen editor.\n if (resolved && !kind.value) zIndex.value = nextZIndex();\n kind.value = resolved?.kind ?? null;\n if (!resolved) return;\n refreshValues(resolved);\n void nextTick(positionMenu);\n};\n\n/** Coalesces menu updates into one per animation frame. */\nconst scheduleUpdate = () => {\n if (!updateFrame) updateFrame = requestAnimationFrame(update);\n};\n\n/** Keeps the menu attached to its reference while the canvas scrolls. */\nconst onCanvasScroll = () => requestAnimationFrame(positionMenu);\n\n/** Runs a table action and refreshes the menu, whose table may have changed. */\nconst runTableCommand = (command: TableCommand) => {\n props.engine.tableCommand(command);\n scheduleUpdate();\n};\n\n/** Aligns the selected image. */\nconst setImageAlign = (align: ImageAlign) => {\n const image = props.engine.selectedImage;\n if (image) props.engine.updateImage(image, { align });\n scheduleUpdate();\n};\n\n/** Shows the alt text form and focuses its input. */\nconst startAltEditing = async () => {\n altEditing.value = true;\n await nextTick();\n altInput.value?.focus();\n};\n\n/** Saves the alt text of the image the menu belongs to and returns focus to the document. */\nconst saveAlt = () => {\n const image = current?.kind === 'image' ? (current.reference as HTMLImageElement) : null;\n altEditing.value = false;\n if (image) props.engine.updateImage(image, { alt: imageAlt.value.trim() || null });\n props.engine.root.focus({ preventScroll: true });\n scheduleUpdate();\n};\n\n/** Deletes the selected image. */\nconst removeImage = () => {\n const image = props.engine.selectedImage;\n if (image) props.engine.removeImage(image);\n};\n\n/** Merge needs a multi-cell selection and split a merged cell. */\nconst isTableCommandDisabled = (command: TableCommand) =>\n (command === 'mergeCells' && !canMerge.value) || (command === 'splitCell' && !canSplit.value);\n\n/** Follows engine changes, canvas scrolling, window resizing and focus moves. */\nonMounted(() => {\n props.scrollTarget.addEventListener('scroll', onCanvasScroll, { passive: true });\n window.addEventListener('resize', scheduleUpdate);\n document.addEventListener('focusin', scheduleUpdate);\n document.addEventListener('focusout', scheduleUpdate);\n disposers.push(\n props.engine.on('selection', scheduleUpdate),\n props.engine.on('update', scheduleUpdate),\n props.engine.on('blur', scheduleUpdate),\n () => props.scrollTarget.removeEventListener('scroll', onCanvasScroll),\n () => window.removeEventListener('resize', scheduleUpdate),\n () => document.removeEventListener('focusin', scheduleUpdate),\n () => document.removeEventListener('focusout', scheduleUpdate)\n );\n scheduleUpdate();\n});\n\n/** Removes every listener and pending update. */\nonBeforeUnmount(() => {\n for (const dispose of disposers) dispose();\n cancelAnimationFrame(updateFrame);\n});\n</script>\n\n<template>\n <Teleport to=\"body\">\n <div\n v-if=\"kind\"\n ref=\"menuRef\"\n class=\"doc-bubble-host doc-bubble\"\n role=\"toolbar\"\n :class=\"{ 'is-hidden': hidden }\"\n :aria-label=\"menuLabel\"\n :style=\"{ left: `${position.left}px`, top: `${position.top}px`, zIndex }\"\n >\n <template v-if=\"kind === 'link'\">\n <a class=\"doc-bubble__link\" target=\"_blank\" rel=\"noopener noreferrer\" :href=\"linkHref\" :title=\"linkHref\">\n <ExternalLink :size=\"13\" />\n <span>{{ linkHref }}</span>\n </a>\n <span class=\"doc-bubble__divider\" />\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.editLink')\"\n :title=\"t('editor.editLink')\"\n @mousedown.prevent\n @click=\"emit('editLink')\"\n >\n <Pencil :size=\"15\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.unlink')\"\n :title=\"t('editor.unlink')\"\n @mousedown.prevent\n @click=\"engine.unsetLink()\"\n >\n <Unlink :size=\"15\" />\n </button>\n </template>\n\n <template v-else-if=\"kind === 'image'\">\n <form v-if=\"altEditing\" class=\"doc-bubble__form\" @submit.prevent=\"saveAlt\">\n <el-input\n ref=\"altInput\"\n v-model=\"imageAlt\"\n size=\"small\"\n :ariaLabel=\"t('editor.imageAlt')\"\n :placeholder=\"t('editor.imageAlt')\"\n />\n <el-button native-type=\"submit\" size=\"small\" type=\"primary\">{{ t('editor.apply') }}</el-button>\n </form>\n <template v-else>\n <button\n v-for=\"alignment in IMAGE_ALIGNMENTS\"\n :key=\"alignment.value\"\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': imageAlign === alignment.value }\"\n :aria-label=\"t(alignment.label)\"\n :title=\"t(alignment.label)\"\n @mousedown.prevent\n @click=\"setImageAlign(alignment.value)\"\n >\n <component :is=\"alignment.icon\" :size=\"15\" />\n </button>\n <span class=\"doc-bubble__divider\" />\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.imageAlt')\"\n :title=\"t('editor.imageAlt')\"\n @mousedown.prevent\n @click=\"startAltEditing\"\n >\n <TextCursorInput :size=\"15\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button is-danger\"\n :aria-label=\"t('editor.delete')\"\n :title=\"t('editor.delete')\"\n @mousedown.prevent\n @click=\"removeImage\"\n >\n <Trash2 :size=\"15\" />\n </button>\n </template>\n </template>\n\n <template v-else>\n <template v-for=\"(group, index) in TABLE_GROUPS\" :key=\"index\">\n <span v-if=\"index\" class=\"doc-bubble__divider\" />\n <button\n v-for=\"action in group\"\n :key=\"action.command\"\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-danger': 'danger' in action }\"\n :aria-label=\"t(action.label)\"\n :disabled=\"isTableCommandDisabled(action.command)\"\n :title=\"t(action.label)\"\n @mousedown.prevent\n @click=\"runTableCommand(action.command)\"\n >\n <component :is=\"action.icon\" :size=\"15\" />\n </button>\n </template>\n </template>\n </div>\n </Teleport>\n</template>\n\n<style scoped>\n/* Menu surface */\n.doc-bubble {\n position: fixed;\n display: flex;\n align-items: center;\n gap: 1px;\n padding: 3px;\n border: 1px solid var(--el-border-color-light);\n border-radius: 8px;\n background: var(--el-bg-color-overlay);\n box-shadow: 0 6px 20px rgb(16 24 40 / 16%);\n}\n\n.doc-bubble.is-hidden {\n visibility: hidden;\n}\n\n.doc-bubble__divider {\n flex: 0 0 1px;\n align-self: stretch;\n margin: 3px 4px;\n background: var(--el-border-color-lighter);\n}\n\n.doc-bubble .doc-tb-button.is-danger:hover:not(:disabled) {\n color: var(--el-color-danger);\n background: var(--el-color-danger-light-9);\n}\n\n/* Link menu */\n.doc-bubble__link {\n display: inline-flex;\n max-width: 260px;\n align-items: center;\n gap: 5px;\n padding: 0 6px;\n color: var(--el-color-primary);\n font-size: 12px;\n text-decoration: none;\n}\n\n.doc-bubble__link span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Image alt text form */\n.doc-bubble__form {\n display: flex;\n width: 280px;\n gap: 4px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';\nimport {\n BetweenHorizontalEnd,\n BetweenHorizontalStart,\n BetweenVerticalEnd,\n BetweenVerticalStart,\n Columns2,\n ExternalLink,\n Grid2x2X,\n PanelTop,\n Pencil,\n Rows2,\n TableCellsMerge,\n TableCellsSplit,\n TextAlignCenter,\n TextAlignEnd,\n TextAlignStart,\n TextCursorInput,\n Trash2,\n Unlink\n} from '@lucide/vue';\nimport { ElButton, ElInput, type InputInstance, useZIndex } from 'element-plus';\n\nimport type { DocumentEngine, TableCommand } from '../core/engine/engine';\nimport { t } from '../core/labels';\n\n/**\n * Floating context toolbars: link actions under a link, image alignment and alt text above a selected image, and\n * row/column/cell actions above the active table. Rendered in `body` and positioned against the canvas viewport.\n */\ndefineOptions({ name: 'EditorBubbleMenus' });\n\ninterface Props {\n /** Engine whose selection decides which menu is shown. */\n engine: DocumentEngine;\n /** Canvas scroll container; menus follow its scrolling and hide when their target leaves it. */\n scrollTarget: HTMLElement;\n}\n\nconst props = defineProps<Props>();\n\ninterface Emits {\n /** The user asked to edit the link under the caret; the toolbar opens its link form. */\n editLink: [];\n}\n\nconst emit = defineEmits<Emits>();\n\ntype MenuKind = 'link' | 'image' | 'table';\ntype ImageAlign = 'left' | 'center' | 'right';\n\n/** Menu to show and the element it is attached to. */\ninterface ResolvedMenu {\n kind: MenuKind;\n /** Element the menu is positioned against. */\n reference: HTMLElement;\n /** Side of the reference the menu prefers. */\n placement: 'above' | 'below';\n}\n\nconst IMAGE_ALIGNMENTS = [\n { value: 'left', icon: TextAlignStart, label: 'editor.align.left' },\n { value: 'center', icon: TextAlignCenter, label: 'editor.align.center' },\n { value: 'right', icon: TextAlignEnd, label: 'editor.align.right' }\n] as const;\n\n/** Table actions in visual groups; `danger` actions are highlighted in red on hover. */\nconst TABLE_GROUPS = [\n [\n { command: 'addRowBefore', icon: BetweenHorizontalStart, label: 'editor.table.addRowBefore' },\n { command: 'addRowAfter', icon: BetweenHorizontalEnd, label: 'editor.table.addRowAfter' },\n { command: 'deleteRow', icon: Rows2, label: 'editor.table.deleteRow', danger: true }\n ],\n [\n { command: 'addColumnBefore', icon: BetweenVerticalStart, label: 'editor.table.addColumnBefore' },\n { command: 'addColumnAfter', icon: BetweenVerticalEnd, label: 'editor.table.addColumnAfter' },\n { command: 'deleteColumn', icon: Columns2, label: 'editor.table.deleteColumn', danger: true }\n ],\n [\n { command: 'mergeCells', icon: TableCellsMerge, label: 'editor.table.mergeCells' },\n { command: 'splitCell', icon: TableCellsSplit, label: 'editor.table.splitCell' },\n { command: 'toggleHeaderRow', icon: PanelTop, label: 'editor.table.headerRow' }\n ],\n [{ command: 'deleteTable', icon: Grid2x2X, label: 'editor.table.delete', danger: true }]\n] as const;\n\n/** Distance between a menu and its reference or the canvas edges, in pixels. */\nconst MENU_GAP = 8;\n\n/** Kind of the visible menu, or `null` when none is shown. */\nconst kind = ref<MenuKind | null>(null);\n/** Viewport coordinates of the menu. */\nconst position = ref({ left: 0, top: 0 });\n/** Whether the reference has scrolled out of the canvas. */\nconst hidden = ref(false);\nconst menuRef = ref<HTMLElement>();\nconst altInput = ref<InputInstance>();\nconst linkHref = ref('');\nconst imageAlign = ref<ImageAlign>('center');\nconst imageAlt = ref('');\n/** Whether the image menu shows the alt text form instead of its buttons. */\nconst altEditing = ref(false);\nconst canMerge = ref(false);\nconst canSplit = ref(false);\n/** Stacking order taken from Element Plus whenever a menu appears. */\nconst zIndex = ref<number>();\n\nconst { nextZIndex } = useZIndex();\n\n/** The menu currently shown with its reference element. */\nlet current: ResolvedMenu | null = null;\nlet updateFrame = 0;\n/** Cleanup callbacks for DOM listeners and engine subscriptions. */\nconst disposers: Array<() => void> = [];\n\n/** Accessible name of the visible menu. */\nconst menuLabel = computed(() => {\n if (kind.value === 'link') return t('editor.link');\n if (kind.value === 'image') return t('editor.image');\n return t('editor.table.title');\n});\n\n/** Whether keyboard focus is inside the editable document. */\nconst isEditorFocused = () => document.activeElement === props.engine.root;\n\n/** Decides which menu the selection calls for: a selected image, a link under a collapsed caret, or a table. */\nconst resolveMenu = (): ResolvedMenu | null => {\n const { engine } = props;\n if (!engine.isEditable) return null;\n const image = engine.selectedImage;\n if (image) return { kind: 'image', reference: image, placement: 'above' };\n if (!isEditorFocused() && !engine.hasCellSelection) return null;\n const link = engine.getActiveLink();\n if (link && (document.getSelection()?.isCollapsed ?? true)) {\n return { kind: 'link', reference: link, placement: 'below' };\n }\n const table = engine.getActiveCell()?.closest('table');\n return table ? { kind: 'table', reference: table, placement: 'above' } : null;\n};\n\n/** Positions the menu next to its reference, kept inside the canvas and flipped when there is no room. */\nconst positionMenu = () => {\n const menu = menuRef.value;\n const reference = current?.reference;\n if (!menu || !reference?.isConnected) return;\n const anchor = reference.getBoundingClientRect();\n const bounds = props.scrollTarget.getBoundingClientRect();\n hidden.value = anchor.bottom < bounds.top || anchor.top > bounds.bottom;\n const { offsetWidth: width, offsetHeight: height } = menu;\n const below = current?.placement === 'below';\n let top = below ? anchor.bottom + MENU_GAP : anchor.top - height - MENU_GAP;\n if (below && top + height > bounds.bottom - MENU_GAP) top = anchor.top - height - MENU_GAP;\n // A menu above a tall table stays visible at the top of the canvas while the table is on screen.\n if (!below && top < bounds.top + MENU_GAP) top = bounds.top + MENU_GAP;\n const preferredLeft = below ? anchor.left : anchor.left + anchor.width / 2 - width / 2;\n const left = Math.min(\n Math.max(preferredLeft, bounds.left + MENU_GAP),\n Math.max(bounds.left + MENU_GAP, bounds.right - width - MENU_GAP)\n );\n position.value = { left, top };\n};\n\n/** Copies the values a menu displays from its reference element and the engine. */\nconst refreshValues = ({ kind: menu, reference }: ResolvedMenu) => {\n if (menu === 'link') linkHref.value = reference.getAttribute('href') ?? '';\n if (menu === 'image') {\n const align = reference.getAttribute('data-align');\n imageAlign.value = align === 'left' || align === 'right' ? align : 'center';\n if (!altEditing.value) imageAlt.value = reference.getAttribute('alt') ?? '';\n }\n if (menu === 'table') {\n canMerge.value = props.engine.canMergeCells();\n canSplit.value = props.engine.canSplitCell();\n }\n};\n\n/** Re-evaluates which menu to show, refreshes its values and repositions it. */\nconst update = () => {\n updateFrame = 0;\n // While the user types into the menu itself (alt text), keep it open where it is.\n if (menuRef.value?.contains(document.activeElement)) {\n positionMenu();\n return;\n }\n const resolved = resolveMenu();\n current = resolved;\n if (resolved?.kind !== 'image') altEditing.value = false;\n // A fresh layer whenever a menu appears keeps it above dialogs and the fullscreen editor.\n if (resolved && !kind.value) zIndex.value = nextZIndex();\n kind.value = resolved?.kind ?? null;\n if (!resolved) return;\n refreshValues(resolved);\n void nextTick(positionMenu);\n};\n\n/** Coalesces menu updates into one per animation frame. */\nconst scheduleUpdate = () => {\n if (!updateFrame) updateFrame = requestAnimationFrame(update);\n};\n\n/** Keeps the menu attached to its reference while the canvas scrolls. */\nconst onCanvasScroll = () => requestAnimationFrame(positionMenu);\n\n/** Runs a table action and refreshes the menu, whose table may have changed. */\nconst runTableCommand = (command: TableCommand) => {\n props.engine.tableCommand(command);\n scheduleUpdate();\n};\n\n/** Aligns the selected image. */\nconst setImageAlign = (align: ImageAlign) => {\n const image = props.engine.selectedImage;\n if (image) props.engine.updateImage(image, { align });\n scheduleUpdate();\n};\n\n/** Shows the alt text form and focuses its input. */\nconst startAltEditing = async () => {\n altEditing.value = true;\n await nextTick();\n altInput.value?.focus();\n};\n\n/** Saves the alt text of the image the menu belongs to and returns focus to the document. */\nconst saveAlt = () => {\n const image = current?.kind === 'image' ? (current.reference as HTMLImageElement) : null;\n altEditing.value = false;\n if (image) props.engine.updateImage(image, { alt: imageAlt.value.trim() || null });\n props.engine.root.focus({ preventScroll: true });\n scheduleUpdate();\n};\n\n/** Deletes the selected image. */\nconst removeImage = () => {\n const image = props.engine.selectedImage;\n if (image) props.engine.removeImage(image);\n};\n\n/** Merge needs a multi-cell selection and split a merged cell. */\nconst isTableCommandDisabled = (command: TableCommand) =>\n (command === 'mergeCells' && !canMerge.value) || (command === 'splitCell' && !canSplit.value);\n\n/** Follows engine changes, canvas scrolling, window resizing and focus moves. */\nonMounted(() => {\n props.scrollTarget.addEventListener('scroll', onCanvasScroll, { passive: true });\n window.addEventListener('resize', scheduleUpdate);\n document.addEventListener('focusin', scheduleUpdate);\n document.addEventListener('focusout', scheduleUpdate);\n disposers.push(\n props.engine.on('selection', scheduleUpdate),\n props.engine.on('update', scheduleUpdate),\n props.engine.on('blur', scheduleUpdate),\n () => props.scrollTarget.removeEventListener('scroll', onCanvasScroll),\n () => window.removeEventListener('resize', scheduleUpdate),\n () => document.removeEventListener('focusin', scheduleUpdate),\n () => document.removeEventListener('focusout', scheduleUpdate)\n );\n scheduleUpdate();\n});\n\n/** Removes every listener and pending update. */\nonBeforeUnmount(() => {\n for (const dispose of disposers) dispose();\n cancelAnimationFrame(updateFrame);\n});\n</script>\n\n<template>\n <Teleport to=\"body\">\n <div\n v-if=\"kind\"\n ref=\"menuRef\"\n class=\"doc-bubble-host doc-bubble\"\n role=\"toolbar\"\n :class=\"{ 'is-hidden': hidden }\"\n :aria-label=\"menuLabel\"\n :style=\"{ left: `${position.left}px`, top: `${position.top}px`, zIndex }\"\n >\n <template v-if=\"kind === 'link'\">\n <a class=\"doc-bubble__link\" target=\"_blank\" rel=\"noopener noreferrer\" :href=\"linkHref\" :title=\"linkHref\">\n <ExternalLink :size=\"13\" />\n <span>{{ linkHref }}</span>\n </a>\n <span class=\"doc-bubble__divider\" />\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.editLink')\"\n :title=\"t('editor.editLink')\"\n @mousedown.prevent\n @click=\"emit('editLink')\"\n >\n <Pencil :size=\"15\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.unlink')\"\n :title=\"t('editor.unlink')\"\n @mousedown.prevent\n @click=\"engine.unsetLink()\"\n >\n <Unlink :size=\"15\" />\n </button>\n </template>\n\n <template v-else-if=\"kind === 'image'\">\n <form v-if=\"altEditing\" class=\"doc-bubble__form\" @submit.prevent=\"saveAlt\">\n <el-input\n ref=\"altInput\"\n v-model=\"imageAlt\"\n size=\"small\"\n :ariaLabel=\"t('editor.imageAlt')\"\n :placeholder=\"t('editor.imageAlt')\"\n />\n <el-button native-type=\"submit\" size=\"small\" type=\"primary\">{{ t('editor.apply') }}</el-button>\n </form>\n <template v-else>\n <button\n v-for=\"alignment in IMAGE_ALIGNMENTS\"\n :key=\"alignment.value\"\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': imageAlign === alignment.value }\"\n :aria-label=\"t(alignment.label)\"\n :title=\"t(alignment.label)\"\n @mousedown.prevent\n @click=\"setImageAlign(alignment.value)\"\n >\n <component :is=\"alignment.icon\" :size=\"15\" />\n </button>\n <span class=\"doc-bubble__divider\" />\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.imageAlt')\"\n :title=\"t('editor.imageAlt')\"\n @mousedown.prevent\n @click=\"startAltEditing\"\n >\n <TextCursorInput :size=\"15\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button is-danger\"\n :aria-label=\"t('editor.delete')\"\n :title=\"t('editor.delete')\"\n @mousedown.prevent\n @click=\"removeImage\"\n >\n <Trash2 :size=\"15\" />\n </button>\n </template>\n </template>\n\n <template v-else>\n <template v-for=\"(group, index) in TABLE_GROUPS\" :key=\"index\">\n <span v-if=\"index\" class=\"doc-bubble__divider\" />\n <button\n v-for=\"action in group\"\n :key=\"action.command\"\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-danger': 'danger' in action }\"\n :aria-label=\"t(action.label)\"\n :disabled=\"isTableCommandDisabled(action.command)\"\n :title=\"t(action.label)\"\n @mousedown.prevent\n @click=\"runTableCommand(action.command)\"\n >\n <component :is=\"action.icon\" :size=\"15\" />\n </button>\n </template>\n </template>\n </div>\n </Teleport>\n</template>\n\n<style scoped>\n/* Menu surface */\n.doc-bubble {\n position: fixed;\n display: flex;\n align-items: center;\n gap: 1px;\n padding: 3px;\n border: 1px solid var(--el-border-color-light);\n border-radius: 8px;\n background: var(--el-bg-color-overlay);\n box-shadow: 0 6px 20px rgb(16 24 40 / 16%);\n}\n\n.doc-bubble.is-hidden {\n visibility: hidden;\n}\n\n.doc-bubble__divider {\n flex: 0 0 1px;\n align-self: stretch;\n margin: 3px 4px;\n background: var(--el-border-color-lighter);\n}\n\n.doc-bubble .doc-tb-button.is-danger:hover:not(:disabled) {\n color: var(--el-color-danger);\n background: var(--el-color-danger-light-9);\n}\n\n/* Link menu */\n.doc-bubble__link {\n display: inline-flex;\n max-width: 260px;\n align-items: center;\n gap: 5px;\n padding: 0 6px;\n color: var(--el-color-primary);\n font-size: 12px;\n text-decoration: none;\n}\n\n.doc-bubble__link span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Image alt text form */\n.doc-bubble__form {\n display: flex;\n width: 280px;\n gap: 4px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue';\n\nimport type { DocumentEngine } from '../core/engine/engine';\nimport { columnBorderAt, columnWidth } from '../core/engine/tables';\n\n/**\n * Interactive layer above the page content: a selection frame with corner handles to resize the selected image, and\n * a guide line for dragging table column borders. Drawn in the stage's unscaled coordinates.\n */\ndefineOptions({ name: 'EditorObjectOverlay' });\n\ninterface Props {\n /** Engine whose selected image and tables are edited. */\n engine: DocumentEngine;\n /** Zoom in percent, needed to convert pointer movement into document pixels. */\n zoom: number;\n}\n\nconst props = defineProps<Props>();\n\n/** Corner of the image frame a resize handle sits on. */\ntype Corner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\n/** Rectangle in the stage's unscaled coordinates. */\ninterface Box {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\n/** Image size shown while resizing. */\ninterface ImageSize {\n width: number;\n height: number;\n}\n\n/** Vertical guide line shown while dragging a column border. */\ninterface ColumnGuide {\n left: number;\n top: number;\n height: number;\n}\n\n/** Column border currently under the pointer. */\ninterface ColumnHover {\n table: HTMLTableElement;\n /** Index of the column whose right border is hovered. */\n index: number;\n}\n\nconst CORNERS: Corner[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n/** Distance from a cell border, in screen pixels at 100% zoom, at which column resizing starts. */\nconst BORDER_TOLERANCE = 4;\nconst MIN_IMAGE_WIDTH = 40;\nconst MIN_COLUMN_WIDTH = 40;\n/** Class on the editable root that shows the column-resize cursor. */\nconst RESIZE_CLASS = 'is-column-resize';\n/** Zoom percentage that shows the document at its real size. */\nconst ACTUAL_SIZE_ZOOM = 100;\nconst PRIMARY_BUTTON = 0;\n\n/** Frame around the selected image, or `null` when no image is selected. */\nconst imageBox = ref<Box | null>(null);\n/** Live size label while an image is being resized. */\nconst imageSize = ref<ImageSize | null>(null);\n/** Guide line while a column border is being dragged. */\nconst guide = ref<ColumnGuide | null>(null);\n\nlet measureFrame = 0;\nlet hover: ColumnHover | null = null;\nlet draggingColumn = false;\n/** Cleanup callbacks for DOM listeners and engine subscriptions. */\nconst disposers: Array<() => void> = [];\n\n/** Formats a number of pixels as a CSS length. */\nconst px = (value: number) => `${value}px`;\n\n/** Zoom as a scale factor. */\nconst scale = () => props.zoom / ACTUAL_SIZE_ZOOM;\n\n/** Box of an element in the unscaled coordinates of the page stage. */\nconst relativeBox = (element: Element): Box => {\n const origin = props.engine.root.getBoundingClientRect();\n const rect = element.getBoundingClientRect();\n const factor = scale();\n return {\n left: (rect.left - origin.left) / factor,\n top: (rect.top - origin.top) / factor,\n width: rect.width / factor,\n height: rect.height / factor\n };\n};\n\n/** Updates the frame around the selected image. */\nconst measure = () => {\n measureFrame = 0;\n const image = props.engine.selectedImage;\n imageBox.value = image && props.engine.isEditable ? relativeBox(image) : null;\n};\n\n/** Coalesces frame measurements into one per animation frame. */\nconst scheduleMeasure = () => {\n if (!measureFrame) measureFrame = requestAnimationFrame(measure);\n};\n\n/** Width available to content inside the editable root's padding; images never grow beyond it. */\nconst contentWidth = () => {\n const { root } = props.engine;\n const style = getComputedStyle(root);\n return root.clientWidth - Number.parseFloat(style.paddingLeft) - Number.parseFloat(style.paddingRight);\n};\n\n/**\n * Resizes the selected image from a corner handle, keeping its aspect ratio. The preview changes the element directly;\n * on release the original attributes are restored and the final size is committed as a single undo step.\n */\nconst startImageResize = (corner: Corner, event: PointerEvent) => {\n const image = props.engine.selectedImage;\n if (!image) return;\n const startX = event.clientX;\n const startWidth = image.getBoundingClientRect().width / scale();\n const ratio =\n image.naturalWidth && image.naturalHeight\n ? image.naturalHeight / image.naturalWidth\n : image.offsetHeight / Math.max(1, image.offsetWidth);\n const originalAttributes = { width: image.getAttribute('width'), height: image.getAttribute('height') };\n const direction = corner.endsWith('left') ? -1 : 1;\n const maxWidth = contentWidth();\n let width = Math.round(startWidth);\n\n /** Previews the size under the pointer. */\n const onPointerMove = (move: PointerEvent) => {\n const next = startWidth + ((move.clientX - startX) / scale()) * direction;\n width = Math.round(Math.min(maxWidth, Math.max(MIN_IMAGE_WIDTH, next)));\n image.setAttribute('width', String(width));\n image.setAttribute('height', String(Math.round(width * ratio)));\n imageSize.value = { width, height: Math.round(width * ratio) };\n scheduleMeasure();\n };\n\n /** Restores the previewed attributes and commits the final size through the engine. */\n const onPointerUp = () => {\n window.removeEventListener('pointermove', onPointerMove);\n window.removeEventListener('pointerup', onPointerUp);\n imageSize.value = null;\n for (const [name, value] of Object.entries(originalAttributes)) {\n if (value === null) image.removeAttribute(name);\n else image.setAttribute(name, value);\n }\n if (width !== Math.round(startWidth)) {\n props.engine.updateImage(image, { width, height: Math.round(width * ratio) });\n }\n scheduleMeasure();\n };\n\n window.addEventListener('pointermove', onPointerMove);\n window.addEventListener('pointerup', onPointerUp);\n};\n\n/** Detects a hovered column border and shows the resize cursor over it. */\nconst onContentMouseMove = (event: MouseEvent) => {\n if (draggingColumn || event.buttons) return;\n const target = event.target as HTMLElement;\n const cell = target.closest?.('td, th') as HTMLTableCellElement | null;\n const table = cell?.closest('table') ?? null;\n const index =\n cell && table && props.engine.isEditable && props.engine.root.contains(table)\n ? columnBorderAt(table, cell, event.clientX, BORDER_TOLERANCE * scale())\n : null;\n hover = index !== null && table ? { table, index } : null;\n props.engine.root.classList.toggle(RESIZE_CLASS, hover !== null);\n};\n\n/**\n * Drags a hovered column border with a guide line, like Word; the width is applied on release. Runs in the capture\n * phase so the engine does not start a text or cell selection.\n */\nconst onContentMouseDown = (event: MouseEvent) => {\n if (!hover || event.button !== PRIMARY_BUTTON) return;\n event.preventDefault();\n event.stopPropagation();\n const { table, index } = hover;\n const startX = event.clientX;\n const startWidth = columnWidth(table, index);\n const tableBox = relativeBox(table);\n const originX = (startX - props.engine.root.getBoundingClientRect().left) / scale();\n let width = startWidth;\n draggingColumn = true;\n guide.value = { left: originX, top: tableBox.top, height: tableBox.height };\n\n /** Moves the guide line with the pointer. */\n const onMouseMove = (move: MouseEvent) => {\n width = Math.max(MIN_COLUMN_WIDTH, startWidth + (move.clientX - startX) / scale());\n guide.value = { left: originX + width - startWidth, top: tableBox.top, height: tableBox.height };\n };\n\n /** Removes the guide and applies the new column width as one undo step. */\n const onMouseUp = () => {\n window.removeEventListener('mousemove', onMouseMove);\n window.removeEventListener('mouseup', onMouseUp);\n draggingColumn = false;\n guide.value = null;\n if (Math.round(width) !== Math.round(startWidth)) props.engine.resizeColumn(table, index, width);\n };\n\n window.addEventListener('mousemove', onMouseMove);\n window.addEventListener('mouseup', onMouseUp);\n};\n\n/** Keeps the image frame aligned when the zoom changes. */\nwatch(() => props.zoom, scheduleMeasure);\n\n/** Listens to pointer movement on the content and to engine changes that move the selected image. */\nonMounted(() => {\n const { root } = props.engine;\n root.addEventListener('mousemove', onContentMouseMove);\n root.addEventListener('mousedown', onContentMouseDown, true);\n disposers.push(\n props.engine.on('selection', scheduleMeasure),\n props.engine.on('update', scheduleMeasure),\n () => root.removeEventListener('mousemove', onContentMouseMove),\n () => root.removeEventListener('mousedown', onContentMouseDown, true)\n );\n scheduleMeasure();\n});\n\n/** Removes every listener and pending measurement. */\nonBeforeUnmount(() => {\n for (const dispose of disposers) dispose();\n cancelAnimationFrame(measureFrame);\n});\n</script>\n\n<template>\n <div class=\"object-overlay\" aria-hidden=\"true\">\n <div\n v-if=\"imageBox\"\n class=\"object-overlay__image\"\n :style=\"{\n left: px(imageBox.left),\n top: px(imageBox.top),\n width: px(imageBox.width),\n height: px(imageBox.height)\n }\"\n >\n <span\n v-for=\"corner in CORNERS\"\n :key=\"corner\"\n class=\"object-overlay__handle\"\n :class=\"`is-${corner}`\"\n @pointerdown.prevent.stop=\"startImageResize(corner, $event)\"\n />\n <span v-if=\"imageSize\" class=\"object-overlay__size\">{{ imageSize.width }} × {{ imageSize.height }}</span>\n </div>\n <div\n v-if=\"guide\"\n class=\"object-overlay__guide\"\n :style=\"{ left: px(guide.left), top: px(guide.top), height: px(guide.height) }\"\n />\n </div>\n</template>\n\n<style scoped>\n.object-overlay {\n position: absolute;\n z-index: 3;\n inset: 0;\n pointer-events: none;\n}\n\n/* Selected image frame and resize handles */\n.object-overlay__image {\n position: absolute;\n box-sizing: border-box;\n outline: 2px solid #3b82f6;\n outline-offset: 1px;\n}\n\n.object-overlay__handle {\n position: absolute;\n width: 10px;\n height: 10px;\n box-sizing: border-box;\n border: 1.5px solid #3b82f6;\n border-radius: 2px;\n background: #fff;\n pointer-events: auto;\n}\n\n.object-overlay__handle.is-top-left {\n top: -6px;\n left: -6px;\n cursor: nwse-resize;\n}\n\n.object-overlay__handle.is-top-right {\n top: -6px;\n right: -6px;\n cursor: nesw-resize;\n}\n\n.object-overlay__handle.is-bottom-left {\n bottom: -6px;\n left: -6px;\n cursor: nesw-resize;\n}\n\n.object-overlay__handle.is-bottom-right {\n right: -6px;\n bottom: -6px;\n cursor: nwse-resize;\n}\n\n.object-overlay__size {\n position: absolute;\n right: 4px;\n bottom: 4px;\n padding: 2px 6px;\n border-radius: 4px;\n color: #fff;\n background: rgb(16 24 40 / 75%);\n font: 11px/16px system-ui, sans-serif;\n}\n\n/* Column resize guide */\n.object-overlay__guide {\n position: absolute;\n width: 0;\n border-left: 1px dashed #3b82f6;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue';\n\nimport type { DocumentEngine } from '../core/engine/engine';\nimport { columnBorderAt, columnWidth } from '../core/engine/tables';\n\n/**\n * Interactive layer above the page content: a selection frame with corner handles to resize the selected image, and\n * a guide line for dragging table column borders. Drawn in the stage's unscaled coordinates.\n */\ndefineOptions({ name: 'EditorObjectOverlay' });\n\ninterface Props {\n /** Engine whose selected image and tables are edited. */\n engine: DocumentEngine;\n /** Zoom in percent, needed to convert pointer movement into document pixels. */\n zoom: number;\n}\n\nconst props = defineProps<Props>();\n\n/** Corner of the image frame a resize handle sits on. */\ntype Corner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\n/** Rectangle in the stage's unscaled coordinates. */\ninterface Box {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\n/** Image size shown while resizing. */\ninterface ImageSize {\n width: number;\n height: number;\n}\n\n/** Vertical guide line shown while dragging a column border. */\ninterface ColumnGuide {\n left: number;\n top: number;\n height: number;\n}\n\n/** Column border currently under the pointer. */\ninterface ColumnHover {\n table: HTMLTableElement;\n /** Index of the column whose right border is hovered. */\n index: number;\n}\n\nconst CORNERS: Corner[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n/** Distance from a cell border, in screen pixels at 100% zoom, at which column resizing starts. */\nconst BORDER_TOLERANCE = 4;\nconst MIN_IMAGE_WIDTH = 40;\nconst MIN_COLUMN_WIDTH = 40;\n/** Class on the editable root that shows the column-resize cursor. */\nconst RESIZE_CLASS = 'is-column-resize';\n/** Zoom percentage that shows the document at its real size. */\nconst ACTUAL_SIZE_ZOOM = 100;\nconst PRIMARY_BUTTON = 0;\n\n/** Frame around the selected image, or `null` when no image is selected. */\nconst imageBox = ref<Box | null>(null);\n/** Live size label while an image is being resized. */\nconst imageSize = ref<ImageSize | null>(null);\n/** Guide line while a column border is being dragged. */\nconst guide = ref<ColumnGuide | null>(null);\n\nlet measureFrame = 0;\nlet hover: ColumnHover | null = null;\nlet draggingColumn = false;\n/** Cleanup callbacks for DOM listeners and engine subscriptions. */\nconst disposers: Array<() => void> = [];\n\n/** Formats a number of pixels as a CSS length. */\nconst px = (value: number) => `${value}px`;\n\n/** Zoom as a scale factor. */\nconst scale = () => props.zoom / ACTUAL_SIZE_ZOOM;\n\n/** Box of an element in the unscaled coordinates of the page stage. */\nconst relativeBox = (element: Element): Box => {\n const origin = props.engine.root.getBoundingClientRect();\n const rect = element.getBoundingClientRect();\n const factor = scale();\n return {\n left: (rect.left - origin.left) / factor,\n top: (rect.top - origin.top) / factor,\n width: rect.width / factor,\n height: rect.height / factor\n };\n};\n\n/** Updates the frame around the selected image. */\nconst measure = () => {\n measureFrame = 0;\n const image = props.engine.selectedImage;\n imageBox.value = image && props.engine.isEditable ? relativeBox(image) : null;\n};\n\n/** Coalesces frame measurements into one per animation frame. */\nconst scheduleMeasure = () => {\n if (!measureFrame) measureFrame = requestAnimationFrame(measure);\n};\n\n/** Width available to content inside the editable root's padding; images never grow beyond it. */\nconst contentWidth = () => {\n const { root } = props.engine;\n const style = getComputedStyle(root);\n return root.clientWidth - Number.parseFloat(style.paddingLeft) - Number.parseFloat(style.paddingRight);\n};\n\n/**\n * Resizes the selected image from a corner handle, keeping its aspect ratio. The preview changes the element directly;\n * on release the original attributes are restored and the final size is committed as a single undo step.\n */\nconst startImageResize = (corner: Corner, event: PointerEvent) => {\n const image = props.engine.selectedImage;\n if (!image) return;\n const startX = event.clientX;\n const startWidth = image.getBoundingClientRect().width / scale();\n const ratio =\n image.naturalWidth && image.naturalHeight\n ? image.naturalHeight / image.naturalWidth\n : image.offsetHeight / Math.max(1, image.offsetWidth);\n const originalAttributes = { width: image.getAttribute('width'), height: image.getAttribute('height') };\n const direction = corner.endsWith('left') ? -1 : 1;\n const maxWidth = contentWidth();\n let width = Math.round(startWidth);\n\n /** Previews the size under the pointer. */\n const onPointerMove = (move: PointerEvent) => {\n const next = startWidth + ((move.clientX - startX) / scale()) * direction;\n width = Math.round(Math.min(maxWidth, Math.max(MIN_IMAGE_WIDTH, next)));\n image.setAttribute('width', String(width));\n image.setAttribute('height', String(Math.round(width * ratio)));\n imageSize.value = { width, height: Math.round(width * ratio) };\n scheduleMeasure();\n };\n\n /** Restores the previewed attributes and commits the final size through the engine. */\n const onPointerUp = () => {\n window.removeEventListener('pointermove', onPointerMove);\n window.removeEventListener('pointerup', onPointerUp);\n imageSize.value = null;\n for (const [name, value] of Object.entries(originalAttributes)) {\n if (value === null) image.removeAttribute(name);\n else image.setAttribute(name, value);\n }\n if (width !== Math.round(startWidth)) {\n props.engine.updateImage(image, { width, height: Math.round(width * ratio) });\n }\n scheduleMeasure();\n };\n\n window.addEventListener('pointermove', onPointerMove);\n window.addEventListener('pointerup', onPointerUp);\n};\n\n/** Detects a hovered column border and shows the resize cursor over it. */\nconst onContentMouseMove = (event: MouseEvent) => {\n if (draggingColumn || event.buttons) return;\n const target = event.target as HTMLElement;\n const cell = target.closest?.('td, th') as HTMLTableCellElement | null;\n const table = cell?.closest('table') ?? null;\n const index =\n cell && table && props.engine.isEditable && props.engine.root.contains(table)\n ? columnBorderAt(table, cell, event.clientX, BORDER_TOLERANCE * scale())\n : null;\n hover = index !== null && table ? { table, index } : null;\n props.engine.root.classList.toggle(RESIZE_CLASS, hover !== null);\n};\n\n/**\n * Drags a hovered column border with a guide line, like Word; the width is applied on release. Runs in the capture\n * phase so the engine does not start a text or cell selection.\n */\nconst onContentMouseDown = (event: MouseEvent) => {\n if (!hover || event.button !== PRIMARY_BUTTON) return;\n event.preventDefault();\n event.stopPropagation();\n const { table, index } = hover;\n const startX = event.clientX;\n const startWidth = columnWidth(table, index);\n const tableBox = relativeBox(table);\n const originX = (startX - props.engine.root.getBoundingClientRect().left) / scale();\n let width = startWidth;\n draggingColumn = true;\n guide.value = { left: originX, top: tableBox.top, height: tableBox.height };\n\n /** Moves the guide line with the pointer. */\n const onMouseMove = (move: MouseEvent) => {\n width = Math.max(MIN_COLUMN_WIDTH, startWidth + (move.clientX - startX) / scale());\n guide.value = { left: originX + width - startWidth, top: tableBox.top, height: tableBox.height };\n };\n\n /** Removes the guide and applies the new column width as one undo step. */\n const onMouseUp = () => {\n window.removeEventListener('mousemove', onMouseMove);\n window.removeEventListener('mouseup', onMouseUp);\n draggingColumn = false;\n guide.value = null;\n if (Math.round(width) !== Math.round(startWidth)) props.engine.resizeColumn(table, index, width);\n };\n\n window.addEventListener('mousemove', onMouseMove);\n window.addEventListener('mouseup', onMouseUp);\n};\n\n/** Keeps the image frame aligned when the zoom changes. */\nwatch(() => props.zoom, scheduleMeasure);\n\n/** Listens to pointer movement on the content and to engine changes that move the selected image. */\nonMounted(() => {\n const { root } = props.engine;\n root.addEventListener('mousemove', onContentMouseMove);\n root.addEventListener('mousedown', onContentMouseDown, true);\n disposers.push(\n props.engine.on('selection', scheduleMeasure),\n props.engine.on('update', scheduleMeasure),\n () => root.removeEventListener('mousemove', onContentMouseMove),\n () => root.removeEventListener('mousedown', onContentMouseDown, true)\n );\n scheduleMeasure();\n});\n\n/** Removes every listener and pending measurement. */\nonBeforeUnmount(() => {\n for (const dispose of disposers) dispose();\n cancelAnimationFrame(measureFrame);\n});\n</script>\n\n<template>\n <div class=\"object-overlay\" aria-hidden=\"true\">\n <div\n v-if=\"imageBox\"\n class=\"object-overlay__image\"\n :style=\"{\n left: px(imageBox.left),\n top: px(imageBox.top),\n width: px(imageBox.width),\n height: px(imageBox.height)\n }\"\n >\n <span\n v-for=\"corner in CORNERS\"\n :key=\"corner\"\n class=\"object-overlay__handle\"\n :class=\"`is-${corner}`\"\n @pointerdown.prevent.stop=\"startImageResize(corner, $event)\"\n />\n <span v-if=\"imageSize\" class=\"object-overlay__size\">{{ imageSize.width }} × {{ imageSize.height }}</span>\n </div>\n <div\n v-if=\"guide\"\n class=\"object-overlay__guide\"\n :style=\"{ left: px(guide.left), top: px(guide.top), height: px(guide.height) }\"\n />\n </div>\n</template>\n\n<style scoped>\n.object-overlay {\n position: absolute;\n z-index: 3;\n inset: 0;\n pointer-events: none;\n}\n\n/* Selected image frame and resize handles */\n.object-overlay__image {\n position: absolute;\n box-sizing: border-box;\n outline: 2px solid #3b82f6;\n outline-offset: 1px;\n}\n\n.object-overlay__handle {\n position: absolute;\n width: 10px;\n height: 10px;\n box-sizing: border-box;\n border: 1.5px solid #3b82f6;\n border-radius: 2px;\n background: #fff;\n pointer-events: auto;\n}\n\n.object-overlay__handle.is-top-left {\n top: -6px;\n left: -6px;\n cursor: nwse-resize;\n}\n\n.object-overlay__handle.is-top-right {\n top: -6px;\n right: -6px;\n cursor: nesw-resize;\n}\n\n.object-overlay__handle.is-bottom-left {\n bottom: -6px;\n left: -6px;\n cursor: nesw-resize;\n}\n\n.object-overlay__handle.is-bottom-right {\n right: -6px;\n bottom: -6px;\n cursor: nwse-resize;\n}\n\n.object-overlay__size {\n position: absolute;\n right: 4px;\n bottom: 4px;\n padding: 2px 6px;\n border-radius: 4px;\n color: #fff;\n background: rgb(16 24 40 / 75%);\n font: 11px/16px system-ui, sans-serif;\n}\n\n/* Column resize guide */\n.object-overlay__guide {\n position: absolute;\n width: 0;\n border-left: 1px dashed #3b82f6;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue';\n\nimport { rangeFromPoint } from '../core/engine/clipboard';\nimport type { DocumentEngine } from '../core/engine/engine';\nimport { selectRange } from '../core/engine/selection';\nimport { t } from '../core/labels';\nimport type { DocumentViewMode, PageMetrics } from '../core/page';\nimport EditorObjectOverlay from './editor-object-overlay.vue';\n\n/**\n * Scrollable grey canvas that shows the document either as paginated sheets (page view) or as one sheet that fills\n * the canvas (web view). Owns the editable element the engine takes over.\n */\ndefineOptions({ name: 'EditorCanvas' });\n\ninterface Props {\n /** Grow with the content between the editor's min and max height instead of filling a fixed height. */\n autoHeight: boolean;\n /** Engine working on the editable element; `null` until the parent has created it. */\n engine: DocumentEngine | null;\n /** Page size and margins in pixels. */\n metrics: PageMetrics;\n /** Number of sheets drawn behind the content in page view. */\n pageCount: number;\n /** Paginated sheets or a single web sheet. */\n viewMode: DocumentViewMode;\n /** Zoom in percent. */\n zoom: number;\n}\n\nconst props = defineProps<Props>();\n\ninterface Emits {\n /** The width available for the sheet changed; widths of a hidden canvas (source mode) are not reported. */\n resize: [contentWidth: number];\n}\n\nconst emit = defineEmits<Emits>();\n\n/** Zoom percentage that shows the document at its real size. */\nconst ACTUAL_SIZE_ZOOM = 100;\n/** Clicks outside the sheet are moved this many pixels inside it before looking up the caret position. */\nconst CARET_HIT_INSET = 2;\nconst PRIMARY_BUTTON = 0;\n\nconst scrollRef = ref<HTMLElement>();\nconst stageRef = ref<HTMLElement>();\n/** The editable element; the engine takes it over as its root. */\nconst contentRef = ref<HTMLElement>();\n/** Content box of the scroll container, i.e. without its grey padding. */\nconst viewport = ref({ width: 0, height: 0 });\n/** Unscaled height of the stage, used to size the scrollable area. */\nconst stageHeight = ref(0);\n\nlet observer: ResizeObserver | undefined;\n\n/** Formats a number of pixels as a CSS length. */\nconst px = (value: number) => `${value}px`;\n\n/** Restricts a value to the `[min, max]` interval. */\nconst clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);\n\n/** Zoom as a scale factor. */\nconst scale = computed(() => props.zoom / ACTUAL_SIZE_ZOOM);\n/** Vertical distance from the top of one sheet to the top of the next. */\nconst period = computed(() => props.metrics.height + props.metrics.gap);\n\n/**\n * Unscaled stage width. In web view the sheet always fills the canvas inside its grey padding at any zoom, so it is\n * the viewport width divided by the scale.\n */\nconst stageWidth = computed(() =>\n props.viewMode === 'page' ? props.metrics.width : Math.max(1, viewport.value.width / scale.value)\n);\n\n/**\n * Minimum height of the web sheet: at least the visible canvas height. A growing editor uses its minimum height\n * instead, so it can shrink back when text is removed.\n */\nconst webMinHeight = computed(() =>\n props.autoHeight\n ? `max(0px, calc((var(--doc-editor-min-height) - 2 * var(--doc-editor-canvas-padding)) / ${scale.value}))`\n : px(viewport.value.height / scale.value)\n);\n\n/**\n * The stage is laid out at 100% and scaled with a transform, so pagination can measure real offsets while the sizer\n * reserves the scaled footprint for scrolling. Page geometry is passed to the content styles as CSS variables.\n */\nconst stageStyle = computed(() => {\n const { metrics, pageCount, viewMode } = props;\n return {\n width: px(stageWidth.value),\n minHeight: viewMode === 'web' ? webMinHeight.value : undefined,\n transform: scale.value === 1 ? undefined : `scale(${scale.value})`,\n '--page-height': px(metrics.height),\n '--pages-height': px(pageCount * period.value - metrics.gap),\n '--margin-top': px(metrics.marginTop),\n '--margin-right': px(metrics.marginRight),\n '--margin-bottom': px(metrics.marginBottom),\n '--margin-left': px(metrics.marginLeft),\n '--page-break-label': JSON.stringify(t('editor.pageBreak'))\n };\n});\n\n/** Scaled footprint of the stage, which defines the scrollable size of the canvas. */\nconst sizerStyle = computed(() => ({\n width: px(stageWidth.value * scale.value),\n height: px(stageHeight.value * scale.value)\n}));\n\n/** Clicks on the grey canvas place the caret on the nearest line of the sheet instead of doing nothing. */\nconst onCanvasMouseDown = (event: MouseEvent) => {\n const scroll = scrollRef.value;\n const content = contentRef.value;\n const target = event.target as HTMLElement;\n if (!scroll || !content || !props.engine || event.button !== PRIMARY_BUTTON) return;\n if (target !== scroll && !target.classList.contains('doc-canvas__sizer')) return;\n // Clicks on the scrollbars also target the scroll container.\n if (target === scroll && (event.offsetX >= scroll.clientWidth || event.offsetY >= scroll.clientHeight)) return;\n\n event.preventDefault();\n const rect = content.getBoundingClientRect();\n const range = rangeFromPoint(\n clamp(event.clientX, rect.left + CARET_HIT_INSET, rect.right - CARET_HIT_INSET),\n clamp(event.clientY, rect.top + CARET_HIT_INSET, rect.bottom - CARET_HIT_INSET)\n );\n if (range && content.contains(range.startContainer)) {\n content.focus({ preventScroll: true });\n selectRange(range);\n } else {\n props.engine.focus('end');\n }\n};\n\n/** Zoom percentage at which the page width fills the canvas, or `0` before the canvas has been measured. */\nconst getFitWidthZoom = () =>\n viewport.value.width ? Math.floor((viewport.value.width / props.metrics.width) * ACTUAL_SIZE_ZOOM) : 0;\n\n/** Pending measurement frame; `0` when none is scheduled. */\nlet measureFrame = 0;\n\n/** Measures the stage height and the canvas content box, reporting width changes to the parent. */\nconst measure = (scroll: HTMLElement, stage: HTMLElement) => {\n measureFrame = 0;\n stageHeight.value = stage.offsetHeight;\n const style = getComputedStyle(scroll);\n const width = scroll.clientWidth - Number.parseFloat(style.paddingLeft) - Number.parseFloat(style.paddingRight);\n const height = scroll.clientHeight - Number.parseFloat(style.paddingTop) - Number.parseFloat(style.paddingBottom);\n // A canvas hidden by source mode reports zero; keep the last real size.\n if (width <= 0 || (width === viewport.value.width && height === viewport.value.height)) return;\n viewport.value = { width, height };\n emit('resize', width);\n};\n\n/**\n * Starts observing the canvas and the stage so sizes follow layout changes. Measuring waits for the next frame:\n * the new sizes resize the observed stage, which inside the observer callback would trigger a ResizeObserver loop.\n */\nonMounted(() => {\n const scroll = scrollRef.value;\n const stage = stageRef.value;\n if (!scroll || !stage || typeof ResizeObserver === 'undefined') return;\n observer = new ResizeObserver(() => {\n if (!measureFrame) measureFrame = requestAnimationFrame(() => measure(scroll, stage));\n });\n observer.observe(scroll);\n observer.observe(stage);\n});\n\n/** Stops observing sizes and drops a pending measurement. */\nonBeforeUnmount(() => {\n observer?.disconnect();\n cancelAnimationFrame(measureFrame);\n});\n\ndefineExpose({\n /** The editable element the engine is created on. */\n contentElement: contentRef,\n /** Zoom percentage that makes the page fill the canvas width. */\n getFitWidthZoom,\n /** The scroll container, followed by floating menus. */\n scrollElement: scrollRef\n});\n</script>\n\n<template>\n <div\n ref=\"scrollRef\"\n class=\"doc-canvas\"\n :class=\"[`is-${viewMode}`, { 'is-auto': autoHeight }]\"\n @mousedown=\"onCanvasMouseDown\"\n >\n <div class=\"doc-canvas__sizer\" :style=\"sizerStyle\">\n <div ref=\"stageRef\" class=\"doc-canvas__stage\" :style=\"stageStyle\">\n <div v-if=\"viewMode === 'page'\" class=\"doc-canvas__sheets\" aria-hidden=\"true\">\n <div\n v-for=\"index in pageCount\"\n :key=\"index\"\n class=\"doc-canvas__sheet\"\n :style=\"{ top: px((index - 1) * period) }\"\n >\n <span class=\"doc-canvas__page-number\">{{ index }}</span>\n </div>\n </div>\n <div ref=\"contentRef\" class=\"doc-canvas__content\" />\n <EditorObjectOverlay v-if=\"engine\" :engine=\"engine\" :zoom=\"zoom\" />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* Canvas and scaled stage */\n.doc-canvas {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n box-sizing: border-box;\n overflow: auto;\n padding: var(--doc-editor-canvas-padding);\n background: #e9ebef;\n overscroll-behavior: contain;\n}\n\n.doc-canvas.is-auto {\n flex: none;\n min-height: var(--doc-editor-min-height);\n max-height: var(--doc-editor-max-height);\n}\n\n.doc-canvas__sizer {\n position: relative;\n margin: 0 auto;\n}\n\n/* Page geometry defaults (A4, normal margins); the stage style overrides them inline from the page settings. */\n.doc-canvas__stage {\n --page-height: 1123px;\n --pages-height: 1123px;\n --margin-top: 96px;\n --margin-right: 96px;\n --margin-bottom: 96px;\n --margin-left: 96px;\n --page-break-label: \"\";\n\n position: absolute;\n top: 0;\n left: 0;\n transform-origin: 0 0;\n}\n\n/* Like a page, the web sheet sits on the grey canvas, separated from the editor frame. */\n.doc-canvas.is-web .doc-canvas__stage {\n background: #fff;\n box-shadow:\n 0 0 0 1px rgb(16 24 40 / 6%),\n 0 1px 4px rgb(16 24 40 / 8%);\n}\n\n/* Page view sheets */\n.doc-canvas__sheets {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.doc-canvas__sheet {\n position: absolute;\n left: 0;\n width: 100%;\n height: var(--page-height);\n background: #fff;\n box-shadow:\n 0 0 0 1px rgb(16 24 40 / 6%),\n 0 2px 8px rgb(16 24 40 / 10%);\n}\n\n.doc-canvas__page-number {\n position: absolute;\n right: 0;\n bottom: calc(var(--margin-bottom) / 2 - 8px);\n left: 0;\n color: #98a2b3;\n font: 11px/16px system-ui, sans-serif;\n text-align: center;\n}\n\n/* Editable content */\n.doc-canvas__content {\n position: relative;\n box-sizing: border-box;\n min-height: inherit;\n outline: none;\n caret-color: #1f2328;\n}\n\n.doc-canvas.is-page .doc-canvas__content {\n min-height: var(--pages-height);\n padding: var(--margin-top) var(--margin-right) var(--margin-bottom) var(--margin-left);\n}\n\n.doc-canvas.is-web .doc-canvas__content {\n padding: 40px 56px;\n}\n\n.doc-canvas.is-auto.is-web .doc-canvas__content {\n padding: 16px 20px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue';\n\nimport { rangeFromPoint } from '../core/engine/clipboard';\nimport type { DocumentEngine } from '../core/engine/engine';\nimport { selectRange } from '../core/engine/selection';\nimport { t } from '../core/labels';\nimport type { DocumentViewMode, PageMetrics } from '../core/page';\nimport EditorObjectOverlay from './editor-object-overlay.vue';\n\n/**\n * Scrollable grey canvas that shows the document either as paginated sheets (page view) or as one sheet that fills\n * the canvas (web view). Owns the editable element the engine takes over.\n */\ndefineOptions({ name: 'EditorCanvas' });\n\ninterface Props {\n /** Grow with the content between the editor's min and max height instead of filling a fixed height. */\n autoHeight: boolean;\n /** Engine working on the editable element; `null` until the parent has created it. */\n engine: DocumentEngine | null;\n /** Page size and margins in pixels. */\n metrics: PageMetrics;\n /** Number of sheets drawn behind the content in page view. */\n pageCount: number;\n /** Paginated sheets or a single web sheet. */\n viewMode: DocumentViewMode;\n /** Zoom in percent. */\n zoom: number;\n}\n\nconst props = defineProps<Props>();\n\ninterface Emits {\n /** The width available for the sheet changed; widths of a hidden canvas (source mode) are not reported. */\n resize: [contentWidth: number];\n}\n\nconst emit = defineEmits<Emits>();\n\n/** Zoom percentage that shows the document at its real size. */\nconst ACTUAL_SIZE_ZOOM = 100;\n/** Clicks outside the sheet are moved this many pixels inside it before looking up the caret position. */\nconst CARET_HIT_INSET = 2;\nconst PRIMARY_BUTTON = 0;\n\nconst scrollRef = ref<HTMLElement>();\nconst stageRef = ref<HTMLElement>();\n/** The editable element; the engine takes it over as its root. */\nconst contentRef = ref<HTMLElement>();\n/** Content box of the scroll container, i.e. without its grey padding. */\nconst viewport = ref({ width: 0, height: 0 });\n/** Unscaled height of the stage, used to size the scrollable area. */\nconst stageHeight = ref(0);\n\nlet observer: ResizeObserver | undefined;\n\n/** Formats a number of pixels as a CSS length. */\nconst px = (value: number) => `${value}px`;\n\n/** Restricts a value to the `[min, max]` interval. */\nconst clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);\n\n/** Zoom as a scale factor. */\nconst scale = computed(() => props.zoom / ACTUAL_SIZE_ZOOM);\n/** Vertical distance from the top of one sheet to the top of the next. */\nconst period = computed(() => props.metrics.height + props.metrics.gap);\n\n/**\n * Unscaled stage width. In web view the sheet always fills the canvas inside its grey padding at any zoom, so it is\n * the viewport width divided by the scale.\n */\nconst stageWidth = computed(() =>\n props.viewMode === 'page' ? props.metrics.width : Math.max(1, viewport.value.width / scale.value)\n);\n\n/**\n * Minimum height of the web sheet: at least the visible canvas height. A growing editor uses its minimum height\n * instead, so it can shrink back when text is removed.\n */\nconst webMinHeight = computed(() =>\n props.autoHeight\n ? `max(0px, calc((var(--doc-editor-min-height) - 2 * var(--doc-editor-canvas-padding)) / ${scale.value}))`\n : px(viewport.value.height / scale.value)\n);\n\n/**\n * The stage is laid out at 100% and scaled with a transform, so pagination can measure real offsets while the sizer\n * reserves the scaled footprint for scrolling. Page geometry is passed to the content styles as CSS variables.\n */\nconst stageStyle = computed(() => {\n const { metrics, pageCount, viewMode } = props;\n return {\n width: px(stageWidth.value),\n minHeight: viewMode === 'web' ? webMinHeight.value : undefined,\n transform: scale.value === 1 ? undefined : `scale(${scale.value})`,\n '--page-height': px(metrics.height),\n '--pages-height': px(pageCount * period.value - metrics.gap),\n '--margin-top': px(metrics.marginTop),\n '--margin-right': px(metrics.marginRight),\n '--margin-bottom': px(metrics.marginBottom),\n '--margin-left': px(metrics.marginLeft),\n '--page-break-label': JSON.stringify(t('editor.pageBreak'))\n };\n});\n\n/** Scaled footprint of the stage, which defines the scrollable size of the canvas. */\nconst sizerStyle = computed(() => ({\n width: px(stageWidth.value * scale.value),\n height: px(stageHeight.value * scale.value)\n}));\n\n/** Clicks on the grey canvas place the caret on the nearest line of the sheet instead of doing nothing. */\nconst onCanvasMouseDown = (event: MouseEvent) => {\n const scroll = scrollRef.value;\n const content = contentRef.value;\n const target = event.target as HTMLElement;\n if (!scroll || !content || !props.engine || event.button !== PRIMARY_BUTTON) return;\n if (target !== scroll && !target.classList.contains('doc-canvas__sizer')) return;\n // Clicks on the scrollbars also target the scroll container.\n if (target === scroll && (event.offsetX >= scroll.clientWidth || event.offsetY >= scroll.clientHeight)) return;\n\n event.preventDefault();\n const rect = content.getBoundingClientRect();\n const range = rangeFromPoint(\n clamp(event.clientX, rect.left + CARET_HIT_INSET, rect.right - CARET_HIT_INSET),\n clamp(event.clientY, rect.top + CARET_HIT_INSET, rect.bottom - CARET_HIT_INSET)\n );\n if (range && content.contains(range.startContainer)) {\n content.focus({ preventScroll: true });\n selectRange(range);\n } else {\n props.engine.focus('end');\n }\n};\n\n/** Zoom percentage at which the page width fills the canvas, or `0` before the canvas has been measured. */\nconst getFitWidthZoom = () =>\n viewport.value.width ? Math.floor((viewport.value.width / props.metrics.width) * ACTUAL_SIZE_ZOOM) : 0;\n\n/** Pending measurement frame; `0` when none is scheduled. */\nlet measureFrame = 0;\n\n/** Measures the stage height and the canvas content box, reporting width changes to the parent. */\nconst measure = (scroll: HTMLElement, stage: HTMLElement) => {\n measureFrame = 0;\n stageHeight.value = stage.offsetHeight;\n const style = getComputedStyle(scroll);\n const width = scroll.clientWidth - Number.parseFloat(style.paddingLeft) - Number.parseFloat(style.paddingRight);\n const height = scroll.clientHeight - Number.parseFloat(style.paddingTop) - Number.parseFloat(style.paddingBottom);\n // A canvas hidden by source mode reports zero; keep the last real size.\n if (width <= 0 || (width === viewport.value.width && height === viewport.value.height)) return;\n viewport.value = { width, height };\n emit('resize', width);\n};\n\n/**\n * Starts observing the canvas and the stage so sizes follow layout changes. Measuring waits for the next frame:\n * the new sizes resize the observed stage, which inside the observer callback would trigger a ResizeObserver loop.\n */\nonMounted(() => {\n const scroll = scrollRef.value;\n const stage = stageRef.value;\n if (!scroll || !stage || typeof ResizeObserver === 'undefined') return;\n observer = new ResizeObserver(() => {\n if (!measureFrame) measureFrame = requestAnimationFrame(() => measure(scroll, stage));\n });\n observer.observe(scroll);\n observer.observe(stage);\n});\n\n/** Stops observing sizes and drops a pending measurement. */\nonBeforeUnmount(() => {\n observer?.disconnect();\n cancelAnimationFrame(measureFrame);\n});\n\ndefineExpose({\n /** The editable element the engine is created on. */\n contentElement: contentRef,\n /** Zoom percentage that makes the page fill the canvas width. */\n getFitWidthZoom,\n /** The scroll container, followed by floating menus. */\n scrollElement: scrollRef\n});\n</script>\n\n<template>\n <div\n ref=\"scrollRef\"\n class=\"doc-canvas\"\n :class=\"[`is-${viewMode}`, { 'is-auto': autoHeight }]\"\n @mousedown=\"onCanvasMouseDown\"\n >\n <div class=\"doc-canvas__sizer\" :style=\"sizerStyle\">\n <div ref=\"stageRef\" class=\"doc-canvas__stage\" :style=\"stageStyle\">\n <div v-if=\"viewMode === 'page'\" class=\"doc-canvas__sheets\" aria-hidden=\"true\">\n <div\n v-for=\"index in pageCount\"\n :key=\"index\"\n class=\"doc-canvas__sheet\"\n :style=\"{ top: px((index - 1) * period) }\"\n >\n <span class=\"doc-canvas__page-number\">{{ index }}</span>\n </div>\n </div>\n <div ref=\"contentRef\" class=\"doc-canvas__content\" />\n <EditorObjectOverlay v-if=\"engine\" :engine=\"engine\" :zoom=\"zoom\" />\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* Canvas and scaled stage */\n.doc-canvas {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n box-sizing: border-box;\n overflow: auto;\n padding: var(--doc-editor-canvas-padding);\n background: #e9ebef;\n overscroll-behavior: contain;\n}\n\n.doc-canvas.is-auto {\n flex: none;\n min-height: var(--doc-editor-min-height);\n max-height: var(--doc-editor-max-height);\n}\n\n.doc-canvas__sizer {\n position: relative;\n margin: 0 auto;\n}\n\n/* Page geometry defaults (A4, normal margins); the stage style overrides them inline from the page settings. */\n.doc-canvas__stage {\n --page-height: 1123px;\n --pages-height: 1123px;\n --margin-top: 96px;\n --margin-right: 96px;\n --margin-bottom: 96px;\n --margin-left: 96px;\n --page-break-label: \"\";\n\n position: absolute;\n top: 0;\n left: 0;\n transform-origin: 0 0;\n}\n\n/* Like a page, the web sheet sits on the grey canvas, separated from the editor frame. */\n.doc-canvas.is-web .doc-canvas__stage {\n background: #fff;\n box-shadow:\n 0 0 0 1px rgb(16 24 40 / 6%),\n 0 1px 4px rgb(16 24 40 / 8%);\n}\n\n/* Page view sheets */\n.doc-canvas__sheets {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.doc-canvas__sheet {\n position: absolute;\n left: 0;\n width: 100%;\n height: var(--page-height);\n background: #fff;\n box-shadow:\n 0 0 0 1px rgb(16 24 40 / 6%),\n 0 2px 8px rgb(16 24 40 / 10%);\n}\n\n.doc-canvas__page-number {\n position: absolute;\n right: 0;\n bottom: calc(var(--margin-bottom) / 2 - 8px);\n left: 0;\n color: #98a2b3;\n font: 11px/16px system-ui, sans-serif;\n text-align: center;\n}\n\n/* Editable content */\n.doc-canvas__content {\n position: relative;\n box-sizing: border-box;\n min-height: inherit;\n outline: none;\n caret-color: #1f2328;\n}\n\n.doc-canvas.is-page .doc-canvas__content {\n min-height: var(--pages-height);\n padding: var(--margin-top) var(--margin-right) var(--margin-bottom) var(--margin-left);\n}\n\n.doc-canvas.is-web .doc-canvas__content {\n padding: 40px 56px;\n}\n\n.doc-canvas.is-auto.is-web .doc-canvas__content {\n padding: 16px 20px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { CaseSensitive, ChevronDown, ChevronRight, ChevronUp, WholeWord, X } from '@lucide/vue';\nimport { ElInput, type InputInstance } from 'element-plus';\n\nimport type { DocumentEngine } from '../core/engine/engine';\nimport { formatShortcut, t } from '../core/labels';\n\n/**\n * Floating find and replace panel. Drives the engine's search controller, mirrors its match counter, and offers\n * replace actions that the engine records as undo steps.\n */\ndefineOptions({ name: 'EditorFindBar' });\n\ninterface Props {\n /** Engine whose document is searched. */\n engine: DocumentEngine;\n /** Hides the replacement row when the document cannot be edited. */\n readonly: boolean | undefined;\n}\n\ninterface Emits {\n /** The user asked to close the panel (close button). */\n close: [];\n}\n\nconst props = defineProps<Props>();\nconst emit = defineEmits<Emits>();\n/** Whether the replacement row is expanded; kept by the parent so Ctrl+H can open it directly. */\nconst showReplace = defineModel<boolean>('replace', { default: false });\n\n/** The longest selection that is copied into the search field when the panel opens. */\nconst MAX_PREFILL_LENGTH = 120;\n\nconst searchInput = ref<InputInstance>();\nconst searchTerm = ref('');\nconst replaceTerm = ref('');\nconst caseSensitive = ref(false);\nconst wholeWord = ref(false);\n/** Current match number and total, as reported by the engine. */\nconst status = ref(props.engine.search.state);\n\nlet stopListening: (() => void) | undefined;\n\n/** Focuses the search field and selects its text, so typing replaces the previous term. */\nconst focus = () => {\n searchInput.value?.focus();\n searchInput.value?.select();\n};\n\n/** Re-runs the search with the current term and options. */\nconst runSearch = () =>\n props.engine.search.setQuery(searchTerm.value, {\n caseSensitive: caseSensitive.value,\n wholeWord: wholeWord.value\n });\n\n/** Returns the Enter key press of an input event, or null for any other key. */\nconst asEnterKey = (event: Event | KeyboardEvent): KeyboardEvent | null =>\n event instanceof KeyboardEvent && event.key === 'Enter' ? event : null;\n\n/** Enter jumps to the next match, Shift+Enter to the previous one. */\nconst onSearchKeydown = (event: Event | KeyboardEvent) => {\n const enter = asEnterKey(event);\n if (!enter) return;\n enter.preventDefault();\n if (enter.shiftKey) props.engine.search.previous();\n else props.engine.search.next();\n};\n\n/** Enter in the replace field replaces the current match. */\nconst onReplaceKeydown = (event: Event | KeyboardEvent) => {\n const enter = asEnterKey(event);\n if (!enter) return;\n enter.preventDefault();\n props.engine.replaceMatch(replaceTerm.value);\n};\n\nwatch([searchTerm, caseSensitive, wholeWord], runSearch);\n\nonMounted(async () => {\n stopListening = props.engine.on('search', state => {\n status.value = state;\n });\n // Like Word, start with the selected text as the search term, selected so that typing replaces it.\n const selected = props.engine.getSelectedText();\n if (selected && selected.length <= MAX_PREFILL_LENGTH && !selected.includes('\\n')) searchTerm.value = selected;\n // select() has to run after v-model has written the prefilled value into the input.\n await nextTick();\n focus();\n});\n\nonBeforeUnmount(() => {\n stopListening?.();\n props.engine.search.clear();\n});\n\ndefineExpose({ focus });\n</script>\n\n<template>\n <div class=\"find-bar\" role=\"search\" :aria-label=\"t('editor.search.title')\">\n <button\n v-if=\"!readonly\"\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': showReplace }\"\n :title=\"t('editor.replace.toggle')\"\n :aria-label=\"t('editor.replace.toggle')\"\n :aria-expanded=\"showReplace\"\n @click=\"showReplace = !showReplace\"\n >\n <ChevronRight class=\"find-bar__chevron\" :size=\"14\" />\n </button>\n\n <div class=\"find-bar__rows\">\n <div class=\"find-bar__row\">\n <el-input\n ref=\"searchInput\"\n v-model=\"searchTerm\"\n class=\"find-bar__input\"\n size=\"small\"\n :placeholder=\"t('editor.search.placeholder')\"\n :ariaLabel=\"t('editor.search.placeholder')\"\n @keydown=\"onSearchKeydown\"\n >\n <template #suffix>\n <span class=\"find-bar__count\">{{ status.current }}/{{ status.total }}</span>\n </template>\n </el-input>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': caseSensitive }\"\n :title=\"t('editor.search.caseSensitive')\"\n :aria-label=\"t('editor.search.caseSensitive')\"\n :aria-pressed=\"caseSensitive\"\n @click=\"caseSensitive = !caseSensitive\"\n >\n <CaseSensitive :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': wholeWord }\"\n :title=\"t('editor.search.wholeWord')\"\n :aria-label=\"t('editor.search.wholeWord')\"\n :aria-pressed=\"wholeWord\"\n @click=\"wholeWord = !wholeWord\"\n >\n <WholeWord :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :disabled=\"!status.total\"\n :title=\"`${t('editor.search.previous')} (${formatShortcut('Shift+Enter')})`\"\n :aria-label=\"t('editor.search.previous')\"\n @click=\"engine.search.previous()\"\n >\n <ChevronUp :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :disabled=\"!status.total\"\n :title=\"`${t('editor.search.next')} (Enter)`\"\n :aria-label=\"t('editor.search.next')\"\n @click=\"engine.search.next()\"\n >\n <ChevronDown :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :title=\"`${t('editor.close')} (Esc)`\"\n :aria-label=\"t('editor.close')\"\n @click=\"emit('close')\"\n >\n <X :size=\"16\" />\n </button>\n </div>\n\n <div v-if=\"showReplace && !readonly\" class=\"find-bar__row\">\n <el-input\n v-model=\"replaceTerm\"\n class=\"find-bar__input\"\n size=\"small\"\n :placeholder=\"t('editor.replace.placeholder')\"\n :ariaLabel=\"t('editor.replace.placeholder')\"\n @keydown=\"onReplaceKeydown\"\n />\n <button\n type=\"button\"\n class=\"doc-tb-button find-bar__text-button\"\n :disabled=\"!status.total\"\n @click=\"engine.replaceMatch(replaceTerm)\"\n >\n {{ t('editor.replace.one') }}\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button find-bar__text-button\"\n :disabled=\"!status.total\"\n @click=\"engine.replaceAllMatches(replaceTerm)\"\n >\n {{ t('editor.replace.all') }}\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* Panel floating over the top-right corner of the canvas. */\n.find-bar {\n position: absolute;\n z-index: 5;\n top: 8px;\n right: 20px;\n display: flex;\n align-items: flex-start;\n gap: 2px;\n padding: 5px;\n border: 1px solid var(--el-border-color-light);\n border-radius: 10px;\n background: var(--el-bg-color-overlay);\n box-shadow: var(--el-box-shadow-light);\n}\n\n.find-bar__rows {\n display: grid;\n gap: 5px;\n}\n\n.find-bar__row {\n display: flex;\n align-items: center;\n gap: 1px;\n}\n\n.find-bar__input {\n width: 220px;\n margin-right: 4px;\n}\n\n.find-bar__count {\n color: var(--el-text-color-secondary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n}\n\n/* The chevron points down while the replace row is open. */\n.find-bar__chevron {\n transition: transform 150ms ease;\n}\n\n.is-active > .find-bar__chevron {\n transform: rotate(90deg);\n}\n\n.find-bar__text-button {\n padding: 0 8px;\n font-size: 12px;\n}\n\n/* Narrow editors: the panel spans the canvas width and the inputs shrink. */\n@container (max-width: 560px) {\n .find-bar {\n right: 8px;\n left: 8px;\n }\n\n .find-bar__rows {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n .find-bar__input {\n width: auto;\n flex: 1 1 auto;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { CaseSensitive, ChevronDown, ChevronRight, ChevronUp, WholeWord, X } from '@lucide/vue';\nimport { ElInput, type InputInstance } from 'element-plus';\n\nimport type { DocumentEngine } from '../core/engine/engine';\nimport { formatShortcut, t } from '../core/labels';\n\n/**\n * Floating find and replace panel. Drives the engine's search controller, mirrors its match counter, and offers\n * replace actions that the engine records as undo steps.\n */\ndefineOptions({ name: 'EditorFindBar' });\n\ninterface Props {\n /** Engine whose document is searched. */\n engine: DocumentEngine;\n /** Hides the replacement row when the document cannot be edited. */\n readonly: boolean | undefined;\n}\n\ninterface Emits {\n /** The user asked to close the panel (close button). */\n close: [];\n}\n\nconst props = defineProps<Props>();\nconst emit = defineEmits<Emits>();\n/** Whether the replacement row is expanded; kept by the parent so Ctrl+H can open it directly. */\nconst showReplace = defineModel<boolean>('replace', { default: false });\n\n/** The longest selection that is copied into the search field when the panel opens. */\nconst MAX_PREFILL_LENGTH = 120;\n\nconst searchInput = ref<InputInstance>();\nconst searchTerm = ref('');\nconst replaceTerm = ref('');\nconst caseSensitive = ref(false);\nconst wholeWord = ref(false);\n/** Current match number and total, as reported by the engine. */\nconst status = ref(props.engine.search.state);\n\nlet stopListening: (() => void) | undefined;\n\n/** Focuses the search field and selects its text, so typing replaces the previous term. */\nconst focus = () => {\n searchInput.value?.focus();\n searchInput.value?.select();\n};\n\n/** Re-runs the search with the current term and options. */\nconst runSearch = () =>\n props.engine.search.setQuery(searchTerm.value, {\n caseSensitive: caseSensitive.value,\n wholeWord: wholeWord.value\n });\n\n/** Returns the Enter key press of an input event, or null for any other key. */\nconst asEnterKey = (event: Event | KeyboardEvent): KeyboardEvent | null =>\n event instanceof KeyboardEvent && event.key === 'Enter' ? event : null;\n\n/** Enter jumps to the next match, Shift+Enter to the previous one. */\nconst onSearchKeydown = (event: Event | KeyboardEvent) => {\n const enter = asEnterKey(event);\n if (!enter) return;\n enter.preventDefault();\n if (enter.shiftKey) props.engine.search.previous();\n else props.engine.search.next();\n};\n\n/** Enter in the replace field replaces the current match. */\nconst onReplaceKeydown = (event: Event | KeyboardEvent) => {\n const enter = asEnterKey(event);\n if (!enter) return;\n enter.preventDefault();\n props.engine.replaceMatch(replaceTerm.value);\n};\n\nwatch([searchTerm, caseSensitive, wholeWord], runSearch);\n\nonMounted(async () => {\n stopListening = props.engine.on('search', state => {\n status.value = state;\n });\n // Like Word, start with the selected text as the search term, selected so that typing replaces it.\n const selected = props.engine.getSelectedText();\n if (selected && selected.length <= MAX_PREFILL_LENGTH && !selected.includes('\\n')) searchTerm.value = selected;\n // select() has to run after v-model has written the prefilled value into the input.\n await nextTick();\n focus();\n});\n\nonBeforeUnmount(() => {\n stopListening?.();\n props.engine.search.clear();\n});\n\ndefineExpose({ focus });\n</script>\n\n<template>\n <div class=\"find-bar\" role=\"search\" :aria-label=\"t('editor.search.title')\">\n <button\n v-if=\"!readonly\"\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': showReplace }\"\n :title=\"t('editor.replace.toggle')\"\n :aria-label=\"t('editor.replace.toggle')\"\n :aria-expanded=\"showReplace\"\n @click=\"showReplace = !showReplace\"\n >\n <ChevronRight class=\"find-bar__chevron\" :size=\"14\" />\n </button>\n\n <div class=\"find-bar__rows\">\n <div class=\"find-bar__row\">\n <el-input\n ref=\"searchInput\"\n v-model=\"searchTerm\"\n class=\"find-bar__input\"\n size=\"small\"\n :placeholder=\"t('editor.search.placeholder')\"\n :ariaLabel=\"t('editor.search.placeholder')\"\n @keydown=\"onSearchKeydown\"\n >\n <template #suffix>\n <span class=\"find-bar__count\">{{ status.current }}/{{ status.total }}</span>\n </template>\n </el-input>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': caseSensitive }\"\n :title=\"t('editor.search.caseSensitive')\"\n :aria-label=\"t('editor.search.caseSensitive')\"\n :aria-pressed=\"caseSensitive\"\n @click=\"caseSensitive = !caseSensitive\"\n >\n <CaseSensitive :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': wholeWord }\"\n :title=\"t('editor.search.wholeWord')\"\n :aria-label=\"t('editor.search.wholeWord')\"\n :aria-pressed=\"wholeWord\"\n @click=\"wholeWord = !wholeWord\"\n >\n <WholeWord :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :disabled=\"!status.total\"\n :title=\"`${t('editor.search.previous')} (${formatShortcut('Shift+Enter')})`\"\n :aria-label=\"t('editor.search.previous')\"\n @click=\"engine.search.previous()\"\n >\n <ChevronUp :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :disabled=\"!status.total\"\n :title=\"`${t('editor.search.next')} (Enter)`\"\n :aria-label=\"t('editor.search.next')\"\n @click=\"engine.search.next()\"\n >\n <ChevronDown :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :title=\"`${t('editor.close')} (Esc)`\"\n :aria-label=\"t('editor.close')\"\n @click=\"emit('close')\"\n >\n <X :size=\"16\" />\n </button>\n </div>\n\n <div v-if=\"showReplace && !readonly\" class=\"find-bar__row\">\n <el-input\n v-model=\"replaceTerm\"\n class=\"find-bar__input\"\n size=\"small\"\n :placeholder=\"t('editor.replace.placeholder')\"\n :ariaLabel=\"t('editor.replace.placeholder')\"\n @keydown=\"onReplaceKeydown\"\n />\n <button\n type=\"button\"\n class=\"doc-tb-button find-bar__text-button\"\n :disabled=\"!status.total\"\n @click=\"engine.replaceMatch(replaceTerm)\"\n >\n {{ t('editor.replace.one') }}\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button find-bar__text-button\"\n :disabled=\"!status.total\"\n @click=\"engine.replaceAllMatches(replaceTerm)\"\n >\n {{ t('editor.replace.all') }}\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* Panel floating over the top-right corner of the canvas. */\n.find-bar {\n position: absolute;\n z-index: 5;\n top: 8px;\n right: 20px;\n display: flex;\n align-items: flex-start;\n gap: 2px;\n padding: 5px;\n border: 1px solid var(--el-border-color-light);\n border-radius: 10px;\n background: var(--el-bg-color-overlay);\n box-shadow: var(--el-box-shadow-light);\n}\n\n.find-bar__rows {\n display: grid;\n gap: 5px;\n}\n\n.find-bar__row {\n display: flex;\n align-items: center;\n gap: 1px;\n}\n\n.find-bar__input {\n width: 220px;\n margin-right: 4px;\n}\n\n.find-bar__count {\n color: var(--el-text-color-secondary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n}\n\n/* The chevron points down while the replace row is open. */\n.find-bar__chevron {\n transition: transform 150ms ease;\n}\n\n.is-active > .find-bar__chevron {\n transform: rotate(90deg);\n}\n\n.find-bar__text-button {\n padding: 0 8px;\n font-size: 12px;\n}\n\n/* Narrow editors: the panel spans the canvas width and the inputs shrink. */\n@container (max-width: 560px) {\n .find-bar {\n right: 8px;\n left: 8px;\n }\n\n .find-bar__rows {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n .find-bar__input {\n width: auto;\n flex: 1 1 auto;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { FileText, Maximize2, Minimize2, Minus, MoveHorizontal, Plus, ScrollText } from '@lucide/vue';\n\nimport { t } from '../core/labels';\nimport { type DocumentViewMode, ZOOM_MAX, ZOOM_MIN, ZOOM_STEP } from '../core/page';\n\n/**\n * Bottom bar of the editor: page position, word and character counts, view switch, zoom controls and\n * the fullscreen toggle.\n */\ndefineOptions({ name: 'EditorStatusBar' });\n\ninterface Props {\n /** Number of characters in the document. */\n characters: number;\n /** Page that contains the caret (page view only). */\n currentPage: number;\n /** Whether the editor currently covers the screen. */\n fullscreen: boolean;\n /** Character limit; 0 means unlimited and shows a plain counter. */\n maxLength: number | undefined;\n /** Number of pages the document occupies (page view only). */\n pageCount: number;\n /** Number of words in the document. */\n words: number;\n}\n\ninterface Emits {\n /** Zoom so the page fills the canvas width. */\n fitWidth: [];\n /** Enter or leave fullscreen. */\n toggleFullscreen: [];\n}\n\ndefineProps<Props>();\nconst emit = defineEmits<Emits>();\n/** Zoom in percent; every change is snapped to the zoom step and clamped to the allowed range. */\nconst zoom = defineModel<number>('zoom', { required: true });\n/** Page layout with sheets, or the continuous web layout. */\nconst viewMode = defineModel<DocumentViewMode>('viewMode', { required: true });\n\n/** Zoom restored by clicking the percentage. */\nconst DEFAULT_ZOOM = 100;\n/** Position of the default zoom mark along the slider, from 0 to 1. */\nconst DEFAULT_ZOOM_RATIO = (DEFAULT_ZOOM - ZOOM_MIN) / (ZOOM_MAX - ZOOM_MIN);\n/** Options of the view switch with their icons and label keys. */\nconst VIEW_MODES = [\n { value: 'page', icon: FileText, label: 'editor.view.page' },\n { value: 'web', icon: ScrollText, label: 'editor.view.web' }\n] as const;\n\n/** Publishes a zoom value snapped to the zoom step and kept within the allowed range. */\nconst setZoom = (value: number) => {\n zoom.value = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, Math.round(value / ZOOM_STEP) * ZOOM_STEP));\n};\n\n/** Applies the value of the zoom slider. */\nconst onSliderInput = (event: Event) => setZoom(Number((event.target as HTMLInputElement).value));\n</script>\n\n<template>\n <footer class=\"status-bar\">\n <div class=\"status-bar__info\" aria-live=\"polite\">\n <span v-if=\"viewMode === 'page'\">{{ t('editor.status.page', { current: currentPage, total: pageCount }) }}</span>\n <span>{{ t('editor.status.words', { count: words }) }}</span>\n <span v-if=\"maxLength\" :class=\"{ 'is-limit': characters >= maxLength }\">{{ characters }} / {{ maxLength }}</span>\n <span v-else>{{ t('editor.status.characters', { count: characters }) }}</span>\n </div>\n\n <div class=\"status-bar__controls\">\n <div class=\"status-bar__segmented\" role=\"radiogroup\" :aria-label=\"t('editor.view.page')\">\n <button\n v-for=\"mode in VIEW_MODES\"\n :key=\"mode.value\"\n type=\"button\"\n class=\"doc-tb-button\"\n role=\"radio\"\n :class=\"{ 'is-active': viewMode === mode.value }\"\n :title=\"t(mode.label)\"\n :aria-label=\"t(mode.label)\"\n :aria-checked=\"viewMode === mode.value\"\n @click=\"viewMode = mode.value\"\n >\n <component :is=\"mode.icon\" :size=\"14\" />\n </button>\n </div>\n\n <button\n type=\"button\"\n class=\"doc-tb-button status-bar__zoom-step\"\n :disabled=\"zoom <= ZOOM_MIN\"\n :title=\"t('editor.zoomOut')\"\n :aria-label=\"t('editor.zoomOut')\"\n @click=\"setZoom(zoom - ZOOM_STEP)\"\n >\n <Minus :size=\"12\" />\n </button>\n <input\n class=\"status-bar__slider\"\n type=\"range\"\n :min=\"ZOOM_MIN\"\n :max=\"ZOOM_MAX\"\n :step=\"ZOOM_STEP\"\n :value=\"zoom\"\n :aria-label=\"t('editor.zoom')\"\n @input=\"onSliderInput\"\n />\n <button\n type=\"button\"\n class=\"doc-tb-button status-bar__zoom-step\"\n :disabled=\"zoom >= ZOOM_MAX\"\n :title=\"t('editor.zoomIn')\"\n :aria-label=\"t('editor.zoomIn')\"\n @click=\"setZoom(zoom + ZOOM_STEP)\"\n >\n <Plus :size=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button status-bar__zoom\"\n :title=\"t('editor.zoomReset')\"\n :aria-label=\"t('editor.zoomReset')\"\n @click=\"setZoom(DEFAULT_ZOOM)\"\n >\n {{ zoom }}%\n </button>\n <button\n v-if=\"viewMode === 'page'\"\n type=\"button\"\n class=\"doc-tb-button\"\n :title=\"t('editor.fitWidth')\"\n :aria-label=\"t('editor.fitWidth')\"\n @click=\"emit('fitWidth')\"\n >\n <MoveHorizontal :size=\"14\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :title=\"t(fullscreen ? 'editor.exitFullscreen' : 'editor.enterFullscreen')\"\n :aria-label=\"t(fullscreen ? 'editor.exitFullscreen' : 'editor.enterFullscreen')\"\n @click=\"emit('toggleFullscreen')\"\n >\n <component :is=\"fullscreen ? Minimize2 : Maximize2\" :size=\"14\" />\n </button>\n </div>\n </footer>\n</template>\n\n<style scoped>\n.status-bar {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n min-height: 32px;\n padding: 0 6px 0 12px;\n border-top: 1px solid var(--el-border-color-lighter);\n color: var(--el-text-color-secondary);\n background: var(--el-bg-color);\n font-size: 12px;\n user-select: none;\n}\n\n.status-bar__info {\n display: flex;\n min-width: 0;\n gap: 14px;\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* Character counter at or over the limit. */\n.is-limit {\n color: var(--el-color-danger);\n font-weight: 600;\n}\n\n.status-bar__controls {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 1px;\n}\n\n/* The bar uses smaller buttons than the toolbar. */\n.status-bar .doc-tb-button {\n min-width: 24px;\n height: 24px;\n font-size: 12px;\n}\n\n/* Page / web view switch. */\n.status-bar__segmented {\n display: flex;\n margin-right: 6px;\n padding: 2px;\n border-radius: 7px;\n background: var(--el-fill-color-light);\n}\n\n.status-bar__segmented .doc-tb-button {\n height: 20px;\n}\n\n.status-bar__segmented .doc-tb-button.is-active {\n background: var(--el-bg-color);\n box-shadow: 0 1px 2px rgb(16 24 40 / 12%);\n}\n\n/* Zoom slider in the office style: a hairline track with a mark at 100% and a narrow thumb. */\n.status-bar__slider {\n --slider-line: var(--el-text-color-placeholder);\n --slider-thumb: var(--el-text-color-regular);\n\n width: 100px;\n height: 16px;\n margin: 0 2px;\n padding: 0;\n cursor: pointer;\n background:\n linear-gradient(var(--slider-line), var(--slider-line)) calc(2px + (100% - 4px) * v-bind(DEFAULT_ZOOM_RATIO)) 50% /\n 1px 8px no-repeat,\n linear-gradient(var(--slider-line), var(--slider-line)) 0 50% / 100% 1px no-repeat;\n appearance: none;\n}\n\n.status-bar__slider:focus {\n outline: none;\n}\n\n.status-bar__slider::-webkit-slider-runnable-track {\n height: 16px;\n background: transparent;\n}\n\n.status-bar__slider::-moz-range-track {\n height: 16px;\n background: transparent;\n}\n\n.status-bar__slider::-webkit-slider-thumb {\n width: 4px;\n height: 12px;\n margin-top: 2px;\n border: none;\n border-radius: 1px;\n background: var(--slider-thumb);\n transition: background-color 0.15s;\n appearance: none;\n}\n\n.status-bar__slider::-moz-range-thumb {\n width: 4px;\n height: 12px;\n border: none;\n border-radius: 1px;\n background: var(--slider-thumb);\n transition: background-color 0.15s;\n}\n\n.status-bar__slider:hover,\n.status-bar__slider:active {\n --slider-thumb: var(--el-color-primary);\n}\n\n.status-bar__slider:focus-visible::-webkit-slider-thumb {\n outline: 2px solid var(--el-color-primary-light-5);\n outline-offset: 1px;\n}\n\n.status-bar__slider:focus-visible::-moz-range-thumb {\n outline: 2px solid var(--el-color-primary-light-5);\n outline-offset: 1px;\n}\n\n/* Zoom in / out buttons are plain glyphs next to the slider. */\n.status-bar .status-bar__zoom-step {\n min-width: 20px;\n height: 20px;\n padding: 0;\n}\n\n.status-bar__zoom {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n}\n\n/* Narrow editors keep only the zoom buttons. */\n@container (max-width: 560px) {\n .status-bar__slider,\n .status-bar__segmented {\n display: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { FileText, Maximize2, Minimize2, Minus, MoveHorizontal, Plus, ScrollText } from '@lucide/vue';\n\nimport { t } from '../core/labels';\nimport { type DocumentViewMode, ZOOM_MAX, ZOOM_MIN, ZOOM_STEP } from '../core/page';\n\n/**\n * Bottom bar of the editor: page position, word and character counts, view switch, zoom controls and\n * the fullscreen toggle.\n */\ndefineOptions({ name: 'EditorStatusBar' });\n\ninterface Props {\n /** Number of characters in the document. */\n characters: number;\n /** Page that contains the caret (page view only). */\n currentPage: number;\n /** Whether the editor currently covers the screen. */\n fullscreen: boolean;\n /** Character limit; 0 means unlimited and shows a plain counter. */\n maxLength: number | undefined;\n /** Number of pages the document occupies (page view only). */\n pageCount: number;\n /** Number of words in the document. */\n words: number;\n}\n\ninterface Emits {\n /** Zoom so the page fills the canvas width. */\n fitWidth: [];\n /** Enter or leave fullscreen. */\n toggleFullscreen: [];\n}\n\ndefineProps<Props>();\nconst emit = defineEmits<Emits>();\n/** Zoom in percent; every change is snapped to the zoom step and clamped to the allowed range. */\nconst zoom = defineModel<number>('zoom', { required: true });\n/** Page layout with sheets, or the continuous web layout. */\nconst viewMode = defineModel<DocumentViewMode>('viewMode', { required: true });\n\n/** Zoom restored by clicking the percentage. */\nconst DEFAULT_ZOOM = 100;\n/** Position of the default zoom mark along the slider, from 0 to 1. */\nconst DEFAULT_ZOOM_RATIO = (DEFAULT_ZOOM - ZOOM_MIN) / (ZOOM_MAX - ZOOM_MIN);\n/** Options of the view switch with their icons and label keys. */\nconst VIEW_MODES = [\n { value: 'page', icon: FileText, label: 'editor.view.page' },\n { value: 'web', icon: ScrollText, label: 'editor.view.web' }\n] as const;\n\n/** Publishes a zoom value snapped to the zoom step and kept within the allowed range. */\nconst setZoom = (value: number) => {\n zoom.value = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, Math.round(value / ZOOM_STEP) * ZOOM_STEP));\n};\n\n/** Applies the value of the zoom slider. */\nconst onSliderInput = (event: Event) => setZoom(Number((event.target as HTMLInputElement).value));\n</script>\n\n<template>\n <footer class=\"status-bar\">\n <div class=\"status-bar__info\" aria-live=\"polite\">\n <span v-if=\"viewMode === 'page'\">{{ t('editor.status.page', { current: currentPage, total: pageCount }) }}</span>\n <span>{{ t('editor.status.words', { count: words }) }}</span>\n <span v-if=\"maxLength\" :class=\"{ 'is-limit': characters >= maxLength }\">{{ characters }} / {{ maxLength }}</span>\n <span v-else>{{ t('editor.status.characters', { count: characters }) }}</span>\n </div>\n\n <div class=\"status-bar__controls\">\n <div class=\"status-bar__segmented\" role=\"radiogroup\" :aria-label=\"t('editor.view.page')\">\n <button\n v-for=\"mode in VIEW_MODES\"\n :key=\"mode.value\"\n type=\"button\"\n class=\"doc-tb-button\"\n role=\"radio\"\n :class=\"{ 'is-active': viewMode === mode.value }\"\n :title=\"t(mode.label)\"\n :aria-label=\"t(mode.label)\"\n :aria-checked=\"viewMode === mode.value\"\n @click=\"viewMode = mode.value\"\n >\n <component :is=\"mode.icon\" :size=\"14\" />\n </button>\n </div>\n\n <button\n type=\"button\"\n class=\"doc-tb-button status-bar__zoom-step\"\n :disabled=\"zoom <= ZOOM_MIN\"\n :title=\"t('editor.zoomOut')\"\n :aria-label=\"t('editor.zoomOut')\"\n @click=\"setZoom(zoom - ZOOM_STEP)\"\n >\n <Minus :size=\"12\" />\n </button>\n <input\n class=\"status-bar__slider\"\n type=\"range\"\n :min=\"ZOOM_MIN\"\n :max=\"ZOOM_MAX\"\n :step=\"ZOOM_STEP\"\n :value=\"zoom\"\n :aria-label=\"t('editor.zoom')\"\n @input=\"onSliderInput\"\n />\n <button\n type=\"button\"\n class=\"doc-tb-button status-bar__zoom-step\"\n :disabled=\"zoom >= ZOOM_MAX\"\n :title=\"t('editor.zoomIn')\"\n :aria-label=\"t('editor.zoomIn')\"\n @click=\"setZoom(zoom + ZOOM_STEP)\"\n >\n <Plus :size=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button status-bar__zoom\"\n :title=\"t('editor.zoomReset')\"\n :aria-label=\"t('editor.zoomReset')\"\n @click=\"setZoom(DEFAULT_ZOOM)\"\n >\n {{ zoom }}%\n </button>\n <button\n v-if=\"viewMode === 'page'\"\n type=\"button\"\n class=\"doc-tb-button\"\n :title=\"t('editor.fitWidth')\"\n :aria-label=\"t('editor.fitWidth')\"\n @click=\"emit('fitWidth')\"\n >\n <MoveHorizontal :size=\"14\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :title=\"t(fullscreen ? 'editor.exitFullscreen' : 'editor.enterFullscreen')\"\n :aria-label=\"t(fullscreen ? 'editor.exitFullscreen' : 'editor.enterFullscreen')\"\n @click=\"emit('toggleFullscreen')\"\n >\n <component :is=\"fullscreen ? Minimize2 : Maximize2\" :size=\"14\" />\n </button>\n </div>\n </footer>\n</template>\n\n<style scoped>\n.status-bar {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n min-height: 32px;\n padding: 0 6px 0 12px;\n border-top: 1px solid var(--el-border-color-lighter);\n color: var(--el-text-color-secondary);\n background: var(--el-bg-color);\n font-size: 12px;\n user-select: none;\n}\n\n.status-bar__info {\n display: flex;\n min-width: 0;\n gap: 14px;\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* Character counter at or over the limit. */\n.is-limit {\n color: var(--el-color-danger);\n font-weight: 600;\n}\n\n.status-bar__controls {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 1px;\n}\n\n/* The bar uses smaller buttons than the toolbar. */\n.status-bar .doc-tb-button {\n min-width: 24px;\n height: 24px;\n font-size: 12px;\n}\n\n/* Page / web view switch. */\n.status-bar__segmented {\n display: flex;\n margin-right: 6px;\n padding: 2px;\n border-radius: 7px;\n background: var(--el-fill-color-light);\n}\n\n.status-bar__segmented .doc-tb-button {\n height: 20px;\n}\n\n.status-bar__segmented .doc-tb-button.is-active {\n background: var(--el-bg-color);\n box-shadow: 0 1px 2px rgb(16 24 40 / 12%);\n}\n\n/* Zoom slider in the office style: a hairline track with a mark at 100% and a narrow thumb. */\n.status-bar__slider {\n --slider-line: var(--el-text-color-placeholder);\n --slider-thumb: var(--el-text-color-regular);\n\n width: 100px;\n height: 16px;\n margin: 0 2px;\n padding: 0;\n cursor: pointer;\n background:\n linear-gradient(var(--slider-line), var(--slider-line)) calc(2px + (100% - 4px) * v-bind(DEFAULT_ZOOM_RATIO)) 50% /\n 1px 8px no-repeat,\n linear-gradient(var(--slider-line), var(--slider-line)) 0 50% / 100% 1px no-repeat;\n appearance: none;\n}\n\n.status-bar__slider:focus {\n outline: none;\n}\n\n.status-bar__slider::-webkit-slider-runnable-track {\n height: 16px;\n background: transparent;\n}\n\n.status-bar__slider::-moz-range-track {\n height: 16px;\n background: transparent;\n}\n\n.status-bar__slider::-webkit-slider-thumb {\n width: 4px;\n height: 12px;\n margin-top: 2px;\n border: none;\n border-radius: 1px;\n background: var(--slider-thumb);\n transition: background-color 0.15s;\n appearance: none;\n}\n\n.status-bar__slider::-moz-range-thumb {\n width: 4px;\n height: 12px;\n border: none;\n border-radius: 1px;\n background: var(--slider-thumb);\n transition: background-color 0.15s;\n}\n\n.status-bar__slider:hover,\n.status-bar__slider:active {\n --slider-thumb: var(--el-color-primary);\n}\n\n.status-bar__slider:focus-visible::-webkit-slider-thumb {\n outline: 2px solid var(--el-color-primary-light-5);\n outline-offset: 1px;\n}\n\n.status-bar__slider:focus-visible::-moz-range-thumb {\n outline: 2px solid var(--el-color-primary-light-5);\n outline-offset: 1px;\n}\n\n/* Zoom in / out buttons are plain glyphs next to the slider. */\n.status-bar .status-bar__zoom-step {\n min-width: 20px;\n height: 20px;\n padding: 0;\n}\n\n.status-bar__zoom {\n min-width: 40px;\n font-variant-numeric: tabular-nums;\n}\n\n/* Narrow editors keep only the zoom buttons. */\n@container (max-width: 560px) {\n .status-bar__slider,\n .status-bar__segmented {\n display: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref } from 'vue';\nimport { ChevronDown, Highlighter } from '@lucide/vue';\nimport { ElPopover } from 'element-plus';\n\nimport { t } from '../core/labels';\n\n/**\n * Word-style split colour button: the main part re-applies the last used colour, the caret opens a palette with\n * a reset option and a custom colour input.\n */\ndefineOptions({ name: 'EditorColorPicker' });\n\ninterface Props {\n /** Colour applied under the caret, used to mark the selected swatch. */\n current: string;\n /** Disables both parts of the button. */\n disabled: boolean;\n /** Whether the picker sets the text colour or the highlight colour. */\n mode: 'text' | 'highlight';\n}\n\ninterface Emits {\n /** A colour was picked; `null` removes the colour. */\n select: [color: string | null];\n}\n\nconst props = defineProps<Props>();\nconst emit = defineEmits<Emits>();\n\n/** Swatches of the palette, ten per row like the grid. */\n// biome-ignore format: one row per hue family keeps the swatch grid readable.\nconst PALETTE = [\n '#000000', '#434343', '#666666', '#999999', '#b7b7b7', '#cccccc', '#d9d9d9', '#efefef', '#f3f3f3', '#ffffff',\n '#980000', '#ff0000', '#ff9900', '#ffff00', '#00ff00', '#00ffff', '#4a86e8', '#0000ff', '#9900ff', '#ff00ff',\n '#e6b8af', '#f4cccc', '#fce5cd', '#fff2cc', '#d9ead3', '#d0e0e3', '#c9daf8', '#cfe2f3', '#d9d2e9', '#ead1dc',\n '#dd7e6b', '#ea9999', '#f9cb9c', '#ffe599', '#b6d7a8', '#a2c4c9', '#a4c2f4', '#9fc5e8', '#b4a7d6', '#d5a6bd',\n '#a61c00', '#cc0000', '#e69138', '#f1c232', '#6aa84f', '#45818e', '#3c78d8', '#3d85c6', '#674ea7', '#a64d79',\n '#5b0f00', '#660000', '#783f04', '#7f6000', '#274e13', '#0c343d', '#1c4587', '#073763', '#20124d', '#4c1130'\n];\n/** Colours offered by the main button before anything was picked. */\nconst INITIAL_COLOR = { text: '#cc0000', highlight: '#ffff00' } as const;\n/** Popover width that fits ten swatches per row. */\nconst POPOVER_WIDTH = 236;\n\nconst visible = ref(false);\n/** Colour re-applied by the main button, like Word. */\nconst lastColor = ref<string>(INITIAL_COLOR[props.mode]);\n\n/** Accessible name and tooltip of the picker. */\nconst label = t(props.mode === 'text' ? 'editor.textColor' : 'editor.highlight');\n\n/** Closes the palette, remembers a picked colour and emits it. */\nconst apply = (color: string | null) => {\n if (color) lastColor.value = color;\n visible.value = false;\n emit('select', color);\n};\n\n/** Applies the value chosen in the native colour input. */\nconst onCustomColor = (event: Event) => apply((event.target as HTMLInputElement).value);\n</script>\n\n<template>\n <div class=\"color-picker\">\n <button\n type=\"button\"\n class=\"doc-tb-button color-picker__apply\"\n :disabled=\"disabled\"\n :title=\"label\"\n :aria-label=\"label\"\n @mousedown.prevent\n @click=\"apply(lastColor)\"\n >\n <span v-if=\"mode === 'text'\" class=\"color-picker__letter\">A</span>\n <Highlighter v-else :size=\"15\" />\n <span class=\"color-picker__bar\" :style=\"{ background: lastColor }\" />\n </button>\n <el-popover\n v-model:visible=\"visible\"\n trigger=\"click\"\n placement=\"bottom-start\"\n popper-class=\"doc-editor-popper\"\n :width=\"POPOVER_WIDTH\"\n :disabled=\"disabled\"\n :show-arrow=\"false\"\n >\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--caret\"\n :disabled=\"disabled\"\n :aria-label=\"label\"\n @mousedown.prevent\n >\n <ChevronDown :size=\"12\" />\n </button>\n </template>\n\n <div class=\"palette\">\n <button type=\"button\" class=\"palette__reset\" @click=\"apply(null)\">\n <span class=\"palette__reset-swatch\" :class=\"`is-${mode}`\" />\n {{ t(mode === 'text' ? 'editor.colors.automatic' : 'editor.colors.none') }}\n </button>\n <div class=\"palette__grid\">\n <button\n v-for=\"color in PALETTE\"\n :key=\"color\"\n type=\"button\"\n class=\"palette__swatch\"\n :class=\"{ 'is-selected': color === current.toLowerCase() }\"\n :style=\"{ background: color }\"\n :title=\"color\"\n :aria-label=\"`${label}: ${color}`\"\n @click=\"apply(color)\"\n />\n </div>\n <label class=\"palette__custom\">\n <input type=\"color\" :value=\"lastColor\" @change=\"onCustomColor\" />\n {{ t('editor.colors.custom') }}\n </label>\n </div>\n </el-popover>\n </div>\n</template>\n\n<style scoped>\n.color-picker {\n display: inline-flex;\n align-items: center;\n}\n\n/* Main button: letter or marker icon with a bar in the last used colour. */\n.color-picker__apply {\n position: relative;\n min-width: 26px;\n padding-bottom: 3px;\n}\n\n.color-picker__letter {\n font-family: Georgia, serif;\n font-size: 15px;\n font-weight: 700;\n}\n\n.color-picker__bar {\n position: absolute;\n right: 6px;\n bottom: 4px;\n left: 6px;\n height: 3px;\n border-radius: 1px;\n box-shadow: inset 0 0 0 1px rgb(0 0 0 / 12%);\n}\n\n/* Palette popover. */\n.palette {\n display: grid;\n gap: 8px;\n}\n\n.palette__reset,\n.palette__custom {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 28px;\n padding: 0 6px;\n border: 0;\n border-radius: 6px;\n color: var(--el-text-color-regular);\n background: transparent;\n font: inherit;\n font-size: 13px;\n text-align: left;\n cursor: pointer;\n}\n\n.palette__reset:hover,\n.palette__custom:hover {\n background: var(--el-fill-color-light);\n}\n\n.palette__reset-swatch {\n width: 16px;\n height: 16px;\n box-sizing: border-box;\n border: 1px solid var(--el-border-color);\n border-radius: 3px;\n}\n\n.palette__reset-swatch.is-text {\n background: #1f2328;\n}\n\n/* A struck-through swatch means \"no highlight\". */\n.palette__reset-swatch.is-highlight {\n background: linear-gradient(to top right, #fff calc(50% - 1px), #e11d48 50%, #fff calc(50% + 1px));\n}\n\n.palette__grid {\n display: grid;\n grid-template-columns: repeat(10, 18px);\n justify-content: space-between;\n gap: 3px;\n}\n\n.palette__swatch {\n width: 18px;\n height: 18px;\n padding: 0;\n border: 1px solid rgb(0 0 0 / 12%);\n border-radius: 3px;\n cursor: pointer;\n transition: transform 100ms ease;\n}\n\n.palette__swatch:hover {\n transform: scale(1.15);\n}\n\n.palette__swatch.is-selected {\n box-shadow:\n 0 0 0 2px #fff,\n 0 0 0 3.5px var(--el-color-primary);\n}\n\n.palette__custom input {\n width: 22px;\n height: 22px;\n padding: 0;\n border: 0;\n background: none;\n cursor: pointer;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref } from 'vue';\nimport { ChevronDown, Highlighter } from '@lucide/vue';\nimport { ElPopover } from 'element-plus';\n\nimport { t } from '../core/labels';\n\n/**\n * Word-style split colour button: the main part re-applies the last used colour, the caret opens a palette with\n * a reset option and a custom colour input.\n */\ndefineOptions({ name: 'EditorColorPicker' });\n\ninterface Props {\n /** Colour applied under the caret, used to mark the selected swatch. */\n current: string;\n /** Disables both parts of the button. */\n disabled: boolean;\n /** Whether the picker sets the text colour or the highlight colour. */\n mode: 'text' | 'highlight';\n}\n\ninterface Emits {\n /** A colour was picked; `null` removes the colour. */\n select: [color: string | null];\n}\n\nconst props = defineProps<Props>();\nconst emit = defineEmits<Emits>();\n\n/** Swatches of the palette, ten per row like the grid. */\n// biome-ignore format: one row per hue family keeps the swatch grid readable.\nconst PALETTE = [\n '#000000', '#434343', '#666666', '#999999', '#b7b7b7', '#cccccc', '#d9d9d9', '#efefef', '#f3f3f3', '#ffffff',\n '#980000', '#ff0000', '#ff9900', '#ffff00', '#00ff00', '#00ffff', '#4a86e8', '#0000ff', '#9900ff', '#ff00ff',\n '#e6b8af', '#f4cccc', '#fce5cd', '#fff2cc', '#d9ead3', '#d0e0e3', '#c9daf8', '#cfe2f3', '#d9d2e9', '#ead1dc',\n '#dd7e6b', '#ea9999', '#f9cb9c', '#ffe599', '#b6d7a8', '#a2c4c9', '#a4c2f4', '#9fc5e8', '#b4a7d6', '#d5a6bd',\n '#a61c00', '#cc0000', '#e69138', '#f1c232', '#6aa84f', '#45818e', '#3c78d8', '#3d85c6', '#674ea7', '#a64d79',\n '#5b0f00', '#660000', '#783f04', '#7f6000', '#274e13', '#0c343d', '#1c4587', '#073763', '#20124d', '#4c1130'\n];\n/** Colours offered by the main button before anything was picked. */\nconst INITIAL_COLOR = { text: '#cc0000', highlight: '#ffff00' } as const;\n/** Popover width that fits ten swatches per row. */\nconst POPOVER_WIDTH = 236;\n\nconst visible = ref(false);\n/** Colour re-applied by the main button, like Word. */\nconst lastColor = ref<string>(INITIAL_COLOR[props.mode]);\n\n/** Accessible name and tooltip of the picker. */\nconst label = t(props.mode === 'text' ? 'editor.textColor' : 'editor.highlight');\n\n/** Closes the palette, remembers a picked colour and emits it. */\nconst apply = (color: string | null) => {\n if (color) lastColor.value = color;\n visible.value = false;\n emit('select', color);\n};\n\n/** Applies the value chosen in the native colour input. */\nconst onCustomColor = (event: Event) => apply((event.target as HTMLInputElement).value);\n</script>\n\n<template>\n <div class=\"color-picker\">\n <button\n type=\"button\"\n class=\"doc-tb-button color-picker__apply\"\n :disabled=\"disabled\"\n :title=\"label\"\n :aria-label=\"label\"\n @mousedown.prevent\n @click=\"apply(lastColor)\"\n >\n <span v-if=\"mode === 'text'\" class=\"color-picker__letter\">A</span>\n <Highlighter v-else :size=\"15\" />\n <span class=\"color-picker__bar\" :style=\"{ background: lastColor }\" />\n </button>\n <el-popover\n v-model:visible=\"visible\"\n trigger=\"click\"\n placement=\"bottom-start\"\n popper-class=\"doc-editor-popper\"\n :width=\"POPOVER_WIDTH\"\n :disabled=\"disabled\"\n :show-arrow=\"false\"\n >\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--caret\"\n :disabled=\"disabled\"\n :aria-label=\"label\"\n @mousedown.prevent\n >\n <ChevronDown :size=\"12\" />\n </button>\n </template>\n\n <div class=\"palette\">\n <button type=\"button\" class=\"palette__reset\" @click=\"apply(null)\">\n <span class=\"palette__reset-swatch\" :class=\"`is-${mode}`\" />\n {{ t(mode === 'text' ? 'editor.colors.automatic' : 'editor.colors.none') }}\n </button>\n <div class=\"palette__grid\">\n <button\n v-for=\"color in PALETTE\"\n :key=\"color\"\n type=\"button\"\n class=\"palette__swatch\"\n :class=\"{ 'is-selected': color === current.toLowerCase() }\"\n :style=\"{ background: color }\"\n :title=\"color\"\n :aria-label=\"`${label}: ${color}`\"\n @click=\"apply(color)\"\n />\n </div>\n <label class=\"palette__custom\">\n <input type=\"color\" :value=\"lastColor\" @change=\"onCustomColor\" />\n {{ t('editor.colors.custom') }}\n </label>\n </div>\n </el-popover>\n </div>\n</template>\n\n<style scoped>\n.color-picker {\n display: inline-flex;\n align-items: center;\n}\n\n/* Main button: letter or marker icon with a bar in the last used colour. */\n.color-picker__apply {\n position: relative;\n min-width: 26px;\n padding-bottom: 3px;\n}\n\n.color-picker__letter {\n font-family: Georgia, serif;\n font-size: 15px;\n font-weight: 700;\n}\n\n.color-picker__bar {\n position: absolute;\n right: 6px;\n bottom: 4px;\n left: 6px;\n height: 3px;\n border-radius: 1px;\n box-shadow: inset 0 0 0 1px rgb(0 0 0 / 12%);\n}\n\n/* Palette popover. */\n.palette {\n display: grid;\n gap: 8px;\n}\n\n.palette__reset,\n.palette__custom {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 28px;\n padding: 0 6px;\n border: 0;\n border-radius: 6px;\n color: var(--el-text-color-regular);\n background: transparent;\n font: inherit;\n font-size: 13px;\n text-align: left;\n cursor: pointer;\n}\n\n.palette__reset:hover,\n.palette__custom:hover {\n background: var(--el-fill-color-light);\n}\n\n.palette__reset-swatch {\n width: 16px;\n height: 16px;\n box-sizing: border-box;\n border: 1px solid var(--el-border-color);\n border-radius: 3px;\n}\n\n.palette__reset-swatch.is-text {\n background: #1f2328;\n}\n\n/* A struck-through swatch means \"no highlight\". */\n.palette__reset-swatch.is-highlight {\n background: linear-gradient(to top right, #fff calc(50% - 1px), #e11d48 50%, #fff calc(50% + 1px));\n}\n\n.palette__grid {\n display: grid;\n grid-template-columns: repeat(10, 18px);\n justify-content: space-between;\n gap: 3px;\n}\n\n.palette__swatch {\n width: 18px;\n height: 18px;\n padding: 0;\n border: 1px solid rgb(0 0 0 / 12%);\n border-radius: 3px;\n cursor: pointer;\n transition: transform 100ms ease;\n}\n\n.palette__swatch:hover {\n transform: scale(1.15);\n}\n\n.palette__swatch.is-selected {\n box-shadow:\n 0 0 0 2px #fff,\n 0 0 0 3.5px var(--el-color-primary);\n}\n\n.palette__custom input {\n width: 22px;\n height: 22px;\n padding: 0;\n border: 0;\n background: none;\n cursor: pointer;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { RectangleHorizontal, RectangleVertical } from '@lucide/vue';\nimport { ElInputNumber } from 'element-plus';\n\nimport { t } from '../core/labels';\nimport {\n MARGIN_PRESETS,\n PAGE_SIZES,\n type PageMargins,\n type PageOrientation,\n type PageSettings,\n type PageSizeKey,\n isSameMargins\n} from '../core/page';\n\n/**\n * Page setup form shown in the toolbar popover: paper size, orientation, margin presets and exact margins.\n * Every change emits a complete new settings object.\n */\ndefineOptions({ name: 'EditorPageSetup' });\n\ninterface Props {\n /** Settings currently applied to the document. */\n page: PageSettings;\n}\n\ninterface Emits {\n /** Emitted with the full updated settings after any change. */\n change: [page: PageSettings];\n}\n\nconst props = defineProps<Props>();\nconst emit = defineEmits<Emits>();\n\n/** Paper sizes in the order they are declared. */\nconst SIZE_KEYS = Object.keys(PAGE_SIZES) as PageSizeKey[];\n/** Orientation options with their icons and label keys. */\nconst ORIENTATIONS = [\n { value: 'portrait', icon: RectangleVertical, label: 'editor.page.portrait' },\n { value: 'landscape', icon: RectangleHorizontal, label: 'editor.page.landscape' }\n] as const;\n/** Exact margin inputs, one per page side. */\nconst MARGIN_FIELDS = [\n { side: 'top', label: 'editor.page.marginTop' },\n { side: 'bottom', label: 'editor.page.marginBottom' },\n { side: 'left', label: 'editor.page.marginLeft' },\n { side: 'right', label: 'editor.page.marginRight' }\n] as const;\n/** Limits of the exact margin inputs, in millimetres. */\nconst MARGIN_INPUT = { min: 0, max: 100, step: 1, precision: 1 } as const;\n\n/** Emits the current settings with `patch` applied. */\nconst update = (patch: Partial<PageSettings>) => emit('change', { ...props.page, ...patch });\n\n/** Switches the paper size. */\nconst setSize = (size: PageSizeKey) => update({ size });\n\n/** Switches between portrait and landscape. */\nconst setOrientation = (orientation: PageOrientation) => update({ orientation });\n\n/** Applies a margin preset; the preset object is copied so it can never be mutated. */\nconst setMargins = (margins: PageMargins) => update({ margins: { ...margins } });\n\n/** Changes one margin; a cleared input counts as zero. */\nconst setMargin = (side: keyof PageMargins, value: number | undefined) =>\n update({ margins: { ...props.page.margins, [side]: value ?? 0 } });\n\n/** Tooltip listing the four margins of a preset in millimetres. */\nconst describeMargins = ({ top, bottom, left, right }: PageMargins) =>\n [\n `${t('editor.page.marginTop')} ${top}`,\n `${t('editor.page.marginBottom')} ${bottom}`,\n `${t('editor.page.marginLeft')} ${left}`,\n `${t('editor.page.marginRight')} ${right} mm`\n ].join(' · ');\n</script>\n\n<template>\n <div class=\"page-setup\">\n <section class=\"page-setup__section\">\n <h4>{{ t('editor.page.size') }}</h4>\n <div class=\"page-setup__grid page-setup__grid--sizes\">\n <button\n v-for=\"key in SIZE_KEYS\"\n :key=\"key\"\n type=\"button\"\n class=\"page-setup__chip\"\n :class=\"{ 'is-active': page.size === key }\"\n :aria-pressed=\"page.size === key\"\n @click=\"setSize(key)\"\n >\n <strong>{{ PAGE_SIZES[key].label }}</strong>\n <small>{{ PAGE_SIZES[key].width }}×{{ PAGE_SIZES[key].height }}</small>\n </button>\n </div>\n </section>\n\n <section class=\"page-setup__section\">\n <h4>{{ t('editor.page.orientation') }}</h4>\n <div class=\"page-setup__grid\">\n <button\n v-for=\"orientation in ORIENTATIONS\"\n :key=\"orientation.value\"\n type=\"button\"\n class=\"page-setup__chip page-setup__chip--row\"\n :class=\"{ 'is-active': page.orientation === orientation.value }\"\n :aria-pressed=\"page.orientation === orientation.value\"\n @click=\"setOrientation(orientation.value)\"\n >\n <component :is=\"orientation.icon\" :size=\"16\" />\n {{ t(orientation.label) }}\n </button>\n </div>\n </section>\n\n <section class=\"page-setup__section\">\n <h4>{{ t('editor.page.margins') }}</h4>\n <div class=\"page-setup__grid page-setup__grid--presets\">\n <button\n v-for=\"preset in MARGIN_PRESETS\"\n :key=\"preset.key\"\n type=\"button\"\n class=\"page-setup__chip\"\n :class=\"{ 'is-active': isSameMargins(preset.margins, page.margins) }\"\n :title=\"describeMargins(preset.margins)\"\n @click=\"setMargins(preset.margins)\"\n >\n {{ t(`editor.page.margins.${preset.key}`) }}\n </button>\n </div>\n <span class=\"page-setup__caption\">{{ t('editor.page.customMargins') }}</span>\n <div class=\"page-setup__grid\">\n <label v-for=\"field in MARGIN_FIELDS\" :key=\"field.side\" class=\"page-setup__field\">\n <span>{{ t(field.label) }}</span>\n <el-input-number\n size=\"small\"\n controls-position=\"right\"\n :model-value=\"page.margins[field.side]\"\n :min=\"MARGIN_INPUT.min\"\n :max=\"MARGIN_INPUT.max\"\n :step=\"MARGIN_INPUT.step\"\n :precision=\"MARGIN_INPUT.precision\"\n @change=\"value => setMargin(field.side, value)\"\n />\n </label>\n </div>\n </section>\n </div>\n</template>\n\n<style scoped>\n.page-setup {\n display: grid;\n gap: 12px;\n color: var(--el-text-color-regular);\n}\n\n.page-setup__section {\n display: grid;\n gap: 6px;\n}\n\n.page-setup__section h4 {\n margin: 0;\n color: var(--el-text-color-primary);\n font-size: 12px;\n font-weight: 600;\n}\n\n/* Two columns by default; paper sizes and margin presets use three. */\n.page-setup__grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 6px;\n}\n\n.page-setup__grid--sizes,\n.page-setup__grid--presets {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n\n/* Selectable option buttons. */\n.page-setup__chip {\n display: flex;\n min-width: 0;\n flex-direction: column;\n align-items: flex-start;\n gap: 1px;\n padding: 5px 8px;\n border: 1px solid var(--el-border-color);\n border-radius: 6px;\n color: inherit;\n background: transparent;\n font: inherit;\n font-size: 12px;\n text-align: left;\n cursor: pointer;\n}\n\n.page-setup__chip:hover {\n border-color: var(--el-color-primary-light-5);\n}\n\n.page-setup__chip.is-active {\n border-color: var(--el-color-primary);\n color: var(--el-color-primary);\n background: var(--el-color-primary-light-9);\n}\n\n.page-setup__chip--row {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n}\n\n.page-setup__chip small {\n color: var(--el-text-color-secondary);\n font-size: 10px;\n white-space: nowrap;\n}\n\n/* Exact margin inputs. */\n.page-setup__caption {\n margin-top: 4px;\n color: var(--el-text-color-secondary);\n font-size: 11px;\n}\n\n.page-setup__field {\n display: grid;\n gap: 3px;\n color: var(--el-text-color-secondary);\n font-size: 11px;\n}\n\n.page-setup__field :deep(.el-input-number) {\n width: 100%;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { RectangleHorizontal, RectangleVertical } from '@lucide/vue';\nimport { ElInputNumber } from 'element-plus';\n\nimport { t } from '../core/labels';\nimport {\n MARGIN_PRESETS,\n PAGE_SIZES,\n type PageMargins,\n type PageOrientation,\n type PageSettings,\n type PageSizeKey,\n isSameMargins\n} from '../core/page';\n\n/**\n * Page setup form shown in the toolbar popover: paper size, orientation, margin presets and exact margins.\n * Every change emits a complete new settings object.\n */\ndefineOptions({ name: 'EditorPageSetup' });\n\ninterface Props {\n /** Settings currently applied to the document. */\n page: PageSettings;\n}\n\ninterface Emits {\n /** Emitted with the full updated settings after any change. */\n change: [page: PageSettings];\n}\n\nconst props = defineProps<Props>();\nconst emit = defineEmits<Emits>();\n\n/** Paper sizes in the order they are declared. */\nconst SIZE_KEYS = Object.keys(PAGE_SIZES) as PageSizeKey[];\n/** Orientation options with their icons and label keys. */\nconst ORIENTATIONS = [\n { value: 'portrait', icon: RectangleVertical, label: 'editor.page.portrait' },\n { value: 'landscape', icon: RectangleHorizontal, label: 'editor.page.landscape' }\n] as const;\n/** Exact margin inputs, one per page side. */\nconst MARGIN_FIELDS = [\n { side: 'top', label: 'editor.page.marginTop' },\n { side: 'bottom', label: 'editor.page.marginBottom' },\n { side: 'left', label: 'editor.page.marginLeft' },\n { side: 'right', label: 'editor.page.marginRight' }\n] as const;\n/** Limits of the exact margin inputs, in millimetres. */\nconst MARGIN_INPUT = { min: 0, max: 100, step: 1, precision: 1 } as const;\n\n/** Emits the current settings with `patch` applied. */\nconst update = (patch: Partial<PageSettings>) => emit('change', { ...props.page, ...patch });\n\n/** Switches the paper size. */\nconst setSize = (size: PageSizeKey) => update({ size });\n\n/** Switches between portrait and landscape. */\nconst setOrientation = (orientation: PageOrientation) => update({ orientation });\n\n/** Applies a margin preset; the preset object is copied so it can never be mutated. */\nconst setMargins = (margins: PageMargins) => update({ margins: { ...margins } });\n\n/** Changes one margin; a cleared input counts as zero. */\nconst setMargin = (side: keyof PageMargins, value: number | undefined) =>\n update({ margins: { ...props.page.margins, [side]: value ?? 0 } });\n\n/** Tooltip listing the four margins of a preset in millimetres. */\nconst describeMargins = ({ top, bottom, left, right }: PageMargins) =>\n [\n `${t('editor.page.marginTop')} ${top}`,\n `${t('editor.page.marginBottom')} ${bottom}`,\n `${t('editor.page.marginLeft')} ${left}`,\n `${t('editor.page.marginRight')} ${right} mm`\n ].join(' · ');\n</script>\n\n<template>\n <div class=\"page-setup\">\n <section class=\"page-setup__section\">\n <h4>{{ t('editor.page.size') }}</h4>\n <div class=\"page-setup__grid page-setup__grid--sizes\">\n <button\n v-for=\"key in SIZE_KEYS\"\n :key=\"key\"\n type=\"button\"\n class=\"page-setup__chip\"\n :class=\"{ 'is-active': page.size === key }\"\n :aria-pressed=\"page.size === key\"\n @click=\"setSize(key)\"\n >\n <strong>{{ PAGE_SIZES[key].label }}</strong>\n <small>{{ PAGE_SIZES[key].width }}×{{ PAGE_SIZES[key].height }}</small>\n </button>\n </div>\n </section>\n\n <section class=\"page-setup__section\">\n <h4>{{ t('editor.page.orientation') }}</h4>\n <div class=\"page-setup__grid\">\n <button\n v-for=\"orientation in ORIENTATIONS\"\n :key=\"orientation.value\"\n type=\"button\"\n class=\"page-setup__chip page-setup__chip--row\"\n :class=\"{ 'is-active': page.orientation === orientation.value }\"\n :aria-pressed=\"page.orientation === orientation.value\"\n @click=\"setOrientation(orientation.value)\"\n >\n <component :is=\"orientation.icon\" :size=\"16\" />\n {{ t(orientation.label) }}\n </button>\n </div>\n </section>\n\n <section class=\"page-setup__section\">\n <h4>{{ t('editor.page.margins') }}</h4>\n <div class=\"page-setup__grid page-setup__grid--presets\">\n <button\n v-for=\"preset in MARGIN_PRESETS\"\n :key=\"preset.key\"\n type=\"button\"\n class=\"page-setup__chip\"\n :class=\"{ 'is-active': isSameMargins(preset.margins, page.margins) }\"\n :title=\"describeMargins(preset.margins)\"\n @click=\"setMargins(preset.margins)\"\n >\n {{ t(`editor.page.margins.${preset.key}`) }}\n </button>\n </div>\n <span class=\"page-setup__caption\">{{ t('editor.page.customMargins') }}</span>\n <div class=\"page-setup__grid\">\n <label v-for=\"field in MARGIN_FIELDS\" :key=\"field.side\" class=\"page-setup__field\">\n <span>{{ t(field.label) }}</span>\n <el-input-number\n size=\"small\"\n controls-position=\"right\"\n :model-value=\"page.margins[field.side]\"\n :min=\"MARGIN_INPUT.min\"\n :max=\"MARGIN_INPUT.max\"\n :step=\"MARGIN_INPUT.step\"\n :precision=\"MARGIN_INPUT.precision\"\n @change=\"value => setMargin(field.side, value)\"\n />\n </label>\n </div>\n </section>\n </div>\n</template>\n\n<style scoped>\n.page-setup {\n display: grid;\n gap: 12px;\n color: var(--el-text-color-regular);\n}\n\n.page-setup__section {\n display: grid;\n gap: 6px;\n}\n\n.page-setup__section h4 {\n margin: 0;\n color: var(--el-text-color-primary);\n font-size: 12px;\n font-weight: 600;\n}\n\n/* Two columns by default; paper sizes and margin presets use three. */\n.page-setup__grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 6px;\n}\n\n.page-setup__grid--sizes,\n.page-setup__grid--presets {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n\n/* Selectable option buttons. */\n.page-setup__chip {\n display: flex;\n min-width: 0;\n flex-direction: column;\n align-items: flex-start;\n gap: 1px;\n padding: 5px 8px;\n border: 1px solid var(--el-border-color);\n border-radius: 6px;\n color: inherit;\n background: transparent;\n font: inherit;\n font-size: 12px;\n text-align: left;\n cursor: pointer;\n}\n\n.page-setup__chip:hover {\n border-color: var(--el-color-primary-light-5);\n}\n\n.page-setup__chip.is-active {\n border-color: var(--el-color-primary);\n color: var(--el-color-primary);\n background: var(--el-color-primary-light-9);\n}\n\n.page-setup__chip--row {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n}\n\n.page-setup__chip small {\n color: var(--el-text-color-secondary);\n font-size: 10px;\n white-space: nowrap;\n}\n\n/* Exact margin inputs. */\n.page-setup__caption {\n margin-top: 4px;\n color: var(--el-text-color-secondary);\n font-size: 11px;\n}\n\n.page-setup__field {\n display: grid;\n gap: 3px;\n color: var(--el-text-color-secondary);\n font-size: 11px;\n}\n\n.page-setup__field :deep(.el-input-number) {\n width: 100%;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue';\nimport { type CheckboxValueType, ElCheckbox } from 'element-plus';\n\nimport { t } from '../core/labels';\n\n/**\n * Word-style table size grid: hovering or focusing a cell previews the size, clicking it inserts a table of\n * that size, optionally with a header row.\n */\ndefineOptions({ name: 'EditorTablePicker' });\n\n/** Table requested by the user. */\ninterface TableSelection {\n /** Number of rows, header row included. */\n rows: number;\n /** Number of columns. */\n cols: number;\n /** Whether the first row is made of header cells. */\n withHeaderRow: boolean;\n}\n\n/** Size currently previewed in the grid; zero rows means nothing is previewed. */\ninterface GridSize {\n /** Previewed row count. */\n rows: number;\n /** Previewed column count. */\n cols: number;\n}\n\ninterface Emits {\n /** A grid cell was clicked. */\n select: [table: TableSelection];\n}\n\nconst emit = defineEmits<Emits>();\n\n/** Rows offered by the grid. */\nconst ROWS = 8;\n/** Columns offered by the grid; the grid CSS uses the same count. */\nconst COLS = 10;\n/** Preview state when no cell is hovered. */\nconst NO_PREVIEW: GridSize = { rows: 0, cols: 0 };\n\nconst preview = ref<GridSize>(NO_PREVIEW);\nconst withHeaderRow = ref(false);\n\n/** \"cols × rows\" of the previewed size, or a hint while nothing is previewed. */\nconst sizeLabel = computed(() =>\n preview.value.rows ? `${preview.value.cols} × ${preview.value.rows}` : t('editor.table.pickSize')\n);\n\n/** Previews a table that ends at the given cell. */\nconst showPreview = (rows: number, cols: number) => {\n preview.value = { rows, cols };\n};\n\n/** Clears the preview when the pointer leaves the grid. */\nconst clearPreview = () => {\n preview.value = NO_PREVIEW;\n};\n\n/** Whether a cell lies inside the previewed table. */\nconst isInPreview = (row: number, col: number) => row <= preview.value.rows && col <= preview.value.cols;\n\n/** Stores the header row checkbox state; the checkbox reports `true` when checked. */\nconst onHeaderRowChange = (value: CheckboxValueType) => {\n withHeaderRow.value = value === true;\n};\n</script>\n\n<template>\n <div class=\"table-picker\">\n <div class=\"table-picker__grid\" @mouseleave=\"clearPreview\">\n <template v-for=\"row in ROWS\" :key=\"row\">\n <button\n v-for=\"col in COLS\"\n :key=\"`${row}-${col}`\"\n type=\"button\"\n class=\"table-picker__cell\"\n :class=\"{ 'is-active': isInPreview(row, col) }\"\n :aria-label=\"`${t('editor.table.insert')}: ${col} × ${row}`\"\n @mouseenter=\"showPreview(row, col)\"\n @focus=\"showPreview(row, col)\"\n @click=\"emit('select', { rows: row, cols: col, withHeaderRow })\"\n />\n </template>\n </div>\n <strong class=\"table-picker__size\">{{ sizeLabel }}</strong>\n <el-checkbox size=\"small\" :model-value=\"withHeaderRow\" @change=\"onHeaderRowChange\">\n {{ t('editor.table.headerRow') }}\n </el-checkbox>\n </div>\n</template>\n\n<style scoped>\n.table-picker {\n display: grid;\n gap: 6px;\n}\n\n/* Ten columns, matching COLS. */\n.table-picker__grid {\n display: grid;\n grid-template-columns: repeat(10, 16px);\n gap: 3px;\n}\n\n.table-picker__cell {\n width: 16px;\n height: 16px;\n padding: 0;\n border: 1px solid var(--el-border-color);\n border-radius: 2px;\n background: var(--el-bg-color);\n cursor: pointer;\n}\n\n/* Cells inside the previewed table. */\n.table-picker__cell.is-active {\n border-color: var(--el-color-primary);\n background: var(--el-color-primary-light-8);\n}\n\n.table-picker__size {\n color: var(--el-text-color-regular);\n font-size: 12px;\n text-align: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue';\nimport { type CheckboxValueType, ElCheckbox } from 'element-plus';\n\nimport { t } from '../core/labels';\n\n/**\n * Word-style table size grid: hovering or focusing a cell previews the size, clicking it inserts a table of\n * that size, optionally with a header row.\n */\ndefineOptions({ name: 'EditorTablePicker' });\n\n/** Table requested by the user. */\ninterface TableSelection {\n /** Number of rows, header row included. */\n rows: number;\n /** Number of columns. */\n cols: number;\n /** Whether the first row is made of header cells. */\n withHeaderRow: boolean;\n}\n\n/** Size currently previewed in the grid; zero rows means nothing is previewed. */\ninterface GridSize {\n /** Previewed row count. */\n rows: number;\n /** Previewed column count. */\n cols: number;\n}\n\ninterface Emits {\n /** A grid cell was clicked. */\n select: [table: TableSelection];\n}\n\nconst emit = defineEmits<Emits>();\n\n/** Rows offered by the grid. */\nconst ROWS = 8;\n/** Columns offered by the grid; the grid CSS uses the same count. */\nconst COLS = 10;\n/** Preview state when no cell is hovered. */\nconst NO_PREVIEW: GridSize = { rows: 0, cols: 0 };\n\nconst preview = ref<GridSize>(NO_PREVIEW);\nconst withHeaderRow = ref(false);\n\n/** \"cols × rows\" of the previewed size, or a hint while nothing is previewed. */\nconst sizeLabel = computed(() =>\n preview.value.rows ? `${preview.value.cols} × ${preview.value.rows}` : t('editor.table.pickSize')\n);\n\n/** Previews a table that ends at the given cell. */\nconst showPreview = (rows: number, cols: number) => {\n preview.value = { rows, cols };\n};\n\n/** Clears the preview when the pointer leaves the grid. */\nconst clearPreview = () => {\n preview.value = NO_PREVIEW;\n};\n\n/** Whether a cell lies inside the previewed table. */\nconst isInPreview = (row: number, col: number) => row <= preview.value.rows && col <= preview.value.cols;\n\n/** Stores the header row checkbox state; the checkbox reports `true` when checked. */\nconst onHeaderRowChange = (value: CheckboxValueType) => {\n withHeaderRow.value = value === true;\n};\n</script>\n\n<template>\n <div class=\"table-picker\">\n <div class=\"table-picker__grid\" @mouseleave=\"clearPreview\">\n <template v-for=\"row in ROWS\" :key=\"row\">\n <button\n v-for=\"col in COLS\"\n :key=\"`${row}-${col}`\"\n type=\"button\"\n class=\"table-picker__cell\"\n :class=\"{ 'is-active': isInPreview(row, col) }\"\n :aria-label=\"`${t('editor.table.insert')}: ${col} × ${row}`\"\n @mouseenter=\"showPreview(row, col)\"\n @focus=\"showPreview(row, col)\"\n @click=\"emit('select', { rows: row, cols: col, withHeaderRow })\"\n />\n </template>\n </div>\n <strong class=\"table-picker__size\">{{ sizeLabel }}</strong>\n <el-checkbox size=\"small\" :model-value=\"withHeaderRow\" @change=\"onHeaderRowChange\">\n {{ t('editor.table.headerRow') }}\n </el-checkbox>\n </div>\n</template>\n\n<style scoped>\n.table-picker {\n display: grid;\n gap: 6px;\n}\n\n/* Ten columns, matching COLS. */\n.table-picker__grid {\n display: grid;\n grid-template-columns: repeat(10, 16px);\n gap: 3px;\n}\n\n.table-picker__cell {\n width: 16px;\n height: 16px;\n padding: 0;\n border: 1px solid var(--el-border-color);\n border-radius: 2px;\n background: var(--el-bg-color);\n cursor: pointer;\n}\n\n/* Cells inside the previewed table. */\n.table-picker__cell.is-active {\n border-color: var(--el-color-primary);\n background: var(--el-color-primary-light-8);\n}\n\n.table-picker__size {\n color: var(--el-text-color-regular);\n font-size: 12px;\n text-align: center;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { type Component, computed, ref } from 'vue';\nimport {\n Bold,\n CalendarDays,\n ChevronDown,\n Code,\n Ellipsis,\n FileCode,\n FileDown,\n FileSliders,\n FileType,\n Grid3x3,\n ImagePlus,\n Italic,\n Languages,\n Link,\n List,\n ListIndentDecrease,\n ListIndentIncrease,\n ListOrdered,\n ListTodo,\n Maximize2,\n Minimize2,\n Omega,\n PilcrowLeft,\n PilcrowRight,\n Plus,\n Printer,\n Quote,\n Redo2,\n RemoveFormatting,\n Search,\n SeparatorHorizontal,\n SquareCode,\n SquareSplitVertical,\n Strikethrough,\n Subscript,\n Superscript,\n TextAlignCenter,\n TextAlignEnd,\n TextAlignJustify,\n TextAlignStart,\n Underline,\n Undo2,\n UnfoldVertical,\n Upload\n} from '@lucide/vue';\nimport {\n type CheckboxValueType,\n ElButton,\n ElCheckbox,\n ElDropdown,\n ElDropdownItem,\n ElDropdownMenu,\n ElInput,\n ElPopover,\n type InputInstance\n} from 'element-plus';\n\nimport type { HeadingTag, TextAlign, TextDirection } from '../core/engine/blocks';\nimport type { DocumentEngine } from '../core/engine/engine';\nimport type { MarkName } from '../core/engine/marks';\nimport { type EditorLabelKey, formatShortcut, t, withShortcut } from '../core/labels';\nimport type { PageSettings } from '../core/page';\nimport type { DocumentMenuAction } from '../core/types';\nimport { type EditorUiState, normalizeFontFamily } from '../core/ui-state';\nimport EditorColorPicker from './editor-color-picker.vue';\nimport EditorPageSetup from './editor-page-setup.vue';\nimport EditorTablePicker from './editor-table-picker.vue';\n\n/**\n * Compact single-row toolbar: history, text style, font, marks, colours, paragraph layout, lists, insertions, search,\n * page setup and the document menu. Reads a flat state snapshot and calls engine commands directly.\n */\ndefineOptions({ name: 'EditorToolbar' });\n\ninterface Props {\n /** Read-only document: every editing control is disabled. */\n disabled: boolean | undefined;\n /** Engine that receives the commands. */\n engine: DocumentEngine;\n /** Whether the find bar is open, shown as the search button state. */\n findOpen: boolean;\n /** Whether the editor is in fullscreen, used for the menu item label. */\n fullscreen: boolean;\n /** Current page settings, edited in the page setup popover. */\n page: PageSettings;\n /** Whether the HTML source is shown; formatting controls are disabled meanwhile. */\n sourceMode: boolean;\n /** Formatting at the caret, used for active states and labels. */\n state: EditorUiState;\n /** Whether images are being uploaded, shown on the upload button. */\n uploading: boolean;\n}\n\nconst props = defineProps<Props>();\n\ninterface Emits {\n /** The search button was pressed. */\n find: [];\n /** Image files were picked from the computer; the editor validates and uploads them. */\n insertImages: [files: File[]];\n /** An action was chosen in the document menu. */\n menu: [action: DocumentMenuAction];\n /** New page settings were chosen in the page setup popover. */\n 'update:page': [page: PageSettings];\n}\n\nconst emit = defineEmits<Emits>();\n\n/** Keys of the state snapshot that hold on/off values. */\ntype BooleanStateKey = {\n [K in keyof EditorUiState]: EditorUiState[K] extends boolean ? K : never;\n}[keyof EditorUiState];\n\n/** A toolbar button that toggles formatting and shows whether it is active. */\ninterface ToggleButton {\n /** State flag that marks the button as active. */\n key: BooleanStateKey;\n icon: Component;\n label: EditorLabelKey;\n /** Shortcut shown in the tooltip, written with `Mod` for Ctrl/⌘. */\n shortcut: string;\n /** Applies the formatting. */\n toggle: (engine: DocumentEngine) => void;\n}\n\n/** Rows, columns and header choice from the table picker. */\ninterface TableSize {\n rows: number;\n cols: number;\n withHeaderRow: boolean;\n}\n\n/** Dropdown command that removes an explicit value and falls back to the document default. */\nconst DEFAULT_COMMAND = 'default';\n/** Font the document uses when no font is applied. */\nconst DEFAULT_FONT = 'Times New Roman';\n/** Font size, in points, the document uses when no size is applied. */\nconst DEFAULT_FONT_SIZE = 12;\n/** Points per CSS pixel. */\nconst POINTS_PER_PIXEL = 0.75;\n\nconst FONT_FAMILIES = [\n { label: 'Times New Roman', value: \"'Times New Roman', Times, serif\" },\n { label: 'Arial', value: 'Arial, Helvetica, sans-serif' },\n { label: 'Calibri', value: 'Calibri, Carlito, sans-serif' },\n { label: 'Cambria', value: 'Cambria, Caladea, serif' },\n { label: 'Georgia', value: 'Georgia, serif' },\n { label: 'Tahoma', value: 'Tahoma, Verdana, sans-serif' },\n { label: 'Verdana', value: 'Verdana, sans-serif' },\n { label: 'Courier New', value: \"'Courier New', Courier, monospace\" }\n].map(font => ({ ...font, normalized: normalizeFontFamily(font.value) }));\n\n/** Point sizes, as in office suites. */\nconst FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 36, 48, 72];\nconst LINE_HEIGHTS = ['1', '1.15', '1.5', '2', '2.5', '3'];\n/** Heading levels offered in the text style menu. */\nconst HEADING_LEVELS = 4;\n\nconst ALIGNMENTS = [\n { value: 'left', icon: TextAlignStart, label: 'editor.align.left', shortcut: 'Mod+Shift+L' },\n { value: 'center', icon: TextAlignCenter, label: 'editor.align.center', shortcut: 'Mod+Shift+E' },\n { value: 'right', icon: TextAlignEnd, label: 'editor.align.right', shortcut: 'Mod+Shift+R' },\n { value: 'justify', icon: TextAlignJustify, label: 'editor.align.justify', shortcut: 'Mod+Shift+J' }\n] as const;\n\nconst DIRECTIONS = [\n { value: 'auto', icon: Languages, label: 'editor.direction.auto' },\n { value: 'ltr', icon: PilcrowRight, label: 'editor.direction.ltr' },\n { value: 'rtl', icon: PilcrowLeft, label: 'editor.direction.rtl' }\n] as const;\n\nconst MARK_BUTTONS: ToggleButton[] = [\n { key: 'bold', icon: Bold, label: 'editor.bold', shortcut: 'Mod+B', toggle: engine => engine.toggleMark('bold') },\n {\n key: 'italic',\n icon: Italic,\n label: 'editor.italic',\n shortcut: 'Mod+I',\n toggle: engine => engine.toggleMark('italic')\n },\n {\n key: 'underline',\n icon: Underline,\n label: 'editor.underline',\n shortcut: 'Mod+U',\n toggle: engine => engine.toggleMark('underline')\n }\n];\n\nconst LIST_BUTTONS: ToggleButton[] = [\n {\n key: 'bulletList',\n icon: List,\n label: 'editor.bulletList',\n shortcut: 'Mod+Shift+8',\n toggle: engine => engine.toggleList('bulletList')\n },\n {\n key: 'orderedList',\n icon: ListOrdered,\n label: 'editor.orderedList',\n shortcut: 'Mod+Shift+7',\n toggle: engine => engine.toggleList('orderedList')\n },\n {\n key: 'taskList',\n icon: ListTodo,\n label: 'editor.taskList',\n shortcut: 'Mod+Shift+9',\n toggle: engine => engine.toggleList('taskList')\n }\n];\n\n/** Marks offered in the \"more formatting\" menu; any other command of that menu clears formatting. */\nconst TEXT_MARKS: MarkName[] = ['strike', 'subscript', 'superscript', 'code'];\n\n// biome-ignore format: compact character table.\nconst SPECIAL_CHARACTERS = [\n '«', '»', '„', '“', '”', '‘', '’', '—', '–', '…', '№', '§', '©', '®', '™', '°', '±', '×',\n '÷', '≈', '≠', '≤', '≥', '∞', '€', '$', '£', '¥', '←', '→', '↑', '↓', '•', '✓', '✗', '¶'\n];\n\n/** URLs used as they are: web and mail links, phone numbers, absolute paths and anchors. */\nconst LINK_WITH_SCHEME = /^(https?:|mailto:|tel:|\\/|#)/i;\nconst EMAIL_ADDRESS = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n/** Image URLs accepted from the image form: web addresses and absolute paths. */\nconst IMAGE_URL = /^(https?:\\/\\/|\\/)/i;\n\n/** Whether the link popover is open. */\nconst linkVisible = ref(false);\nconst linkInput = ref<InputInstance>();\nconst linkUrl = ref('');\n/** Text for a new link inserted at a collapsed caret. */\nconst linkText = ref('');\nconst linkNewTab = ref(false);\n/** Whether the link form asks for link text, because there is neither a selection nor an existing link. */\nconst linkNeedsText = ref(false);\n/** Whether the image popover is open. */\nconst imageVisible = ref(false);\nconst imageUrl = ref('');\nconst fileInput = ref<HTMLInputElement>();\n/** Whether the table picker popover is open. */\nconst tableVisible = ref(false);\n/** Whether the special characters popover is open. */\nconst charactersVisible = ref(false);\n\n/** Formatting controls are unavailable for read-only documents and while the HTML source is shown. */\nconst locked = computed(() => props.disabled || props.sourceMode);\n\n/** Name of the block type at the caret, shown on the text style dropdown. */\nconst blockLabel = computed(() => {\n if (props.state.headingLevel) return t('editor.heading', { level: props.state.headingLevel });\n if (props.state.codeBlock) return t('editor.codeBlock');\n if (props.state.blockquote) return t('editor.quote');\n return t('editor.paragraph');\n});\n\n/** Font name at the caret; unknown fonts show the first family of their stack. */\nconst fontLabel = computed(() => {\n const family = props.state.fontFamily;\n if (!family) return DEFAULT_FONT;\n const known = FONT_FAMILIES.find(font => font.normalized === family);\n return known?.label ?? (family.split(',')[0] ?? family).trim();\n});\n\n/** Font size at the caret in points, rounded to half points; pixel sizes from pasted content are converted. */\nconst fontSizeLabel = computed(() => {\n const size = Number.parseFloat(props.state.fontSize);\n if (!Number.isFinite(size)) return String(DEFAULT_FONT_SIZE);\n const points = props.state.fontSize.endsWith('px') ? size * POINTS_PER_PIXEL : size;\n return String(Math.round(points * 2) / 2);\n});\n\n/** Icon of the alignment at the caret. */\nconst alignIcon = computed(\n () => ALIGNMENTS.find(alignment => alignment.value === props.state.align)?.icon ?? TextAlignStart\n);\n\n/** Icon of the text direction at the caret. */\nconst directionIcon = computed(\n () => DIRECTIONS.find(direction => direction.value === props.state.direction)?.icon ?? Languages\n);\n\n/** Applies a choice from the text style menu: paragraph, heading level, quote or code block. */\nconst setBlock = (command: unknown) => {\n switch (command) {\n case 'paragraph':\n props.engine.setBlockType('P');\n break;\n case 'blockquote':\n props.engine.toggleBlockquote();\n break;\n case 'codeBlock':\n props.engine.toggleCodeBlock();\n break;\n default:\n props.engine.setBlockType(`H${Number(command)}` as HeadingTag);\n }\n};\n\n/** Applies a font family, or removes it for the default entry. */\nconst setFontFamily = (command: unknown) => {\n const family = String(command);\n props.engine.setTextStyle('fontFamily', family === DEFAULT_COMMAND ? null : family);\n};\n\n/** Applies a font size in points, or removes it for the default entry. */\nconst setFontSize = (command: unknown) => {\n const size = String(command);\n props.engine.setTextStyle('fontSize', size === DEFAULT_COMMAND ? null : `${size}pt`);\n};\n\n/** Applies a paragraph line height, or removes it for the default entry. */\nconst setLineHeight = (command: unknown) => {\n const lineHeight = String(command);\n props.engine.setLineHeight(lineHeight === DEFAULT_COMMAND ? null : lineHeight);\n};\n\n/** Aligns the selected paragraphs. */\nconst setAlign = (command: unknown) => props.engine.setTextAlign(String(command) as TextAlign);\n\n/** Sets the writing direction of the selected paragraphs. */\nconst setDirection = (command: unknown) => props.engine.setTextDirection(String(command) as TextDirection);\n\n/** Toggles a mark from the \"more formatting\" menu, or clears all formatting. */\nconst runTextCommand = (command: unknown) => {\n if (TEXT_MARKS.includes(command as MarkName)) props.engine.toggleMark(command as MarkName);\n else props.engine.clearFormatting();\n};\n\n/** Today's date as `dd.mm.yyyy`, the format used in Uzbek documents. */\nconst formatToday = () => {\n const today = new Date();\n const twoDigits = (value: number) => String(value).padStart(2, '0');\n return `${twoDigits(today.getDate())}.${twoDigits(today.getMonth() + 1)}.${today.getFullYear()}`;\n};\n\n/** Inserts an element from the insert menu: page break, today's date or a horizontal rule. */\nconst insertBlock = (command: unknown) => {\n switch (command) {\n case 'pageBreak':\n props.engine.insertPageBreak();\n break;\n case 'date':\n props.engine.insertText(formatToday());\n break;\n default:\n props.engine.insertHorizontalRule();\n }\n};\n\n/** Forwards a document menu choice to the editor. */\nconst onMenuCommand = (command: unknown) => emit('menu', command as DocumentMenuAction);\n\n/** Fills the link form from the link under the caret before the popover opens. */\nconst prepareLink = () => {\n const link = props.engine.getActiveLink();\n linkUrl.value = link?.getAttribute('href') ?? '';\n linkNewTab.value = link?.getAttribute('target') === '_blank';\n linkNeedsText.value = !link && !props.engine.getSelectedText();\n linkText.value = '';\n};\n\n/** Opens the link popover; used by the Mod+K shortcut and the link bubble menu. */\nconst openLink = () => {\n if (!locked.value) linkVisible.value = true;\n};\n\n/** Completes what users usually type: bare domains get `https://`, e-mail addresses get `mailto:`. */\nconst normalizeUrl = (value: string) => {\n const url = value.trim();\n if (!url || LINK_WITH_SCHEME.test(url)) return url;\n if (EMAIL_ADDRESS.test(url)) return `mailto:${url}`;\n return `https://${url}`;\n};\n\n/** Mirrors the \"open in new tab\" checkbox into the form state. */\nconst onLinkNewTabChange = (value: CheckboxValueType) => {\n linkNewTab.value = Boolean(value);\n};\n\n/** Removes the link under the caret and closes the form. */\nconst removeLink = () => {\n linkVisible.value = false;\n props.engine.unsetLink();\n};\n\n/** Creates or updates the link; an empty address removes it. */\nconst applyLink = () => {\n const href = normalizeUrl(linkUrl.value);\n if (!href) return removeLink();\n linkVisible.value = false;\n props.engine.setLink(\n href,\n linkNewTab.value ? '_blank' : null,\n linkNeedsText.value ? linkText.value.trim() : undefined\n );\n};\n\n/** Opens the file dialog for images. */\nconst pickImages = () => {\n imageVisible.value = false;\n fileInput.value?.click();\n};\n\n/** Passes the picked files to the editor and resets the input so the same file can be picked again. */\nconst onFilesPicked = (event: Event) => {\n const input = event.target as HTMLInputElement;\n const files = Array.from(input.files ?? []);\n input.value = '';\n if (files.length) emit('insertImages', files);\n};\n\n/** Inserts an image from the typed address when it is a web address or an absolute path. */\nconst insertImageUrl = () => {\n const src = imageUrl.value.trim();\n if (!IMAGE_URL.test(src)) return;\n imageVisible.value = false;\n imageUrl.value = '';\n props.engine.insertImage(src);\n};\n\n/** Inserts a table of the size chosen in the picker. */\nconst insertTable = (table: TableSize) => {\n tableVisible.value = false;\n props.engine.insertTable(table.rows, table.cols, table.withHeaderRow);\n};\n\n/** Types a special character at the caret. */\nconst insertCharacter = (character: string) => {\n charactersVisible.value = false;\n props.engine.insertText(character);\n};\n\ndefineExpose({\n /** Opens the link popover. */\n openLink\n});\n</script>\n\n<template>\n <div class=\"doc-toolbar\" role=\"toolbar\" :aria-label=\"t('editor.toolbar')\">\n <div class=\"doc-toolbar__group\">\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.undo')\"\n :disabled=\"locked || !state.canUndo\"\n :title=\"withShortcut('editor.undo', 'Mod+Z')\"\n @mousedown.prevent\n @click=\"engine.undo()\"\n >\n <Undo2 :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.redo')\"\n :disabled=\"locked || !state.canRedo\"\n :title=\"withShortcut('editor.redo', 'Mod+Y')\"\n @mousedown.prevent\n @click=\"engine.redo()\"\n >\n <Redo2 :size=\"16\" />\n </button>\n </div>\n\n <span class=\"doc-toolbar__divider\" />\n\n <div class=\"doc-toolbar__group\">\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"setBlock\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select doc-toolbar__block\"\n :disabled=\"locked\"\n :title=\"t('editor.textStyle')\"\n @mousedown.prevent\n >\n <span class=\"doc-tb-button__label\">{{ blockLabel }}</span>\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item\n command=\"paragraph\"\n :class=\"{ 'is-selected': !state.headingLevel && !state.codeBlock && !state.blockquote }\"\n >\n {{ t('editor.paragraph') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+Alt+0') }}</span>\n </el-dropdown-item>\n <el-dropdown-item\n v-for=\"level in HEADING_LEVELS\"\n :key=\"level\"\n :class=\"{ 'is-selected': state.headingLevel === level }\"\n :command=\"level\"\n >\n <span :class=\"`doc-menu__h${level}`\">{{ t('editor.heading', { level }) }}</span>\n <span class=\"doc-menu__hint\">{{ formatShortcut(`Mod+Alt+${level}`) }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"blockquote\" divided :class=\"{ 'is-selected': state.blockquote }\">\n <Quote :size=\"14\" />\n {{ t('editor.quote') }}\n </el-dropdown-item>\n <el-dropdown-item command=\"codeBlock\" :class=\"{ 'is-selected': state.codeBlock }\">\n <SquareCode :size=\"14\" />\n {{ t('editor.codeBlock') }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n\n <el-dropdown\n max-height=\"320px\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n @command=\"setFontFamily\"\n >\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select doc-toolbar__font\"\n :disabled=\"locked\"\n :title=\"t('editor.fontFamily')\"\n @mousedown.prevent\n >\n <span class=\"doc-tb-button__label\">{{ fontLabel }}</span>\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item :class=\"{ 'is-selected': !state.fontFamily }\" :command=\"DEFAULT_COMMAND\">\n {{ t('editor.defaultFont') }}\n </el-dropdown-item>\n <el-dropdown-item\n v-for=\"font in FONT_FAMILIES\"\n :key=\"font.value\"\n :class=\"{ 'is-selected': state.fontFamily === font.normalized }\"\n :command=\"font.value\"\n :style=\"{ fontFamily: font.value }\"\n >\n {{ font.label }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n\n <el-dropdown\n max-height=\"320px\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n @command=\"setFontSize\"\n >\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select doc-toolbar__size\"\n :disabled=\"locked\"\n :title=\"t('editor.fontSize')\"\n @mousedown.prevent\n >\n <span class=\"doc-tb-button__label\">{{ fontSizeLabel }}</span>\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu doc-toolbar__size-menu\">\n <el-dropdown-item :class=\"{ 'is-selected': !state.fontSize }\" :command=\"DEFAULT_COMMAND\">\n {{ t('editor.defaultSize') }}\n </el-dropdown-item>\n <el-dropdown-item\n v-for=\"size in FONT_SIZES\"\n :key=\"size\"\n :class=\"{ 'is-selected': state.fontSize === `${size}pt` }\"\n :command=\"size\"\n >\n {{ size }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n </div>\n\n <span class=\"doc-toolbar__divider\" />\n\n <div class=\"doc-toolbar__group\">\n <button\n v-for=\"button in MARK_BUTTONS\"\n :key=\"button.key\"\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': state[button.key] }\"\n :aria-label=\"t(button.label)\"\n :aria-pressed=\"state[button.key]\"\n :disabled=\"locked\"\n :title=\"withShortcut(button.label, button.shortcut)\"\n @mousedown.prevent\n @click=\"button.toggle(engine)\"\n >\n <component :is=\"button.icon\" :size=\"16\" />\n </button>\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"runTextCommand\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--caret\"\n :class=\"{ 'is-active': state.strike || state.subscript || state.superscript || state.code }\"\n :aria-label=\"t('editor.moreFormatting')\"\n :disabled=\"locked\"\n :title=\"t('editor.moreFormatting')\"\n @mousedown.prevent\n >\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item command=\"strike\" :class=\"{ 'is-selected': state.strike }\">\n <Strikethrough :size=\"14\" />\n {{ t('editor.strike') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+Shift+S') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"subscript\" :class=\"{ 'is-selected': state.subscript }\">\n <Subscript :size=\"14\" />\n {{ t('editor.subscript') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+,') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"superscript\" :class=\"{ 'is-selected': state.superscript }\">\n <Superscript :size=\"14\" />\n {{ t('editor.superscript') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+.') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"code\" :class=\"{ 'is-selected': state.code }\">\n <Code :size=\"14\" />\n {{ t('editor.inlineCode') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+E') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"clear\" divided>\n <RemoveFormatting :size=\"14\" />\n {{ t('editor.clearFormatting') }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n <EditorColorPicker\n mode=\"text\"\n :current=\"state.color\"\n :disabled=\"locked\"\n @select=\"engine.setTextStyle('color', $event)\"\n />\n <EditorColorPicker\n mode=\"highlight\"\n :current=\"state.highlight\"\n :disabled=\"locked\"\n @select=\"engine.setHighlight($event)\"\n />\n </div>\n\n <span class=\"doc-toolbar__divider\" />\n\n <div class=\"doc-toolbar__group\">\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"setAlign\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select\"\n :aria-label=\"t('editor.align')\"\n :disabled=\"locked\"\n :title=\"t('editor.align')\"\n @mousedown.prevent\n >\n <component :is=\"alignIcon\" :size=\"16\" />\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item\n v-for=\"alignment in ALIGNMENTS\"\n :key=\"alignment.value\"\n :class=\"{ 'is-selected': state.align === alignment.value }\"\n :command=\"alignment.value\"\n >\n <component :is=\"alignment.icon\" :size=\"14\" />\n {{ t(alignment.label) }}\n <span class=\"doc-menu__hint\">{{ formatShortcut(alignment.shortcut) }}</span>\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"setLineHeight\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select\"\n :aria-label=\"t('editor.lineHeight')\"\n :disabled=\"locked\"\n :title=\"t('editor.lineHeight')\"\n @mousedown.prevent\n >\n <UnfoldVertical :size=\"16\" />\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item :class=\"{ 'is-selected': !state.lineHeight }\" :command=\"DEFAULT_COMMAND\">\n {{ t('editor.defaultLineHeight') }}\n </el-dropdown-item>\n <el-dropdown-item\n v-for=\"lineHeight in LINE_HEIGHTS\"\n :key=\"lineHeight\"\n :class=\"{ 'is-selected': state.lineHeight === lineHeight }\"\n :command=\"lineHeight\"\n >\n {{ lineHeight }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"setDirection\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select\"\n :aria-label=\"t('editor.textDirection')\"\n :disabled=\"locked\"\n :title=\"t('editor.textDirection')\"\n @mousedown.prevent\n >\n <component :is=\"directionIcon\" :size=\"16\" />\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item\n v-for=\"direction in DIRECTIONS\"\n :key=\"direction.value\"\n :class=\"{ 'is-selected': state.direction === direction.value }\"\n :command=\"direction.value\"\n >\n <component :is=\"direction.icon\" :size=\"14\" />\n {{ t(direction.label) }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n\n <button\n v-for=\"button in LIST_BUTTONS\"\n :key=\"button.key\"\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': state[button.key] }\"\n :aria-label=\"t(button.label)\"\n :aria-pressed=\"state[button.key]\"\n :disabled=\"locked\"\n :title=\"withShortcut(button.label, button.shortcut)\"\n @mousedown.prevent\n @click=\"button.toggle(engine)\"\n >\n <component :is=\"button.icon\" :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.outdent')\"\n :disabled=\"locked\"\n :title=\"withShortcut('editor.outdent', 'Shift+Tab')\"\n @mousedown.prevent\n @click=\"engine.indent(-1)\"\n >\n <ListIndentDecrease :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.indent')\"\n :disabled=\"locked\"\n :title=\"withShortcut('editor.indent', 'Tab')\"\n @mousedown.prevent\n @click=\"engine.indent(1)\"\n >\n <ListIndentIncrease :size=\"16\" />\n </button>\n </div>\n\n <span class=\"doc-toolbar__divider\" />\n\n <div class=\"doc-toolbar__group\">\n <el-popover\n v-model:visible=\"linkVisible\"\n placement=\"bottom-start\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n :width=\"300\"\n @after-enter=\"linkInput?.focus()\"\n @before-enter=\"prepareLink\"\n >\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': state.link }\"\n :aria-label=\"t('editor.link')\"\n :disabled=\"locked\"\n :title=\"withShortcut('editor.link', 'Mod+K')\"\n @mousedown.prevent\n >\n <Link :size=\"16\" />\n </button>\n </template>\n <form class=\"doc-toolbar__form\" @submit.prevent=\"applyLink\">\n <label v-if=\"linkNeedsText\">\n {{ t('editor.linkText') }}\n <el-input v-model=\"linkText\" size=\"small\" />\n </label>\n <label>\n {{ t('editor.linkUrl') }}\n <el-input ref=\"linkInput\" v-model=\"linkUrl\" placeholder=\"https://\" size=\"small\" />\n </label>\n <el-checkbox size=\"small\" :model-value=\"linkNewTab\" @change=\"onLinkNewTabChange\">\n {{ t('editor.linkNewTab') }}\n </el-checkbox>\n <div class=\"doc-toolbar__form-actions\">\n <el-button v-if=\"state.link\" size=\"small\" text type=\"danger\" @click=\"removeLink\">\n {{ t('editor.unlink') }}\n </el-button>\n <el-button native-type=\"submit\" size=\"small\" type=\"primary\">{{ t('editor.apply') }}</el-button>\n </div>\n </form>\n </el-popover>\n\n <el-popover\n v-model:visible=\"imageVisible\"\n placement=\"bottom-start\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n :width=\"300\"\n >\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.image')\"\n :disabled=\"locked\"\n :title=\"t('editor.image')\"\n @mousedown.prevent\n >\n <ImagePlus :size=\"16\" />\n </button>\n </template>\n <div class=\"doc-toolbar__form\">\n <el-button size=\"small\" :icon=\"Upload\" :loading=\"uploading\" @click=\"pickImages\">\n {{ t('editor.uploadImage') }}\n </el-button>\n <span class=\"doc-toolbar__caption\">{{ t('editor.or') }}</span>\n <form class=\"doc-toolbar__inline-form\" @submit.prevent=\"insertImageUrl\">\n <el-input\n v-model=\"imageUrl\"\n placeholder=\"https://example.com/image.png\"\n size=\"small\"\n :ariaLabel=\"t('editor.image')\"\n />\n <el-button native-type=\"submit\" size=\"small\" type=\"primary\" :disabled=\"!imageUrl.trim()\">\n {{ t('editor.insert') }}\n </el-button>\n </form>\n </div>\n </el-popover>\n <input ref=\"fileInput\" type=\"file\" accept=\"image/*\" hidden multiple @change=\"onFilesPicked\" />\n\n <el-popover\n v-model:visible=\"tableVisible\"\n placement=\"bottom-start\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n :width=\"212\"\n >\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.table.insert')\"\n :disabled=\"locked\"\n :title=\"t('editor.table.insert')\"\n @mousedown.prevent\n >\n <Grid3x3 :size=\"16\" />\n </button>\n </template>\n <EditorTablePicker v-if=\"tableVisible\" @select=\"insertTable\" />\n </el-popover>\n\n <el-popover\n v-model:visible=\"charactersVisible\"\n placement=\"bottom-start\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n :width=\"258\"\n >\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.specialCharacters')\"\n :disabled=\"locked\"\n :title=\"t('editor.specialCharacters')\"\n @mousedown.prevent\n >\n <Omega :size=\"16\" />\n </button>\n </template>\n <div class=\"doc-toolbar__characters\">\n <button\n v-for=\"character in SPECIAL_CHARACTERS\"\n :key=\"character\"\n type=\"button\"\n :title=\"character\"\n @mousedown.prevent\n @click=\"insertCharacter(character)\"\n >\n {{ character }}\n </button>\n </div>\n </el-popover>\n\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"insertBlock\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select\"\n :aria-label=\"t('editor.insertMore')\"\n :disabled=\"locked\"\n :title=\"t('editor.insertMore')\"\n @mousedown.prevent\n >\n <Plus :size=\"16\" />\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item command=\"pageBreak\">\n <SquareSplitVertical :size=\"14\" />\n {{ t('editor.pageBreak') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+Enter') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"horizontalRule\">\n <SeparatorHorizontal :size=\"14\" />\n {{ t('editor.horizontalRule') }}\n </el-dropdown-item>\n <el-dropdown-item command=\"date\">\n <CalendarDays :size=\"14\" />\n {{ t('editor.insertDate') }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n </div>\n\n <span class=\"doc-toolbar__spacer\" />\n\n <div class=\"doc-toolbar__group\">\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': findOpen }\"\n :aria-label=\"t('editor.search.title')\"\n :disabled=\"sourceMode\"\n :title=\"withShortcut('editor.search.title', 'Mod+F')\"\n @mousedown.prevent\n @click=\"emit('find')\"\n >\n <Search :size=\"16\" />\n </button>\n\n <el-popover placement=\"bottom-end\" popper-class=\"doc-editor-popper\" trigger=\"click\" :width=\"316\">\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.page.setup')\"\n :title=\"t('editor.page.setup')\"\n @mousedown.prevent\n >\n <FileSliders :size=\"16\" />\n </button>\n </template>\n <EditorPageSetup :page=\"page\" @change=\"emit('update:page', $event)\" />\n </el-popover>\n\n <el-dropdown placement=\"bottom-end\" popper-class=\"doc-editor-popper\" trigger=\"click\" @command=\"onMenuCommand\">\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.more')\"\n :title=\"t('editor.more')\"\n @mousedown.prevent\n >\n <Ellipsis :size=\"16\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item command=\"print\">\n <Printer :size=\"14\" />\n {{ t('editor.print') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+P') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"exportWord\">\n <FileType :size=\"14\" />\n {{ t('editor.exportWord') }}\n </el-dropdown-item>\n <el-dropdown-item command=\"exportHtml\">\n <FileDown :size=\"14\" />\n {{ t('editor.exportHtml') }}\n </el-dropdown-item>\n <el-dropdown-item command=\"source\" divided :class=\"{ 'is-selected': sourceMode }\">\n <FileCode :size=\"14\" />\n {{ t('editor.source') }}\n </el-dropdown-item>\n <el-dropdown-item command=\"fullscreen\">\n <component :is=\"fullscreen ? Minimize2 : Maximize2\" :size=\"14\" />\n {{ t(fullscreen ? 'editor.exitFullscreen' : 'editor.enterFullscreen') }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* Toolbar layout */\n.doc-toolbar {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 2px;\n box-sizing: border-box;\n min-height: 40px;\n padding: 5px 8px;\n overflow-x: auto;\n border-bottom: 1px solid var(--el-border-color-lighter);\n background: var(--el-bg-color);\n scrollbar-width: thin;\n}\n\n.doc-toolbar__group {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 1px;\n}\n\n.doc-toolbar__divider {\n flex: 0 0 1px;\n align-self: stretch;\n margin: 4px 5px;\n background: var(--el-border-color-lighter);\n}\n\n.doc-toolbar__spacer {\n flex: 1 1 auto;\n min-width: 8px;\n}\n\n/* Dropdown triggers with a value label */\n.doc-toolbar__block {\n width: 108px;\n}\n\n.doc-toolbar__font {\n width: 118px;\n}\n\n.doc-toolbar__size {\n width: 50px;\n}\n\n.doc-toolbar__size-menu :deep(.el-dropdown-menu__item) {\n min-width: 96px;\n}\n\n/* Popover forms (link and image) */\n.doc-toolbar__form {\n display: grid;\n gap: 8px;\n}\n\n.doc-toolbar__form label {\n display: grid;\n gap: 4px;\n color: var(--el-text-color-secondary);\n font-size: 12px;\n}\n\n.doc-toolbar__form-actions {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n}\n\n.doc-toolbar__caption {\n color: var(--el-text-color-secondary);\n font-size: 11px;\n text-align: center;\n}\n\n.doc-toolbar__inline-form {\n display: flex;\n gap: 6px;\n}\n\n/* Special characters grid */\n.doc-toolbar__characters {\n display: grid;\n grid-template-columns: repeat(9, minmax(0, 1fr));\n gap: 2px;\n}\n\n.doc-toolbar__characters button {\n height: 26px;\n padding: 0;\n border: 0;\n border-radius: 4px;\n color: inherit;\n background: transparent;\n font-size: 15px;\n cursor: pointer;\n}\n\n.doc-toolbar__characters button:hover {\n background: var(--el-fill-color-light);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { type Component, computed, ref } from 'vue';\nimport {\n Bold,\n CalendarDays,\n ChevronDown,\n Code,\n Ellipsis,\n FileCode,\n FileDown,\n FileSliders,\n FileType,\n Grid3x3,\n ImagePlus,\n Italic,\n Languages,\n Link,\n List,\n ListIndentDecrease,\n ListIndentIncrease,\n ListOrdered,\n ListTodo,\n Maximize2,\n Minimize2,\n Omega,\n PilcrowLeft,\n PilcrowRight,\n Plus,\n Printer,\n Quote,\n Redo2,\n RemoveFormatting,\n Search,\n SeparatorHorizontal,\n SquareCode,\n SquareSplitVertical,\n Strikethrough,\n Subscript,\n Superscript,\n TextAlignCenter,\n TextAlignEnd,\n TextAlignJustify,\n TextAlignStart,\n Underline,\n Undo2,\n UnfoldVertical,\n Upload\n} from '@lucide/vue';\nimport {\n type CheckboxValueType,\n ElButton,\n ElCheckbox,\n ElDropdown,\n ElDropdownItem,\n ElDropdownMenu,\n ElInput,\n ElPopover,\n type InputInstance\n} from 'element-plus';\n\nimport type { HeadingTag, TextAlign, TextDirection } from '../core/engine/blocks';\nimport type { DocumentEngine } from '../core/engine/engine';\nimport type { MarkName } from '../core/engine/marks';\nimport { type EditorLabelKey, formatShortcut, t, withShortcut } from '../core/labels';\nimport type { PageSettings } from '../core/page';\nimport type { DocumentMenuAction } from '../core/types';\nimport { type EditorUiState, normalizeFontFamily } from '../core/ui-state';\nimport EditorColorPicker from './editor-color-picker.vue';\nimport EditorPageSetup from './editor-page-setup.vue';\nimport EditorTablePicker from './editor-table-picker.vue';\n\n/**\n * Compact single-row toolbar: history, text style, font, marks, colours, paragraph layout, lists, insertions, search,\n * page setup and the document menu. Reads a flat state snapshot and calls engine commands directly.\n */\ndefineOptions({ name: 'EditorToolbar' });\n\ninterface Props {\n /** Read-only document: every editing control is disabled. */\n disabled: boolean | undefined;\n /** Engine that receives the commands. */\n engine: DocumentEngine;\n /** Whether the find bar is open, shown as the search button state. */\n findOpen: boolean;\n /** Whether the editor is in fullscreen, used for the menu item label. */\n fullscreen: boolean;\n /** Current page settings, edited in the page setup popover. */\n page: PageSettings;\n /** Whether the HTML source is shown; formatting controls are disabled meanwhile. */\n sourceMode: boolean;\n /** Formatting at the caret, used for active states and labels. */\n state: EditorUiState;\n /** Whether images are being uploaded, shown on the upload button. */\n uploading: boolean;\n}\n\nconst props = defineProps<Props>();\n\ninterface Emits {\n /** The search button was pressed. */\n find: [];\n /** Image files were picked from the computer; the editor validates and uploads them. */\n insertImages: [files: File[]];\n /** An action was chosen in the document menu. */\n menu: [action: DocumentMenuAction];\n /** New page settings were chosen in the page setup popover. */\n 'update:page': [page: PageSettings];\n}\n\nconst emit = defineEmits<Emits>();\n\n/** Keys of the state snapshot that hold on/off values. */\ntype BooleanStateKey = {\n [K in keyof EditorUiState]: EditorUiState[K] extends boolean ? K : never;\n}[keyof EditorUiState];\n\n/** A toolbar button that toggles formatting and shows whether it is active. */\ninterface ToggleButton {\n /** State flag that marks the button as active. */\n key: BooleanStateKey;\n icon: Component;\n label: EditorLabelKey;\n /** Shortcut shown in the tooltip, written with `Mod` for Ctrl/⌘. */\n shortcut: string;\n /** Applies the formatting. */\n toggle: (engine: DocumentEngine) => void;\n}\n\n/** Rows, columns and header choice from the table picker. */\ninterface TableSize {\n rows: number;\n cols: number;\n withHeaderRow: boolean;\n}\n\n/** Dropdown command that removes an explicit value and falls back to the document default. */\nconst DEFAULT_COMMAND = 'default';\n/** Font the document uses when no font is applied. */\nconst DEFAULT_FONT = 'Times New Roman';\n/** Font size, in points, the document uses when no size is applied. */\nconst DEFAULT_FONT_SIZE = 12;\n/** Points per CSS pixel. */\nconst POINTS_PER_PIXEL = 0.75;\n\nconst FONT_FAMILIES = [\n { label: 'Times New Roman', value: \"'Times New Roman', Times, serif\" },\n { label: 'Arial', value: 'Arial, Helvetica, sans-serif' },\n { label: 'Calibri', value: 'Calibri, Carlito, sans-serif' },\n { label: 'Cambria', value: 'Cambria, Caladea, serif' },\n { label: 'Georgia', value: 'Georgia, serif' },\n { label: 'Tahoma', value: 'Tahoma, Verdana, sans-serif' },\n { label: 'Verdana', value: 'Verdana, sans-serif' },\n { label: 'Courier New', value: \"'Courier New', Courier, monospace\" }\n].map(font => ({ ...font, normalized: normalizeFontFamily(font.value) }));\n\n/** Point sizes, as in office suites. */\nconst FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 36, 48, 72];\nconst LINE_HEIGHTS = ['1', '1.15', '1.5', '2', '2.5', '3'];\n/** Heading levels offered in the text style menu. */\nconst HEADING_LEVELS = 4;\n\nconst ALIGNMENTS = [\n { value: 'left', icon: TextAlignStart, label: 'editor.align.left', shortcut: 'Mod+Shift+L' },\n { value: 'center', icon: TextAlignCenter, label: 'editor.align.center', shortcut: 'Mod+Shift+E' },\n { value: 'right', icon: TextAlignEnd, label: 'editor.align.right', shortcut: 'Mod+Shift+R' },\n { value: 'justify', icon: TextAlignJustify, label: 'editor.align.justify', shortcut: 'Mod+Shift+J' }\n] as const;\n\nconst DIRECTIONS = [\n { value: 'auto', icon: Languages, label: 'editor.direction.auto' },\n { value: 'ltr', icon: PilcrowRight, label: 'editor.direction.ltr' },\n { value: 'rtl', icon: PilcrowLeft, label: 'editor.direction.rtl' }\n] as const;\n\nconst MARK_BUTTONS: ToggleButton[] = [\n { key: 'bold', icon: Bold, label: 'editor.bold', shortcut: 'Mod+B', toggle: engine => engine.toggleMark('bold') },\n {\n key: 'italic',\n icon: Italic,\n label: 'editor.italic',\n shortcut: 'Mod+I',\n toggle: engine => engine.toggleMark('italic')\n },\n {\n key: 'underline',\n icon: Underline,\n label: 'editor.underline',\n shortcut: 'Mod+U',\n toggle: engine => engine.toggleMark('underline')\n }\n];\n\nconst LIST_BUTTONS: ToggleButton[] = [\n {\n key: 'bulletList',\n icon: List,\n label: 'editor.bulletList',\n shortcut: 'Mod+Shift+8',\n toggle: engine => engine.toggleList('bulletList')\n },\n {\n key: 'orderedList',\n icon: ListOrdered,\n label: 'editor.orderedList',\n shortcut: 'Mod+Shift+7',\n toggle: engine => engine.toggleList('orderedList')\n },\n {\n key: 'taskList',\n icon: ListTodo,\n label: 'editor.taskList',\n shortcut: 'Mod+Shift+9',\n toggle: engine => engine.toggleList('taskList')\n }\n];\n\n/** Marks offered in the \"more formatting\" menu; any other command of that menu clears formatting. */\nconst TEXT_MARKS: MarkName[] = ['strike', 'subscript', 'superscript', 'code'];\n\n// biome-ignore format: compact character table.\nconst SPECIAL_CHARACTERS = [\n '«', '»', '„', '“', '”', '‘', '’', '—', '–', '…', '№', '§', '©', '®', '™', '°', '±', '×',\n '÷', '≈', '≠', '≤', '≥', '∞', '€', '$', '£', '¥', '←', '→', '↑', '↓', '•', '✓', '✗', '¶'\n];\n\n/** URLs used as they are: web and mail links, phone numbers, absolute paths and anchors. */\nconst LINK_WITH_SCHEME = /^(https?:|mailto:|tel:|\\/|#)/i;\nconst EMAIL_ADDRESS = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n/** Image URLs accepted from the image form: web addresses and absolute paths. */\nconst IMAGE_URL = /^(https?:\\/\\/|\\/)/i;\n\n/** Whether the link popover is open. */\nconst linkVisible = ref(false);\nconst linkInput = ref<InputInstance>();\nconst linkUrl = ref('');\n/** Text for a new link inserted at a collapsed caret. */\nconst linkText = ref('');\nconst linkNewTab = ref(false);\n/** Whether the link form asks for link text, because there is neither a selection nor an existing link. */\nconst linkNeedsText = ref(false);\n/** Whether the image popover is open. */\nconst imageVisible = ref(false);\nconst imageUrl = ref('');\nconst fileInput = ref<HTMLInputElement>();\n/** Whether the table picker popover is open. */\nconst tableVisible = ref(false);\n/** Whether the special characters popover is open. */\nconst charactersVisible = ref(false);\n\n/** Formatting controls are unavailable for read-only documents and while the HTML source is shown. */\nconst locked = computed(() => props.disabled || props.sourceMode);\n\n/** Name of the block type at the caret, shown on the text style dropdown. */\nconst blockLabel = computed(() => {\n if (props.state.headingLevel) return t('editor.heading', { level: props.state.headingLevel });\n if (props.state.codeBlock) return t('editor.codeBlock');\n if (props.state.blockquote) return t('editor.quote');\n return t('editor.paragraph');\n});\n\n/** Font name at the caret; unknown fonts show the first family of their stack. */\nconst fontLabel = computed(() => {\n const family = props.state.fontFamily;\n if (!family) return DEFAULT_FONT;\n const known = FONT_FAMILIES.find(font => font.normalized === family);\n return known?.label ?? (family.split(',')[0] ?? family).trim();\n});\n\n/** Font size at the caret in points, rounded to half points; pixel sizes from pasted content are converted. */\nconst fontSizeLabel = computed(() => {\n const size = Number.parseFloat(props.state.fontSize);\n if (!Number.isFinite(size)) return String(DEFAULT_FONT_SIZE);\n const points = props.state.fontSize.endsWith('px') ? size * POINTS_PER_PIXEL : size;\n return String(Math.round(points * 2) / 2);\n});\n\n/** Icon of the alignment at the caret. */\nconst alignIcon = computed(\n () => ALIGNMENTS.find(alignment => alignment.value === props.state.align)?.icon ?? TextAlignStart\n);\n\n/** Icon of the text direction at the caret. */\nconst directionIcon = computed(\n () => DIRECTIONS.find(direction => direction.value === props.state.direction)?.icon ?? Languages\n);\n\n/** Applies a choice from the text style menu: paragraph, heading level, quote or code block. */\nconst setBlock = (command: unknown) => {\n switch (command) {\n case 'paragraph':\n props.engine.setBlockType('P');\n break;\n case 'blockquote':\n props.engine.toggleBlockquote();\n break;\n case 'codeBlock':\n props.engine.toggleCodeBlock();\n break;\n default:\n props.engine.setBlockType(`H${Number(command)}` as HeadingTag);\n }\n};\n\n/** Applies a font family, or removes it for the default entry. */\nconst setFontFamily = (command: unknown) => {\n const family = String(command);\n props.engine.setTextStyle('fontFamily', family === DEFAULT_COMMAND ? null : family);\n};\n\n/** Applies a font size in points, or removes it for the default entry. */\nconst setFontSize = (command: unknown) => {\n const size = String(command);\n props.engine.setTextStyle('fontSize', size === DEFAULT_COMMAND ? null : `${size}pt`);\n};\n\n/** Applies a paragraph line height, or removes it for the default entry. */\nconst setLineHeight = (command: unknown) => {\n const lineHeight = String(command);\n props.engine.setLineHeight(lineHeight === DEFAULT_COMMAND ? null : lineHeight);\n};\n\n/** Aligns the selected paragraphs. */\nconst setAlign = (command: unknown) => props.engine.setTextAlign(String(command) as TextAlign);\n\n/** Sets the writing direction of the selected paragraphs. */\nconst setDirection = (command: unknown) => props.engine.setTextDirection(String(command) as TextDirection);\n\n/** Toggles a mark from the \"more formatting\" menu, or clears all formatting. */\nconst runTextCommand = (command: unknown) => {\n if (TEXT_MARKS.includes(command as MarkName)) props.engine.toggleMark(command as MarkName);\n else props.engine.clearFormatting();\n};\n\n/** Today's date as `dd.mm.yyyy`, the format used in Uzbek documents. */\nconst formatToday = () => {\n const today = new Date();\n const twoDigits = (value: number) => String(value).padStart(2, '0');\n return `${twoDigits(today.getDate())}.${twoDigits(today.getMonth() + 1)}.${today.getFullYear()}`;\n};\n\n/** Inserts an element from the insert menu: page break, today's date or a horizontal rule. */\nconst insertBlock = (command: unknown) => {\n switch (command) {\n case 'pageBreak':\n props.engine.insertPageBreak();\n break;\n case 'date':\n props.engine.insertText(formatToday());\n break;\n default:\n props.engine.insertHorizontalRule();\n }\n};\n\n/** Forwards a document menu choice to the editor. */\nconst onMenuCommand = (command: unknown) => emit('menu', command as DocumentMenuAction);\n\n/** Fills the link form from the link under the caret before the popover opens. */\nconst prepareLink = () => {\n const link = props.engine.getActiveLink();\n linkUrl.value = link?.getAttribute('href') ?? '';\n linkNewTab.value = link?.getAttribute('target') === '_blank';\n linkNeedsText.value = !link && !props.engine.getSelectedText();\n linkText.value = '';\n};\n\n/** Opens the link popover; used by the Mod+K shortcut and the link bubble menu. */\nconst openLink = () => {\n if (!locked.value) linkVisible.value = true;\n};\n\n/** Completes what users usually type: bare domains get `https://`, e-mail addresses get `mailto:`. */\nconst normalizeUrl = (value: string) => {\n const url = value.trim();\n if (!url || LINK_WITH_SCHEME.test(url)) return url;\n if (EMAIL_ADDRESS.test(url)) return `mailto:${url}`;\n return `https://${url}`;\n};\n\n/** Mirrors the \"open in new tab\" checkbox into the form state. */\nconst onLinkNewTabChange = (value: CheckboxValueType) => {\n linkNewTab.value = Boolean(value);\n};\n\n/** Removes the link under the caret and closes the form. */\nconst removeLink = () => {\n linkVisible.value = false;\n props.engine.unsetLink();\n};\n\n/** Creates or updates the link; an empty address removes it. */\nconst applyLink = () => {\n const href = normalizeUrl(linkUrl.value);\n if (!href) return removeLink();\n linkVisible.value = false;\n props.engine.setLink(\n href,\n linkNewTab.value ? '_blank' : null,\n linkNeedsText.value ? linkText.value.trim() : undefined\n );\n};\n\n/** Opens the file dialog for images. */\nconst pickImages = () => {\n imageVisible.value = false;\n fileInput.value?.click();\n};\n\n/** Passes the picked files to the editor and resets the input so the same file can be picked again. */\nconst onFilesPicked = (event: Event) => {\n const input = event.target as HTMLInputElement;\n const files = Array.from(input.files ?? []);\n input.value = '';\n if (files.length) emit('insertImages', files);\n};\n\n/** Inserts an image from the typed address when it is a web address or an absolute path. */\nconst insertImageUrl = () => {\n const src = imageUrl.value.trim();\n if (!IMAGE_URL.test(src)) return;\n imageVisible.value = false;\n imageUrl.value = '';\n props.engine.insertImage(src);\n};\n\n/** Inserts a table of the size chosen in the picker. */\nconst insertTable = (table: TableSize) => {\n tableVisible.value = false;\n props.engine.insertTable(table.rows, table.cols, table.withHeaderRow);\n};\n\n/** Types a special character at the caret. */\nconst insertCharacter = (character: string) => {\n charactersVisible.value = false;\n props.engine.insertText(character);\n};\n\ndefineExpose({\n /** Opens the link popover. */\n openLink\n});\n</script>\n\n<template>\n <div class=\"doc-toolbar\" role=\"toolbar\" :aria-label=\"t('editor.toolbar')\">\n <div class=\"doc-toolbar__group\">\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.undo')\"\n :disabled=\"locked || !state.canUndo\"\n :title=\"withShortcut('editor.undo', 'Mod+Z')\"\n @mousedown.prevent\n @click=\"engine.undo()\"\n >\n <Undo2 :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.redo')\"\n :disabled=\"locked || !state.canRedo\"\n :title=\"withShortcut('editor.redo', 'Mod+Y')\"\n @mousedown.prevent\n @click=\"engine.redo()\"\n >\n <Redo2 :size=\"16\" />\n </button>\n </div>\n\n <span class=\"doc-toolbar__divider\" />\n\n <div class=\"doc-toolbar__group\">\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"setBlock\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select doc-toolbar__block\"\n :disabled=\"locked\"\n :title=\"t('editor.textStyle')\"\n @mousedown.prevent\n >\n <span class=\"doc-tb-button__label\">{{ blockLabel }}</span>\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item\n command=\"paragraph\"\n :class=\"{ 'is-selected': !state.headingLevel && !state.codeBlock && !state.blockquote }\"\n >\n {{ t('editor.paragraph') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+Alt+0') }}</span>\n </el-dropdown-item>\n <el-dropdown-item\n v-for=\"level in HEADING_LEVELS\"\n :key=\"level\"\n :class=\"{ 'is-selected': state.headingLevel === level }\"\n :command=\"level\"\n >\n <span :class=\"`doc-menu__h${level}`\">{{ t('editor.heading', { level }) }}</span>\n <span class=\"doc-menu__hint\">{{ formatShortcut(`Mod+Alt+${level}`) }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"blockquote\" divided :class=\"{ 'is-selected': state.blockquote }\">\n <Quote :size=\"14\" />\n {{ t('editor.quote') }}\n </el-dropdown-item>\n <el-dropdown-item command=\"codeBlock\" :class=\"{ 'is-selected': state.codeBlock }\">\n <SquareCode :size=\"14\" />\n {{ t('editor.codeBlock') }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n\n <el-dropdown\n max-height=\"320px\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n @command=\"setFontFamily\"\n >\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select doc-toolbar__font\"\n :disabled=\"locked\"\n :title=\"t('editor.fontFamily')\"\n @mousedown.prevent\n >\n <span class=\"doc-tb-button__label\">{{ fontLabel }}</span>\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item :class=\"{ 'is-selected': !state.fontFamily }\" :command=\"DEFAULT_COMMAND\">\n {{ t('editor.defaultFont') }}\n </el-dropdown-item>\n <el-dropdown-item\n v-for=\"font in FONT_FAMILIES\"\n :key=\"font.value\"\n :class=\"{ 'is-selected': state.fontFamily === font.normalized }\"\n :command=\"font.value\"\n :style=\"{ fontFamily: font.value }\"\n >\n {{ font.label }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n\n <el-dropdown\n max-height=\"320px\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n @command=\"setFontSize\"\n >\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select doc-toolbar__size\"\n :disabled=\"locked\"\n :title=\"t('editor.fontSize')\"\n @mousedown.prevent\n >\n <span class=\"doc-tb-button__label\">{{ fontSizeLabel }}</span>\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu doc-toolbar__size-menu\">\n <el-dropdown-item :class=\"{ 'is-selected': !state.fontSize }\" :command=\"DEFAULT_COMMAND\">\n {{ t('editor.defaultSize') }}\n </el-dropdown-item>\n <el-dropdown-item\n v-for=\"size in FONT_SIZES\"\n :key=\"size\"\n :class=\"{ 'is-selected': state.fontSize === `${size}pt` }\"\n :command=\"size\"\n >\n {{ size }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n </div>\n\n <span class=\"doc-toolbar__divider\" />\n\n <div class=\"doc-toolbar__group\">\n <button\n v-for=\"button in MARK_BUTTONS\"\n :key=\"button.key\"\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': state[button.key] }\"\n :aria-label=\"t(button.label)\"\n :aria-pressed=\"state[button.key]\"\n :disabled=\"locked\"\n :title=\"withShortcut(button.label, button.shortcut)\"\n @mousedown.prevent\n @click=\"button.toggle(engine)\"\n >\n <component :is=\"button.icon\" :size=\"16\" />\n </button>\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"runTextCommand\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--caret\"\n :class=\"{ 'is-active': state.strike || state.subscript || state.superscript || state.code }\"\n :aria-label=\"t('editor.moreFormatting')\"\n :disabled=\"locked\"\n :title=\"t('editor.moreFormatting')\"\n @mousedown.prevent\n >\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item command=\"strike\" :class=\"{ 'is-selected': state.strike }\">\n <Strikethrough :size=\"14\" />\n {{ t('editor.strike') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+Shift+S') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"subscript\" :class=\"{ 'is-selected': state.subscript }\">\n <Subscript :size=\"14\" />\n {{ t('editor.subscript') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+,') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"superscript\" :class=\"{ 'is-selected': state.superscript }\">\n <Superscript :size=\"14\" />\n {{ t('editor.superscript') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+.') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"code\" :class=\"{ 'is-selected': state.code }\">\n <Code :size=\"14\" />\n {{ t('editor.inlineCode') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+E') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"clear\" divided>\n <RemoveFormatting :size=\"14\" />\n {{ t('editor.clearFormatting') }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n <EditorColorPicker\n mode=\"text\"\n :current=\"state.color\"\n :disabled=\"locked\"\n @select=\"engine.setTextStyle('color', $event)\"\n />\n <EditorColorPicker\n mode=\"highlight\"\n :current=\"state.highlight\"\n :disabled=\"locked\"\n @select=\"engine.setHighlight($event)\"\n />\n </div>\n\n <span class=\"doc-toolbar__divider\" />\n\n <div class=\"doc-toolbar__group\">\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"setAlign\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select\"\n :aria-label=\"t('editor.align')\"\n :disabled=\"locked\"\n :title=\"t('editor.align')\"\n @mousedown.prevent\n >\n <component :is=\"alignIcon\" :size=\"16\" />\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item\n v-for=\"alignment in ALIGNMENTS\"\n :key=\"alignment.value\"\n :class=\"{ 'is-selected': state.align === alignment.value }\"\n :command=\"alignment.value\"\n >\n <component :is=\"alignment.icon\" :size=\"14\" />\n {{ t(alignment.label) }}\n <span class=\"doc-menu__hint\">{{ formatShortcut(alignment.shortcut) }}</span>\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"setLineHeight\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select\"\n :aria-label=\"t('editor.lineHeight')\"\n :disabled=\"locked\"\n :title=\"t('editor.lineHeight')\"\n @mousedown.prevent\n >\n <UnfoldVertical :size=\"16\" />\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item :class=\"{ 'is-selected': !state.lineHeight }\" :command=\"DEFAULT_COMMAND\">\n {{ t('editor.defaultLineHeight') }}\n </el-dropdown-item>\n <el-dropdown-item\n v-for=\"lineHeight in LINE_HEIGHTS\"\n :key=\"lineHeight\"\n :class=\"{ 'is-selected': state.lineHeight === lineHeight }\"\n :command=\"lineHeight\"\n >\n {{ lineHeight }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"setDirection\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select\"\n :aria-label=\"t('editor.textDirection')\"\n :disabled=\"locked\"\n :title=\"t('editor.textDirection')\"\n @mousedown.prevent\n >\n <component :is=\"directionIcon\" :size=\"16\" />\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item\n v-for=\"direction in DIRECTIONS\"\n :key=\"direction.value\"\n :class=\"{ 'is-selected': state.direction === direction.value }\"\n :command=\"direction.value\"\n >\n <component :is=\"direction.icon\" :size=\"14\" />\n {{ t(direction.label) }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n\n <button\n v-for=\"button in LIST_BUTTONS\"\n :key=\"button.key\"\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': state[button.key] }\"\n :aria-label=\"t(button.label)\"\n :aria-pressed=\"state[button.key]\"\n :disabled=\"locked\"\n :title=\"withShortcut(button.label, button.shortcut)\"\n @mousedown.prevent\n @click=\"button.toggle(engine)\"\n >\n <component :is=\"button.icon\" :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.outdent')\"\n :disabled=\"locked\"\n :title=\"withShortcut('editor.outdent', 'Shift+Tab')\"\n @mousedown.prevent\n @click=\"engine.indent(-1)\"\n >\n <ListIndentDecrease :size=\"16\" />\n </button>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.indent')\"\n :disabled=\"locked\"\n :title=\"withShortcut('editor.indent', 'Tab')\"\n @mousedown.prevent\n @click=\"engine.indent(1)\"\n >\n <ListIndentIncrease :size=\"16\" />\n </button>\n </div>\n\n <span class=\"doc-toolbar__divider\" />\n\n <div class=\"doc-toolbar__group\">\n <el-popover\n v-model:visible=\"linkVisible\"\n placement=\"bottom-start\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n :width=\"300\"\n @after-enter=\"linkInput?.focus()\"\n @before-enter=\"prepareLink\"\n >\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': state.link }\"\n :aria-label=\"t('editor.link')\"\n :disabled=\"locked\"\n :title=\"withShortcut('editor.link', 'Mod+K')\"\n @mousedown.prevent\n >\n <Link :size=\"16\" />\n </button>\n </template>\n <form class=\"doc-toolbar__form\" @submit.prevent=\"applyLink\">\n <label v-if=\"linkNeedsText\">\n {{ t('editor.linkText') }}\n <el-input v-model=\"linkText\" size=\"small\" />\n </label>\n <label>\n {{ t('editor.linkUrl') }}\n <el-input ref=\"linkInput\" v-model=\"linkUrl\" placeholder=\"https://\" size=\"small\" />\n </label>\n <el-checkbox size=\"small\" :model-value=\"linkNewTab\" @change=\"onLinkNewTabChange\">\n {{ t('editor.linkNewTab') }}\n </el-checkbox>\n <div class=\"doc-toolbar__form-actions\">\n <el-button v-if=\"state.link\" size=\"small\" text type=\"danger\" @click=\"removeLink\">\n {{ t('editor.unlink') }}\n </el-button>\n <el-button native-type=\"submit\" size=\"small\" type=\"primary\">{{ t('editor.apply') }}</el-button>\n </div>\n </form>\n </el-popover>\n\n <el-popover\n v-model:visible=\"imageVisible\"\n placement=\"bottom-start\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n :width=\"300\"\n >\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.image')\"\n :disabled=\"locked\"\n :title=\"t('editor.image')\"\n @mousedown.prevent\n >\n <ImagePlus :size=\"16\" />\n </button>\n </template>\n <div class=\"doc-toolbar__form\">\n <el-button size=\"small\" :icon=\"Upload\" :loading=\"uploading\" @click=\"pickImages\">\n {{ t('editor.uploadImage') }}\n </el-button>\n <span class=\"doc-toolbar__caption\">{{ t('editor.or') }}</span>\n <form class=\"doc-toolbar__inline-form\" @submit.prevent=\"insertImageUrl\">\n <el-input\n v-model=\"imageUrl\"\n placeholder=\"https://example.com/image.png\"\n size=\"small\"\n :ariaLabel=\"t('editor.image')\"\n />\n <el-button native-type=\"submit\" size=\"small\" type=\"primary\" :disabled=\"!imageUrl.trim()\">\n {{ t('editor.insert') }}\n </el-button>\n </form>\n </div>\n </el-popover>\n <input ref=\"fileInput\" type=\"file\" accept=\"image/*\" hidden multiple @change=\"onFilesPicked\" />\n\n <el-popover\n v-model:visible=\"tableVisible\"\n placement=\"bottom-start\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n :width=\"212\"\n >\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.table.insert')\"\n :disabled=\"locked\"\n :title=\"t('editor.table.insert')\"\n @mousedown.prevent\n >\n <Grid3x3 :size=\"16\" />\n </button>\n </template>\n <EditorTablePicker v-if=\"tableVisible\" @select=\"insertTable\" />\n </el-popover>\n\n <el-popover\n v-model:visible=\"charactersVisible\"\n placement=\"bottom-start\"\n popper-class=\"doc-editor-popper\"\n trigger=\"click\"\n :disabled=\"locked\"\n :width=\"258\"\n >\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.specialCharacters')\"\n :disabled=\"locked\"\n :title=\"t('editor.specialCharacters')\"\n @mousedown.prevent\n >\n <Omega :size=\"16\" />\n </button>\n </template>\n <div class=\"doc-toolbar__characters\">\n <button\n v-for=\"character in SPECIAL_CHARACTERS\"\n :key=\"character\"\n type=\"button\"\n :title=\"character\"\n @mousedown.prevent\n @click=\"insertCharacter(character)\"\n >\n {{ character }}\n </button>\n </div>\n </el-popover>\n\n <el-dropdown popper-class=\"doc-editor-popper\" trigger=\"click\" :disabled=\"locked\" @command=\"insertBlock\">\n <button\n type=\"button\"\n class=\"doc-tb-button doc-tb-button--select\"\n :aria-label=\"t('editor.insertMore')\"\n :disabled=\"locked\"\n :title=\"t('editor.insertMore')\"\n @mousedown.prevent\n >\n <Plus :size=\"16\" />\n <ChevronDown :size=\"12\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item command=\"pageBreak\">\n <SquareSplitVertical :size=\"14\" />\n {{ t('editor.pageBreak') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+Enter') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"horizontalRule\">\n <SeparatorHorizontal :size=\"14\" />\n {{ t('editor.horizontalRule') }}\n </el-dropdown-item>\n <el-dropdown-item command=\"date\">\n <CalendarDays :size=\"14\" />\n {{ t('editor.insertDate') }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n </div>\n\n <span class=\"doc-toolbar__spacer\" />\n\n <div class=\"doc-toolbar__group\">\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :class=\"{ 'is-active': findOpen }\"\n :aria-label=\"t('editor.search.title')\"\n :disabled=\"sourceMode\"\n :title=\"withShortcut('editor.search.title', 'Mod+F')\"\n @mousedown.prevent\n @click=\"emit('find')\"\n >\n <Search :size=\"16\" />\n </button>\n\n <el-popover placement=\"bottom-end\" popper-class=\"doc-editor-popper\" trigger=\"click\" :width=\"316\">\n <template #reference>\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.page.setup')\"\n :title=\"t('editor.page.setup')\"\n @mousedown.prevent\n >\n <FileSliders :size=\"16\" />\n </button>\n </template>\n <EditorPageSetup :page=\"page\" @change=\"emit('update:page', $event)\" />\n </el-popover>\n\n <el-dropdown placement=\"bottom-end\" popper-class=\"doc-editor-popper\" trigger=\"click\" @command=\"onMenuCommand\">\n <button\n type=\"button\"\n class=\"doc-tb-button\"\n :aria-label=\"t('editor.more')\"\n :title=\"t('editor.more')\"\n @mousedown.prevent\n >\n <Ellipsis :size=\"16\" />\n </button>\n <template #dropdown>\n <el-dropdown-menu class=\"doc-menu\">\n <el-dropdown-item command=\"print\">\n <Printer :size=\"14\" />\n {{ t('editor.print') }}\n <span class=\"doc-menu__hint\">{{ formatShortcut('Mod+P') }}</span>\n </el-dropdown-item>\n <el-dropdown-item command=\"exportWord\">\n <FileType :size=\"14\" />\n {{ t('editor.exportWord') }}\n </el-dropdown-item>\n <el-dropdown-item command=\"exportHtml\">\n <FileDown :size=\"14\" />\n {{ t('editor.exportHtml') }}\n </el-dropdown-item>\n <el-dropdown-item command=\"source\" divided :class=\"{ 'is-selected': sourceMode }\">\n <FileCode :size=\"14\" />\n {{ t('editor.source') }}\n </el-dropdown-item>\n <el-dropdown-item command=\"fullscreen\">\n <component :is=\"fullscreen ? Minimize2 : Maximize2\" :size=\"14\" />\n {{ t(fullscreen ? 'editor.exitFullscreen' : 'editor.enterFullscreen') }}\n </el-dropdown-item>\n </el-dropdown-menu>\n </template>\n </el-dropdown>\n </div>\n </div>\n</template>\n\n<style scoped>\n/* Toolbar layout */\n.doc-toolbar {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 2px;\n box-sizing: border-box;\n min-height: 40px;\n padding: 5px 8px;\n overflow-x: auto;\n border-bottom: 1px solid var(--el-border-color-lighter);\n background: var(--el-bg-color);\n scrollbar-width: thin;\n}\n\n.doc-toolbar__group {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 1px;\n}\n\n.doc-toolbar__divider {\n flex: 0 0 1px;\n align-self: stretch;\n margin: 4px 5px;\n background: var(--el-border-color-lighter);\n}\n\n.doc-toolbar__spacer {\n flex: 1 1 auto;\n min-width: 8px;\n}\n\n/* Dropdown triggers with a value label */\n.doc-toolbar__block {\n width: 108px;\n}\n\n.doc-toolbar__font {\n width: 118px;\n}\n\n.doc-toolbar__size {\n width: 50px;\n}\n\n.doc-toolbar__size-menu :deep(.el-dropdown-menu__item) {\n min-width: 96px;\n}\n\n/* Popover forms (link and image) */\n.doc-toolbar__form {\n display: grid;\n gap: 8px;\n}\n\n.doc-toolbar__form label {\n display: grid;\n gap: 4px;\n color: var(--el-text-color-secondary);\n font-size: 12px;\n}\n\n.doc-toolbar__form-actions {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n}\n\n.doc-toolbar__caption {\n color: var(--el-text-color-secondary);\n font-size: 11px;\n text-align: center;\n}\n\n.doc-toolbar__inline-form {\n display: flex;\n gap: 6px;\n}\n\n/* Special characters grid */\n.doc-toolbar__characters {\n display: grid;\n grid-template-columns: repeat(9, minmax(0, 1fr));\n gap: 2px;\n}\n\n.doc-toolbar__characters button {\n height: 26px;\n padding: 0;\n border: 0;\n border-radius: 4px;\n color: inherit;\n background: transparent;\n font-size: 15px;\n cursor: pointer;\n}\n\n.doc-toolbar__characters button:hover {\n background: var(--el-fill-color-light);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';\nimport { useZIndex } from 'element-plus';\n\nimport { DocumentEngine } from '../core/engine/engine';\nimport { t } from '../core/labels';\nimport {\n type DocumentViewMode,\n type PageSettings,\n ZOOM_MAX,\n ZOOM_MIN,\n createPageSettings,\n getPageMetrics\n} from '../core/page';\nimport { type PaginationController, createPagination } from '../core/pagination';\nimport type { DocumentImageUploadHandler, DocumentMenuAction } from '../core/types';\nimport { EMPTY_UI_STATE, type EditorUiState, isSameUiState } from '../core/ui-state';\nimport EditorBubbleMenus from './editor-bubble-menus.vue';\n// biome-ignore lint/style/useImportType: Component is rendered in the template.\nimport EditorCanvas from './editor-canvas.vue';\n// biome-ignore lint/style/useImportType: Component is rendered in the template.\nimport EditorFindBar from './editor-find-bar.vue';\nimport EditorStatusBar from './editor-status-bar.vue';\n// biome-ignore lint/style/useImportType: Component is rendered in the template.\nimport EditorToolbar from './editor-toolbar.vue';\n\nimport '../styles/document-content.css';\nimport '../styles/editor-ui.css';\n\n/**\n * Word-like document editor. Wires the editing engine to the toolbar, the page or web canvas, the find bar, the\n * status bar, and the contextual bubble menus. The document HTML is bound with `v-model`, page settings with\n * `v-model:page`.\n */\ndefineOptions({ name: 'DocumentEditor', inheritAttrs: false });\n\n/** A CSS length: numbers are pixels, strings are used as written (for example, `'100%'` or `'auto'`). */\ntype CssSize = number | string;\n\ninterface Props {\n /** Places the caret at the end of the document as soon as the editor is ready. */\n autofocus?: boolean;\n /** Gray space around the page or web sheet that separates the document from the editor frame. */\n canvasPadding?: CssSize;\n /** View shown first; form fields use the lighter web view. */\n defaultViewMode?: DocumentViewMode;\n /** Makes the document read-only and disables every editing control. */\n disabled?: boolean;\n /** Height of the whole editor, or `'auto'` to grow with the content between `minHeight` and `maxHeight`. */\n height?: CssSize;\n /** Largest height of an auto-height editor; longer documents scroll inside it. */\n maxHeight?: CssSize;\n /** Largest accepted image file, in megabytes. */\n maxImageSizeMb?: number;\n /** Largest number of characters the document may contain; `0` means unlimited. */\n maxLength?: number;\n /** Smallest height of an auto-height editor. */\n minHeight?: CssSize;\n /** Text shown while the document is empty; falls back to the translated default placeholder. */\n placeholder?: string;\n /** Used as the print title and the exported file name. */\n title?: string;\n /** Uploads an inserted image and resolves with its URL; without it images are embedded as data URLs. */\n uploadImage?: DocumentImageUploadHandler;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n autofocus: false,\n canvasPadding: 50,\n defaultViewMode: 'page',\n disabled: false,\n height: 760,\n maxHeight: 600,\n maxImageSizeMb: 10,\n maxLength: 0,\n minHeight: 240,\n placeholder: '',\n title: '',\n uploadImage: undefined\n});\n\ninterface Emits {\n /** The editing surface lost focus; pending model updates have already been written. */\n blur: [];\n /** The editing surface received focus. */\n focus: [];\n /** An image was rejected by validation or its upload failed. */\n uploadError: [error: unknown];\n}\n\nconst emit = defineEmits<Emits>();\n\n/** Document HTML. An empty document is written as an empty string; typing updates it after a short delay. */\nconst model = defineModel<string>({ default: '' });\n\n/** Paper size, orientation, and margins used by the page view, printing, and export. */\nconst page = defineModel<PageSettings>('page', { default: createPageSettings });\n\n/** Delay before typing is serialized into the model; serializing the whole document per keystroke is expensive. */\nconst MODEL_UPDATE_DELAY = 200;\n/** MIME types accepted as images. */\nconst IMAGE_MIME = /^image\\//;\nconst BYTES_PER_MEGABYTE = 1024 * 1024;\n/** Zoom percentage that shows the document at its real size. */\nconst ACTUAL_SIZE_ZOOM = 100;\n/** Closing tags after which the HTML source view starts a new line. */\nconst SOURCE_LINE_ENDINGS = /(<\\/(?:p|h[1-6]|li|ul|ol|blockquote|pre|table|thead|tbody|tr|div)>|<hr>)(?!\\n)/g;\n\n/** Converts a size prop into a CSS length; numbers are pixels. */\nconst toCssSize = (value: CssSize) => (typeof value === 'number' ? `${value}px` : value);\n\nconst sectionRef = ref<HTMLElement>();\nconst canvasRef = ref<InstanceType<typeof EditorCanvas>>();\nconst toolbarRef = ref<InstanceType<typeof EditorToolbar>>();\nconst findBarRef = ref<InstanceType<typeof EditorFindBar>>();\n\n/** The editing engine, created once the canvas has mounted its editable element. */\nconst engine = shallowRef<DocumentEngine | null>(null);\n/** Where the editor is teleported in fullscreen: the surrounding dialog or drawer, otherwise `body`. */\nconst fullscreenTarget = shallowRef<HTMLElement | 'body'>('body');\n/** Stacking order taken from Element Plus when fullscreen starts. */\nconst fullscreenZIndex = ref<number>();\nconst viewMode = ref<DocumentViewMode>(props.defaultViewMode);\n/** Zoom in percent. */\nconst zoom = ref(ACTUAL_SIZE_ZOOM);\nconst fullscreen = ref(false);\n/** Whether the raw HTML textarea replaces the canvas. */\nconst sourceMode = ref(false);\n/** HTML edited in source mode; applied to the document when source mode is left. */\nconst sourceHtml = ref('');\nconst findOpen = ref(false);\n/** Whether the find bar also shows the replacement row. */\nconst replaceOpen = ref(false);\n/** Whether images are currently being read or uploaded. */\nconst uploading = ref(false);\nconst pageCount = ref(1);\n/** Page that contains the caret, shown in the status bar. */\nconst currentPage = ref(1);\n/** Toolbar snapshot; replaced only when a visible value changes, so the toolbar does not re-render per keystroke. */\nconst uiState = shallowRef<EditorUiState>(EMPTY_UI_STATE);\nconst stats = shallowRef({ words: 0, characters: 0 });\n\nconst { nextZIndex } = useZIndex();\n\nlet pagination: PaginationController | null = null;\n/** Last HTML written to the model, used to ignore the model echo of our own updates. */\nlet lastEmitted = model.value;\n/** Whether the document changed since it was last serialized into the model. */\nlet dirty = false;\nlet modelTimer: ReturnType<typeof setTimeout> | undefined;\nlet uiSyncFrame = 0;\n/** Body `overflow` value to restore when fullscreen ends. */\nlet previousBodyOverflow = '';\n/** Until the user picks zoom, narrow containers (phones, side panels) shrink the page to fit. */\nlet autoZoom = true;\n/** Last content width reported by the canvas, used by automatic zoom. */\nlet canvasWidth = 0;\n/** Unsubscribe callbacks for engine events. */\nconst disposers: Array<() => void> = [];\n\n/** Page geometry in pixels for the chosen paper settings. */\nconst metrics = computed(() => getPageMetrics(page.value));\n/** Scroll container of the canvas, which bubble menus follow while scrolling. */\nconst canvasScroll = computed(() => canvasRef.value?.scrollElement);\n/** Auto height applies only outside fullscreen, where the editor always fills the viewport. */\nconst autoHeight = computed(() => props.height === 'auto' && !fullscreen.value);\n/** CSS variables shared with the canvas, plus the fullscreen stacking order. */\nconst rootStyle = computed(() => ({\n '--doc-editor-canvas-padding': toCssSize(props.canvasPadding),\n '--doc-editor-height': toCssSize(props.height),\n '--doc-editor-min-height': toCssSize(props.minHeight),\n '--doc-editor-max-height': toCssSize(props.maxHeight),\n zIndex: fullscreen.value ? fullscreenZIndex.value : undefined\n}));\n\n/** Recounts words and characters for the status bar. */\nconst refreshStats = () => {\n if (engine.value) stats.value = engine.value.getStats();\n};\n\n/** Writes the current document HTML into the model right away and cancels a pending delayed update. */\nconst flushModel = () => {\n clearTimeout(modelTimer);\n modelTimer = undefined;\n dirty = false;\n const instance = engine.value;\n if (!instance) return;\n refreshStats();\n const html = instance.isEmpty ? '' : instance.getHTML();\n if (html === lastEmitted) return;\n lastEmitted = html;\n model.value = html;\n};\n\n/** Marks the document as changed and serializes it once typing pauses. */\nconst scheduleModelUpdate = () => {\n dirty = true;\n clearTimeout(modelTimer);\n modelTimer = setTimeout(flushModel, MODEL_UPDATE_DELAY);\n};\n\n/** Page number that contains the caret; always 1 outside a multipage page view. */\nconst readCurrentPage = () => {\n const instance = engine.value;\n const selection = document.getSelection();\n if (!instance || viewMode.value !== 'page' || pageCount.value < 2 || !selection?.rangeCount) return 1;\n const range = selection.getRangeAt(0);\n const container = range.startContainer;\n if (!instance.root.contains(container)) return currentPage.value;\n // A collapsed caret in an empty line has no client rect; its element's box is close enough.\n const caretRect =\n range.getClientRects()[0] ??\n (container instanceof Element ? container : container.parentElement)?.getBoundingClientRect();\n if (!caretRect) return currentPage.value;\n const offset = (caretRect.top - instance.root.getBoundingClientRect().top) / (zoom.value / ACTUAL_SIZE_ZOOM);\n const pageNumber = Math.floor(offset / (metrics.value.height + metrics.value.gap)) + 1;\n return Math.min(pageCount.value, Math.max(1, pageNumber));\n};\n\n/** Reads the toolbar state and current page; the snapshot is published only when a visible value changed. */\nconst syncUiState = () => {\n uiSyncFrame = 0;\n const instance = engine.value;\n if (!instance) return;\n const next = instance.getState();\n if (!isSameUiState(uiState.value, next)) uiState.value = next;\n currentPage.value = readCurrentPage();\n};\n\n/** Coalesces toolbar state reads into one per animation frame. */\nconst scheduleUiSync = () => {\n if (!uiSyncFrame) uiSyncFrame = requestAnimationFrame(syncUiState);\n};\n\n/** Reads an image file as a data URL, used when no upload handler is provided. */\nconst readImageAsDataUrl = (file: File) =>\n new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () =>\n typeof reader.result === 'string' ? resolve(reader.result) : reject(new Error(t('editor.imageReadError')));\n reader.onerror = () => reject(reader.error ?? new Error(t('editor.imageReadError')));\n reader.readAsDataURL(file);\n });\n\n/** Returns the reason a file cannot be inserted as an image, or `null` when it is acceptable. */\nconst validateImage = (file: File) => {\n if (!IMAGE_MIME.test(file.type)) return new Error(t('editor.imageTypeError'));\n if (file.size > props.maxImageSizeMb * BYTES_PER_MEGABYTE) {\n return new Error(t('editor.imageSizeError', { size: props.maxImageSizeMb }));\n }\n return null;\n};\n\n/** Validates, uploads (or embeds) and inserts images one after another; failures are reported via `uploadError`. */\nconst insertImages = async (files: File[]) => {\n if (!engine.value || props.disabled || !files.length) return;\n uploading.value = true;\n try {\n for (const file of files) {\n const error = validateImage(file);\n if (error) {\n emit('uploadError', error);\n continue;\n }\n try {\n const src = props.uploadImage ? await props.uploadImage(file) : await readImageAsDataUrl(file);\n if (src) engine.value?.insertImage(src, file.name);\n else emit('uploadError', new Error(t('editor.imageUploadError')));\n } catch (uploadError) {\n emit('uploadError', uploadError);\n }\n }\n } finally {\n uploading.value = false;\n }\n};\n\n/** Opens the find bar, optionally with the replacement row, or refocuses its input when it is already open. */\nconst openFind = (withReplace: boolean) => {\n if (sourceMode.value) return;\n if (withReplace && !props.disabled) replaceOpen.value = true;\n if (findOpen.value) findBarRef.value?.focus();\n findOpen.value = true;\n};\n\n/** Closes the find bar and returns focus to the document. */\nconst closeFind = () => {\n findOpen.value = false;\n engine.value?.focus();\n};\n\n/** Toolbar search button: opens or closes the find bar. */\nconst toggleFind = () => (findOpen.value ? closeFind() : openFind(false));\n\n/** Puts block-level closing tags on their own lines so the HTML source is readable. */\nconst formatHtml = (html: string) => html.replace(SOURCE_LINE_ENDINGS, '$1\\n');\n\n/** Switches between the canvas and the HTML source; leaving source mode applies the edited HTML as one undo step. */\nconst toggleSource = () => {\n const instance = engine.value;\n if (!instance) return;\n if (!sourceMode.value) {\n if (dirty) flushModel();\n findOpen.value = false;\n sourceHtml.value = formatHtml(instance.getHTML());\n sourceMode.value = true;\n return;\n }\n sourceMode.value = false;\n if (!props.disabled) {\n // Parsing through the sanitizer drops scripts, handlers and any markup the editor does not support.\n instance.setContent(sourceHtml.value, { addToHistory: true, emitUpdate: true });\n flushModel();\n }\n void nextTick(() => instance.focus());\n};\n\n/** Document HTML, page settings, and title used for printing and export. */\nconst exportSnapshot = () => ({\n html: engine.value && !engine.value.isEmpty ? engine.value.getHTML() : model.value,\n page: page.value,\n title: props.title || t('editor.document')\n});\n\n/** Opens the browser print dialog for the document; the export module is loaded on demand. */\nconst print = async () => {\n const { buildPrintableHtml, printHtml } = await import('../core/export');\n printHtml(buildPrintableHtml(exportSnapshot()));\n};\n\n/** Downloads the document as a standalone HTML page or as a Word-compatible `.doc` file. */\nconst exportDocument = async (format: 'html' | 'word') => {\n const { buildPrintableHtml, buildWordHtml, downloadFile, toFileName } = await import('../core/export');\n const snapshot = exportSnapshot();\n const fileName = toFileName(snapshot.title);\n if (format === 'word') downloadFile(buildWordHtml(snapshot), `${fileName}.doc`, 'application/msword');\n else downloadFile(buildPrintableHtml(snapshot), `${fileName}.html`, 'text/html;charset=utf-8');\n};\n\n/** Runs an action chosen in the toolbar's \"more\" menu. */\nconst onMenu = (action: DocumentMenuAction) => {\n switch (action) {\n case 'source':\n toggleSource();\n break;\n case 'print':\n void print();\n break;\n case 'exportHtml':\n void exportDocument('html');\n break;\n case 'exportWord':\n void exportDocument('word');\n break;\n default:\n fullscreen.value = !fullscreen.value;\n }\n};\n\n/** Applies zoom chosen by the user, which also turns automatic zoom off. */\nconst setZoom = (value: number) => {\n autoZoom = false;\n zoom.value = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, value));\n};\n\n/** Zooms so the page width fills the canvas. */\nconst fitWidth = () => {\n const next = canvasRef.value?.getFitWidthZoom();\n if (next) setZoom(next);\n};\n\n/** Shrinks the page to fit narrow canvases, never above actual size, until the user picks zoom. */\nconst applyAutoZoom = () => {\n if (!autoZoom || !canvasWidth || viewMode.value !== 'page') return;\n const fitting = Math.floor((canvasWidth / metrics.value.width) * ACTUAL_SIZE_ZOOM);\n zoom.value = Math.min(ACTUAL_SIZE_ZOOM, Math.max(ZOOM_MIN, fitting));\n};\n\n/** Remembers the canvas width reported after a resize and re-evaluates automatic zoom. */\nconst onCanvasResize = (width: number) => {\n canvasWidth = width;\n applyAutoZoom();\n};\n\n/**\n * Editor-level shortcuts: find (Mod+F), replace (Mod+H), link (Mod+K), print (Mod+P) and Escape. Physical key codes\n * keep the shortcuts working with non-Latin keyboard layouts.\n */\nconst onKeydown = (event: KeyboardEvent) => {\n const mod = event.ctrlKey || event.metaKey;\n const key = event.code;\n if (mod && !event.altKey && !event.shiftKey && key === 'KeyF') {\n event.preventDefault();\n openFind(false);\n } else if (mod && !event.altKey && key === 'KeyH') {\n event.preventDefault();\n openFind(true);\n } else if (mod && key === 'KeyK' && !props.disabled && !sourceMode.value) {\n event.preventDefault();\n toolbarRef.value?.openLink();\n } else if (mod && key === 'KeyP') {\n event.preventDefault();\n void print();\n } else if (event.key === 'Escape' && findOpen.value) {\n // Escape handled by the editor must not also close a surrounding dialog.\n event.stopPropagation();\n closeFind();\n } else if (event.key === 'Escape' && fullscreen.value) {\n event.stopPropagation();\n fullscreen.value = false;\n }\n};\n\n/** Replaces the document when the model is changed from outside (not by the editor's own updates). */\nwatch(model, value => {\n const instance = engine.value;\n if (!instance || value === lastEmitted) return;\n clearTimeout(modelTimer);\n dirty = false;\n lastEmitted = value;\n instance.setContent(value);\n refreshStats();\n pagination?.schedule();\n});\n\n/** Keeps the engine's editability in sync with the `disabled` prop. */\nwatch(\n () => props.disabled,\n disabled => engine.value?.setEditable(!disabled)\n);\n\n/** Paginates in page view only; the web view is one continuous sheet. */\nwatch([metrics, viewMode], ([nextMetrics, mode]) => pagination?.setMetrics(mode === 'page' ? nextMetrics : null));\n\n/** The current page depends on pagination, zoom, and view mode. */\nwatch([pageCount, zoom, viewMode], scheduleUiSync);\n\n/** Paper or view changes can make the page too wide for the canvas again. */\nwatch([metrics, viewMode], applyAutoZoom);\n\n/** Enters or leaves fullscreen: picks the teleport target and stacking order, and locks page scrolling. */\nwatch(fullscreen, value => {\n if (value) {\n // Inside a dialog or drawer, the editor stays in it, so Element Plus' focus trap keeps allowing input.\n fullscreenTarget.value = sectionRef.value?.closest<HTMLElement>('.el-dialog, .el-drawer') ?? 'body';\n // Element Plus' own counter puts the editor above open modals and below poppers opened from it.\n fullscreenZIndex.value = nextZIndex();\n previousBodyOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n } else {\n document.body.style.overflow = previousBodyOverflow;\n }\n // Teleporting moves the editor DOM, which drops the browser selection.\n void nextTick(() => engine.value?.focus());\n});\n\n/** Creates the engine on the canvas' editable element and connects pagination and engine events. */\nonMounted(() => {\n const root = canvasRef.value?.contentElement;\n if (!root) return;\n const instance = new DocumentEngine(root, {\n content: model.value,\n editable: !props.disabled,\n maxLength: props.maxLength,\n placeholder: () => props.placeholder || t('editor.placeholder'),\n onImageFiles: files => void insertImages(files)\n });\n root.setAttribute('aria-label', props.title || t('editor.document'));\n pagination = createPagination(root, {\n onPageCount: count => {\n pageCount.value = count;\n },\n isComposing: () => instance.composing\n });\n pagination.setMetrics(viewMode.value === 'page' ? metrics.value : null);\n disposers.push(\n instance.on('update', () => {\n scheduleModelUpdate();\n pagination?.schedule();\n }),\n instance.on('selection', scheduleUiSync),\n instance.on('composition', active => {\n if (!active) pagination?.schedule();\n }),\n instance.on('focus', () => emit('focus')),\n instance.on('blur', () => {\n if (dirty) flushModel();\n emit('blur');\n })\n );\n engine.value = instance;\n refreshStats();\n scheduleUiSync();\n if (props.autofocus) instance.focus('end');\n});\n\n/** Writes pending edits into the model before the engine is destroyed, then releases every resource. */\nonBeforeUnmount(() => {\n if (dirty) flushModel();\n cancelAnimationFrame(uiSyncFrame);\n for (const dispose of disposers) dispose();\n pagination?.destroy();\n engine.value?.destroy();\n if (fullscreen.value) document.body.style.overflow = previousBodyOverflow;\n});\n\ndefineExpose({\n /** The editing engine, for advanced integrations. */\n engine,\n /** Downloads the document as an HTML page. */\n exportHtml: () => exportDocument('html'),\n /** Downloads the document as a Word-compatible file. */\n exportWord: () => exportDocument('word'),\n /** Moves keyboard focus into the document. */\n focus: () => engine.value?.focus(),\n /** Returns the document HTML, including edits not yet written to the model. */\n getHTML: () => {\n flushModel();\n return lastEmitted;\n },\n /** Opens the print dialog. */\n print\n});\n</script>\n\n<template>\n <Teleport :to=\"fullscreenTarget\" :disabled=\"!fullscreen\">\n <section\n ref=\"sectionRef\"\n v-bind=\"$attrs\"\n class=\"document-editor\"\n :class=\"{ 'is-auto-height': autoHeight, 'is-fullscreen': fullscreen }\"\n :style=\"rootStyle\"\n @keydown=\"onKeydown\"\n >\n <EditorToolbar\n v-if=\"engine\"\n ref=\"toolbarRef\"\n :disabled=\"disabled\"\n :engine=\"engine\"\n :find-open=\"findOpen\"\n :fullscreen=\"fullscreen\"\n :page=\"page\"\n :source-mode=\"sourceMode\"\n :state=\"uiState\"\n :uploading=\"uploading\"\n @find=\"toggleFind\"\n @insert-images=\"insertImages\"\n @menu=\"onMenu\"\n @update:page=\"page = $event\"\n />\n\n <div class=\"document-editor__body\">\n <EditorCanvas\n v-show=\"!sourceMode\"\n ref=\"canvasRef\"\n :auto-height=\"autoHeight\"\n :engine=\"engine\"\n :metrics=\"metrics\"\n :page-count=\"pageCount\"\n :view-mode=\"viewMode\"\n :zoom=\"zoom\"\n @resize=\"onCanvasResize\"\n />\n <textarea\n v-if=\"sourceMode\"\n v-model=\"sourceHtml\"\n class=\"document-editor__source\"\n spellcheck=\"false\"\n :aria-label=\"t('editor.source')\"\n :readonly=\"disabled\"\n />\n <EditorFindBar\n v-if=\"engine && findOpen && !sourceMode\"\n ref=\"findBarRef\"\n v-model:replace=\"replaceOpen\"\n :engine=\"engine\"\n :readonly=\"disabled\"\n @close=\"closeFind\"\n />\n <div v-if=\"uploading\" class=\"document-editor__uploading\" role=\"status\">{{ t('editor.uploading') }}</div>\n </div>\n\n <EditorStatusBar\n v-model:view-mode=\"viewMode\"\n :characters=\"stats.characters\"\n :current-page=\"currentPage\"\n :fullscreen=\"fullscreen\"\n :max-length=\"maxLength\"\n :page-count=\"pageCount\"\n :words=\"stats.words\"\n :zoom=\"zoom\"\n @fit-width=\"fitWidth\"\n @toggle-fullscreen=\"fullscreen = !fullscreen\"\n @update:zoom=\"setZoom\"\n />\n\n <EditorBubbleMenus\n v-if=\"engine && canvasScroll && !disabled && !sourceMode\"\n :engine=\"engine\"\n :scroll-target=\"canvasScroll\"\n @edit-link=\"toolbarRef?.openLink()\"\n />\n </section>\n </Teleport>\n</template>\n\n<style scoped>\n/* Editor frame. The size variables mirror the prop defaults; the component overrides them inline from the props. */\n.document-editor {\n --doc-editor-canvas-padding: 50px;\n --doc-editor-height: 760px;\n --doc-editor-min-height: 240px;\n --doc-editor-max-height: 600px;\n\n position: relative;\n display: flex;\n width: 100%;\n min-width: 0;\n box-sizing: border-box;\n height: var(--doc-editor-height);\n min-height: 360px;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid var(--el-border-color);\n border-radius: 10px;\n background: var(--el-bg-color);\n container-type: inline-size;\n transition: border-color 160ms ease;\n}\n\n.document-editor:focus-within {\n border-color: var(--el-color-primary-light-5);\n}\n\n.document-editor.is-auto-height {\n height: auto;\n min-height: 0;\n}\n\n/* The stacking order comes from Element Plus at runtime (inline z-index). */\n.document-editor.is-fullscreen {\n position: fixed;\n inset: 0;\n height: 100dvh;\n border: 0;\n border-radius: 0;\n}\n\n/* Body: canvas, HTML source and overlays */\n.document-editor__body {\n position: relative;\n display: flex;\n min-height: 0;\n flex: 1 1 auto;\n flex-direction: column;\n}\n\n.document-editor__source {\n flex: 1 1 auto;\n min-height: 0;\n box-sizing: border-box;\n margin: 0;\n padding: 16px 20px;\n border: 0;\n outline: none;\n color: #1f2328;\n background: #fbfcfd;\n font: 13px/1.6 ui-monospace, SFMono-Regular, Consolas, monospace;\n resize: none;\n tab-size: 2;\n}\n\n.document-editor__uploading {\n position: absolute;\n bottom: 12px;\n left: 50%;\n padding: 6px 12px;\n border-radius: 999px;\n color: #fff;\n background: rgb(16 24 40 / 80%);\n font-size: 12px;\n pointer-events: none;\n transform: translateX(-50%);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';\nimport { useZIndex } from 'element-plus';\n\nimport { DocumentEngine } from '../core/engine/engine';\nimport { t } from '../core/labels';\nimport {\n type DocumentViewMode,\n type PageSettings,\n ZOOM_MAX,\n ZOOM_MIN,\n createPageSettings,\n getPageMetrics\n} from '../core/page';\nimport { type PaginationController, createPagination } from '../core/pagination';\nimport type { DocumentImageUploadHandler, DocumentMenuAction } from '../core/types';\nimport { EMPTY_UI_STATE, type EditorUiState, isSameUiState } from '../core/ui-state';\nimport EditorBubbleMenus from './editor-bubble-menus.vue';\n// biome-ignore lint/style/useImportType: Component is rendered in the template.\nimport EditorCanvas from './editor-canvas.vue';\n// biome-ignore lint/style/useImportType: Component is rendered in the template.\nimport EditorFindBar from './editor-find-bar.vue';\nimport EditorStatusBar from './editor-status-bar.vue';\n// biome-ignore lint/style/useImportType: Component is rendered in the template.\nimport EditorToolbar from './editor-toolbar.vue';\n\nimport '../styles/document-content.css';\nimport '../styles/editor-ui.css';\n\n/**\n * Word-like document editor. Wires the editing engine to the toolbar, the page or web canvas, the find bar, the\n * status bar, and the contextual bubble menus. The document HTML is bound with `v-model`, page settings with\n * `v-model:page`.\n */\ndefineOptions({ name: 'DocumentEditor', inheritAttrs: false });\n\n/** A CSS length: numbers are pixels, strings are used as written (for example, `'100%'` or `'auto'`). */\ntype CssSize = number | string;\n\ninterface Props {\n /** Places the caret at the end of the document as soon as the editor is ready. */\n autofocus?: boolean;\n /** Gray space around the page or web sheet that separates the document from the editor frame. */\n canvasPadding?: CssSize;\n /** View shown first; form fields use the lighter web view. */\n defaultViewMode?: DocumentViewMode;\n /** Makes the document read-only and disables every editing control. */\n disabled?: boolean;\n /** Height of the whole editor, or `'auto'` to grow with the content between `minHeight` and `maxHeight`. */\n height?: CssSize;\n /** Largest height of an auto-height editor; longer documents scroll inside it. */\n maxHeight?: CssSize;\n /** Largest accepted image file, in megabytes. */\n maxImageSizeMb?: number;\n /** Largest number of characters the document may contain; `0` means unlimited. */\n maxLength?: number;\n /** Smallest height of an auto-height editor. */\n minHeight?: CssSize;\n /** Text shown while the document is empty; falls back to the translated default placeholder. */\n placeholder?: string;\n /** Used as the print title and the exported file name. */\n title?: string;\n /** Uploads an inserted image and resolves with its URL; without it images are embedded as data URLs. */\n uploadImage?: DocumentImageUploadHandler;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n autofocus: false,\n canvasPadding: 50,\n defaultViewMode: 'page',\n disabled: false,\n height: 760,\n maxHeight: 600,\n maxImageSizeMb: 10,\n maxLength: 0,\n minHeight: 240,\n placeholder: '',\n title: '',\n uploadImage: undefined\n});\n\ninterface Emits {\n /** The editing surface lost focus; pending model updates have already been written. */\n blur: [];\n /** The editing surface received focus. */\n focus: [];\n /** An image was rejected by validation or its upload failed. */\n uploadError: [error: unknown];\n}\n\nconst emit = defineEmits<Emits>();\n\n/** Document HTML. An empty document is written as an empty string; typing updates it after a short delay. */\nconst model = defineModel<string>({ default: '' });\n\n/** Paper size, orientation, and margins used by the page view, printing, and export. */\nconst page = defineModel<PageSettings>('page', { default: createPageSettings });\n\n/** Delay before typing is serialized into the model; serializing the whole document per keystroke is expensive. */\nconst MODEL_UPDATE_DELAY = 200;\n/** MIME types accepted as images. */\nconst IMAGE_MIME = /^image\\//;\nconst BYTES_PER_MEGABYTE = 1024 * 1024;\n/** Zoom percentage that shows the document at its real size. */\nconst ACTUAL_SIZE_ZOOM = 100;\n/** Closing tags after which the HTML source view starts a new line. */\nconst SOURCE_LINE_ENDINGS = /(<\\/(?:p|h[1-6]|li|ul|ol|blockquote|pre|table|thead|tbody|tr|div)>|<hr>)(?!\\n)/g;\n\n/** Converts a size prop into a CSS length; numbers are pixels. */\nconst toCssSize = (value: CssSize) => (typeof value === 'number' ? `${value}px` : value);\n\nconst sectionRef = ref<HTMLElement>();\nconst canvasRef = ref<InstanceType<typeof EditorCanvas>>();\nconst toolbarRef = ref<InstanceType<typeof EditorToolbar>>();\nconst findBarRef = ref<InstanceType<typeof EditorFindBar>>();\n\n/** The editing engine, created once the canvas has mounted its editable element. */\nconst engine = shallowRef<DocumentEngine | null>(null);\n/** Where the editor is teleported in fullscreen: the surrounding dialog or drawer, otherwise `body`. */\nconst fullscreenTarget = shallowRef<HTMLElement | 'body'>('body');\n/** Stacking order taken from Element Plus when fullscreen starts. */\nconst fullscreenZIndex = ref<number>();\nconst viewMode = ref<DocumentViewMode>(props.defaultViewMode);\n/** Zoom in percent. */\nconst zoom = ref(ACTUAL_SIZE_ZOOM);\nconst fullscreen = ref(false);\n/** Whether the raw HTML textarea replaces the canvas. */\nconst sourceMode = ref(false);\n/** HTML edited in source mode; applied to the document when source mode is left. */\nconst sourceHtml = ref('');\nconst findOpen = ref(false);\n/** Whether the find bar also shows the replacement row. */\nconst replaceOpen = ref(false);\n/** Whether images are currently being read or uploaded. */\nconst uploading = ref(false);\nconst pageCount = ref(1);\n/** Page that contains the caret, shown in the status bar. */\nconst currentPage = ref(1);\n/** Toolbar snapshot; replaced only when a visible value changes, so the toolbar does not re-render per keystroke. */\nconst uiState = shallowRef<EditorUiState>(EMPTY_UI_STATE);\nconst stats = shallowRef({ words: 0, characters: 0 });\n\nconst { nextZIndex } = useZIndex();\n\nlet pagination: PaginationController | null = null;\n/** Last HTML written to the model, used to ignore the model echo of our own updates. */\nlet lastEmitted = model.value;\n/** Whether the document changed since it was last serialized into the model. */\nlet dirty = false;\nlet modelTimer: ReturnType<typeof setTimeout> | undefined;\nlet uiSyncFrame = 0;\n/** Body `overflow` value to restore when fullscreen ends. */\nlet previousBodyOverflow = '';\n/** Until the user picks zoom, narrow containers (phones, side panels) shrink the page to fit. */\nlet autoZoom = true;\n/** Last content width reported by the canvas, used by automatic zoom. */\nlet canvasWidth = 0;\n/** Unsubscribe callbacks for engine events. */\nconst disposers: Array<() => void> = [];\n\n/** Page geometry in pixels for the chosen paper settings. */\nconst metrics = computed(() => getPageMetrics(page.value));\n/** Scroll container of the canvas, which bubble menus follow while scrolling. */\nconst canvasScroll = computed(() => canvasRef.value?.scrollElement);\n/** Auto height applies only outside fullscreen, where the editor always fills the viewport. */\nconst autoHeight = computed(() => props.height === 'auto' && !fullscreen.value);\n/** CSS variables shared with the canvas, plus the fullscreen stacking order. */\nconst rootStyle = computed(() => ({\n '--doc-editor-canvas-padding': toCssSize(props.canvasPadding),\n '--doc-editor-height': toCssSize(props.height),\n '--doc-editor-min-height': toCssSize(props.minHeight),\n '--doc-editor-max-height': toCssSize(props.maxHeight),\n zIndex: fullscreen.value ? fullscreenZIndex.value : undefined\n}));\n\n/** Recounts words and characters for the status bar. */\nconst refreshStats = () => {\n if (engine.value) stats.value = engine.value.getStats();\n};\n\n/** Writes the current document HTML into the model right away and cancels a pending delayed update. */\nconst flushModel = () => {\n clearTimeout(modelTimer);\n modelTimer = undefined;\n dirty = false;\n const instance = engine.value;\n if (!instance) return;\n refreshStats();\n const html = instance.isEmpty ? '' : instance.getHTML();\n if (html === lastEmitted) return;\n lastEmitted = html;\n model.value = html;\n};\n\n/** Marks the document as changed and serializes it once typing pauses. */\nconst scheduleModelUpdate = () => {\n dirty = true;\n clearTimeout(modelTimer);\n modelTimer = setTimeout(flushModel, MODEL_UPDATE_DELAY);\n};\n\n/** Page number that contains the caret; always 1 outside a multipage page view. */\nconst readCurrentPage = () => {\n const instance = engine.value;\n const selection = document.getSelection();\n if (!instance || viewMode.value !== 'page' || pageCount.value < 2 || !selection?.rangeCount) return 1;\n const range = selection.getRangeAt(0);\n const container = range.startContainer;\n if (!instance.root.contains(container)) return currentPage.value;\n // A collapsed caret in an empty line has no client rect; its element's box is close enough.\n const caretRect =\n range.getClientRects()[0] ??\n (container instanceof Element ? container : container.parentElement)?.getBoundingClientRect();\n if (!caretRect) return currentPage.value;\n const offset = (caretRect.top - instance.root.getBoundingClientRect().top) / (zoom.value / ACTUAL_SIZE_ZOOM);\n const pageNumber = Math.floor(offset / (metrics.value.height + metrics.value.gap)) + 1;\n return Math.min(pageCount.value, Math.max(1, pageNumber));\n};\n\n/** Reads the toolbar state and current page; the snapshot is published only when a visible value changed. */\nconst syncUiState = () => {\n uiSyncFrame = 0;\n const instance = engine.value;\n if (!instance) return;\n const next = instance.getState();\n if (!isSameUiState(uiState.value, next)) uiState.value = next;\n currentPage.value = readCurrentPage();\n};\n\n/** Coalesces toolbar state reads into one per animation frame. */\nconst scheduleUiSync = () => {\n if (!uiSyncFrame) uiSyncFrame = requestAnimationFrame(syncUiState);\n};\n\n/** Reads an image file as a data URL, used when no upload handler is provided. */\nconst readImageAsDataUrl = (file: File) =>\n new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () =>\n typeof reader.result === 'string' ? resolve(reader.result) : reject(new Error(t('editor.imageReadError')));\n reader.onerror = () => reject(reader.error ?? new Error(t('editor.imageReadError')));\n reader.readAsDataURL(file);\n });\n\n/** Returns the reason a file cannot be inserted as an image, or `null` when it is acceptable. */\nconst validateImage = (file: File) => {\n if (!IMAGE_MIME.test(file.type)) return new Error(t('editor.imageTypeError'));\n if (file.size > props.maxImageSizeMb * BYTES_PER_MEGABYTE) {\n return new Error(t('editor.imageSizeError', { size: props.maxImageSizeMb }));\n }\n return null;\n};\n\n/** Validates, uploads (or embeds) and inserts images one after another; failures are reported via `uploadError`. */\nconst insertImages = async (files: File[]) => {\n if (!engine.value || props.disabled || !files.length) return;\n uploading.value = true;\n try {\n for (const file of files) {\n const error = validateImage(file);\n if (error) {\n emit('uploadError', error);\n continue;\n }\n try {\n const src = props.uploadImage ? await props.uploadImage(file) : await readImageAsDataUrl(file);\n if (src) engine.value?.insertImage(src, file.name);\n else emit('uploadError', new Error(t('editor.imageUploadError')));\n } catch (uploadError) {\n emit('uploadError', uploadError);\n }\n }\n } finally {\n uploading.value = false;\n }\n};\n\n/** Opens the find bar, optionally with the replacement row, or refocuses its input when it is already open. */\nconst openFind = (withReplace: boolean) => {\n if (sourceMode.value) return;\n if (withReplace && !props.disabled) replaceOpen.value = true;\n if (findOpen.value) findBarRef.value?.focus();\n findOpen.value = true;\n};\n\n/** Closes the find bar and returns focus to the document. */\nconst closeFind = () => {\n findOpen.value = false;\n engine.value?.focus();\n};\n\n/** Toolbar search button: opens or closes the find bar. */\nconst toggleFind = () => (findOpen.value ? closeFind() : openFind(false));\n\n/** Puts block-level closing tags on their own lines so the HTML source is readable. */\nconst formatHtml = (html: string) => html.replace(SOURCE_LINE_ENDINGS, '$1\\n');\n\n/** Switches between the canvas and the HTML source; leaving source mode applies the edited HTML as one undo step. */\nconst toggleSource = () => {\n const instance = engine.value;\n if (!instance) return;\n if (!sourceMode.value) {\n if (dirty) flushModel();\n findOpen.value = false;\n sourceHtml.value = formatHtml(instance.getHTML());\n sourceMode.value = true;\n return;\n }\n sourceMode.value = false;\n if (!props.disabled) {\n // Parsing through the sanitizer drops scripts, handlers and any markup the editor does not support.\n instance.setContent(sourceHtml.value, { addToHistory: true, emitUpdate: true });\n flushModel();\n }\n void nextTick(() => instance.focus());\n};\n\n/** Document HTML, page settings, and title used for printing and export. */\nconst exportSnapshot = () => ({\n html: engine.value && !engine.value.isEmpty ? engine.value.getHTML() : model.value,\n page: page.value,\n title: props.title || t('editor.document')\n});\n\n/** Opens the browser print dialog for the document; the export module is loaded on demand. */\nconst print = async () => {\n const { buildPrintableHtml, printHtml } = await import('../core/export');\n printHtml(buildPrintableHtml(exportSnapshot()));\n};\n\n/** Downloads the document as a standalone HTML page or as a Word-compatible `.doc` file. */\nconst exportDocument = async (format: 'html' | 'word') => {\n const { buildPrintableHtml, buildWordHtml, downloadFile, toFileName } = await import('../core/export');\n const snapshot = exportSnapshot();\n const fileName = toFileName(snapshot.title);\n if (format === 'word') downloadFile(buildWordHtml(snapshot), `${fileName}.doc`, 'application/msword');\n else downloadFile(buildPrintableHtml(snapshot), `${fileName}.html`, 'text/html;charset=utf-8');\n};\n\n/** Runs an action chosen in the toolbar's \"more\" menu. */\nconst onMenu = (action: DocumentMenuAction) => {\n switch (action) {\n case 'source':\n toggleSource();\n break;\n case 'print':\n void print();\n break;\n case 'exportHtml':\n void exportDocument('html');\n break;\n case 'exportWord':\n void exportDocument('word');\n break;\n default:\n fullscreen.value = !fullscreen.value;\n }\n};\n\n/** Applies zoom chosen by the user, which also turns automatic zoom off. */\nconst setZoom = (value: number) => {\n autoZoom = false;\n zoom.value = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, value));\n};\n\n/** Zooms so the page width fills the canvas. */\nconst fitWidth = () => {\n const next = canvasRef.value?.getFitWidthZoom();\n if (next) setZoom(next);\n};\n\n/** Shrinks the page to fit narrow canvases, never above actual size, until the user picks zoom. */\nconst applyAutoZoom = () => {\n if (!autoZoom || !canvasWidth || viewMode.value !== 'page') return;\n const fitting = Math.floor((canvasWidth / metrics.value.width) * ACTUAL_SIZE_ZOOM);\n zoom.value = Math.min(ACTUAL_SIZE_ZOOM, Math.max(ZOOM_MIN, fitting));\n};\n\n/** Remembers the canvas width reported after a resize and re-evaluates automatic zoom. */\nconst onCanvasResize = (width: number) => {\n canvasWidth = width;\n applyAutoZoom();\n};\n\n/**\n * Editor-level shortcuts: find (Mod+F), replace (Mod+H), link (Mod+K), print (Mod+P) and Escape. Physical key codes\n * keep the shortcuts working with non-Latin keyboard layouts.\n */\nconst onKeydown = (event: KeyboardEvent) => {\n const mod = event.ctrlKey || event.metaKey;\n const key = event.code;\n if (mod && !event.altKey && !event.shiftKey && key === 'KeyF') {\n event.preventDefault();\n openFind(false);\n } else if (mod && !event.altKey && key === 'KeyH') {\n event.preventDefault();\n openFind(true);\n } else if (mod && key === 'KeyK' && !props.disabled && !sourceMode.value) {\n event.preventDefault();\n toolbarRef.value?.openLink();\n } else if (mod && key === 'KeyP') {\n event.preventDefault();\n void print();\n } else if (event.key === 'Escape' && findOpen.value) {\n // Escape handled by the editor must not also close a surrounding dialog.\n event.stopPropagation();\n closeFind();\n } else if (event.key === 'Escape' && fullscreen.value) {\n event.stopPropagation();\n fullscreen.value = false;\n }\n};\n\n/** Replaces the document when the model is changed from outside (not by the editor's own updates). */\nwatch(model, value => {\n const instance = engine.value;\n if (!instance || value === lastEmitted) return;\n clearTimeout(modelTimer);\n dirty = false;\n lastEmitted = value;\n instance.setContent(value);\n refreshStats();\n pagination?.schedule();\n});\n\n/** Keeps the engine's editability in sync with the `disabled` prop. */\nwatch(\n () => props.disabled,\n disabled => engine.value?.setEditable(!disabled)\n);\n\n/** Paginates in page view only; the web view is one continuous sheet. */\nwatch([metrics, viewMode], ([nextMetrics, mode]) => pagination?.setMetrics(mode === 'page' ? nextMetrics : null));\n\n/** The current page depends on pagination, zoom, and view mode. */\nwatch([pageCount, zoom, viewMode], scheduleUiSync);\n\n/** Paper or view changes can make the page too wide for the canvas again. */\nwatch([metrics, viewMode], applyAutoZoom);\n\n/** Enters or leaves fullscreen: picks the teleport target and stacking order, and locks page scrolling. */\nwatch(fullscreen, value => {\n if (value) {\n // Inside a dialog or drawer, the editor stays in it, so Element Plus' focus trap keeps allowing input.\n fullscreenTarget.value = sectionRef.value?.closest<HTMLElement>('.el-dialog, .el-drawer') ?? 'body';\n // Element Plus' own counter puts the editor above open modals and below poppers opened from it.\n fullscreenZIndex.value = nextZIndex();\n previousBodyOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n } else {\n document.body.style.overflow = previousBodyOverflow;\n }\n // Teleporting moves the editor DOM, which drops the browser selection.\n void nextTick(() => engine.value?.focus());\n});\n\n/** Creates the engine on the canvas' editable element and connects pagination and engine events. */\nonMounted(() => {\n const root = canvasRef.value?.contentElement;\n if (!root) return;\n const instance = new DocumentEngine(root, {\n content: model.value,\n editable: !props.disabled,\n maxLength: props.maxLength,\n placeholder: () => props.placeholder || t('editor.placeholder'),\n onImageFiles: files => void insertImages(files)\n });\n root.setAttribute('aria-label', props.title || t('editor.document'));\n pagination = createPagination(root, {\n onPageCount: count => {\n pageCount.value = count;\n },\n isComposing: () => instance.composing\n });\n pagination.setMetrics(viewMode.value === 'page' ? metrics.value : null);\n disposers.push(\n instance.on('update', () => {\n scheduleModelUpdate();\n pagination?.schedule();\n }),\n instance.on('selection', scheduleUiSync),\n instance.on('composition', active => {\n if (!active) pagination?.schedule();\n }),\n instance.on('focus', () => emit('focus')),\n instance.on('blur', () => {\n if (dirty) flushModel();\n emit('blur');\n })\n );\n engine.value = instance;\n refreshStats();\n scheduleUiSync();\n if (props.autofocus) instance.focus('end');\n});\n\n/** Writes pending edits into the model before the engine is destroyed, then releases every resource. */\nonBeforeUnmount(() => {\n if (dirty) flushModel();\n cancelAnimationFrame(uiSyncFrame);\n for (const dispose of disposers) dispose();\n pagination?.destroy();\n engine.value?.destroy();\n if (fullscreen.value) document.body.style.overflow = previousBodyOverflow;\n});\n\ndefineExpose({\n /** The editing engine, for advanced integrations. */\n engine,\n /** Downloads the document as an HTML page. */\n exportHtml: () => exportDocument('html'),\n /** Downloads the document as a Word-compatible file. */\n exportWord: () => exportDocument('word'),\n /** Moves keyboard focus into the document. */\n focus: () => engine.value?.focus(),\n /** Returns the document HTML, including edits not yet written to the model. */\n getHTML: () => {\n flushModel();\n return lastEmitted;\n },\n /** Opens the print dialog. */\n print\n});\n</script>\n\n<template>\n <Teleport :to=\"fullscreenTarget\" :disabled=\"!fullscreen\">\n <section\n ref=\"sectionRef\"\n v-bind=\"$attrs\"\n class=\"document-editor\"\n :class=\"{ 'is-auto-height': autoHeight, 'is-fullscreen': fullscreen }\"\n :style=\"rootStyle\"\n @keydown=\"onKeydown\"\n >\n <EditorToolbar\n v-if=\"engine\"\n ref=\"toolbarRef\"\n :disabled=\"disabled\"\n :engine=\"engine\"\n :find-open=\"findOpen\"\n :fullscreen=\"fullscreen\"\n :page=\"page\"\n :source-mode=\"sourceMode\"\n :state=\"uiState\"\n :uploading=\"uploading\"\n @find=\"toggleFind\"\n @insert-images=\"insertImages\"\n @menu=\"onMenu\"\n @update:page=\"page = $event\"\n />\n\n <div class=\"document-editor__body\">\n <EditorCanvas\n v-show=\"!sourceMode\"\n ref=\"canvasRef\"\n :auto-height=\"autoHeight\"\n :engine=\"engine\"\n :metrics=\"metrics\"\n :page-count=\"pageCount\"\n :view-mode=\"viewMode\"\n :zoom=\"zoom\"\n @resize=\"onCanvasResize\"\n />\n <textarea\n v-if=\"sourceMode\"\n v-model=\"sourceHtml\"\n class=\"document-editor__source\"\n spellcheck=\"false\"\n :aria-label=\"t('editor.source')\"\n :readonly=\"disabled\"\n />\n <EditorFindBar\n v-if=\"engine && findOpen && !sourceMode\"\n ref=\"findBarRef\"\n v-model:replace=\"replaceOpen\"\n :engine=\"engine\"\n :readonly=\"disabled\"\n @close=\"closeFind\"\n />\n <div v-if=\"uploading\" class=\"document-editor__uploading\" role=\"status\">{{ t('editor.uploading') }}</div>\n </div>\n\n <EditorStatusBar\n v-model:view-mode=\"viewMode\"\n :characters=\"stats.characters\"\n :current-page=\"currentPage\"\n :fullscreen=\"fullscreen\"\n :max-length=\"maxLength\"\n :page-count=\"pageCount\"\n :words=\"stats.words\"\n :zoom=\"zoom\"\n @fit-width=\"fitWidth\"\n @toggle-fullscreen=\"fullscreen = !fullscreen\"\n @update:zoom=\"setZoom\"\n />\n\n <EditorBubbleMenus\n v-if=\"engine && canvasScroll && !disabled && !sourceMode\"\n :engine=\"engine\"\n :scroll-target=\"canvasScroll\"\n @edit-link=\"toolbarRef?.openLink()\"\n />\n </section>\n </Teleport>\n</template>\n\n<style scoped>\n/* Editor frame. The size variables mirror the prop defaults; the component overrides them inline from the props. */\n.document-editor {\n --doc-editor-canvas-padding: 50px;\n --doc-editor-height: 760px;\n --doc-editor-min-height: 240px;\n --doc-editor-max-height: 600px;\n\n position: relative;\n display: flex;\n width: 100%;\n min-width: 0;\n box-sizing: border-box;\n height: var(--doc-editor-height);\n min-height: 360px;\n flex-direction: column;\n overflow: hidden;\n border: 1px solid var(--el-border-color);\n border-radius: 10px;\n background: var(--el-bg-color);\n container-type: inline-size;\n transition: border-color 160ms ease;\n}\n\n.document-editor:focus-within {\n border-color: var(--el-color-primary-light-5);\n}\n\n.document-editor.is-auto-height {\n height: auto;\n min-height: 0;\n}\n\n/* The stacking order comes from Element Plus at runtime (inline z-index). */\n.document-editor.is-fullscreen {\n position: fixed;\n inset: 0;\n height: 100dvh;\n border: 0;\n border-radius: 0;\n}\n\n/* Body: canvas, HTML source and overlays */\n.document-editor__body {\n position: relative;\n display: flex;\n min-height: 0;\n flex: 1 1 auto;\n flex-direction: column;\n}\n\n.document-editor__source {\n flex: 1 1 auto;\n min-height: 0;\n box-sizing: border-box;\n margin: 0;\n padding: 16px 20px;\n border: 0;\n outline: none;\n color: #1f2328;\n background: #fbfcfd;\n font: 13px/1.6 ui-monospace, SFMono-Regular, Consolas, monospace;\n resize: none;\n tab-size: 2;\n}\n\n.document-editor__uploading {\n position: absolute;\n bottom: 12px;\n left: 50%;\n padding: 6px 12px;\n border-radius: 999px;\n color: #fff;\n background: rgb(16 24 40 / 80%);\n font-size: 12px;\n pointer-events: none;\n transform: translateX(-50%);\n}\n</style>\n","<script setup lang=\"ts\">\nimport DocumentEditor from './components/document-editor.vue';\nimport type { DocumentImageUploadHandler } from './core/types';\n\n/**\n * Rich text field for forms: a wrapper around the document editor in the web view, growing with its content\n * between `minHeight` and `maxHeight`.\n */\ndefineOptions({ name: 'RichTextEditor' });\n\ninterface Props {\n /** Places the caret at the end of the content once the editor is ready. */\n autofocus?: boolean;\n /** Gray space around the sheet, in pixels or any CSS length. */\n canvasPadding?: number | string;\n /** Makes the content read-only and disables the toolbar. */\n disabled?: boolean;\n /** Height at which the field stops growing and starts scrolling. */\n maxHeight?: number | string;\n /** Largest accepted image file, in megabytes. */\n maxImageSizeMb?: number;\n /** Character limit; 0 means unlimited. */\n maxLength?: number;\n /** Smallest height of the field, including the gray space around the sheet. */\n minHeight?: number | string;\n /** Text shown while the field is empty. */\n placeholder?: string;\n /** Uploads an image and resolves with its URL; without it images are embedded as data URLs. */\n uploadImage?: DocumentImageUploadHandler;\n}\n\ninterface Emits {\n /** The editable area lost focus. */\n blur: [];\n /** The editable area received focus. */\n focus: [];\n /** An image was rejected or could not be uploaded. */\n uploadError: [error: unknown];\n}\n\nwithDefaults(defineProps<Props>(), {\n autofocus: false,\n canvasPadding: 50,\n disabled: false,\n maxHeight: 600,\n maxImageSizeMb: 10,\n maxLength: 0,\n minHeight: 240,\n placeholder: '',\n uploadImage: undefined\n});\n\nconst emit = defineEmits<Emits>();\n/** Field value as sanitized HTML; an empty document is an empty string. */\nconst model = defineModel<string>({ default: '' });\n</script>\n\n<template>\n <DocumentEditor\n v-model=\"model\"\n class=\"rich-text-editor\"\n default-view-mode=\"web\"\n height=\"auto\"\n :autofocus=\"autofocus\"\n :canvas-padding=\"canvasPadding\"\n :disabled=\"disabled\"\n :max-height=\"maxHeight\"\n :max-image-size-mb=\"maxImageSizeMb\"\n :max-length=\"maxLength\"\n :min-height=\"minHeight\"\n :placeholder=\"placeholder\"\n :upload-image=\"uploadImage\"\n @blur=\"emit('blur')\"\n @focus=\"emit('focus')\"\n @upload-error=\"emit('uploadError', $event)\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport DocumentEditor from './components/document-editor.vue';\nimport type { DocumentImageUploadHandler } from './core/types';\n\n/**\n * Rich text field for forms: a wrapper around the document editor in the web view, growing with its content\n * between `minHeight` and `maxHeight`.\n */\ndefineOptions({ name: 'RichTextEditor' });\n\ninterface Props {\n /** Places the caret at the end of the content once the editor is ready. */\n autofocus?: boolean;\n /** Gray space around the sheet, in pixels or any CSS length. */\n canvasPadding?: number | string;\n /** Makes the content read-only and disables the toolbar. */\n disabled?: boolean;\n /** Height at which the field stops growing and starts scrolling. */\n maxHeight?: number | string;\n /** Largest accepted image file, in megabytes. */\n maxImageSizeMb?: number;\n /** Character limit; 0 means unlimited. */\n maxLength?: number;\n /** Smallest height of the field, including the gray space around the sheet. */\n minHeight?: number | string;\n /** Text shown while the field is empty. */\n placeholder?: string;\n /** Uploads an image and resolves with its URL; without it images are embedded as data URLs. */\n uploadImage?: DocumentImageUploadHandler;\n}\n\ninterface Emits {\n /** The editable area lost focus. */\n blur: [];\n /** The editable area received focus. */\n focus: [];\n /** An image was rejected or could not be uploaded. */\n uploadError: [error: unknown];\n}\n\nwithDefaults(defineProps<Props>(), {\n autofocus: false,\n canvasPadding: 50,\n disabled: false,\n maxHeight: 600,\n maxImageSizeMb: 10,\n maxLength: 0,\n minHeight: 240,\n placeholder: '',\n uploadImage: undefined\n});\n\nconst emit = defineEmits<Emits>();\n/** Field value as sanitized HTML; an empty document is an empty string. */\nconst model = defineModel<string>({ default: '' });\n</script>\n\n<template>\n <DocumentEditor\n v-model=\"model\"\n class=\"rich-text-editor\"\n default-view-mode=\"web\"\n height=\"auto\"\n :autofocus=\"autofocus\"\n :canvas-padding=\"canvasPadding\"\n :disabled=\"disabled\"\n :max-height=\"maxHeight\"\n :max-image-size-mb=\"maxImageSizeMb\"\n :max-length=\"maxLength\"\n :min-height=\"minHeight\"\n :placeholder=\"placeholder\"\n :upload-image=\"uploadImage\"\n @blur=\"emit('blur')\"\n @focus=\"emit('focus')\"\n @upload-error=\"emit('uploadError', $event)\"\n />\n</template>\n"],"mappings":"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"}