@remyxjs/core 1.0.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2454 -0
- package/dist/convertCsv-B8RVtdcs.cjs +2 -0
- package/dist/convertCsv-B8RVtdcs.cjs.map +1 -0
- package/dist/convertCsv-CKzZjzLJ.js +2 -0
- package/dist/convertCsv-CKzZjzLJ.js.map +1 -0
- package/dist/convertDocx-4q89XLLv.cjs +2 -0
- package/dist/convertDocx-4q89XLLv.cjs.map +1 -0
- package/dist/convertDocx-Dmx88twM.js +2 -0
- package/dist/convertDocx-Dmx88twM.js.map +1 -0
- package/dist/convertHtml-CtYVhiTh.js +2 -0
- package/dist/convertHtml-CtYVhiTh.js.map +1 -0
- package/dist/convertHtml-DbHrdrD3.cjs +2 -0
- package/dist/convertHtml-DbHrdrD3.cjs.map +1 -0
- package/dist/convertMarkdown-Di239Gtn.js +2 -0
- package/dist/convertMarkdown-Di239Gtn.js.map +1 -0
- package/dist/convertMarkdown-eJ9Nkoid.cjs +2 -0
- package/dist/convertMarkdown-eJ9Nkoid.cjs.map +1 -0
- package/dist/convertPdf-CFA1eNNH.js +2 -0
- package/dist/convertPdf-CFA1eNNH.js.map +1 -0
- package/dist/convertPdf-CSLmTrB8.cjs +2 -0
- package/dist/convertPdf-CSLmTrB8.cjs.map +1 -0
- package/dist/convertRtf-08CoScGD.js +2 -0
- package/dist/convertRtf-08CoScGD.js.map +1 -0
- package/dist/convertRtf-BfiBLMig.cjs +2 -0
- package/dist/convertRtf-BfiBLMig.cjs.map +1 -0
- package/dist/convertText-BpgzHRuh.cjs +2 -0
- package/dist/convertText-BpgzHRuh.cjs.map +1 -0
- package/dist/convertText-sa7PxKTe.js +2 -0
- package/dist/convertText-sa7PxKTe.js.map +1 -0
- package/dist/index-4syk9eEO.js +2 -0
- package/dist/index-4syk9eEO.js.map +1 -0
- package/dist/index-B25zSs0W.js +2 -0
- package/dist/index-B25zSs0W.js.map +1 -0
- package/dist/index-B7VT6ZLa.cjs +2 -0
- package/dist/index-B7VT6ZLa.cjs.map +1 -0
- package/dist/index-BCpytFKJ.js +2 -0
- package/dist/index-BCpytFKJ.js.map +1 -0
- package/dist/index-BNKANY5i.cjs +2 -0
- package/dist/index-BNKANY5i.cjs.map +1 -0
- package/dist/index-B_g_579T.cjs +2 -0
- package/dist/index-B_g_579T.cjs.map +1 -0
- package/dist/index-BvwyeoMb.js +3 -0
- package/dist/index-BvwyeoMb.js.map +1 -0
- package/dist/index-Bw7mlUQo.js +2 -0
- package/dist/index-Bw7mlUQo.js.map +1 -0
- package/dist/index-Byatzd-A.js +2 -0
- package/dist/index-Byatzd-A.js.map +1 -0
- package/dist/index-C0z9eZLm.cjs +2 -0
- package/dist/index-C0z9eZLm.cjs.map +1 -0
- package/dist/index-C88XPqjX.js +2 -0
- package/dist/index-C88XPqjX.js.map +1 -0
- package/dist/index-CI6FPF49.cjs +2 -0
- package/dist/index-CI6FPF49.cjs.map +1 -0
- package/dist/index-CLZF5_GB.cjs +2 -0
- package/dist/index-CLZF5_GB.cjs.map +1 -0
- package/dist/index-CXSwYlG4.cjs +2 -0
- package/dist/index-CXSwYlG4.cjs.map +1 -0
- package/dist/index-Ch9gotLk.js +2 -0
- package/dist/index-Ch9gotLk.js.map +1 -0
- package/dist/index-CifDpN1Y.js +2 -0
- package/dist/index-CifDpN1Y.js.map +1 -0
- package/dist/index-D5o8VpWJ.cjs +2 -0
- package/dist/index-D5o8VpWJ.cjs.map +1 -0
- package/dist/index-DKT1bABL.js +2 -0
- package/dist/index-DKT1bABL.js.map +1 -0
- package/dist/index-DWcn72PW.js +2 -0
- package/dist/index-DWcn72PW.js.map +1 -0
- package/dist/index-DjCGzPEv.cjs +2 -0
- package/dist/index-DjCGzPEv.cjs.map +1 -0
- package/dist/index-Dq0Jr1Ae.js +2 -0
- package/dist/index-Dq0Jr1Ae.js.map +1 -0
- package/dist/index-Dw0MVypb.cjs +2 -0
- package/dist/index-Dw0MVypb.cjs.map +1 -0
- package/dist/index-FEo3LShh.cjs +2 -0
- package/dist/index-FEo3LShh.cjs.map +1 -0
- package/dist/index-O1hzAUzi.cjs +2 -0
- package/dist/index-O1hzAUzi.cjs.map +1 -0
- package/dist/index-T1ZyLzeF.cjs +2 -0
- package/dist/index-T1ZyLzeF.cjs.map +1 -0
- package/dist/index-iRikoCdK.cjs +2 -0
- package/dist/index-iRikoCdK.cjs.map +1 -0
- package/dist/index-l6Yddj6x.js +2 -0
- package/dist/index-l6Yddj6x.js.map +1 -0
- package/dist/index-rD8LZENp.js +2 -0
- package/dist/index-rD8LZENp.js.map +1 -0
- package/dist/remyx-core.cjs +2 -0
- package/dist/remyx-core.cjs.map +1 -0
- package/dist/remyx-core.css +1 -0
- package/dist/remyx-core.js +2 -0
- package/dist/remyx-core.js.map +1 -0
- package/dist/themes/callouts.css +79 -0
- package/dist/themes/collaboration.css +117 -0
- package/dist/themes/comments.css +198 -0
- package/dist/themes/dark.css +109 -0
- package/dist/themes/forest.css +109 -0
- package/dist/themes/light.css +4 -0
- package/dist/themes/links.css +115 -0
- package/dist/themes/math-toc-analytics.css +129 -0
- package/dist/themes/ocean.css +109 -0
- package/dist/themes/rose.css +109 -0
- package/dist/themes/spellcheck.css +173 -0
- package/dist/themes/sunset.css +109 -0
- package/dist/themes/templates.css +87 -0
- package/dist/themes/variables.css +3222 -0
- package/package.json +80 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-BvwyeoMb.js","sources":["../src/core/EventBus.js","../src/utils/dom.js","../src/core/Selection.js","../src/core/CommandRegistry.js","../src/utils/hash.js","../src/core/History.js","../src/utils/platform.js","../src/core/KeyboardManager.js","../src/constants/schema.js","../src/core/Sanitizer.js","../src/utils/pasteClean.js","../src/utils/escapeHTML.js","../src/utils/documentConverter/shared.js","../src/utils/documentConverter/index.js","../src/constants/defaults.js","../src/utils/fileValidation.js","../src/utils/markdownConverter.js","../src/utils/insertPlainText.js","../src/core/Clipboard.js","../src/core/DragDrop.js","../src/plugins/PluginManager.js","../src/core/EditorEngine.js","../src/core/EditorBus.js","../src/utils/toolbarConfig.js","../src/constants/commands.js","../src/constants/keybindings.js","../src/core/SharedResources.js","../src/commands/formatting.js","../src/commands/headings.js","../src/commands/alignment.js","../src/commands/lists.js","../src/commands/links.js","../src/commands/images.js","../src/commands/tables.js","../src/commands/blocks.js","../src/commands/blockConvert.js","../src/commands/fontControls.js","../src/commands/media.js","../src/commands/findReplace.js","../src/commands/sourceMode.js","../src/commands/fullscreen.js","../src/commands/distractionFree.js","../src/commands/splitView.js","../src/commands/colorPresets.js","../src/commands/markdownToggle.js","../src/commands/attachments.js","../src/commands/importDocument.js","../src/commands/slashCommands.js","../src/autosave/providers.js","../src/core/AutosaveManager.js","../src/plugins/createPlugin.js","../src/plugins/builtins/WordCountPlugin.js","../src/plugins/builtins/AutolinkPlugin.js","../src/plugins/builtins/PlaceholderPlugin.js","../src/plugins/builtins/syntaxHighlight/tokenizers.js","../src/plugins/builtins/syntaxHighlight/SyntaxHighlightPlugin.js","../src/plugins/builtins/tableFeatures/filter.js","../src/plugins/builtins/tableFeatures/TablePlugin.js","../src/plugins/builtins/tableFeatures/resize.js","../src/plugins/builtins/BlockTemplatePlugin.js","../src/plugins/builtins/commentsFeatures/CommentsPlugin.js","../src/plugins/builtins/calloutFeatures/CalloutPlugin.js","../src/plugins/builtins/linkFeatures/LinkPlugin.js","../src/plugins/builtins/templateFeatures/TemplatePlugin.js","../src/plugins/builtins/keyboardFeatures/KeyboardPlugin.js","../src/plugins/builtins/dragDropFeatures/DragDropPlugin.js","../src/plugins/builtins/mathFeatures/MathPlugin.js","../src/plugins/builtins/tocFeatures/TocPlugin.js","../src/plugins/builtins/analyticsFeatures/AnalyticsPlugin.js","../src/plugins/builtins/spellcheckFeatures/GrammarEngine.js","../src/plugins/builtins/spellcheckFeatures/SpellcheckPlugin.js","../src/plugins/builtins/collaborationFeatures/CrdtEngine.js","../src/plugins/builtins/collaborationFeatures/AwarenessProtocol.js","../src/plugins/builtins/collaborationFeatures/transports/WebSocketTransport.js","../src/plugins/builtins/collaborationFeatures/CollaborationPlugin.js","../src/workers/WorkerPool.js","../src/core/VirtualScroller.js","../src/utils/exportUtils.js","../src/utils/fontConfig.js","../src/utils/formatHTML.js","../src/utils/rtl.js","../src/i18n/locales/en.js","../src/i18n/index.js","../src/utils/performance.js","../src/utils/themeConfig.js","../src/utils/themePresets.js","../src/utils/toolbarItemTheme.js","../src/config/defineConfig.js","../src/config/loadConfig.js","../src/config/pluginResolver.js"],"sourcesContent":["/**\n * A simple publish/subscribe event system.\n */\nexport class EventBus {\n /**\n * Creates a new EventBus instance with an empty listener map.\n */\n constructor() {\n this._listeners = new Map()\n this._keyedHandlers = new Map() // Maps \"event:key\" to handler for keyed subscriptions\n // Task 275: Emit depth counter to prevent infinite recursion\n this._emitDepth = 0\n }\n\n /**\n * Subscribes a handler to an event.\n * @param {string} event - The event name to listen for\n * @param {Function} handler - The callback function to invoke when the event fires\n * @param {Object} [options] - Optional configuration\n * @param {string} [options.key] - If provided, replaces any existing handler with the same key\n * @returns {Function} An unsubscribe function that removes this listener when called\n */\n on(event, handler, options) {\n if (!this._listeners.has(event)) {\n this._listeners.set(event, new Set())\n }\n\n // Keyed handler: replace existing handler with same key\n if (options?.key) {\n const compositeKey = `${event}:${options.key}`\n const existing = this._keyedHandlers.get(compositeKey)\n if (existing) {\n this.off(event, existing)\n }\n this._keyedHandlers.set(compositeKey, handler)\n }\n\n this._listeners.get(event).add(handler)\n return () => this.off(event, handler)\n }\n\n /**\n * Removes a specific handler from an event.\n * @param {string} event - The event name to unsubscribe from\n * @param {Function} handler - The handler function to remove\n * @returns {void}\n */\n off(event, handler) {\n const handlers = this._listeners.get(event)\n if (handlers) {\n handlers.delete(handler)\n if (handlers.size === 0) {\n this._listeners.delete(event)\n }\n }\n }\n\n /**\n * Subscribes a handler that will be called at most once, then automatically removed.\n * @param {string} event - The event name to listen for\n * @param {Function} handler - The callback function to invoke once\n * @returns {Function} An unsubscribe function that removes this listener when called\n */\n once(event, handler) {\n const wrapper = (...args) => {\n this.off(event, wrapper)\n handler(...args)\n }\n return this.on(event, wrapper)\n }\n\n /**\n * Emits an event, invoking all registered handlers with the given data.\n * Errors in individual handlers are caught and logged without interrupting other handlers.\n * @param {string} event - The event name to emit\n * @param {*} [data] - Optional data to pass to each handler\n * @returns {void}\n */\n emit(event, data) {\n const handlers = this._listeners.get(event)\n if (handlers) {\n // Task 275: Track emit depth to prevent infinite recursion\n this._emitDepth++\n try {\n handlers.forEach((handler) => {\n try {\n handler(data)\n } catch (err) {\n console.error(`EventBus error in \"${event}\" handler:`, err)\n // Forward handler errors to 'error' event (with recursion guard)\n if (event !== 'error' && this._emitDepth < 3) {\n this.emit('error', { event, error: err, data })\n }\n }\n })\n } finally {\n this._emitDepth--\n }\n }\n }\n\n /**\n * Removes all listeners for a specific event, or all listeners entirely.\n * @param {string} [event] - The event name to clear. If omitted, all listeners are removed.\n * @returns {void}\n */\n removeAllListeners(event) {\n if (event) {\n this._listeners.delete(event)\n } else {\n this._listeners.clear()\n }\n }\n}\n","/** Item 11: Shared block-level tags set used by walkUpToBlock and closestBlock */\nconst BLOCK_LEVEL_TAGS = new Set(['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DIV', 'BLOCKQUOTE', 'PRE', 'LI', 'TD', 'TH'])\n\n/**\n * Item 11: Walk up the DOM from `node` to `boundary`, returning the first\n * block-level ancestor element. Shared helper for dom.js and Selection.js.\n * @param {Node} node - Starting node\n * @param {Node} boundary - Stop walking at this element (exclusive)\n * @returns {HTMLElement|null}\n */\nexport function walkUpToBlock(node, boundary) {\n let el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n while (el && el !== boundary) {\n if (BLOCK_LEVEL_TAGS.has(el.tagName)) return el\n el = el.parentElement\n }\n return null\n}\n\nexport function closestBlock(node, root) {\n return walkUpToBlock(node, root)\n}\n\nexport function closestTag(node, tagName, root) {\n const tag = tagName.toUpperCase()\n let el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n while (el && el !== root) {\n if (el.tagName === tag) return el\n el = el.parentElement\n }\n return null\n}\n\nexport function wrapInTag(range, tagName, attrs = {}) {\n const el = document.createElement(tagName)\n Object.entries(attrs).forEach(([key, value]) => el.setAttribute(key, value))\n try {\n range.surroundContents(el)\n } catch {\n const fragment = range.extractContents()\n el.appendChild(fragment)\n range.insertNode(el)\n }\n return el\n}\n\nexport function unwrapTag(element) {\n const parent = element.parentNode\n while (element.firstChild) {\n parent.insertBefore(element.firstChild, element)\n }\n parent.removeChild(element)\n}\n\n/** Length of the random suffix in generated IDs */\nconst GENERATED_ID_LENGTH = 9\n\n/**\n * Generates a non-cryptographic random ID for internal DOM element tracking.\n * WARNING: Uses Math.random() which is NOT cryptographically secure.\n * Do NOT use these IDs for security tokens, session IDs, or any\n * security-critical purpose. Use crypto.getRandomValues() instead.\n * @returns {string} A prefixed random ID like 'rmx-abc123def'\n */\nexport function generateId() {\n return 'rmx-' + Math.random().toString(36).substr(2, GENERATED_ID_LENGTH)\n}\n\nexport function isBlockEmpty(block) {\n if (!block) return true\n const text = block.textContent.trim()\n return text === '' && !block.querySelector('img, iframe, hr, input')\n}\n","import { walkUpToBlock } from '../utils/dom.js'\n\n// Pre-compiled regex patterns (avoid recompilation on every selection change)\nconst HEADING_REGEX = /^h[1-6]$/\n\n// Pre-compiled format tag map for O(1) lookups (avoids repeated string comparisons)\nconst FORMAT_TAG_MAP = {\n bold: new Set(['STRONG', 'B']),\n italic: new Set(['EM', 'I']),\n underline: new Set(['U']),\n strikethrough: new Set(['S', 'DEL']),\n subscript: new Set(['SUB']),\n superscript: new Set(['SUP']),\n}\n\n/**\n * @typedef {Object} SelectionBookmark\n * @property {number} startOffset - Character offset of the selection start within the editor\n * @property {number} endOffset - Character offset of the selection end within the editor\n * @property {boolean} collapsed - Whether the selection is collapsed (cursor with no range)\n */\n\n/**\n * @typedef {Object} LinkInfo\n * @property {string} href - The link URL\n * @property {string} text - The link text content\n * @property {string} target - The link target attribute\n */\n\n/**\n * @typedef {Object} ActiveFormats\n * @property {boolean} bold - Whether bold formatting is active\n * @property {boolean} italic - Whether italic formatting is active\n * @property {boolean} underline - Whether underline formatting is active\n * @property {boolean} strikethrough - Whether strikethrough formatting is active\n * @property {boolean} subscript - Whether subscript formatting is active\n * @property {boolean} superscript - Whether superscript formatting is active\n * @property {string|null} heading - The heading level tag (e.g., 'h1', 'h2') or null\n * @property {string} alignment - Text alignment ('left', 'center', 'right', 'justify')\n * @property {boolean} orderedList - Whether inside an ordered list\n * @property {boolean} unorderedList - Whether inside an unordered list\n * @property {boolean} blockquote - Whether inside a blockquote\n * @property {boolean} codeBlock - Whether inside a code block (pre)\n * @property {LinkInfo|null} link - Link info if inside a link, or null\n * @property {string|null} fontFamily - Current font family or null\n * @property {string|null} fontSize - Current font size or null\n * @property {string|null} foreColor - Current text color or null\n * @property {string|null} backColor - Current background color or null\n */\n\nexport class Selection {\n /**\n * Creates a new Selection manager.\n * @param {HTMLElement} editorElement - The editor DOM element to manage selections within\n */\n constructor(editorElement) {\n this.editor = editorElement\n this._cachedRange = null\n this._cacheGeneration = 0\n }\n\n /**\n * Invalidates the cached range. Call this when the selection may have changed\n * (e.g. after a command execution or DOM mutation).\n */\n invalidateCache() {\n this._cacheGeneration++\n this._cachedRange = null\n }\n\n /**\n * Returns the current window Selection object.\n * @returns {globalThis.Selection} The browser's Selection object\n */\n getSelection() {\n return window.getSelection()\n }\n\n /**\n * Returns the current Range if it is within the editor, or null.\n * @returns {Range|null} The current selection range within the editor, or null\n */\n getRange() {\n // Return cached range if still valid within same synchronous cycle\n if (this._cachedRange !== null) {\n return this._cachedRange\n }\n const sel = this.getSelection()\n if (!sel || sel.rangeCount === 0) {\n this._cachedRange = null\n return null\n }\n const range = sel.getRangeAt(0)\n if (!this.isWithinEditor(range.commonAncestorContainer)) {\n this._cachedRange = null\n return null\n }\n this._cachedRange = range\n // Schedule cache invalidation at end of current synchronous cycle\n const gen = ++this._cacheGeneration\n Promise.resolve().then(() => {\n if (this._cacheGeneration === gen) {\n this._cachedRange = null\n }\n })\n return range\n }\n\n /**\n * Sets the current selection to the given range.\n * Silently ignores errors from detached nodes or invalid offsets.\n * @param {Range} range - The Range object to apply as the current selection\n * @returns {void}\n */\n setRange(range) {\n this._cachedRange = null\n try {\n const sel = this.getSelection()\n sel.removeAllRanges()\n sel.addRange(range)\n } catch {\n // Range may reference detached nodes or invalid offsets — silently ignore\n }\n }\n\n /**\n * Checks whether a given node is within the editor element.\n * @param {Node|null} node - The DOM node to check\n * @returns {boolean} True if the node is contained within the editor\n */\n isWithinEditor(node) {\n if (!node) return false\n const el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n return this.editor.contains(el)\n }\n\n /**\n * Checks whether the current selection is collapsed (a caret with no range).\n * @returns {boolean} True if the selection is collapsed or no selection exists\n */\n isCollapsed() {\n const sel = this.getSelection()\n return sel ? sel.isCollapsed : true\n }\n\n /**\n * Returns the selected text content as a string.\n * @returns {string} The selected text, or an empty string if nothing is selected\n */\n getSelectedText() {\n const sel = this.getSelection()\n return sel ? sel.toString() : ''\n }\n\n /**\n * Returns the selected content as an HTML string.\n * @returns {string} The selected HTML, or an empty string if nothing is selected\n */\n getSelectedHTML() {\n const range = this.getRange()\n if (!range) return ''\n const fragment = range.cloneContents()\n const div = document.createElement('div')\n div.appendChild(fragment)\n return div.innerHTML\n }\n\n /**\n * Saves the current selection as a character-offset bookmark that can\n * survive DOM mutations. Returns null if no range is active.\n * @returns {SelectionBookmark|null} The bookmark object, or null\n */\n save() {\n const range = this.getRange()\n if (!range) return null\n\n const preRange = document.createRange()\n preRange.selectNodeContents(this.editor)\n preRange.setEnd(range.startContainer, range.startOffset)\n const startOffset = preRange.toString().length\n\n return {\n startOffset,\n endOffset: startOffset + range.toString().length,\n collapsed: range.collapsed,\n }\n }\n\n /**\n * Restores the selection from a previously saved bookmark.\n * Falls back to the end of the editor if the exact position cannot be restored.\n * @param {SelectionBookmark|null} bookmark - The bookmark to restore, or null to do nothing\n * @returns {void}\n */\n restore(bookmark) {\n if (!bookmark) return\n const textWalker = document.createTreeWalker(\n this.editor,\n NodeFilter.SHOW_TEXT,\n null\n )\n\n let charCount = 0\n let startNode = null\n let startNodeOffset = 0\n let endNode = null\n let endNodeOffset = 0\n\n while (textWalker.nextNode()) {\n const node = textWalker.currentNode\n const nodeLength = node.textContent.length\n const nextCount = charCount + nodeLength\n\n if (!startNode && nextCount >= bookmark.startOffset) {\n startNode = node\n startNodeOffset = bookmark.startOffset - charCount\n }\n if (!endNode && nextCount >= bookmark.endOffset) {\n endNode = node\n endNodeOffset = bookmark.endOffset - charCount\n break\n }\n charCount = nextCount\n }\n\n // Note: offset may be imprecise if DOM changed significantly between save() and restore(),\n // but the try/catch fallback below handles this by moving cursor to end of editor.\n if (startNode) {\n try {\n const range = document.createRange()\n range.setStart(startNode, Math.min(startNodeOffset, startNode.textContent.length))\n if (endNode) {\n range.setEnd(endNode, Math.min(endNodeOffset, endNode.textContent.length))\n } else {\n range.collapse(true)\n }\n this.setRange(range)\n } catch {\n // DOM structure changed between save and restore — fall back to end of editor\n try {\n const fallbackRange = document.createRange()\n fallbackRange.selectNodeContents(this.editor)\n fallbackRange.collapse(false)\n this.setRange(fallbackRange)\n } catch {\n // Editor element may not be in the DOM — nothing we can do\n }\n }\n }\n }\n\n /**\n * Collapses the current selection to the start or end.\n * @param {boolean} [toEnd=false] - If true, collapse to the end; otherwise collapse to the start\n * @returns {void}\n */\n collapse(toEnd = false) {\n const sel = this.getSelection()\n if (sel && sel.rangeCount > 0) {\n if (toEnd) {\n sel.collapseToEnd()\n } else {\n sel.collapseToStart()\n }\n }\n }\n\n /**\n * Returns the nearest element containing the current selection.\n * If the selection is in a text node, returns its parent element.\n * @returns {HTMLElement|null} The parent element, or null if no range exists\n */\n getParentElement() {\n const range = this.getRange()\n if (!range) return null\n const container = range.commonAncestorContainer\n return container.nodeType === Node.TEXT_NODE ? container.parentElement : container\n }\n\n /**\n * Returns the nearest block-level ancestor element of the current selection.\n * @returns {HTMLElement|null} The parent block element (p, h1-h6, div, blockquote, pre, li, td, th), or null\n */\n getParentBlock() {\n const el = this.getParentElement()\n if (!el) return null\n // Item 11: Use shared walkUpToBlock helper\n return walkUpToBlock(el, this.editor)\n }\n\n /**\n * Finds the closest ancestor element with the given tag name.\n * @param {string} tagName - The HTML tag name to search for (case-insensitive)\n * @returns {HTMLElement|null} The matching ancestor element, or null if not found\n */\n getClosestElement(tagName) {\n let el = this.getParentElement()\n const tag = tagName.toUpperCase()\n while (el && el !== this.editor) {\n if (el.tagName === tag) return el\n el = el.parentElement\n }\n return null\n }\n\n /**\n * Inserts HTML at the current selection using Range-based DOM manipulation.\n * CSP-compatible — does not use document.execCommand.\n * Automatically sanitizes input through the editor's Sanitizer if available\n * to prevent XSS. Falls back to unsanitized insertion only if no sanitizer\n * is configured (e.g. in unit tests with standalone Selection instances).\n * @param {string} html - The HTML string to insert\n * @returns {void}\n */\n insertHTML(html) {\n const safeHtml = this._sanitizer ? this._sanitizer.sanitize(html) : html\n const sel = this.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n range.deleteContents()\n const template = document.createElement('template')\n template.innerHTML = safeHtml\n const fragment = template.content\n const lastChild = fragment.lastChild\n range.insertNode(fragment)\n // Move cursor after the inserted content\n if (lastChild) {\n const newRange = document.createRange()\n newRange.setStartAfter(lastChild)\n newRange.collapse(true)\n sel.removeAllRanges()\n sel.addRange(newRange)\n }\n }\n\n /**\n * Attaches a Sanitizer instance for automatic HTML sanitization in insertHTML().\n * Called by EditorEngine during initialization.\n * @param {import('./Sanitizer.js').Sanitizer} sanitizer\n * @returns {void}\n */\n setSanitizer(sanitizer) {\n this._sanitizer = sanitizer\n }\n\n /**\n * Inserts a DOM node at the current selection, replacing any selected content.\n * Moves the cursor to after the inserted node.\n * @param {Node} node - The DOM node to insert\n * @returns {void}\n */\n insertNode(node) {\n const range = this.getRange()\n if (!range) return\n range.deleteContents()\n range.insertNode(node)\n range.setStartAfter(node)\n range.collapse(true)\n this.setRange(range)\n }\n\n /**\n * Wraps the current selection with a new element of the given tag name.\n * Does nothing if the selection is collapsed.\n * @param {string} tagName - The HTML tag name for the wrapper element\n * @param {Object} [attrs={}] - Key-value pairs of attributes to set on the wrapper\n * @returns {HTMLElement|null} The created wrapper element, or null if the selection was collapsed\n */\n wrapWith(tagName, attrs = {}) {\n const range = this.getRange()\n if (!range || range.collapsed) return null\n const el = document.createElement(tagName)\n Object.entries(attrs).forEach(([key, value]) => {\n el.setAttribute(key, value)\n })\n try {\n range.surroundContents(el)\n } catch {\n const fragment = range.extractContents()\n el.appendChild(fragment)\n range.insertNode(el)\n }\n this.setRange(range)\n return el\n }\n\n /**\n * Unwraps the closest ancestor matching the given tag name, preserving its children.\n * @param {string} tagName - The HTML tag name of the element to unwrap (case-insensitive)\n * @returns {void}\n */\n unwrap(tagName) {\n const el = this.getClosestElement(tagName)\n if (!el) return\n const parent = el.parentNode\n while (el.firstChild) {\n parent.insertBefore(el.firstChild, el)\n }\n parent.removeChild(el)\n }\n\n /**\n * Detects and returns all active formatting states at the current selection.\n * @returns {ActiveFormats} An object describing all active formats\n */\n getActiveFormats() {\n const formats = {\n bold: false,\n italic: false,\n underline: false,\n strikethrough: false,\n subscript: false,\n superscript: false,\n heading: null,\n alignment: 'left',\n orderedList: false,\n unorderedList: false,\n blockquote: false,\n codeBlock: false,\n link: null,\n fontFamily: null,\n fontSize: null,\n foreColor: null,\n backColor: null,\n }\n\n // Detect inline formats via DOM traversal using pre-compiled FORMAT_TAG_MAP\n // Task 243: Removed queryCommandState fallback — DOM traversal handles all format detection\n try {\n const sel = window.getSelection()\n if (sel && sel.rangeCount > 0) {\n let node = sel.anchorNode\n // Walk up from the anchor node to the editor root, checking tag names against Sets\n while (node && node !== this.editor) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const tag = node.tagName\n for (const format in FORMAT_TAG_MAP) {\n if (!formats[format] && FORMAT_TAG_MAP[format].has(tag)) {\n formats[format] = true\n }\n }\n }\n node = node.parentNode\n }\n }\n } catch {\n // DOM traversal can fail with detached nodes\n }\n\n const block = this.getParentBlock()\n if (block) {\n const tag = block.tagName.toLowerCase()\n if (HEADING_REGEX.test(tag)) {\n formats.heading = tag\n }\n const align = block.style.textAlign || window.getComputedStyle(block).textAlign\n if (align) {\n formats.alignment = align === 'start' ? 'left' : align === 'end' ? 'right' : align\n }\n }\n\n let el = this.getParentElement()\n while (el && el !== this.editor) {\n const tag = el.tagName\n if (tag === 'OL') formats.orderedList = true\n if (tag === 'UL') formats.unorderedList = true\n if (tag === 'BLOCKQUOTE') formats.blockquote = true\n if (tag === 'PRE') formats.codeBlock = true\n if (tag === 'A') formats.link = { href: el.href, text: el.textContent, target: el.target }\n el = el.parentElement\n }\n\n try {\n formats.fontFamily = document.queryCommandValue('fontName') || null\n formats.fontSize = document.queryCommandValue('fontSize') || null\n formats.foreColor = document.queryCommandValue('foreColor') || null\n formats.backColor = document.queryCommandValue('backColor') || null\n } catch {\n // ignore\n }\n\n return formats\n }\n\n /**\n * Returns the bounding rectangle of the current selection range.\n * @returns {DOMRect|null} The bounding client rect, or null if no range exists\n */\n getBoundingRect() {\n const range = this.getRange()\n if (!range) return null\n return range.getBoundingClientRect()\n }\n}\n","/**\n * @typedef {Object} CommandDefinition\n * @property {Function} execute - The function to run when the command is executed, receives (engine, ...args)\n * @property {Function} [isActive] - Returns true if the command's effect is currently active, receives (engine)\n * @property {Function} [isEnabled] - Returns true if the command can be executed, receives (engine)\n * @property {string|null} [shortcut] - Keyboard shortcut string (e.g., 'Ctrl+B')\n * @property {Object} [meta] - Arbitrary metadata for the command\n */\n\n/**\n * @typedef {Object} RegisteredCommand\n * @property {string} name - The command name\n * @property {Function} execute - The execute function\n * @property {Function} isActive - Returns whether the command is currently active\n * @property {Function} isEnabled - Returns whether the command is enabled\n * @property {string|null} shortcut - The keyboard shortcut, or null\n * @property {Object} meta - Arbitrary metadata\n */\n\n/**\n * Registry for editor commands. Commands are named operations that can be\n * executed, queried for active/enabled state, and optionally bound to keyboard shortcuts.\n */\nexport class CommandRegistry {\n /**\n * Creates a new CommandRegistry.\n * @param {import('./EditorEngine.js').EditorEngine} engine - The editor engine instance\n */\n constructor(engine) {\n this.engine = engine\n this._commands = new Map()\n }\n\n /**\n * Registers a command with the given name. If the command defines a shortcut,\n * it is also registered with the keyboard manager.\n * @param {string} name - Unique name for the command\n * @param {CommandDefinition} command - The command definition\n * @returns {void}\n */\n register(name, command) {\n this._commands.set(name, {\n name,\n execute: command.execute,\n isActive: command.isActive || (() => false),\n isEnabled: command.isEnabled || (() => true),\n shortcut: command.shortcut || null,\n meta: command.meta || {},\n // Task 251: Support skipSnapshot option\n skipSnapshot: command.skipSnapshot || false,\n })\n\n if (command.shortcut) {\n this.engine.keyboard.register(command.shortcut, name)\n }\n\n // Register alternate shortcuts as fallback bindings for cross-platform support\n if (command.alternateShortcuts) {\n for (const alt of command.alternateShortcuts) {\n this.engine.keyboard.register(alt, name)\n }\n }\n }\n\n /**\n * Executes a registered command by name. Takes a history snapshot before\n * execution and emits command:executed and content:change events.\n * @param {string} name - The name of the command to execute\n * @param {...*} args - Additional arguments passed to the command's execute function\n * @returns {*} The result of the command's execute function, or false if the command is not found or disabled\n */\n execute(name, ...args) {\n const command = this._commands.get(name)\n if (!command) {\n console.warn(`Command \"${name}\" not found`)\n return false\n }\n if (!command.isEnabled(this.engine)) {\n return false\n }\n\n // Task 251: Skip snapshot if command has skipSnapshot: true\n if (!command.skipSnapshot) {\n this.engine.history.snapshot()\n }\n const result = command.execute(this.engine, ...args)\n this.engine.eventBus.emit('command:executed', { name, args, result })\n this.engine.eventBus.emit('content:change')\n return result\n }\n\n /**\n * Checks whether a command is currently in an active state (e.g., bold is active when text is bold).\n * @param {string} name - The command name\n * @returns {boolean} True if the command exists and is active\n */\n isActive(name) {\n const command = this._commands.get(name)\n if (!command) return false\n return command.isActive(this.engine)\n }\n\n /**\n * Checks whether a command is currently enabled and can be executed.\n * @param {string} name - The command name\n * @returns {boolean} True if the command exists and is enabled\n */\n isEnabled(name) {\n const command = this._commands.get(name)\n if (!command) return false\n return command.isEnabled(this.engine)\n }\n\n /**\n * Returns the registered command object for the given name.\n * @param {string} name - The command name\n * @returns {RegisteredCommand|undefined} The command object, or undefined if not found\n */\n get(name) {\n return this._commands.get(name)\n }\n\n /**\n * Returns an array of all registered command names.\n * @returns {string[]} Array of command names\n */\n getAll() {\n return Array.from(this._commands.keys())\n }\n\n /**\n * Checks whether a command with the given name is registered.\n * @param {string} name - The command name\n * @returns {boolean} True if the command exists\n */\n has(name) {\n return this._commands.has(name)\n }\n}\n","/**\n * Lightweight djb2 hash function for fast string comparison.\n * Returns a 32-bit integer hash.\n * @param {string} str\n * @returns {number}\n */\nexport function djb2Hash(str) {\n let hash = 5381\n for (let i = 0, len = str.length; i < len; i++) {\n hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0\n }\n return hash\n}\n","// Item 8: djb2Hash moved to utils/hash.js, re-exported here for backward compat\nimport { djb2Hash } from '../utils/hash.js'\nexport { djb2Hash }\n\n/**\n * Threshold in characters above which diff-based storage is used\n * instead of full snapshots, to reduce memory usage for large documents.\n * @type {number}\n */\nconst DIFF_THRESHOLD = 5000\n\n/**\n * Compute a simple character-level diff between two strings.\n * Finds the common prefix and suffix, then stores only the middle\n * replacement segment.\n *\n * @param {string} oldStr - The previous HTML string\n * @param {string} newStr - The new HTML string\n * @returns {{ prefixLen: number, suffixLen: number, insert: string }}\n */\nfunction computeDiff(oldStr, newStr) {\n const minLen = Math.min(oldStr.length, newStr.length)\n\n // Find common prefix length\n let prefixLen = 0\n while (prefixLen < minLen && oldStr[prefixLen] === newStr[prefixLen]) {\n prefixLen++\n }\n\n // Find common suffix length (not overlapping with prefix)\n let suffixLen = 0\n const maxSuffix = minLen - prefixLen\n while (\n suffixLen < maxSuffix &&\n oldStr[oldStr.length - 1 - suffixLen] === newStr[newStr.length - 1 - suffixLen]\n ) {\n suffixLen++\n }\n\n // The inserted/replacement segment from the new string\n const insert = newStr.slice(prefixLen, newStr.length - suffixLen || undefined)\n\n return { prefixLen, suffixLen, insert }\n}\n\n/**\n * Apply a diff to reconstruct the new string from the old string.\n *\n * @param {string} baseStr - The base HTML string\n * @param {{ prefixLen: number, suffixLen: number, insert: string }} diff\n * @returns {string} The reconstructed HTML string\n */\nfunction applyDiff(baseStr, diff) {\n const { prefixLen, suffixLen, insert } = diff\n const prefix = baseStr.slice(0, prefixLen)\n const suffix = suffixLen > 0 ? baseStr.slice(baseStr.length - suffixLen) : ''\n return prefix + insert + suffix\n}\n\n/**\n * @typedef {Object} HistoryOptions\n * @property {number} [maxSize=100] - Maximum number of undo states to retain\n * @property {number} [debounceMs=300] - Debounce interval in milliseconds for automatic snapshots\n * @property {number} [coalesceMs=1000] - Window in which rapid keystrokes are coalesced into a single undo step\n */\n\n/**\n * @typedef {Object} HistoryEntry\n * @property {'full'|'diff'} type - Whether this is a full snapshot or a diff\n * @property {string} [html] - Full HTML content (when type === 'full')\n * @property {{ prefixLen: number, suffixLen: number, insert: string }} [patch] - Diff patch (when type === 'diff')\n * @property {import('./Selection.js').SelectionBookmark|null} bookmark - Selection bookmark\n */\n\n/**\n * @typedef {Object} HistoryState\n * @property {string} html - The HTML content at the time of the snapshot\n * @property {import('./Selection.js').SelectionBookmark|null} bookmark - The selection bookmark at the time of the snapshot\n */\n\n/**\n * Manages undo/redo history for the editor using DOM mutation observation\n * and debounced snapshots.\n *\n * For documents larger than 5000 characters, uses diff-based compression\n * to store only the changes between states, significantly reducing memory\n * usage for large documents.\n */\nexport class History {\n /**\n * Creates a new History manager.\n * @param {import('./EditorEngine.js').EditorEngine} engine - The editor engine instance\n * @param {HistoryOptions} [options={}] - History configuration options\n */\n constructor(engine, options = {}) {\n this.engine = engine\n this.maxSize = options.maxSize || 100\n this.debounceMs = options.debounceMs || 300\n this.coalesceMs = options.coalesceMs || 1000\n this._undoStack = []\n this._redoStack = []\n this._observer = null\n this._debounceTimer = null\n this._coalesceTimer = null\n this._isPerformingUndoRedo = false\n this._isCoalescing = false\n this._lastSnapshot = null\n this._lastHash = null\n }\n\n /**\n * Initializes history tracking by taking an initial snapshot and\n * starting a MutationObserver on the editor element.\n * Task 249: Removed attributes: true from observer options.\n * @returns {void}\n */\n init() {\n this._takeSnapshot()\n this._observer = new MutationObserver(() => {\n if (this._isPerformingUndoRedo) return\n this._debouncedSnapshot()\n })\n this._observer.observe(this.engine.element, {\n childList: true,\n characterData: true,\n subtree: true,\n })\n }\n\n /**\n * Destroys the history manager by disconnecting the MutationObserver\n * and clearing the debounce timer.\n * @returns {void}\n */\n destroy() {\n if (this._observer) {\n this._observer.disconnect()\n this._observer = null\n }\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer)\n this._debounceTimer = null\n }\n if (this._coalesceTimer) {\n clearTimeout(this._coalesceTimer)\n this._coalesceTimer = null\n }\n this._isCoalescing = false\n }\n\n /**\n * Takes an immediate snapshot of the current editor state, cancelling\n * any pending debounced snapshot.\n * @returns {void}\n */\n snapshot() {\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer)\n this._debounceTimer = null\n }\n this._takeSnapshot()\n }\n\n /**\n * Schedules a debounced snapshot with operation coalescing.\n *\n * Rapid keystrokes within the coalesce window (default 1000ms) are\n * batched into a single undo step. The debounce timer (default 300ms)\n * fires first, but if the coalesce window hasn't expired yet, the\n * snapshot updates the top of the undo stack instead of pushing a\n * new entry. This gives the user natural undo boundaries at typing\n * pauses rather than one entry per character.\n *\n * @private\n * @returns {void}\n */\n _debouncedSnapshot() {\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer)\n }\n this._debounceTimer = setTimeout(() => {\n if (this._isCoalescing) {\n // Still within the coalesce window — update the top of the stack\n this._updateTopSnapshot()\n } else {\n // Start a new coalesce window\n this._isCoalescing = true\n this._takeSnapshot()\n if (this._coalesceTimer) clearTimeout(this._coalesceTimer)\n this._coalesceTimer = setTimeout(() => {\n this._isCoalescing = false\n }, this.coalesceMs)\n }\n }, this.debounceMs)\n }\n\n /**\n * Creates a history entry, using diff compression for large documents.\n *\n * @private\n * @param {string} html - The HTML to store\n * @param {import('./Selection.js').SelectionBookmark|null} bookmark - The selection bookmark\n * @returns {HistoryEntry}\n */\n _createEntry(html, bookmark) {\n // For short content, or when there's no previous snapshot, store full HTML\n if (html.length < DIFF_THRESHOLD || !this._lastSnapshot) {\n return { type: 'full', html, bookmark }\n }\n\n // For large content, compute and store a diff against the last snapshot\n const patch = computeDiff(this._lastSnapshot, html)\n\n // If the diff is larger than the full HTML (rare edge case), store full\n if (patch.insert.length >= html.length * 0.8) {\n return { type: 'full', html, bookmark }\n }\n\n return { type: 'diff', patch, bookmark }\n }\n\n /**\n * Resolves a history entry to its full HTML string.\n * For full entries, returns html directly. For diff entries,\n * walks backward through the stack to find the base snapshot\n * and applies diffs forward.\n *\n * @private\n * @param {HistoryEntry[]} stack - The stack (undo or redo) containing the entry\n * @param {number} index - Index of the entry in the stack\n * @returns {string} The full HTML string\n */\n _resolveEntry(stack, index) {\n const entry = stack[index]\n if (entry.type === 'full') {\n return entry.html\n }\n\n // Walk backward to find the nearest full snapshot\n let baseIndex = index - 1\n while (baseIndex >= 0 && stack[baseIndex].type !== 'full') {\n baseIndex--\n }\n\n // Start from the base full snapshot\n let html\n if (baseIndex >= 0) {\n html = stack[baseIndex].html\n } else {\n // Fallback: use lastSnapshot if no full entry found in stack\n // This shouldn't normally happen since the first entry is always full\n html = this._lastSnapshot || ''\n }\n\n // Apply diffs forward from baseIndex+1 to index\n for (let i = baseIndex + 1; i <= index; i++) {\n if (stack[i].type === 'diff') {\n html = applyDiff(html, stack[i].patch)\n } else {\n html = stack[i].html\n }\n }\n\n return html\n }\n\n /**\n * Updates the top entry on the undo stack with the current state\n * instead of pushing a new entry. Used during coalescing to batch\n * rapid keystrokes.\n * Task 237: Hash raw innerHTML directly instead of normalizing.\n * @private\n * @returns {void}\n */\n _updateTopSnapshot() {\n const html = this.engine.element.innerHTML\n const hash = djb2Hash(html)\n\n if (hash === this._lastHash && html === this._lastSnapshot) return\n\n const bookmark = this.engine.selection.save()\n\n if (this._undoStack.length > 0) {\n // Replace the top entry — always store full for the top during coalescing\n // since we're overwriting frequently\n this._undoStack[this._undoStack.length - 1] = this._createEntry(html, bookmark)\n } else {\n this._undoStack.push({ type: 'full', html, bookmark })\n }\n\n this._lastSnapshot = html\n this._lastHash = hash\n }\n\n /**\n * Captures the current editor HTML and selection bookmark, pushing it\n * onto the undo stack. Clears the redo stack. Skips if content is unchanged.\n * Task 237: Hash raw innerHTML directly instead of normalizing.\n * @private\n * @returns {void}\n */\n _takeSnapshot() {\n const html = this.engine.element.innerHTML\n\n // Fast path: compare hashes before full string comparison\n const hash = djb2Hash(html)\n if (hash === this._lastHash && html === this._lastSnapshot) return\n\n const bookmark = this.engine.selection.save()\n const entry = this._createEntry(html, bookmark)\n\n this._undoStack.push(entry)\n if (this._undoStack.length > this.maxSize) {\n this._undoStack.shift()\n }\n this._redoStack = []\n this._lastSnapshot = html\n this._lastHash = hash\n }\n\n /**\n * Temporarily disconnects the MutationObserver to prevent recursive snapshots.\n * @private\n * @returns {void}\n */\n _disconnectObserver() {\n if (this._observer) {\n this._observer.disconnect()\n }\n }\n\n /**\n * Reconnects the MutationObserver after an undo/redo operation.\n * Task 249: Removed attributes: true from observer options.\n * @private\n * @returns {void}\n */\n _reconnectObserver() {\n if (this._observer) {\n this._observer.observe(this.engine.element, {\n childList: true,\n characterData: true,\n subtree: true,\n })\n }\n }\n\n /**\n * Undoes the last change by restoring the previous state from the undo stack.\n * The current state is pushed onto the redo stack. Emits history:undo and content:change events.\n * Task 238: Skip re-sanitize and normalize+hash since content was already sanitized when snapshotted.\n * @returns {void}\n */\n undo() {\n if (!this.canUndo()) return\n\n this._isPerformingUndoRedo = true\n this._disconnectObserver()\n\n const currentHtml = this.engine.element.innerHTML\n const currentBookmark = this.engine.selection.save()\n this._redoStack.push({ type: 'full', html: currentHtml, bookmark: currentBookmark })\n\n const stateIndex = this._undoStack.length - 1\n const stateHtml = this._resolveEntry(this._undoStack, stateIndex)\n const stateBookmark = this._undoStack[stateIndex].bookmark\n this._undoStack.pop()\n\n // Task 238: Skip re-sanitize — content was already sanitized when snapshotted.\n // Skip normalize+hash since _isPerformingUndoRedo prevents observer from snapshotting.\n this.engine.element.innerHTML = stateHtml\n this._lastSnapshot = stateHtml\n this._lastHash = djb2Hash(stateHtml)\n\n if (stateBookmark) {\n this.engine.selection.restore(stateBookmark)\n }\n\n this._reconnectObserver()\n this._isPerformingUndoRedo = false\n this.engine.eventBus.emit('history:undo')\n this.engine.eventBus.emit('content:change')\n }\n\n /**\n * Redoes the last undone change by restoring state from the redo stack.\n * The current state is pushed onto the undo stack. Emits history:redo and content:change events.\n * Task 238: Skip re-sanitize and normalize+hash since content was already sanitized when snapshotted.\n * @returns {void}\n */\n redo() {\n if (!this.canRedo()) return\n\n this._isPerformingUndoRedo = true\n this._disconnectObserver()\n\n const currentHtml = this.engine.element.innerHTML\n const currentBookmark = this.engine.selection.save()\n this._undoStack.push({ type: 'full', html: currentHtml, bookmark: currentBookmark })\n\n const stateIndex = this._redoStack.length - 1\n const stateHtml = this._resolveEntry(this._redoStack, stateIndex)\n const stateBookmark = this._redoStack[stateIndex].bookmark\n this._redoStack.pop()\n\n // Task 238: Skip re-sanitize — content was already sanitized when snapshotted.\n // Skip normalize+hash since _isPerformingUndoRedo prevents observer from snapshotting.\n this.engine.element.innerHTML = stateHtml\n this._lastSnapshot = stateHtml\n this._lastHash = djb2Hash(stateHtml)\n\n if (stateBookmark) {\n this.engine.selection.restore(stateBookmark)\n }\n\n this._reconnectObserver()\n this._isPerformingUndoRedo = false\n this.engine.eventBus.emit('history:redo')\n this.engine.eventBus.emit('content:change')\n }\n\n /**\n * Checks whether there are states available to undo.\n * @returns {boolean} True if the undo stack is not empty\n */\n canUndo() {\n return this._undoStack.length > 0\n }\n\n /**\n * Checks whether there are states available to redo.\n * @returns {boolean} True if the redo stack is not empty\n */\n canRedo() {\n return this._redoStack.length > 0\n }\n\n /**\n * Clears all undo and redo history.\n * @returns {void}\n */\n clear() {\n this._undoStack = []\n this._redoStack = []\n this._lastSnapshot = null\n this._lastHash = null\n }\n}\n","let _isMac = null\n\nexport function isMac() {\n if (_isMac === null) {\n _isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform)\n }\n return _isMac\n}\n\nexport function getModKey() {\n return isMac() ? '⌘' : 'Ctrl'\n}\n","import { isMac } from '../utils/platform.js'\n\nexport class KeyboardManager {\n constructor(engine) {\n this.engine = engine\n this._shortcuts = new Map()\n /** Item 17: Cache for normalized shortcuts to avoid re-computing on every keydown */\n this._normalizeCache = new Map()\n this._handleKeyDown = this._handleKeyDown.bind(this)\n }\n\n init() {\n this.engine.element.addEventListener('keydown', this._handleKeyDown)\n }\n\n destroy() {\n this.engine.element.removeEventListener('keydown', this._handleKeyDown)\n }\n\n register(shortcut, commandName) {\n const normalized = this._normalizeShortcut(shortcut)\n this._shortcuts.set(normalized, commandName)\n }\n\n unregister(shortcut) {\n const normalized = this._normalizeShortcut(shortcut)\n this._shortcuts.delete(normalized)\n }\n\n getShortcutForCommand(commandName) {\n for (const [shortcut, name] of this._shortcuts) {\n if (name === commandName) return shortcut\n }\n return null\n }\n\n getShortcutLabel(shortcut) {\n if (!shortcut) return ''\n const parts = shortcut.split('+')\n return parts.map((p) => {\n if (p === 'mod') return isMac() ? '\\u2318' : 'Ctrl'\n if (p === 'shift') return isMac() ? '\\u21E7' : 'Shift'\n if (p === 'alt') return isMac() ? '\\u2325' : 'Alt'\n return p.toUpperCase()\n }).join(isMac() ? '' : '+')\n }\n\n _normalizeShortcut(shortcut) {\n // Item 17: Use cache for normalized shortcuts\n if (this._normalizeCache.has(shortcut)) {\n return this._normalizeCache.get(shortcut)\n }\n const normalized = shortcut.toLowerCase().split('+').sort().join('+')\n this._normalizeCache.set(shortcut, normalized)\n return normalized\n }\n\n _handleKeyDown(e) {\n const parts = []\n if (isMac() ? e.metaKey : e.ctrlKey) parts.push('mod')\n if (e.shiftKey) parts.push('shift')\n if (e.altKey) parts.push('alt')\n\n const key = e.key.toLowerCase()\n if (!['control', 'meta', 'shift', 'alt'].includes(key)) {\n parts.push(key)\n }\n\n const normalized = parts.sort().join('+')\n const commandName = this._shortcuts.get(normalized)\n\n if (commandName) {\n e.preventDefault()\n e.stopPropagation()\n this.engine.commands.execute(commandName)\n }\n }\n}\n","export const ALLOWED_TAGS = {\n p: ['class', 'style'],\n h1: ['class'], h2: ['class'], h3: ['class'], h4: ['class'], h5: ['class'], h6: ['class'],\n strong: [], b: [], em: [], i: [], u: [], s: [], del: [],\n sub: [], sup: [],\n a: ['href', 'target', 'rel', 'title', 'class', 'data-attachment', 'data-filename', 'data-filesize'],\n img: ['src', 'alt', 'width', 'height', 'style', 'class'],\n ul: ['class'], ol: ['class', 'start', 'type'], li: ['class'],\n table: ['class', 'data-sort-state'], thead: [], tbody: [], tr: ['class'],\n th: ['colspan', 'rowspan', 'class', 'style', 'data-sort-dir', 'data-sort-priority', 'data-filter-value', 'data-cell-format', 'data-raw-value', 'data-formula'],\n td: ['colspan', 'rowspan', 'class', 'style', 'data-cell-format', 'data-raw-value', 'data-formula', 'data-col-width'],\n blockquote: ['class'],\n pre: ['class', 'data-language'], code: ['class', 'data-language'],\n hr: [],\n br: [],\n div: ['class', 'style', 'data-embed-url'],\n span: ['style', 'class'],\n input: ['type', 'checked', 'disabled', 'class'],\n label: ['class'],\n mark: ['class'],\n iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen', 'sandbox', 'class', 'style'],\n}\n\nexport const ALLOWED_STYLES = [\n 'color', 'background-color', 'font-family', 'font-size',\n 'text-align', 'text-decoration', 'font-weight', 'font-style',\n 'width', 'height', 'max-width', 'float', 'margin', 'margin-left', 'margin-right',\n 'display', 'padding', 'border', 'min-width',\n]\n","import { ALLOWED_TAGS, ALLOWED_STYLES } from '../constants/schema.js'\nimport { djb2Hash } from '../utils/hash.js'\n\n// Pre-compiled regex (avoid recompilation on every href check during sanitization)\nconst JS_PROTOCOL_REGEX = /^\\s*javascript\\s*:/i\n\n// CSS value injection patterns (expression(), @import, behavior:, javascript:)\nconst CSS_INJECTION_REGEX = /expression\\s*\\(|@import|behavior\\s*:|javascript\\s*:/i\n\n// Tags whose children should be removed entirely (not just unwrapped)\nconst DANGEROUS_REMOVE_TAGS = new Set([\n 'script', 'style', 'svg', 'math', 'form', 'object', 'embed', 'applet', 'template',\n])\n\n/**\n * Default iframe src domain allowlist.\n * Only iframes whose src matches one of these hostnames (or their subdomains)\n * are permitted. All others are removed during sanitization.\n */\nconst DEFAULT_IFRAME_ALLOWED_DOMAINS = [\n 'www.youtube.com',\n 'youtube.com',\n 'www.youtube-nocookie.com',\n 'player.vimeo.com',\n 'www.dailymotion.com',\n 'geo.dailymotion.com',\n]\n\n/**\n * Check whether an iframe src URL is on an allowed domain.\n * Returns true if the URL's hostname matches or is a subdomain of an allowed domain.\n */\nfunction isAllowedIframeDomain(src, allowedDomains) {\n if (!src) return false\n try {\n const url = new URL(src)\n // Only allow https (and protocol-relative URLs which the browser resolves)\n if (url.protocol !== 'https:') return false\n const hostname = url.hostname.toLowerCase()\n return allowedDomains.some(domain => {\n const d = domain.toLowerCase()\n return hostname === d || hostname.endsWith('.' + d)\n })\n } catch {\n return false\n }\n}\n\n/**\n * @typedef {Object} SanitizerOptions\n * @property {Object} [allowedTags] - Map of allowed tag names to arrays of allowed attribute names\n * @property {string[]} [allowedStyles] - List of allowed CSS property names\n * @property {string[]} [iframeAllowedDomains] - List of allowed iframe src hostnames\n */\n\n/**\n * Sanitizes HTML content by removing disallowed tags, attributes, and styles.\n * Prevents XSS through event handler attributes, javascript: URLs, CSS injection,\n * and iframe src domain restrictions.\n */\n// LRU cache max size for sanitize results\nconst SANITIZE_CACHE_MAX = 50\n\nexport class Sanitizer {\n /**\n * Creates a new Sanitizer instance.\n * @param {SanitizerOptions} [options={}] - Sanitizer configuration\n */\n constructor(options = {}) {\n this.allowedTags = options.allowedTags || ALLOWED_TAGS\n this.allowedStyles = options.allowedStyles || ALLOWED_STYLES\n this.iframeAllowedDomains = options.iframeAllowedDomains || DEFAULT_IFRAME_ALLOWED_DOMAINS\n this._cache = new Map()\n // Task 250: Hoist DOMParser as a class property for reuse\n this._parser = new DOMParser()\n }\n\n /**\n * Sanitizes an HTML string by parsing it and removing disallowed elements,\n * attributes, event handlers, javascript: URLs, and dangerous CSS values.\n * Dangerous tags (script, style, svg, etc.) are removed with all children;\n * other disallowed tags are unwrapped (children preserved).\n * Results are cached in a simple LRU cache (max 50 entries).\n * @param {string} html - The HTML string to sanitize\n * @returns {string} The sanitized HTML string, or empty string if input is falsy\n */\n sanitize(html) {\n if (!html) return ''\n\n // Item 3: Use hash for fast lookup but verify with full string comparison on hit\n const cacheKey = djb2Hash(html)\n\n // Check LRU cache — verify full string to avoid hash collisions\n if (this._cache.has(cacheKey)) {\n const cached = this._cache.get(cacheKey)\n if (cached.input === html) {\n // Move to end (most recently used)\n this._cache.delete(cacheKey)\n this._cache.set(cacheKey, cached)\n return cached.output\n }\n // Hash collision — fall through to recompute\n }\n\n // Task 250: Use hoisted DOMParser instance\n const doc = this._parser.parseFromString(`<body>${html}</body>`, 'text/html')\n this._cleanNode(doc.body)\n const result = doc.body.innerHTML\n\n // Store in cache with both hash and original string, evict oldest if over limit\n if (this._cache.size >= SANITIZE_CACHE_MAX) {\n const firstKey = this._cache.keys().next().value\n this._cache.delete(firstKey)\n }\n this._cache.set(cacheKey, { input: html, output: result })\n\n return result\n }\n\n /**\n * Recursively cleans a DOM node by removing disallowed children, attributes,\n * and sanitizing styles and href values.\n * @private\n * @param {Node} node - The DOM node to clean\n * @returns {void}\n */\n _cleanNode(node) {\n // Task 271: Reverse iteration to avoid Array.from() allocation\n for (let i = node.childNodes.length - 1; i >= 0; i--) {\n const child = node.childNodes[i]\n if (child.nodeType === Node.TEXT_NODE) continue\n if (child.nodeType === Node.COMMENT_NODE) {\n node.removeChild(child)\n continue\n }\n if (child.nodeType !== Node.ELEMENT_NODE) {\n node.removeChild(child)\n continue\n }\n\n const tagName = child.tagName.toLowerCase()\n const allowedAttrs = this.allowedTags[tagName]\n\n if (!allowedAttrs) {\n if (DANGEROUS_REMOVE_TAGS.has(tagName)) {\n // Remove entirely including children — these tags can contain harmful structures\n node.removeChild(child)\n } else {\n // Unwrap: keep children but remove the tag\n while (child.firstChild) {\n node.insertBefore(child.firstChild, child)\n }\n node.removeChild(child)\n }\n continue\n }\n\n // Remove disallowed attributes + explicitly block on* event handlers\n const attrs = Array.from(child.attributes)\n for (const attr of attrs) {\n // Defense-in-depth: block all event handler attributes regardless of allowlist\n if (attr.name.startsWith('on')) {\n child.removeAttribute(attr.name)\n continue\n }\n if (attr.name === 'style') {\n if (allowedAttrs.includes('style')) {\n this._cleanStyles(child)\n } else {\n child.removeAttribute('style')\n }\n } else if (!allowedAttrs.includes(attr.name)) {\n child.removeAttribute(attr.name)\n }\n }\n\n // Sanitize href to prevent javascript: URLs\n if (child.hasAttribute('href')) {\n const href = child.getAttribute('href')\n if (href && JS_PROTOCOL_REGEX.test(href)) {\n child.setAttribute('href', '#')\n }\n }\n\n // Restrict <input> to type=\"checkbox\" only (prevent phishing via hidden/password/submit inputs)\n if (tagName === 'input') {\n const inputType = (child.getAttribute('type') || '').toLowerCase()\n if (inputType !== 'checkbox') {\n node.removeChild(child)\n continue\n }\n }\n\n // Restrict <iframe> src to allowed domains only (YouTube, Vimeo, Dailymotion by default)\n if (tagName === 'iframe') {\n const src = child.getAttribute('src')\n if (!isAllowedIframeDomain(src, this.iframeAllowedDomains)) {\n node.removeChild(child)\n continue\n }\n }\n\n this._cleanNode(child)\n }\n }\n\n /**\n * Cleans inline styles on an element, keeping only allowed CSS properties\n * and blocking CSS injection vectors.\n * @private\n * @param {HTMLElement} element - The element whose styles should be cleaned\n * @returns {void}\n */\n _cleanStyles(element) {\n const style = element.style\n const cleanedStyles = []\n\n for (const prop of this.allowedStyles) {\n const value = style.getPropertyValue(prop)\n if (value) {\n // Block CSS value injection vectors (expression(), @import, behavior:, javascript:)\n if (CSS_INJECTION_REGEX.test(value)) continue\n cleanedStyles.push(`${prop}: ${value}`)\n }\n }\n\n if (cleanedStyles.length > 0) {\n element.setAttribute('style', cleanedStyles.join('; '))\n } else {\n element.removeAttribute('style')\n }\n }\n}\n","/**\n * Paste cleaning pipeline for rich text from various sources.\n * Handles Microsoft Word, Google Docs, LibreOffice, Apple Pages,\n * and other rich text editors.\n *\n * Source detection runs only the relevant cleaner pipeline,\n * avoiding unnecessary regex passes for unrelated sources.\n */\n\n// Reusable DOMParser instance (avoid creating a new one per paste clean call)\nconst _parser = typeof DOMParser !== 'undefined' ? new DOMParser() : null\n\n// Pre-compiled regex patterns for list detection (hoisted from inner loop)\nconst BULLET_PATTERN = /^[\\s]*[·•●○◦▪▫–—-]\\s*/\nconst NUMBER_PATTERN = /^[\\s]*\\d+[.)]\\s*/\nconst LETTER_PATTERN = /^[\\s]*[a-zA-Z][.)]\\s*/\nconst INDENT_PATTERN = /margin-left|padding-left|text-indent/i\n// Combined list-prefix strip pattern (single regex instead of 3 separate .replace calls)\nconst LIST_PREFIX_PATTERN = /^[\\s]*(?:[·•●○◦▪▫–—-]|\\d+[.)]|[a-zA-Z][.)])\\s*/\n\nexport function cleanPastedHTML(html) {\n if (!html) return ''\n\n let cleaned = html\n\n // ── Strip meta/head wrappers that some sources include (always run) ──\n cleaned = cleaned.replace(/<meta[^>]*>/gi, '')\n cleaned = cleaned.replace(/<\\/?html[^>]*>/gi, '')\n cleaned = cleaned.replace(/<head[\\s\\S]*?<\\/head>/gi, '')\n cleaned = cleaned.replace(/<\\/?body[^>]*>/gi, '')\n cleaned = cleaned.replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n cleaned = cleaned.replace(/<title[\\s\\S]*?<\\/title>/gi, '')\n // Strip dangerous embedded content (SVG can contain scripts, MathML can contain exploits)\n cleaned = cleaned.replace(/<svg[\\s\\S]*?<\\/svg>/gi, '')\n cleaned = cleaned.replace(/<math[\\s\\S]*?<\\/math>/gi, '')\n cleaned = cleaned.replace(/<object[\\s\\S]*?<\\/object>/gi, '')\n cleaned = cleaned.replace(/<link[^>]*>/gi, '')\n\n // ── Detect paste source ──\n const isWord = /mso-|class=\"Mso|<o:p|<w:/.test(html)\n const isGoogleDocs = /docs-internal|id=\"h\\.[a-z0-9]+\"/.test(html)\n const isGoogleSheets = /google-sheets-html-origin/.test(html)\n const isLibreOffice = /<text:|<office:|class=\"P\\d+\"/.test(html)\n const isApplePages = /apple-content-edited|class=\"[sp]\\d+\"/.test(html)\n\n // ── Microsoft Word cleanup (only if Word detected) ──\n if (isWord) {\n cleaned = cleaned.replace(/<!--\\[if[\\s\\S]*?endif\\]-->/gi, '')\n cleaned = cleaned.replace(/<!--[\\s\\S]*?-->/g, '')\n cleaned = cleaned.replace(/<o:p[\\s\\S]*?<\\/o:p>/gi, '')\n cleaned = cleaned.replace(/<w:[\\s\\S]*?<\\/w:[\\s\\S]*?>/gi, '')\n cleaned = cleaned.replace(/<m:[\\s\\S]*?<\\/m:[\\s\\S]*?>/gi, '')\n cleaned = cleaned.replace(/<\\/?(xml|st1|v:|o:)[^>]*>/gi, '')\n\n // Remove mso-* styles (Word-specific CSS)\n cleaned = cleaned.replace(/\\s*mso-[^:]+:[^;\"]+;?/gi, '')\n\n // Remove class=\"Mso*\" (Word paragraph classes)\n cleaned = cleaned.replace(/\\s*class=\"Mso[^\"]*\"/gi, '')\n\n // Remove Word-specific attributes\n cleaned = cleaned.replace(/\\s*lang=\"[^\"]*\"/gi, '')\n }\n\n // ── Google Docs cleanup (only if Google Docs detected) ──\n if (isGoogleDocs) {\n cleaned = cleaned.replace(/<b\\s+id=\"docs-internal[^\"]*\"[^>]*>([\\s\\S]*?)<\\/b>/gi, '$1')\n cleaned = cleaned.replace(/\\s*id=\"docs-internal[^\"]*\"/gi, '')\n cleaned = cleaned.replace(/\\s*id=\"h\\.[a-z0-9]+\"/gi, '')\n cleaned = cleaned.replace(/\\s*class=\"c\\d+\"/gi, '')\n cleaned = cleaned.replace(/\\s*dir=\"ltr\"/gi, '')\n cleaned = cleaned.replace(/\\s*role=\"presentation\"/gi, '')\n\n // Google Docs wraps content in span with inline styles for weight/style\n // Convert <span style=\"font-weight: 700\"> to <strong>\n cleaned = cleaned.replace(\n /<span\\s+style=\"[^\"]*font-weight:\\s*(700|bold)[^\"]*\">([\\s\\S]*?)<\\/span>/gi,\n '<strong>$2</strong>'\n )\n cleaned = cleaned.replace(\n /<span\\s+style=\"[^\"]*font-style:\\s*italic[^\"]*\">([\\s\\S]*?)<\\/span>/gi,\n '<em>$2</em>'\n )\n cleaned = cleaned.replace(\n /<span\\s+style=\"[^\"]*text-decoration:\\s*line-through[^\"]*\">([\\s\\S]*?)<\\/span>/gi,\n '<s>$2</s>'\n )\n }\n\n // ── LibreOffice / OpenOffice cleanup (only if LibreOffice detected) ──\n if (isLibreOffice) {\n cleaned = cleaned.replace(/<\\/?(text|office|table|draw|style|number|fo|svg):[^>]*>/gi, '')\n cleaned = cleaned.replace(/\\s*class=\"P\\d+\"/gi, '')\n cleaned = cleaned.replace(/\\s*class=\"T\\d+\"/gi, '')\n cleaned = cleaned.replace(/\\s*class=\"Table\\d+\"/gi, '')\n }\n\n // ── Google Sheets cleanup (only if Google Sheets detected) ──\n if (isGoogleSheets) {\n cleaned = cleaned.replace(/<google-sheets-html-origin[\\s\\S]*?\\/>/gi, '')\n cleaned = cleaned.replace(/<\\/?google-sheets-html-origin[^>]*>/gi, '')\n cleaned = cleaned.replace(/<col[^>]*>/gi, '')\n cleaned = cleaned.replace(/<\\/?colgroup[^>]*>/gi, '')\n }\n\n // ── Excel cleanup (Excel tables use mso- styles like Word) ──\n if (isWord && /<table/i.test(html)) {\n // Strip Excel-specific xmlns attributes\n cleaned = cleaned.replace(/\\s*xmlns:[a-z]=\"[^\"]*\"/gi, '')\n // Remove <col> and <colgroup> tags from Excel tables\n cleaned = cleaned.replace(/<col[^>]*>/gi, '')\n cleaned = cleaned.replace(/<\\/?colgroup[^>]*>/gi, '')\n }\n\n // ── Apple Pages / iWork cleanup (only if Apple Pages detected) ──\n if (isApplePages) {\n cleaned = cleaned.replace(/\\s*class=\"(s|p)\\d+\"/gi, '')\n cleaned = cleaned.replace(/<div\\s+apple-content-edited=\"true\"[^>]*>/gi, '<div>')\n }\n\n // ── Common cleanup (always run) ──\n\n // Remove @font-face declarations (may contain tracking URLs)\n cleaned = cleaned.replace(/@font-face\\s*\\{[^}]*\\}/gi, '')\n\n // Remove empty style attributes\n cleaned = cleaned.replace(/\\s*style=\"\\s*\"/gi, '')\n\n // Remove empty class attributes\n cleaned = cleaned.replace(/\\s*class=\"\\s*\"/gi, '')\n\n // Remove empty spans (no attributes)\n cleaned = cleaned.replace(/<span\\s*>([\\s\\S]*?)<\\/span>/gi, '$1')\n\n // Normalize whitespace in tags\n cleaned = cleaned.replace(/<(\\w+)\\s+>/g, '<$1>')\n\n // Remove empty paragraphs (except intentional line breaks)\n cleaned = cleaned.replace(/<p[^>]*>\\s*<\\/p>/gi, '')\n\n // Remove empty divs\n cleaned = cleaned.replace(/<div[^>]*>\\s*<\\/div>/gi, '')\n\n // ── Font tag conversion (always run) ──\n // Use flexible regexes that match attributes anywhere in the tag (not just first position)\n cleaned = cleaned.replace(/<font\\s[^>]*?face=\"([^\"]*)\"[^>]*>([\\s\\S]*?)<\\/font>/gi,\n '<span style=\"font-family: $1\">$2</span>')\n cleaned = cleaned.replace(/<font\\s[^>]*?color=\"([^\"]*)\"[^>]*>([\\s\\S]*?)<\\/font>/gi,\n '<span style=\"color: $1\">$2</span>')\n cleaned = cleaned.replace(/<font\\s[^>]*?size=\"([^\"]*)\"[^>]*>([\\s\\S]*?)<\\/font>/gi, '$2')\n cleaned = cleaned.replace(/<\\/?font[^>]*>/gi, '')\n\n // ── Normalize semantic tags (always run) ──\n cleaned = cleaned.replace(/<b(\\s|>)/gi, '<strong$1')\n cleaned = cleaned.replace(/<\\/b>/gi, '</strong>')\n cleaned = cleaned.replace(/<i(\\s|>)/gi, '<em$1')\n cleaned = cleaned.replace(/<\\/i>/gi, '</em>')\n\n // ── Convert Word-style list paragraphs to real lists (only if Word detected) ──\n if (isWord) {\n cleaned = convertWordListParagraphs(cleaned)\n }\n\n // ── Final whitespace cleanup ──\n cleaned = cleaned.replace(/(<br\\s*\\/?\\s*>){3,}/gi, '<br><br>')\n\n return cleaned.trim()\n}\n\n\n/**\n * Detect Word-style list paragraphs and convert them to proper HTML lists.\n * Word often exports lists as:\n * <p style=\"margin-left:36pt;text-indent:-18pt\">· Item text</p>\n * <p style=\"margin-left:36pt;text-indent:-18pt\">1. Item text</p>\n */\nfunction convertWordListParagraphs(html) {\n if (!_parser) return html\n const doc = _parser.parseFromString(`<body>${html}</body>`, 'text/html')\n const paragraphs = doc.querySelectorAll('p')\n\n let inList = false\n let listType = null\n let listEl = null\n\n for (const p of paragraphs) {\n const text = p.textContent.trim()\n const style = p.getAttribute('style') || ''\n const hasIndent = INDENT_PATTERN.test(style)\n\n const isBullet = BULLET_PATTERN.test(text)\n const isNumber = NUMBER_PATTERN.test(text) || LETTER_PATTERN.test(text)\n\n if ((isBullet || isNumber) && hasIndent) {\n const type = isBullet ? 'ul' : 'ol'\n\n if (!inList || listType !== type) {\n listEl = doc.createElement(type)\n p.parentNode.insertBefore(listEl, p)\n inList = true\n listType = type\n }\n\n const li = doc.createElement('li')\n // Single combined regex instead of 3 separate .replace() calls\n li.innerHTML = p.innerHTML.replace(LIST_PREFIX_PATTERN, '')\n listEl.appendChild(li)\n p.parentNode.removeChild(p)\n } else {\n inList = false\n listType = null\n listEl = null\n }\n }\n\n return doc.body.innerHTML\n}\n\n\n/**\n * Detect whether plain text looks like Markdown content.\n * Returns true if the text contains enough markdown patterns\n * to justify converting it rather than treating it as plain text.\n *\n * @param {string} text - Plain text to check\n * @returns {boolean}\n */\n// Pre-compiled markdown detection patterns (avoid recompilation per line)\nconst MD_HEADING = /^#{1,6}\\s+\\S/\nconst MD_UNORDERED = /^[-*+]\\s+\\S/\nconst MD_ORDERED = /^\\d+[.)]\\s+\\S/\nconst MD_TASK = /^[-*+]\\s+\\[[ xX]\\]\\s/\nconst MD_BLOCKQUOTE = /^>\\s/\nconst MD_HR = /^([-*_])\\1{2,}$/\nconst MD_CODE_FENCE = /^```/\nconst MD_BOLD = /\\*\\*[^*]+\\*\\*/\nconst MD_BOLD_ALT = /__[^_]+__/\nconst MD_ITALIC = /(?<!\\w)\\*[^*\\s][^*]*\\*(?!\\w)/\nconst MD_LINK = /\\[.+\\]\\(.+\\)/\nconst MD_IMAGE = /!\\[.*\\]\\(.+\\)/\nconst MD_TABLE = /^\\|.+\\|/\nconst MD_TABLE_SEP = /^\\|[\\s-:|]+\\|$/\nconst MD_INLINE_CODE = /`[^`]+`/\n\nexport function looksLikeMarkdown(text) {\n if (!text || text.length < 3) return false\n\n const lines = text.split('\\n')\n let markdownSignals = 0\n let totalNonEmptyLines = 0\n\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed) continue\n totalNonEmptyLines++\n\n if (MD_HEADING.test(trimmed)) { markdownSignals += 2; continue }\n if (MD_UNORDERED.test(trimmed)) { markdownSignals++; continue }\n if (MD_ORDERED.test(trimmed)) { markdownSignals++; continue }\n if (MD_TASK.test(trimmed)) { markdownSignals += 2; continue }\n if (MD_BLOCKQUOTE.test(trimmed)) { markdownSignals++; continue }\n if (MD_HR.test(trimmed)) { markdownSignals++; continue }\n if (MD_CODE_FENCE.test(trimmed)) { markdownSignals += 2; continue }\n if (MD_BOLD.test(trimmed) || MD_BOLD_ALT.test(trimmed)) { markdownSignals++; continue }\n if (MD_ITALIC.test(trimmed)) { markdownSignals++; continue }\n if (MD_LINK.test(trimmed)) { markdownSignals++; continue }\n if (MD_IMAGE.test(trimmed)) { markdownSignals += 2; continue }\n if (MD_TABLE.test(trimmed)) { markdownSignals++; continue }\n if (MD_TABLE_SEP.test(trimmed)) { markdownSignals++; continue }\n if (MD_INLINE_CODE.test(trimmed)) { markdownSignals++; continue }\n }\n\n if (totalNonEmptyLines === 0) return false\n\n // If at least 30% of non-empty lines have markdown signals,\n // or if there are 2+ signals total, it's probably markdown\n const ratio = markdownSignals / totalNonEmptyLines\n return ratio >= 0.3 || markdownSignals >= 2\n}\n","/**\n * Escapes HTML special characters in a string to prevent XSS.\n * Shared utility to avoid duplicating the pattern across modules (Task 261).\n * @param {string} str - The string to escape\n * @returns {string} The escaped string\n */\nexport function escapeHTML(str) {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\n/**\n * Escapes HTML special characters including double quotes.\n * Used in contexts where attribute values need escaping.\n * @param {string} str - The string to escape\n * @returns {string} The escaped string\n */\nexport function escapeHTMLAttr(str) {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n","import { escapeHTMLAttr } from '../escapeHTML.js'\n\n/**\n * Shared utilities for document converters.\n */\n\n/**\n * Get the file extension from a filename.\n */\nexport function getExtension(filename) {\n const dot = filename.lastIndexOf('.')\n return dot >= 0 ? filename.slice(dot).toLowerCase() : ''\n}\n\n/**\n * Read a file as text.\n */\nexport function readAsText(file) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = (e) => resolve(e.target.result)\n reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`))\n reader.readAsText(file)\n })\n}\n\n/**\n * Read a file as ArrayBuffer.\n */\nexport function readAsArrayBuffer(file) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = (e) => resolve(e.target.result)\n reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`))\n reader.readAsArrayBuffer(file)\n })\n}\n\n/**\n * Escape HTML special characters.\n * Task 261: Re-exports shared escapeHTMLAttr as escapeHtml for backward compatibility.\n */\nexport function escapeHtml(str) {\n return escapeHTMLAttr(str)\n}\n","import { getExtension } from './shared.js'\nimport { cleanPastedHTML } from '../pasteClean.js'\n\n/**\n * Supported document formats with their MIME types and extensions.\n */\nconst FORMAT_MAP = {\n // DOCX\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',\n // PDF\n 'application/pdf': 'pdf',\n // Markdown\n 'text/markdown': 'markdown',\n 'text/x-markdown': 'markdown',\n // HTML\n 'text/html': 'html',\n // Plain text\n 'text/plain': 'text',\n // CSV\n 'text/csv': 'csv',\n 'application/csv': 'csv',\n // RTF\n 'text/rtf': 'rtf',\n 'application/rtf': 'rtf',\n}\n\nconst EXTENSION_MAP = {\n '.docx': 'docx',\n '.pdf': 'pdf',\n '.md': 'markdown',\n '.markdown': 'markdown',\n '.html': 'html',\n '.htm': 'html',\n '.txt': 'text',\n '.csv': 'csv',\n '.tsv': 'csv',\n '.rtf': 'rtf',\n}\n\n/**\n * Detect the format of a file by MIME type and extension.\n */\nfunction detectFormat(file) {\n // Try MIME type first\n if (file.type && FORMAT_MAP[file.type]) {\n return FORMAT_MAP[file.type]\n }\n // Fall back to extension\n const ext = getExtension(file.name || '')\n return EXTENSION_MAP[ext] || null\n}\n\n/**\n * Check if a file can be converted/imported.\n */\nexport function isImportableFile(file) {\n return detectFormat(file) !== null\n}\n\n/**\n * Get the accept string for file inputs (supported extensions).\n */\nexport function getSupportedExtensions() {\n return Object.keys(EXTENSION_MAP).join(',')\n}\n\n/**\n * Get a human-readable list of supported format names.\n */\nexport function getSupportedFormatNames() {\n return ['PDF', 'DOCX', 'Markdown', 'HTML', 'TXT', 'CSV', 'TSV', 'RTF']\n}\n\n/**\n * Convert a document file to HTML.\n * Each format converter is dynamically imported for tree-shaking.\n *\n * @param {File} file - The file to convert\n * @returns {Promise<string>} The converted HTML content\n * @throws {Error} If the format is unsupported or conversion fails\n */\nexport async function convertDocument(file) {\n const format = detectFormat(file)\n\n if (!format) {\n throw new Error(`Unsupported file format: ${file.name}`)\n }\n\n const converters = {\n docx: () => import('./convertDocx.js'),\n pdf: () => import('./convertPdf.js'),\n markdown: () => import('./convertMarkdown.js'),\n html: () => import('./convertHtml.js'),\n text: () => import('./convertText.js'),\n csv: () => import('./convertCsv.js'),\n rtf: () => import('./convertRtf.js'),\n }\n\n const mod = await converters[format]()\n const raw = await mod.default(file)\n // Pre-clean imported HTML to strip script/style/object tags\n return typeof raw === 'string' ? cleanPastedHTML(raw) : raw\n}\n","export const DEFAULT_TOOLBAR = [\n ['undo', 'redo'],\n ['headings', 'fontFamily', 'fontSize'],\n ['bold', 'italic', 'underline', 'strikethrough'],\n ['foreColor', 'backColor'],\n ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'],\n ['orderedList', 'unorderedList', 'taskList'],\n ['outdent', 'indent'],\n ['link', 'image', 'attachment', 'importDocument', 'table', 'embedMedia', 'blockquote', 'codeBlock', 'horizontalRule'],\n ['subscript', 'superscript', 'removeFormat'],\n ['insertCallout', 'insertMath', 'insertToc', 'insertBookmark', 'insertMergeTag'],\n ['findReplace', 'toggleMarkdown', 'sourceMode', 'toggleAnalytics', 'toggleSpellcheck', 'distractionFree', 'toggleSplitView', 'export', 'commandPalette', 'fullscreen'],\n]\n\nexport const DEFAULT_FONTS = [\n 'Arial',\n 'Arial Black',\n 'Courier New',\n 'Georgia',\n 'Helvetica',\n 'Impact',\n 'Lucida Console',\n 'Palatino Linotype',\n 'Tahoma',\n 'Times New Roman',\n 'Trebuchet MS',\n 'Verdana',\n 'Comic Sans MS',\n 'Segoe UI',\n 'Roboto',\n 'Open Sans',\n]\n\nexport const DEFAULT_FONT_SIZES = [\n { label: '8px', value: '8px' },\n { label: '10px', value: '10px' },\n { label: '12px', value: '12px' },\n { label: '14px', value: '14px' },\n { label: '16px', value: '16px' },\n { label: '18px', value: '18px' },\n { label: '20px', value: '20px' },\n { label: '24px', value: '24px' },\n { label: '28px', value: '28px' },\n { label: '32px', value: '32px' },\n { label: '36px', value: '36px' },\n { label: '48px', value: '48px' },\n { label: '64px', value: '64px' },\n { label: '72px', value: '72px' },\n]\n\nexport const DEFAULT_COLORS = [\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 '#cc4125', '#e06666', '#f6b26b', '#ffd966', '#93c47d', '#76a5af', '#6d9eeb', '#6fa8dc', '#8e7cc3', '#c27ba0',\n '#a61c00', '#cc0000', '#e69138', '#f1c232', '#6aa84f', '#45818e', '#3c78d8', '#3d85c6', '#674ea7', '#a64d79',\n '#85200c', '#990000', '#b45f06', '#bf9000', '#38761d', '#134f5c', '#1155cc', '#0b5394', '#351c75', '#741b47',\n '#5b0f00', '#660000', '#783f04', '#7f6000', '#274e13', '#0c343d', '#1c4587', '#073763', '#20124d', '#4c1130',\n]\n\nexport const DEFAULT_MENU_BAR = [\n { label: 'File', items: ['importDocument', 'export'] },\n { label: 'Edit', items: ['undo', 'redo', '---', 'findReplace'] },\n { label: 'View', items: ['fullscreen', 'distractionFree', 'toggleSplitView', '---', 'toggleMarkdown', 'sourceMode', '---', 'toggleAnalytics', 'toggleSpellcheck'] },\n { label: 'Insert', items: [\n 'link', 'image', 'table', 'attachment', 'embedMedia',\n '---', 'blockquote', 'codeBlock', 'horizontalRule',\n '---', 'insertCallout', 'insertMath', 'insertToc', 'insertBookmark', 'insertMergeTag',\n '---', 'addComment',\n ]},\n { label: 'Format', items: [\n 'bold', 'italic', 'underline', 'strikethrough', '---',\n 'subscript', 'superscript', '---',\n { label: 'Alignment', items: ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'] },\n '---', 'orderedList', 'unorderedList', 'taskList',\n '---', 'foreColor', 'backColor',\n '---', { label: 'Typography', items: ['lineHeight', 'letterSpacing', 'paragraphSpacing'] },\n '---', 'removeFormat',\n ]},\n]\n\n/**\n * Default maximum file size for pasted/dropped images and documents (10 MB).\n * Files exceeding this limit will be rejected with a warning.\n * Can be overridden via `options.maxFileSize` on the editor instance.\n */\nexport const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024\n\nexport const HEADING_OPTIONS = [\n { label: 'Normal', value: 'p', tag: 'p' },\n { label: 'Heading 1', value: 'h1', tag: 'h1' },\n { label: 'Heading 2', value: 'h2', tag: 'h2' },\n { label: 'Heading 3', value: 'h3', tag: 'h3' },\n { label: 'Heading 4', value: 'h4', tag: 'h4' },\n { label: 'Heading 5', value: 'h5', tag: 'h5' },\n { label: 'Heading 6', value: 'h6', tag: 'h6' },\n]\n","import { DEFAULT_MAX_FILE_SIZE } from '../constants/defaults.js'\n\n/**\n * Check if a file exceeds the given maximum size.\n * Emits a 'file:too-large' event on the provided eventBus and logs a warning.\n *\n * @param {File} file - The file to check\n * @param {number} maxBytes - Maximum allowed file size in bytes (0 = no limit)\n * @param {{ eventBus?: { emit: Function } }} [context] - Optional context with eventBus for emitting events\n * @returns {boolean} true if the file exceeds the limit\n */\nexport function exceedsMaxFileSize(file, maxBytes, context) {\n const maxSize = maxBytes ?? DEFAULT_MAX_FILE_SIZE\n if (maxSize > 0 && file.size > maxSize) {\n const sizeMB = (file.size / (1024 * 1024)).toFixed(1)\n const limitMB = (maxSize / (1024 * 1024)).toFixed(0)\n console.warn(`[Remyx] File \"${file.name}\" (${sizeMB} MB) exceeds the ${limitMB} MB limit.`)\n if (context?.eventBus) {\n context.eventBus.emit('file:too-large', { file, maxSize })\n }\n return true\n }\n return false\n}\n","import TurndownService from 'turndown'\nimport { gfm } from 'turndown-plugin-gfm'\nimport { marked } from 'marked'\nimport { escapeHTML } from './escapeHTML.js'\n\n// Lazy singletons — zero overhead when outputFormat is 'html'\nlet turndownInstance = null\nlet markedConfigured = false\n\nfunction getTurndown() {\n if (!turndownInstance) {\n turndownInstance = new TurndownService({\n headingStyle: 'atx',\n hr: '---',\n bulletListMarker: '-',\n codeBlockStyle: 'fenced',\n fence: '```',\n emDelimiter: '*',\n strongDelimiter: '**',\n linkStyle: 'inlined',\n })\n turndownInstance.use(gfm)\n\n // Preserve <br> as markdown line breaks\n turndownInstance.addRule('lineBreak', {\n filter: 'br',\n replacement: () => ' \\n',\n })\n\n // Handle editor-specific task list checkboxes\n turndownInstance.addRule('taskCheckbox', {\n filter(node) {\n return (\n node.nodeName === 'INPUT' &&\n node.type === 'checkbox' &&\n node.classList.contains('rmx-task-checkbox')\n )\n },\n replacement(content, node) {\n return node.checked ? '[x] ' : '[ ] '\n },\n })\n\n // Preserve language identifier on fenced code blocks (data-language attribute)\n turndownInstance.addRule('fencedCodeBlockWithLanguage', {\n filter(node) {\n return (\n node.nodeName === 'PRE' &&\n node.firstChild &&\n node.firstChild.nodeName === 'CODE'\n )\n },\n replacement(content, node) {\n const code = node.firstChild\n const language = code.getAttribute('data-language') || ''\n const text = code.textContent\n return `\\n\\n\\`\\`\\`${language}\\n${text}\\n\\`\\`\\`\\n\\n`\n },\n })\n\n // Preserve underline as <u> tag in markdown (non-standard but useful)\n turndownInstance.addRule('underline', {\n filter: ['u'],\n replacement(content) {\n return `<u>${content}</u>`\n },\n })\n }\n return turndownInstance\n}\n\n// Safe URL protocol allowlist for markdown-generated links and images\nconst SAFE_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:'])\n\nfunction isSafeUrl(href) {\n if (!href) return false\n // Allow relative URLs and anchors\n if (/^(\\/|#)/.test(href)) return true\n try {\n // Decode percent-encoding to prevent bypasses like java%73cript:\n const decoded = decodeURIComponent(href)\n const url = new URL(decoded, 'https://placeholder.invalid')\n return SAFE_PROTOCOLS.has(url.protocol)\n } catch {\n return false\n }\n}\n\nfunction ensureMarkedConfigured() {\n if (!markedConfigured) {\n marked.setOptions({\n gfm: true,\n breaks: false,\n })\n\n // Override link and image renderers to block dangerous protocols (javascript:, vbscript:, data:text/html)\n const renderer = new marked.Renderer()\n renderer.link = ({ href, title, tokens }) => {\n const text = marked.Parser.parseInline(tokens)\n if (href && !isSafeUrl(href)) {\n return text\n }\n const titleAttr = title ? ` title=\"${title.replace(/\"/g, '"')}\"` : ''\n return `<a href=\"${href}\"${titleAttr}>${text}</a>`\n }\n renderer.image = ({ href, title, text }) => {\n if (href && !isSafeUrl(href) && !href.startsWith('data:image/')) {\n return text || ''\n }\n const titleAttr = title ? ` title=\"${title.replace(/\"/g, '"')}\"` : ''\n return `<img src=\"${href}\" alt=\"${text || ''}\"${titleAttr} />`\n }\n // Preserve language identifiers on fenced code blocks as data-language attributes\n renderer.code = ({ text, lang }) => {\n const langAttr = lang ? ` data-language=\"${lang.replace(/\"/g, '"')}\"` : ''\n const escaped = escapeHTML(text)\n return `<pre${langAttr}><code${langAttr}>${escaped}</code></pre>\\n`\n }\n\n marked.use({ renderer })\n\n markedConfigured = true\n }\n}\n\n/**\n * Convert HTML to Markdown (GFM)\n * @param {string} html\n * @returns {string}\n */\nexport function htmlToMarkdown(html) {\n if (!html || html === '<p><br></p>') return ''\n return getTurndown().turndown(html)\n}\n\n/**\n * Convert Markdown to HTML\n * @param {string} md\n * @returns {string}\n */\nexport function markdownToHtml(md) {\n if (!md) return ''\n ensureMarkedConfigured()\n return marked.parse(md)\n}\n","import { looksLikeMarkdown } from './pasteClean.js'\nimport { markdownToHtml } from './markdownConverter.js'\nimport { escapeHTML } from './escapeHTML.js'\n\n/**\n * Insert plain text into the editor with smart markdown detection.\n * Shared utility used by Clipboard, DragDrop, and useContextMenu.\n * @param {import('../core/EditorEngine.js').EditorEngine} engine\n * @param {string} text\n */\nexport function insertPlainText(engine, text) {\n if (engine.outputFormat === 'markdown' || looksLikeMarkdown(text)) {\n let parsedHtml = markdownToHtml(text)\n parsedHtml = engine.sanitizer.sanitize(parsedHtml)\n engine.selection.insertHTML(parsedHtml)\n } else {\n const escaped = escapeHTML(text)\n const formatted = escaped\n .split(/\\n\\n+/)\n .map((para) => `<p>${para.replace(/\\n/g, '<br>')}</p>`)\n .join('')\n engine.selection.insertHTML(formatted || '<p><br></p>')\n }\n}\n","import { cleanPastedHTML } from '../utils/pasteClean.js'\nimport { isImportableFile, convertDocument } from '../utils/documentConverter/index.js'\nimport { exceedsMaxFileSize } from '../utils/fileValidation.js'\nimport { insertPlainText } from '../utils/insertPlainText.js'\n\nexport class Clipboard {\n constructor(engine) {\n this.engine = engine\n this._handlePaste = this._handlePaste.bind(this)\n this._handleCopy = this._handleCopy.bind(this)\n this._handleCut = this._handleCut.bind(this)\n }\n\n init() {\n this.engine.element.addEventListener('paste', this._handlePaste)\n this.engine.element.addEventListener('copy', this._handleCopy)\n this.engine.element.addEventListener('cut', this._handleCut)\n }\n\n destroy() {\n this.engine.element.removeEventListener('paste', this._handlePaste)\n this.engine.element.removeEventListener('copy', this._handleCopy)\n this.engine.element.removeEventListener('cut', this._handleCut)\n }\n\n _handlePaste(e) {\n e.preventDefault()\n this.engine.history.snapshot()\n\n const clipboardData = e.clipboardData || window.clipboardData\n let html = clipboardData.getData('text/html')\n const text = clipboardData.getData('text/plain')\n\n // Handle file paste (images and attachments)\n const files = Array.from(clipboardData.files || [])\n const imageFile = files.find((f) => f.type.startsWith('image/'))\n if (imageFile) {\n this._handleImagePaste(imageFile)\n return\n }\n\n // Handle importable document files (PDF, DOCX, etc.)\n const importableFiles = files.filter((f) => !f.type.startsWith('image/') && isImportableFile(f))\n if (importableFiles.length > 0) {\n // Serialize async conversions to prevent race conditions with insertHTML\n const validFiles = importableFiles.filter((f) => !this._exceedsMaxFileSize(f))\n let chain = Promise.resolve()\n for (const file of validFiles) {\n chain = chain.then(() =>\n convertDocument(file)\n .then((html) => {\n const sanitized = this.engine.sanitizer.sanitize(html)\n this.engine.selection.insertHTML(sanitized)\n this.engine.eventBus.emit('content:change')\n })\n .catch((err) => {\n console.warn('Document import failed on paste:', err.message)\n })\n )\n }\n return\n }\n\n // Handle non-image file paste as attachment\n const nonImageFiles = files.filter((f) => !f.type.startsWith('image/'))\n if (nonImageFiles.length > 0 && this.engine.options.uploadHandler) {\n // Serialize uploads to prevent race conditions with concurrent insertAttachment calls\n let chain = Promise.resolve()\n for (const file of nonImageFiles) {\n chain = chain.then(() =>\n this.engine.options.uploadHandler(file).then((url) => {\n this.engine.commands.execute('insertAttachment', {\n url,\n filename: file.name,\n filesize: file.size,\n })\n }).catch((err) => {\n console.error(`File upload failed for \"${file.name}\":`, err)\n this.engine.eventBus.emit('upload:error', { file, error: err })\n })\n )\n }\n return\n }\n\n // Table-aware paste: if caret is in a table cell and clipboard has TSV or table HTML\n const caretCell = this._getCaretCell()\n if (caretCell) {\n const table = caretCell.closest('table')\n if (table) {\n // Try TSV paste first\n if (text && this._looksLikeTSV(text)) {\n this._pasteIntoTable(table, caretCell, text)\n this.engine.eventBus.emit('paste', { html, text })\n this.engine.eventBus.emit('content:change')\n return\n }\n // Try HTML table paste\n if (html && /<table/i.test(html)) {\n const cleaned = cleanPastedHTML(html)\n const tsvFromHtml = this._htmlTableToTSV(cleaned)\n if (tsvFromHtml) {\n this._pasteIntoTable(table, caretCell, tsvFromHtml)\n this.engine.eventBus.emit('paste', { html, text })\n this.engine.eventBus.emit('content:change')\n return\n }\n }\n }\n }\n\n if (html) {\n // Rich text paste — clean source-specific markup, then sanitize\n html = cleanPastedHTML(html)\n html = this.engine.sanitizer.sanitize(html)\n this.engine.selection.insertHTML(html)\n } else if (text) {\n this._handleTextPaste(text)\n }\n\n this.engine.eventBus.emit('paste', { html, text })\n this.engine.eventBus.emit('content:change')\n }\n\n /**\n * Handle plain-text paste with smart format detection.\n * Task 256: Uses shared insertPlainText utility.\n */\n _handleTextPaste(text) {\n insertPlainText(this.engine, text)\n }\n\n _handleCopy(e) {\n // Table-aware copy: if selection is within a table, generate clean HTML + TSV\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return\n\n const anchor = sel.anchorNode?.nodeType === Node.TEXT_NODE\n ? sel.anchorNode.parentElement : sel.anchorNode\n const focus = sel.focusNode?.nodeType === Node.TEXT_NODE\n ? sel.focusNode.parentElement : sel.focusNode\n\n const anchorTable = anchor?.closest?.('table.rmx-table')\n const focusTable = focus?.closest?.('table.rmx-table')\n\n if (anchorTable && focusTable && anchorTable === focusTable) {\n // Selection is within a single table — produce TSV + clean HTML\n const table = anchorTable\n const rows = table.querySelectorAll('thead tr, tbody tr')\n const tsvLines = []\n let htmlRows = ''\n\n for (const row of rows) {\n if (row.classList.contains('rmx-row-hidden')) continue\n const cells = row.querySelectorAll('td, th')\n const tsvCells = []\n let htmlCells = ''\n for (const cell of cells) {\n const text = cell.textContent.trim()\n // Escape TSV: if text contains tab or newline, quote it\n if (text.includes('\\t') || text.includes('\\n') || text.includes('\"')) {\n tsvCells.push('\"' + text.replace(/\"/g, '\"\"') + '\"')\n } else {\n tsvCells.push(text)\n }\n const tag = cell.tagName.toLowerCase()\n htmlCells += `<${tag}>${cell.innerHTML}</${tag}>`\n }\n tsvLines.push(tsvCells.join('\\t'))\n htmlRows += `<tr>${htmlCells}</tr>`\n }\n\n const tsv = tsvLines.join('\\n')\n const html = `<table><tbody>${htmlRows}</tbody></table>`\n\n e.preventDefault()\n e.clipboardData.setData('text/plain', tsv)\n e.clipboardData.setData('text/html', html)\n }\n // Otherwise let browser handle default copy\n }\n\n _handleCut() {\n // Capture state before the cut happens\n this.engine.history.snapshot()\n // Let browser handle default cut, just record for undo\n setTimeout(() => {\n this.engine.eventBus.emit('content:change')\n }, 0)\n }\n\n /**\n * Check if a file exceeds the configured maximum size.\n * @param {File} file\n * @returns {boolean} true if the file is too large\n */\n _exceedsMaxFileSize(file) {\n return exceedsMaxFileSize(file, this.engine.options.maxFileSize, { eventBus: this.engine.eventBus })\n }\n\n _getCaretCell() {\n const sel = window.getSelection()\n if (!sel || !sel.anchorNode) return null\n const node = sel.anchorNode.nodeType === Node.TEXT_NODE\n ? sel.anchorNode.parentElement : sel.anchorNode\n return node?.closest?.('td, th') || null\n }\n\n _looksLikeTSV(text) {\n if (!text.includes('\\t')) return false\n const lines = text.trim().split('\\n')\n if (lines.length < 1) return false\n // Check if at least first line has tabs\n return lines[0].includes('\\t')\n }\n\n _htmlTableToTSV(html) {\n const parser = new DOMParser()\n const doc = parser.parseFromString(html, 'text/html')\n const table = doc.querySelector('table')\n if (!table) return null\n const lines = []\n const rows = table.querySelectorAll('tr')\n for (const row of rows) {\n const cells = row.querySelectorAll('td, th')\n lines.push(Array.from(cells).map(c => c.textContent.trim()).join('\\t'))\n }\n return lines.join('\\n')\n }\n\n _pasteIntoTable(table, startCell, tsv) {\n const tbody = table.querySelector('tbody') || table\n const rows = tsv.trim().split('\\n').map(line => line.split('\\t'))\n const allRows = Array.from(table.querySelectorAll('tbody tr'))\n const startRow = startCell.closest('tr')\n let startRowIdx = allRows.indexOf(startRow)\n if (startRowIdx < 0) startRowIdx = 0\n let startColIdx = 0\n let prev = startCell.previousElementSibling\n while (prev) {\n startColIdx += prev.colSpan || 1\n prev = prev.previousElementSibling\n }\n\n for (let r = 0; r < rows.length; r++) {\n const rowIdx = startRowIdx + r\n // Add rows if needed\n while (allRows.length <= rowIdx) {\n const firstRow = allRows[0] || startRow\n const colCount = firstRow ? firstRow.cells.length : rows[0].length\n const newRow = document.createElement('tr')\n for (let c = 0; c < colCount; c++) {\n const td = document.createElement('td')\n td.innerHTML = '<br>'\n newRow.appendChild(td)\n }\n tbody.appendChild(newRow)\n allRows.push(newRow)\n }\n\n const tr = allRows[rowIdx]\n for (let c = 0; c < rows[r].length; c++) {\n const colIdx = startColIdx + c\n // Add columns if needed\n while (tr.cells.length <= colIdx) {\n const td = document.createElement('td')\n td.innerHTML = '<br>'\n tr.appendChild(td)\n // Also add to other rows for consistency\n allRows.forEach((otherRow, idx) => {\n if (idx !== rowIdx && otherRow.cells.length <= colIdx) {\n const otherTd = document.createElement('td')\n otherTd.innerHTML = '<br>'\n otherRow.appendChild(otherTd)\n }\n })\n }\n const cell = tr.cells[colIdx]\n if (cell) {\n cell.textContent = rows[r][c]\n }\n }\n }\n }\n\n _handleImagePaste(file) {\n if (this._exceedsMaxFileSize(file)) return\n\n if (this.engine.options.uploadHandler) {\n this.engine.options.uploadHandler(file).then((url) => {\n this.engine.commands.execute('insertImage', { src: url, alt: file.name })\n }).catch((err) => {\n console.error(`Image upload failed for \"${file.name}\":`, err)\n this.engine.eventBus.emit('upload:error', { file, error: err })\n })\n } else {\n const reader = new FileReader()\n reader.onprogress = (e) => {\n if (e.lengthComputable) {\n this.engine.eventBus.emit('upload:progress', {\n loaded: e.loaded,\n total: e.total,\n percent: Math.round((e.loaded / e.total) * 100),\n })\n }\n }\n reader.onload = (e) => {\n const src = e.target.result\n this.engine.commands.execute('insertImage', { src, alt: file.name })\n }\n reader.readAsDataURL(file)\n }\n }\n}\n","import { cleanPastedHTML } from '../utils/pasteClean.js'\nimport { isImportableFile, convertDocument } from '../utils/documentConverter/index.js'\nimport { exceedsMaxFileSize } from '../utils/fileValidation.js'\nimport { insertPlainText } from '../utils/insertPlainText.js'\n\n/** Custom MIME type for inter-editor content transfer */\nconst REMYX_MIME = 'application/x-remyx-content'\n\n/** Block-level tags that can be dragged/reordered */\nconst DRAGGABLE_BLOCKS = new Set([\n 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',\n 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'TABLE', 'HR', 'DIV',\n])\n\n/** Tags whose direct children can be reordered via drag */\nconst REORDERABLE_CHILDREN = new Set(['UL', 'OL', 'TBODY', 'TABLE'])\n\n/**\n * Global registry of active DragDrop instances for inter-editor dragging.\n * Task 266: Changed from Map to WeakMap to avoid memory leaks.\n * @type {WeakMap<HTMLElement, DragDrop>}\n */\nconst _editorRegistry = new WeakMap()\n\n/**\n * Track all active DragDrop instances for iteration (WeakMap is not iterable).\n * @type {Set<DragDrop>}\n */\nconst _activeInstances = new Set()\n\nexport class DragDrop {\n constructor(engine) {\n this.engine = engine\n\n // Drag state\n this._dragSource = null // The block element being dragged\n this._dragSourceEditor = null // The DragDrop instance that owns the drag source\n this._dropTarget = null // The element we'd drop before/after\n this._dropPosition = null // 'before' | 'after'\n this._ghostEl = null // Ghost preview element\n this._dropIndicator = null // Drop indicator line element\n this._isExternalDrag = false // True when dragging from outside the editor\n this._enterCount = 0 // Counter to handle nested dragenter/dragleave\n // Task 253: Cached block positions for drag operations\n this._blockRects = null\n this._scrollHandler = null\n\n this._handleDragOver = this._handleDragOver.bind(this)\n this._handleDrop = this._handleDrop.bind(this)\n this._handleDragEnter = this._handleDragEnter.bind(this)\n this._handleDragLeave = this._handleDragLeave.bind(this)\n this._handleDragStart = this._handleDragStart.bind(this)\n this._handleDragEnd = this._handleDragEnd.bind(this)\n }\n\n init() {\n const el = this.engine.element\n el.addEventListener('dragover', this._handleDragOver)\n el.addEventListener('drop', this._handleDrop)\n el.addEventListener('dragenter', this._handleDragEnter)\n el.addEventListener('dragleave', this._handleDragLeave)\n el.addEventListener('dragstart', this._handleDragStart)\n el.addEventListener('dragend', this._handleDragEnd)\n _editorRegistry.set(el, this)\n _activeInstances.add(this)\n }\n\n destroy() {\n const el = this.engine.element\n el.removeEventListener('dragover', this._handleDragOver)\n el.removeEventListener('drop', this._handleDrop)\n el.removeEventListener('dragenter', this._handleDragEnter)\n el.removeEventListener('dragleave', this._handleDragLeave)\n el.removeEventListener('dragstart', this._handleDragStart)\n el.removeEventListener('dragend', this._handleDragEnd)\n _editorRegistry.delete(el)\n _activeInstances.delete(this)\n this._cleanupDrag()\n }\n\n // ── Public API for React layer ──────────────────────────────────────\n\n /**\n * Returns the closest draggable block element for a given DOM node.\n * Used by the React BlockDragHandle to identify what to drag.\n * @param {Node} node\n * @returns {HTMLElement|null}\n */\n getDraggableBlock(node) {\n return this._closestDraggableBlock(node)\n }\n\n /**\n * Make a block element draggable. Called by the React layer when\n * a drag handle is attached to a block.\n * @param {HTMLElement} block\n */\n makeBlockDraggable(block) {\n if (block && !block.hasAttribute('draggable')) {\n block.setAttribute('draggable', 'true')\n }\n }\n\n /**\n * Remove draggable attribute from a block element.\n * @param {HTMLElement} block\n */\n unmakeBlockDraggable(block) {\n if (block) {\n block.removeAttribute('draggable')\n }\n }\n\n /**\n * Returns true if a block drag is currently in progress.\n * @returns {boolean}\n */\n isDragging() {\n return !!this._dragSource\n }\n\n /**\n * Get the current drag state for the React layer to render overlays.\n * @returns {{ isDragging: boolean, isExternalDrag: boolean, dropTarget: HTMLElement|null, dropPosition: string|null }}\n */\n getDragState() {\n return {\n isDragging: !!this._dragSource || this._isExternalDrag,\n isExternalDrag: this._isExternalDrag,\n dropTarget: this._dropTarget,\n dropPosition: this._dropPosition,\n }\n }\n\n // ── Drag Start (internal block drags) ───────────────────────────────\n\n _handleDragStart(e) {\n // Check for reorderable sub-block items (LI, TR) first\n const reorderItem = this._getReorderableItem(e.target)\n const block = reorderItem || this._closestDraggableBlock(e.target)\n if (!block) return\n\n this._dragSource = block\n this._dragSourceEditor = this\n\n // Capture content before adding visual classes\n const html = block.outerHTML\n const text = block.textContent\n\n // Set data for inter-editor transfer\n e.dataTransfer.setData(REMYX_MIME, html)\n e.dataTransfer.setData('text/html', html)\n e.dataTransfer.setData('text/plain', text)\n e.dataTransfer.effectAllowed = 'move'\n\n // Create ghost preview\n this._createGhostPreview(block, e)\n\n // Task 253: Cache block positions at drag start\n this._cacheBlockRects()\n\n // Add dragging class to source (after capturing content)\n block.classList.add('rmx-block-dragging')\n\n // Notify all editors about the drag source\n for (const instance of _activeInstances) {\n instance._dragSourceEditor = this\n instance._dragSource = block\n }\n\n this.engine.eventBus.emit('drag:start', { block })\n }\n\n _handleDragEnd(e) {\n // Remove dragging class\n if (this._dragSource) {\n this._dragSource.classList.remove('rmx-block-dragging')\n }\n\n // Notify all editors to clean up\n for (const instance of _activeInstances) {\n instance._cleanupDrag()\n }\n\n this.engine.eventBus.emit('drag:end')\n }\n\n // ── Drag Over ───────────────────────────────────────────────────────\n\n _handleDragOver(e) {\n e.preventDefault()\n\n // Determine drop effect\n if (this._dragSource) {\n e.dataTransfer.dropEffect = 'move'\n } else {\n e.dataTransfer.dropEffect = 'copy'\n }\n\n // Calculate drop position for block reordering / insertion\n if (this._dragSource || this._isExternalDrag) {\n this._updateDropTarget(e)\n }\n }\n\n // ── Drag Enter / Leave ──────────────────────────────────────────────\n\n _handleDragEnter(e) {\n e.preventDefault()\n this._enterCount++\n\n if (this._enterCount === 1) {\n // Determine if this is an external drag (files from desktop, etc.)\n if (!this._dragSource) {\n this._isExternalDrag = true\n }\n this.engine.element.classList.add('rmx-drag-over')\n this.engine.eventBus.emit('drag:enter', {\n isExternal: this._isExternalDrag,\n types: Array.from(e.dataTransfer.types || []),\n })\n }\n }\n\n _handleDragLeave(e) {\n this._enterCount--\n\n if (this._enterCount <= 0) {\n this._enterCount = 0\n this.engine.element.classList.remove('rmx-drag-over')\n this._removeDropIndicator()\n this._dropTarget = null\n this._dropPosition = null\n\n if (this._isExternalDrag && !this._dragSource) {\n this._isExternalDrag = false\n }\n\n this.engine.eventBus.emit('drag:leave')\n }\n }\n\n // ── Drop ────────────────────────────────────────────────────────────\n\n _handleDrop(e) {\n e.preventDefault()\n this._enterCount = 0\n this.engine.element.classList.remove('rmx-drag-over')\n this._removeDropIndicator()\n\n // ── Internal block reorder / inter-editor move ──\n if (this._dragSource && this._dropTarget) {\n this._handleBlockDrop(e)\n this._cleanupDrag()\n return\n }\n\n // ── File drops ──\n const files = Array.from(e.dataTransfer.files)\n const imageFiles = files.filter((f) => f.type.startsWith('image/'))\n\n if (imageFiles.length > 0) {\n this._handleImageDrop(e, imageFiles)\n this._cleanupDrag()\n return\n }\n\n const importableFiles = files.filter((f) => !f.type.startsWith('image/') && isImportableFile(f))\n if (importableFiles.length > 0) {\n this._handleDocumentDrop(e, importableFiles)\n this._cleanupDrag()\n return\n }\n\n const nonImageFiles = files.filter((f) => !f.type.startsWith('image/'))\n if (nonImageFiles.length > 0 && this.engine.options.uploadHandler) {\n this._handleFileDrop(e, nonImageFiles)\n this._cleanupDrag()\n return\n }\n\n // ── Inter-editor content (no drag source in this editor) ──\n const remyxContent = e.dataTransfer.getData(REMYX_MIME)\n if (remyxContent && !this._dragSource) {\n this._handleInterEditorDrop(e, remyxContent)\n this._cleanupDrag()\n return\n }\n\n // ── HTML / text content drops ──\n this._setCursorAtDropPoint(e)\n this.engine.history.snapshot()\n\n const html = e.dataTransfer.getData('text/html')\n if (html) {\n let cleaned = cleanPastedHTML(html)\n cleaned = this.engine.sanitizer.sanitize(cleaned)\n this.engine.selection.insertHTML(cleaned)\n this.engine.eventBus.emit('content:change')\n } else {\n const text = e.dataTransfer.getData('text/plain')\n if (text) {\n this._handleTextDrop(text)\n this.engine.eventBus.emit('content:change')\n }\n }\n\n this.engine.eventBus.emit('drop', { files, html })\n this._cleanupDrag()\n }\n\n // ── Block drop (reorder / inter-editor move) ───────────────────────\n\n _handleBlockDrop(e) {\n const source = this._dragSource\n const target = this._dropTarget\n const position = this._dropPosition\n const sourceEditor = this._dragSourceEditor\n\n if (!source || !target || !position) return\n if (source === target) return\n\n // Take snapshots in both editors\n this.engine.history.snapshot()\n if (sourceEditor && sourceEditor !== this) {\n sourceEditor.engine.history.snapshot()\n }\n\n // Handle list item reordering\n const sourceReorderable = this._getReorderableItem(source)\n const targetReorderable = this._getReorderableItem(target)\n\n if (sourceReorderable && targetReorderable &&\n sourceReorderable.parentElement === targetReorderable.parentElement) {\n // Reordering within the same list/table\n if (position === 'before') {\n targetReorderable.parentElement.insertBefore(sourceReorderable, targetReorderable)\n } else {\n targetReorderable.parentElement.insertBefore(sourceReorderable, targetReorderable.nextSibling)\n }\n } else {\n // Block-level move\n const insertionParent = target.parentElement\n if (!insertionParent) return\n\n if (position === 'before') {\n insertionParent.insertBefore(source, target)\n } else {\n insertionParent.insertBefore(source, target.nextSibling)\n }\n }\n\n // If source was from a different editor, notify that editor\n if (sourceEditor && sourceEditor !== this) {\n sourceEditor.engine.eventBus.emit('content:change')\n }\n\n this.engine.eventBus.emit('content:change')\n this.engine.eventBus.emit('drag:reorder', { source, target, position })\n }\n\n /**\n * Handle content dropped from another Remyx editor instance.\n */\n _handleInterEditorDrop(e, html) {\n this._setCursorAtDropPoint(e)\n this.engine.history.snapshot()\n\n const sanitized = this.engine.sanitizer.sanitize(html)\n this.engine.selection.insertHTML(sanitized)\n this.engine.eventBus.emit('content:change')\n this.engine.eventBus.emit('drag:inter-editor', { html: sanitized })\n }\n\n // ── Block rect caching (Task 253) ──────────────────────────────────\n\n /**\n * Cache block positions at drag start. Invalidate on scroll.\n */\n _cacheBlockRects() {\n this._blockRects = new Map()\n const blocks = this._getTopLevelBlocks()\n for (const block of blocks) {\n this._blockRects.set(block, block.getBoundingClientRect())\n }\n // Invalidate cache on scroll\n this._scrollHandler = () => { this._blockRects = null }\n this.engine.element.addEventListener('scroll', this._scrollHandler, { passive: true })\n }\n\n /**\n * Get cached rect for a block, or compute fresh if cache invalidated.\n * @param {HTMLElement} block\n * @returns {DOMRect}\n */\n _getBlockRect(block) {\n if (this._blockRects && this._blockRects.has(block)) {\n return this._blockRects.get(block)\n }\n return block.getBoundingClientRect()\n }\n\n // ── Drop target calculation ─────────────────────────────────────────\n\n /**\n * Calculate and update the drop target based on cursor position.\n * Emits drag:indicator event for the React layer to position the indicator.\n * Task 253: Uses cached block rects instead of calling getBoundingClientRect() on every block.\n */\n _updateDropTarget(e) {\n const editorEl = this.engine.element\n\n // If dragging a reorderable item (LI/TR), iterate siblings instead of top-level blocks\n const sourceReorderable = this._dragSource ? this._getReorderableItem(this._dragSource) : null\n const blocks = sourceReorderable && sourceReorderable.parentElement\n ? Array.from(sourceReorderable.parentElement.children).filter(\n c => c.tagName === sourceReorderable.tagName\n )\n : this._getTopLevelBlocks()\n\n if (blocks.length === 0) {\n this._dropTarget = null\n this._dropPosition = null\n this._removeDropIndicator()\n return\n }\n\n const mouseY = e.clientY\n let closestBlock = null\n let closestPosition = null\n let closestDistance = Infinity\n\n for (const block of blocks) {\n // Skip the block being dragged\n if (block === this._dragSource) continue\n\n const rect = this._getBlockRect(block)\n const midY = rect.top + rect.height / 2\n\n const distTop = Math.abs(mouseY - rect.top)\n const distBottom = Math.abs(mouseY - rect.bottom)\n\n if (distTop < closestDistance) {\n closestDistance = distTop\n closestBlock = block\n closestPosition = 'before'\n }\n if (distBottom < closestDistance) {\n closestDistance = distBottom\n closestBlock = block\n closestPosition = 'after'\n }\n\n // Also check if mouse is within block — pick top or bottom half\n if (mouseY >= rect.top && mouseY <= rect.bottom) {\n closestBlock = block\n closestPosition = mouseY < midY ? 'before' : 'after'\n closestDistance = 0\n break\n }\n }\n\n if (closestBlock !== this._dropTarget || closestPosition !== this._dropPosition) {\n this._dropTarget = closestBlock\n this._dropPosition = closestPosition\n\n if (closestBlock) {\n this._showDropIndicator(closestBlock, closestPosition)\n } else {\n this._removeDropIndicator()\n }\n }\n }\n\n /**\n * Get all top-level block elements in the editor.\n * @returns {HTMLElement[]}\n */\n _getTopLevelBlocks() {\n const blocks = []\n const el = this.engine.element\n for (let i = 0; i < el.children.length; i++) {\n const child = el.children[i]\n if (child.nodeType === Node.ELEMENT_NODE &&\n !child.classList.contains('rmx-drop-indicator') &&\n !child.classList.contains('rmx-drag-ghost')) {\n blocks.push(child)\n }\n }\n return blocks\n }\n\n // ── Drop indicator ──────────────────────────────────────────────────\n\n _showDropIndicator(targetBlock, position) {\n if (!this._dropIndicator) {\n this._dropIndicator = document.createElement('div')\n this._dropIndicator.className = 'rmx-drop-indicator'\n this._dropIndicator.setAttribute('aria-hidden', 'true')\n }\n\n const editorEl = this.engine.element\n const editorRect = editorEl.getBoundingClientRect()\n const blockRect = targetBlock.getBoundingClientRect()\n\n // Position the indicator as a horizontal line\n const top = position === 'before'\n ? blockRect.top - editorRect.top + editorEl.scrollTop\n : blockRect.bottom - editorRect.top + editorEl.scrollTop\n\n this._dropIndicator.style.top = `${top}px`\n\n if (!this._dropIndicator.parentElement) {\n editorEl.appendChild(this._dropIndicator)\n }\n\n this.engine.eventBus.emit('drag:indicator', {\n target: targetBlock,\n position,\n top,\n })\n }\n\n _removeDropIndicator() {\n if (this._dropIndicator && this._dropIndicator.parentElement) {\n this._dropIndicator.parentElement.removeChild(this._dropIndicator)\n }\n this._dropIndicator = null\n }\n\n // ── Ghost preview ───────────────────────────────────────────────────\n\n _createGhostPreview(block, e) {\n const ghost = block.cloneNode(true)\n ghost.className = 'rmx-drag-ghost'\n ghost.style.position = 'absolute'\n ghost.style.top = '-9999px'\n ghost.style.left = '-9999px'\n ghost.style.width = `${block.offsetWidth}px`\n ghost.style.pointerEvents = 'none'\n\n document.body.appendChild(ghost)\n this._ghostEl = ghost\n\n // Use the ghost as the drag image\n const rect = block.getBoundingClientRect()\n const offsetX = e.clientX - rect.left\n const offsetY = e.clientY - rect.top\n e.dataTransfer.setDragImage(ghost, offsetX, offsetY)\n\n // The browser captures the ghost image synchronously during dragstart,\n // so we keep it in the DOM until drag end (_removeGhostPreview in _cleanupDrag).\n // No further action needed here per frame.\n }\n\n _removeGhostPreview() {\n if (this._ghostEl && this._ghostEl.parentElement) {\n this._ghostEl.parentElement.removeChild(this._ghostEl)\n }\n this._ghostEl = null\n }\n\n // ── Helpers ─────────────────────────────────────────────────────────\n\n /**\n * Find the closest draggable block from a given node.\n * @param {Node} node\n * @returns {HTMLElement|null}\n */\n _closestDraggableBlock(node) {\n let el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n const editorEl = this.engine.element\n while (el && el !== editorEl) {\n if (el.parentElement === editorEl && DRAGGABLE_BLOCKS.has(el.tagName)) {\n return el\n }\n el = el.parentElement\n }\n return null\n }\n\n /**\n * For reordering, get the reorderable item (LI or TR) from a node.\n * @param {Node} node\n * @returns {HTMLElement|null}\n */\n _getReorderableItem(node) {\n let el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node\n const editorEl = this.engine.element\n while (el && el !== editorEl) {\n if (el.tagName === 'LI' || el.tagName === 'TR') {\n // Verify parent is a reorderable container\n if (el.parentElement && REORDERABLE_CHILDREN.has(el.parentElement.tagName)) {\n return el\n }\n }\n el = el.parentElement\n }\n return null\n }\n\n /**\n * Clean up all drag state.\n */\n _cleanupDrag() {\n if (this._dragSource) {\n this._dragSource.classList.remove('rmx-block-dragging')\n }\n this._dragSource = null\n this._dragSourceEditor = null\n this._dropTarget = null\n this._dropPosition = null\n this._isExternalDrag = false\n this._enterCount = 0\n // Task 253: Clean up block rect cache\n this._blockRects = null\n if (this._scrollHandler) {\n this.engine.element.removeEventListener('scroll', this._scrollHandler)\n this._scrollHandler = null\n }\n this._removeDropIndicator()\n this._removeGhostPreview()\n this.engine.element.classList.remove('rmx-drag-over')\n }\n\n /**\n * Check if a file exceeds the configured maximum size.\n * @param {File} file\n * @returns {boolean} true if the file is too large\n */\n _exceedsMaxFileSize(file) {\n return exceedsMaxFileSize(file, this.engine.options.maxFileSize, { eventBus: this.engine.eventBus })\n }\n\n _handleImageDrop(e, imageFiles) {\n this._setCursorAtDropPoint(e)\n this.engine.history.snapshot()\n\n const validFiles = imageFiles.filter((f) => !this._exceedsMaxFileSize(f))\n // Serialize uploads to prevent race conditions with concurrent insertImage calls\n let chain = Promise.resolve()\n for (const file of validFiles) {\n chain = chain.then(() => {\n if (this.engine.options.uploadHandler) {\n return this.engine.options.uploadHandler(file).then((url) => {\n this.engine.commands.execute('insertImage', { src: url, alt: file.name })\n }).catch((err) => {\n console.error(`Image upload failed for \"${file.name}\":`, err)\n this.engine.eventBus.emit('upload:error', { file, error: err })\n })\n } else {\n return new Promise((resolve) => {\n const reader = new FileReader()\n reader.onprogress = (ev) => {\n if (ev.lengthComputable) {\n this.engine.eventBus.emit('upload:progress', {\n loaded: ev.loaded,\n total: ev.total,\n percent: Math.round((ev.loaded / ev.total) * 100),\n })\n }\n }\n reader.onload = (ev) => {\n this.engine.commands.execute('insertImage', { src: ev.target.result, alt: file.name })\n resolve()\n }\n reader.onerror = () => resolve()\n reader.readAsDataURL(file)\n })\n }\n })\n }\n }\n\n _handleDocumentDrop(e, files) {\n this._setCursorAtDropPoint(e)\n this.engine.history.snapshot()\n\n files.filter((f) => !this._exceedsMaxFileSize(f)).forEach((file) => {\n convertDocument(file)\n .then((html) => {\n const sanitized = this.engine.sanitizer.sanitize(html)\n this.engine.selection.insertHTML(sanitized)\n this.engine.eventBus.emit('content:change')\n })\n .catch((err) => {\n console.warn('Document import failed on drop:', err.message)\n })\n })\n }\n\n _handleFileDrop(e, files) {\n this._setCursorAtDropPoint(e)\n this.engine.history.snapshot()\n\n // Serialize uploads to prevent race conditions with concurrent insertAttachment calls\n let chain = Promise.resolve()\n for (const file of files) {\n chain = chain.then(() =>\n this.engine.options.uploadHandler(file).then((url) => {\n this.engine.commands.execute('insertAttachment', {\n url,\n filename: file.name,\n filesize: file.size,\n })\n }).catch((err) => {\n console.error(`File upload failed for \"${file.name}\":`, err)\n this.engine.eventBus.emit('upload:error', { file, error: err })\n })\n )\n }\n }\n\n _setCursorAtDropPoint(e) {\n const range = document.caretRangeFromPoint\n ? document.caretRangeFromPoint(e.clientX, e.clientY)\n : null\n\n if (range) {\n const sel = window.getSelection()\n sel.removeAllRanges()\n sel.addRange(range)\n }\n }\n\n /**\n * Handle dropped plain text with smart format detection.\n * Task 256: Uses shared insertPlainText utility.\n */\n _handleTextDrop(text) {\n insertPlainText(this.engine, text)\n }\n}\n\n/**\n * Expose the editor registry for testing purposes.\n * @returns {Map<HTMLElement, DragDrop>}\n */\nDragDrop._getRegistry = () => _editorRegistry\nDragDrop._getActiveInstances = () => _activeInstances\n","/**\n * @typedef {Object} PluginAPI\n * @property {HTMLElement} element - The editor DOM element (read-only)\n * @property {Function} executeCommand - Execute a registered command by name\n * @property {Function} on - Subscribe to an editor event\n * @property {Function} off - Unsubscribe from an editor event\n * @property {Function} getSelection - Get the current window Selection\n * @property {Function} getRange - Get the current selection Range\n * @property {Function} getActiveFormats - Get current active formatting states\n * @property {Function} getHTML - Get sanitized HTML content\n * @property {Function} getText - Get plain text content\n * @property {Function} isEmpty - Check if editor is empty\n * @property {Object} options - Editor options (read-only copy)\n * @property {Function} getSetting - Get a plugin-scoped setting value\n * @property {Function} setSetting - Set a plugin-scoped setting value\n */\n\n/**\n * @typedef {Object} PluginDefinition\n * @property {string} name - Unique plugin name\n * @property {string} [version='0.0.0'] - Plugin version\n * @property {string} [description=''] - Human-readable description\n * @property {string} [author=''] - Author\n * @property {string[]} [dependencies=[]] - Plugin names this plugin depends on\n * @property {boolean} [requiresFullAccess=false] - If true, receives full engine instead of restricted API\n * @property {boolean} [lazy=false] - If true, plugin is not initialized in initAll() but on first use\n * @property {Function} [init] - Initialization function, receives the plugin API or full engine\n * @property {Function} [destroy] - Cleanup function, receives the plugin API or full engine\n * @property {Function|null} [onContentChange] - Called on content:change events\n * @property {Function|null} [onSelectionChange] - Called on selectionchange events\n * @property {Array<import('../core/CommandRegistry.js').CommandDefinition>} [commands] - Commands to register\n * @property {Array} [toolbarItems] - Toolbar item definitions\n * @property {Array} [statusBarItems] - Status bar item definitions\n * @property {Array} [contextMenuItems] - Context menu item definitions\n * @property {Array} [settingsSchema] - Schema for plugin-specific settings\n * @property {Object} [defaultSettings] - Default setting values\n */\n\n/**\n * Creates a restricted API facade for plugins.\n * Plugins receive this facade instead of the full engine reference,\n * limiting what they can access to prevent accidental or malicious\n * bypass of sanitization, history corruption, or content exfiltration.\n *\n * @param {import('../core/EditorEngine.js').EditorEngine} engine - The full editor engine\n * @param {string} pluginName - The plugin name (for scoped settings)\n * @param {PluginManager} manager - The plugin manager (for settings access)\n * @returns {PluginAPI} A restricted API surface\n */\nfunction createPluginAPI(engine, pluginName, manager) {\n return {\n /** The editor DOM element (read-only access) */\n get element() { return engine.element },\n\n /**\n * Execute a registered command by name.\n * @param {string} name - The command name\n * @param {...*} args - Additional arguments for the command\n * @returns {*} The command result\n */\n executeCommand(name, ...args) { return engine.commands.execute(name, ...args) },\n\n /**\n * Subscribe to an editor event.\n * @param {string} event - The event name\n * @param {Function} handler - The event handler\n * @returns {Function} An unsubscribe function\n */\n on(event, handler) { return engine.eventBus.on(event, handler) },\n\n /**\n * Unsubscribe from an editor event.\n * @param {string} event - The event name\n * @param {Function} handler - The handler to remove\n * @returns {void}\n */\n off(event, handler) { engine.eventBus.off(event, handler) },\n\n /**\n * Get the current window Selection object.\n * @returns {globalThis.Selection} The browser Selection\n */\n getSelection() { return engine.selection.getSelection() },\n\n /**\n * Get the current selection Range within the editor.\n * @returns {Range|null} The current range, or null\n */\n getRange() { return engine.selection.getRange() },\n\n /**\n * Get current active formatting states at the selection.\n * @returns {import('../core/Selection.js').ActiveFormats} Active format states\n */\n getActiveFormats() { return engine.selection.getActiveFormats() },\n\n /**\n * Get sanitized HTML content of the editor.\n * @returns {string} The sanitized HTML\n */\n getHTML() { return engine.getHTML() },\n\n /**\n * Get plain text content of the editor.\n * @returns {string} The text content\n */\n getText() { return engine.getText() },\n\n /**\n * Check if the editor content is empty.\n * @returns {boolean} True if empty\n */\n isEmpty() { return engine.isEmpty() },\n\n /** Editor options (read-only copy) */\n get options() { return { ...engine.options } },\n\n /**\n * Get a plugin-scoped setting value.\n * @param {string} key - Setting key\n * @returns {*} The setting value\n */\n getSetting(key) { return manager.getPluginSetting(pluginName, key) },\n\n /**\n * Set a plugin-scoped setting value.\n * @param {string} key - Setting key\n * @param {*} value - The value to set\n */\n setSetting(key, value) { manager.setPluginSetting(pluginName, key, value) },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Plugin Registry — discover and list available plugins\n// ---------------------------------------------------------------------------\n\n/**\n * @typedef {Object} PluginRegistryEntry\n * @property {string} name - Plugin identifier\n * @property {string} version - Version string\n * @property {string} description - What the plugin does\n * @property {string} author - Author name\n * @property {string[]} [tags] - Search tags\n * @property {Function} [factory] - Factory function that returns the plugin definition\n */\n\n/** @type {Map<string, PluginRegistryEntry>} */\nconst _globalRegistry = new Map()\n\n/**\n * Register a plugin in the global registry for discovery.\n * This does NOT install the plugin — it makes it discoverable via `listRegisteredPlugins`.\n * @param {PluginRegistryEntry} entry\n */\nexport function registerPluginInRegistry(entry) {\n if (!entry || !entry.name) return\n _globalRegistry.set(entry.name, entry)\n}\n\n/**\n * Remove a plugin from the global registry.\n * @param {string} name\n * @returns {boolean}\n */\nexport function unregisterPluginFromRegistry(name) {\n return _globalRegistry.delete(name)\n}\n\n/**\n * List all plugins in the global registry.\n * @returns {PluginRegistryEntry[]}\n */\nexport function listRegisteredPlugins() {\n return Array.from(_globalRegistry.values())\n}\n\n/**\n * Search the global registry by name, description, or tags.\n * @param {string} query\n * @returns {PluginRegistryEntry[]}\n */\nexport function searchPluginRegistry(query) {\n if (!query) return listRegisteredPlugins()\n const q = query.toLowerCase()\n return listRegisteredPlugins().filter(e =>\n e.name.toLowerCase().includes(q) ||\n (e.description || '').toLowerCase().includes(q) ||\n (e.tags || []).some(t => t.toLowerCase().includes(q))\n )\n}\n\n// ---------------------------------------------------------------------------\n// PluginManager\n// ---------------------------------------------------------------------------\n\n/**\n * Manages editor plugins.\n *\n * **Security notice:** Plugins receive a restricted API facade by default,\n * which limits access to safe operations (executing commands, subscribing\n * to events, reading content). The full engine reference is NOT exposed.\n *\n * If a plugin requires full engine access (e.g., built-in plugins), it can\n * declare `requiresFullAccess: true` in its definition — but third-party\n * plugins should be audited before granting this level of access.\n *\n * **Lazy loading:** Plugins can declare `lazy: true` to defer initialization\n * until first use. Lazy plugins are registered and their commands are\n * available, but init() is not called until the plugin is explicitly\n * activated or one of its commands is executed.\n *\n * **Dependencies:** Plugins can declare `dependencies: ['otherPlugin']` to\n * ensure they are initialized after their dependencies. Circular dependencies\n * are detected and reported.\n *\n * **Lifecycle hooks:** Beyond `init`/`destroy`, plugins can declare\n * `onContentChange` and `onSelectionChange` callbacks. These are wired\n * automatically during initialization.\n *\n * **Scoped settings:** Plugins can declare a `settingsSchema` and\n * `defaultSettings`. Settings are stored per-plugin and accessible via\n * `getSetting`/`setSetting` on the plugin API, or `getPluginSetting`/\n * `setPluginSetting` on the manager.\n *\n * **Sandboxing:** Each plugin's `init`, `destroy`, `onContentChange`, and\n * `onSelectionChange` calls are wrapped in try/catch. A single failing\n * plugin cannot crash the editor or other plugins.\n */\nexport class PluginManager {\n /**\n * Creates a new PluginManager.\n * @param {import('../core/EditorEngine.js').EditorEngine} engine - The editor engine instance\n */\n constructor(engine) {\n this.engine = engine\n this._plugins = new Map()\n /** @private Set of plugin names that have been initialized */\n this._initialized = new Set()\n /** @private Map of command name -> plugin name for lazy activation */\n this._commandPluginMap = new Map()\n /** @private Per-plugin settings store: Map<pluginName, Map<key, value>> */\n this._settings = new Map()\n /** @private Unsub functions for lifecycle hooks */\n this._lifecycleUnsubs = new Map()\n }\n\n // -----------------------------------------------------------------------\n // Plugin Settings\n // -----------------------------------------------------------------------\n\n /**\n * Get a plugin-scoped setting value.\n * Falls back to the plugin's defaultSettings if not explicitly set.\n * @param {string} pluginName\n * @param {string} key\n * @returns {*}\n */\n getPluginSetting(pluginName, key) {\n const settings = this._settings.get(pluginName)\n if (settings && settings.has(key)) return settings.get(key)\n const plugin = this._plugins.get(pluginName)\n if (plugin?.defaultSettings && key in plugin.defaultSettings) {\n return plugin.defaultSettings[key]\n }\n return undefined\n }\n\n /**\n * Set a plugin-scoped setting value.\n * Validates against the settingsSchema if present.\n * @param {string} pluginName\n * @param {string} key\n * @param {*} value\n * @returns {boolean} true if the setting was accepted\n */\n setPluginSetting(pluginName, key, value) {\n const plugin = this._plugins.get(pluginName)\n if (!plugin) return false\n\n // Validate against schema if available\n if (plugin.settingsSchema && plugin.settingsSchema.length > 0) {\n const schema = plugin.settingsSchema.find(s => s.key === key)\n if (schema) {\n // Type check\n if (schema.type === 'boolean' && typeof value !== 'boolean') return false\n if (schema.type === 'number' && typeof value !== 'number') return false\n if (schema.type === 'string' && typeof value !== 'string') return false\n if (schema.type === 'select') {\n const validValues = (schema.options || []).map(o => o.value)\n if (!validValues.includes(value)) return false\n }\n // Custom validation\n if (schema.validate && !schema.validate(value)) return false\n }\n }\n\n if (!this._settings.has(pluginName)) {\n this._settings.set(pluginName, new Map())\n }\n this._settings.get(pluginName).set(key, value)\n this.engine.eventBus.emit('plugin:settingChanged', { pluginName, key, value })\n return true\n }\n\n /**\n * Get all settings for a plugin as a plain object.\n * @param {string} pluginName\n * @returns {Object}\n */\n getPluginSettings(pluginName) {\n const plugin = this._plugins.get(pluginName)\n const result = { ...(plugin?.defaultSettings || {}) }\n const settings = this._settings.get(pluginName)\n if (settings) {\n for (const [k, v] of settings) result[k] = v\n }\n return result\n }\n\n // -----------------------------------------------------------------------\n // Dependency Resolution\n // -----------------------------------------------------------------------\n\n /**\n * Resolve plugin initialization order based on dependencies.\n * Uses topological sort. Detects circular dependencies.\n * @param {PluginDefinition[]} plugins - Plugins to sort\n * @returns {{ sorted: PluginDefinition[], circular: string[][] }} sorted list and any circular deps\n */\n _resolveDependencyOrder(plugins) {\n const nameSet = new Set(plugins.map(p => p.name))\n const graph = new Map() // name -> [dependency names]\n const inDegree = new Map()\n\n for (const p of plugins) {\n // Only include dependencies that are actually registered\n const deps = (p.dependencies || []).filter(d => nameSet.has(d))\n graph.set(p.name, deps)\n inDegree.set(p.name, deps.length)\n }\n\n // Kahn's algorithm for topological sort\n const queue = []\n for (const [name, degree] of inDegree) {\n if (degree === 0) queue.push(name)\n }\n\n const sorted = []\n while (queue.length > 0) {\n const name = queue.shift()\n sorted.push(name)\n // For each plugin that depends on `name`, decrement its in-degree\n for (const [other, deps] of graph) {\n if (deps.includes(name)) {\n inDegree.set(other, inDegree.get(other) - 1)\n if (inDegree.get(other) === 0) queue.push(other)\n }\n }\n }\n\n // Detect circular dependencies\n const circular = []\n if (sorted.length < plugins.length) {\n const remaining = plugins.filter(p => !sorted.includes(p.name)).map(p => p.name)\n circular.push(remaining)\n }\n\n const pluginMap = new Map(plugins.map(p => [p.name, p]))\n return {\n sorted: sorted.map(name => pluginMap.get(name)),\n circular,\n }\n }\n\n // -----------------------------------------------------------------------\n // Registration\n // -----------------------------------------------------------------------\n\n /**\n * Registers a plugin and its commands. Does nothing if the plugin has no\n * name or is already registered.\n *\n * For lazy plugins, commands are wrapped to trigger plugin initialization\n * on first execution.\n *\n * @param {PluginDefinition} plugin - The plugin definition to register\n * @returns {void}\n */\n register(plugin) {\n if (!plugin || !plugin.name) {\n console.warn('Plugin must have a name')\n return\n }\n if (this._plugins.has(plugin.name)) {\n console.warn(`Plugin \"${plugin.name}\" already registered`)\n return\n }\n this._plugins.set(plugin.name, plugin)\n\n // Initialize default settings\n if (plugin.defaultSettings) {\n if (!this._settings.has(plugin.name)) {\n this._settings.set(plugin.name, new Map())\n }\n }\n\n // Register any commands the plugin provides\n if (plugin.commands) {\n plugin.commands.forEach((cmd) => {\n if (plugin.lazy) {\n // Track which plugin owns this command for lazy activation\n this._commandPluginMap.set(cmd.name, plugin.name)\n\n // Wrap the command execute to auto-init the plugin on first use\n const originalExecute = cmd.execute\n const self = this\n const wrappedCmd = {\n ...cmd,\n execute(...args) {\n self.activatePlugin(plugin.name)\n // After activation, re-register with original execute\n // so subsequent calls skip the wrapper\n self.engine.commands.register(cmd.name, { ...cmd, execute: originalExecute })\n return originalExecute.apply(this, args)\n }\n }\n this.engine.commands.register(cmd.name, wrappedCmd)\n } else {\n this.engine.commands.register(cmd.name, cmd)\n }\n })\n }\n\n this.engine.eventBus.emit('plugin:registered', { name: plugin.name })\n }\n\n // -----------------------------------------------------------------------\n // Initialization\n // -----------------------------------------------------------------------\n\n /**\n * Initializes a single plugin by calling its init function and\n * wiring lifecycle hooks (onContentChange, onSelectionChange).\n * Trusted plugins (requiresFullAccess) receive the full engine;\n * others receive the restricted API facade.\n *\n * @private\n * @param {PluginDefinition} plugin - The plugin to initialize\n * @returns {void}\n */\n _initPlugin(plugin) {\n if (this._initialized.has(plugin.name)) return\n\n try {\n const api = plugin.requiresFullAccess\n ? this.engine\n : createPluginAPI(this.engine, plugin.name, this)\n\n if (plugin.init) {\n plugin.init(api)\n }\n\n // Wire lifecycle hooks\n const unsubs = []\n\n if (typeof plugin.onContentChange === 'function') {\n const handler = () => {\n try { plugin.onContentChange(api) }\n catch (err) {\n console.error(`Plugin \"${plugin.name}\" onContentChange error:`, err)\n this.engine.eventBus.emit('plugin:error', { name: plugin.name, error: err, hook: 'onContentChange' })\n }\n }\n unsubs.push(this.engine.eventBus.on('content:change', handler))\n }\n\n if (typeof plugin.onSelectionChange === 'function') {\n const handler = () => {\n try { plugin.onSelectionChange(api) }\n catch (err) {\n console.error(`Plugin \"${plugin.name}\" onSelectionChange error:`, err)\n this.engine.eventBus.emit('plugin:error', { name: plugin.name, error: err, hook: 'onSelectionChange' })\n }\n }\n unsubs.push(this.engine.eventBus.on('selection:change', handler))\n }\n\n if (unsubs.length > 0) {\n this._lifecycleUnsubs.set(plugin.name, unsubs)\n }\n\n this._initialized.add(plugin.name)\n this.engine.eventBus.emit('plugin:initialized', { name: plugin.name })\n } catch (err) {\n console.error(`Error initializing plugin \"${plugin.name}\":`, err)\n this.engine.eventBus.emit('plugin:error', { name: plugin.name, error: err })\n }\n }\n\n /**\n * Activates a lazy plugin by initializing it. Has no effect if the plugin\n * is already initialized or does not exist.\n *\n * @param {string} name - The plugin name to activate\n * @returns {void}\n */\n activatePlugin(name) {\n const plugin = this._plugins.get(name)\n if (!plugin) {\n console.warn(`Plugin \"${name}\" not found`)\n return\n }\n if (this._initialized.has(name)) return\n\n // Initialize dependencies first\n for (const dep of (plugin.dependencies || [])) {\n if (this._plugins.has(dep) && !this._initialized.has(dep)) {\n this.activatePlugin(dep)\n }\n }\n\n this._initPlugin(plugin)\n }\n\n /**\n * Initializes all registered plugins by calling their init functions.\n * Plugins are initialized in dependency order. Lazy plugins are skipped\n * and will be initialized on first use. Circular dependencies are\n * detected, reported, and the involved plugins are still initialized\n * (in registration order) with a warning.\n *\n * @returns {void}\n */\n initAll() {\n const nonLazy = []\n this._plugins.forEach((plugin) => {\n if (!plugin.lazy) nonLazy.push(plugin)\n })\n\n const { sorted, circular } = this._resolveDependencyOrder(nonLazy)\n\n // Report circular dependencies\n if (circular.length > 0) {\n for (const cycle of circular) {\n console.warn(`Circular plugin dependencies detected: ${cycle.join(' → ')}`)\n this.engine.eventBus.emit('plugin:circularDependency', { plugins: cycle })\n }\n }\n\n // Initialize in dependency-resolved order\n for (const plugin of sorted) {\n this._initPlugin(plugin)\n }\n\n // Initialize any plugins not in sorted (circular deps) in registration order\n for (const plugin of nonLazy) {\n if (!this._initialized.has(plugin.name)) {\n this._initPlugin(plugin)\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Destruction\n // -----------------------------------------------------------------------\n\n /**\n * Destroys all registered plugins by calling their destroy functions,\n * cleaning up lifecycle hook subscriptions, and clearing all state.\n * Errors are caught, logged, and emitted as plugin:error events.\n * @returns {void}\n */\n destroyAll() {\n this._plugins.forEach((plugin) => {\n try {\n // For lazy plugins, only call destroy if they were initialized.\n // For non-lazy plugins, always call destroy (original behavior).\n const shouldDestroy = plugin.lazy\n ? this._initialized.has(plugin.name)\n : true\n if (shouldDestroy && plugin.destroy) {\n const api = plugin.requiresFullAccess\n ? this.engine\n : createPluginAPI(this.engine, plugin.name, this)\n plugin.destroy(api)\n }\n\n // Clean up lifecycle hook subscriptions\n const unsubs = this._lifecycleUnsubs.get(plugin.name)\n if (unsubs) {\n for (const unsub of unsubs) unsub?.()\n }\n } catch (err) {\n console.error(`Error destroying plugin \"${plugin.name}\":`, err)\n this.engine.eventBus.emit('plugin:error', { name: plugin.name, error: err })\n }\n })\n this._plugins.clear()\n this._initialized.clear()\n this._commandPluginMap.clear()\n this._settings.clear()\n this._lifecycleUnsubs.clear()\n }\n\n // -----------------------------------------------------------------------\n // Query methods\n // -----------------------------------------------------------------------\n\n /**\n * Returns a registered plugin by name.\n * @param {string} name - The plugin name\n * @returns {PluginDefinition|undefined} The plugin definition, or undefined if not found\n */\n get(name) {\n return this._plugins.get(name)\n }\n\n /**\n * Returns all registered plugins as an array.\n * @returns {PluginDefinition[]} Array of all registered plugin definitions\n */\n getAll() {\n return Array.from(this._plugins.values())\n }\n\n /**\n * Checks whether a plugin with the given name is registered.\n * @param {string} name - The plugin name\n * @returns {boolean} True if the plugin is registered\n */\n has(name) {\n return this._plugins.has(name)\n }\n\n /**\n * Checks whether a plugin has been initialized.\n * @param {string} name - The plugin name\n * @returns {boolean} True if the plugin has been initialized\n */\n isInitialized(name) {\n return this._initialized.has(name)\n }\n}\n","import { EventBus } from './EventBus.js'\nimport { Selection } from './Selection.js'\nimport { CommandRegistry } from './CommandRegistry.js'\nimport { History } from './History.js'\nimport { KeyboardManager } from './KeyboardManager.js'\nimport { Sanitizer } from './Sanitizer.js'\nimport { Clipboard } from './Clipboard.js'\nimport { DragDrop } from './DragDrop.js'\nimport { PluginManager } from '../plugins/PluginManager.js'\n\n/**\n * @typedef {Object} EditorOptions\n * @property {string} [outputFormat='html'] - Output format ('html' or 'markdown')\n * @property {Object} [history] - History configuration options\n * @property {number} [history.maxSize=100] - Maximum number of undo states\n * @property {number} [history.debounceMs=300] - Debounce interval for snapshots in ms\n * @property {Object} [sanitize] - Sanitizer configuration options\n * @property {Object} [sanitize.allowedTags] - Map of allowed HTML tags to their allowed attributes\n * @property {string[]} [sanitize.allowedStyles] - List of allowed CSS style properties\n */\n\n/**\n * Shared static handler for selectionchange events (Task 236).\n * A single document-level listener dispatches to the correct engine instance.\n * @type {WeakMap<HTMLElement, EditorEngine>}\n */\nconst _selectionHandlers = new WeakMap()\nlet _selectionListenerAttached = false\n\nfunction _sharedSelectionChangeHandler() {\n // Try activeElement first (most common case)\n const activeElement = document.activeElement\n if (activeElement) {\n let el = activeElement\n while (el) {\n const engine = _selectionHandlers.get(el)\n if (engine) {\n engine._handleSelectionChange()\n return\n }\n el = el.parentElement\n }\n }\n\n // Fallback: check selection's anchor node to find the editor\n const sel = window.getSelection()\n if (sel && sel.anchorNode) {\n let node = sel.anchorNode\n while (node) {\n const engine = _selectionHandlers.get(node)\n if (engine) {\n engine._handleSelectionChange()\n return\n }\n node = node.parentNode\n }\n }\n}\n\nfunction _ensureSelectionListener() {\n if (!_selectionListenerAttached) {\n document.addEventListener('selectionchange', _sharedSelectionChangeHandler)\n _selectionListenerAttached = true\n }\n}\n\nexport class EditorEngine {\n /**\n * Creates a new EditorEngine instance.\n * @param {HTMLElement} element - The DOM element to use as the editor\n * @param {EditorOptions} [options={}] - Configuration options\n */\n constructor(element, options = {}) {\n this.element = element\n this.options = options\n this.outputFormat = options.outputFormat || 'html'\n this.isSourceMode = false\n this.isMarkdownMode = false\n this._isDestroyed = false\n\n // Task 272: HTML dirty flag for caching getHTML() results\n this._htmlDirty = true\n this._lastHTML = ''\n\n // Task 273: Text cache for shared getText() calls in getWordCount/getCharCount\n this._textCache = null\n this._textCacheDirty = true\n\n this.eventBus = new EventBus()\n this.selection = new Selection(element)\n this.keyboard = new KeyboardManager(this)\n this.commands = new CommandRegistry(this)\n this.history = new History(this, options.history)\n this.sanitizer = new Sanitizer(options.sanitize)\n this.selection.setSanitizer(this.sanitizer)\n this.clipboard = new Clipboard(this)\n this.dragDrop = new DragDrop(this)\n this.plugins = new PluginManager(this)\n\n this._handleInput = this._handleInput.bind(this)\n this._handleSelectionChange = this._handleSelectionChange.bind(this)\n this._handleFocus = this._handleFocus.bind(this)\n this._handleBlur = this._handleBlur.bind(this)\n this._handleClick = this._handleClick.bind(this)\n }\n\n /**\n * Initializes the editor by setting up contenteditable attributes,\n * event listeners, and all subsystems (keyboard, history, clipboard, drag/drop, plugins).\n * @throws {Error} If initialization fails\n * @returns {void}\n */\n init() {\n try {\n this.element.setAttribute('contenteditable', 'true')\n this.element.setAttribute('role', 'textbox')\n this.element.setAttribute('aria-multiline', 'true')\n this.element.setAttribute('spellcheck', 'true')\n\n // Ensure there's at least one paragraph\n if (!this.element.innerHTML.trim()) {\n this.element.innerHTML = '<p><br></p>'\n }\n\n this.element.addEventListener('input', this._handleInput)\n this.element.addEventListener('focus', this._handleFocus)\n this.element.addEventListener('blur', this._handleBlur)\n this.element.addEventListener('click', this._handleClick)\n\n // Task 236: Use shared static selectionchange handler\n _selectionHandlers.set(this.element, this)\n _ensureSelectionListener()\n\n this.keyboard.init()\n this.history.init()\n this.clipboard.init()\n this.dragDrop.init()\n this.plugins.initAll()\n } catch (err) {\n console.error('EditorEngine init failed:', err)\n this.eventBus.emit('editor:error', { phase: 'init', error: err })\n throw err\n }\n }\n\n /**\n * Destroys the editor by removing all event listeners, cleaning up subsystems,\n * and removing the contenteditable attribute. Safe to call multiple times.\n * @returns {void}\n */\n destroy() {\n if (this._isDestroyed) return\n this._isDestroyed = true\n\n this.element.removeEventListener('input', this._handleInput)\n this.element.removeEventListener('focus', this._handleFocus)\n this.element.removeEventListener('blur', this._handleBlur)\n this.element.removeEventListener('click', this._handleClick)\n\n // Task 236: Remove from shared selection handler registry\n _selectionHandlers.delete(this.element)\n\n this.eventBus.emit('destroy')\n this.keyboard.destroy()\n this.history.destroy()\n this.clipboard.destroy()\n this.dragDrop.destroy()\n this.plugins.destroyAll()\n this.eventBus.removeAllListeners()\n\n this.element.removeAttribute('contenteditable')\n }\n\n /**\n * Returns the sanitized HTML content of the editor.\n * Task 272: Uses dirty flag to cache results.\n * @returns {string} The sanitized HTML content\n */\n getHTML() {\n if (!this._htmlDirty) return this._lastHTML\n this._lastHTML = this.sanitizer.sanitize(this.element.innerHTML)\n this._htmlDirty = false\n return this._lastHTML\n }\n\n /**\n * Sets the editor content to the given HTML after sanitizing it.\n * If the sanitized result is empty, inserts an empty paragraph.\n * @param {string} html - The HTML content to set\n * @returns {void}\n */\n setHTML(html) {\n const sanitized = this.sanitizer.sanitize(html)\n this.element.innerHTML = sanitized || '<p><br></p>'\n this._htmlDirty = true\n this._textCacheDirty = true\n }\n\n /**\n * Returns the plain text content of the editor.\n * @returns {string} The text content\n */\n getText() {\n if (!this._textCacheDirty && this._textCache !== null) return this._textCache\n this._textCache = this.element.textContent || ''\n this._textCacheDirty = false\n return this._textCache\n }\n\n /**\n * Checks whether the editor content is empty.\n * @returns {boolean} True if the editor contains no meaningful text\n */\n isEmpty() {\n const text = this.getText().trim()\n return text === '' || text === '\\n'\n }\n\n /**\n * Focuses the editor element.\n * @returns {void}\n */\n focus() {\n this.element.focus()\n }\n\n /**\n * Blurs (unfocuses) the editor element.\n * @returns {void}\n */\n blur() {\n this.element.blur()\n }\n\n /**\n * Executes a registered command by name.\n * @param {string} name - The name of the command to execute\n * @param {...*} args - Additional arguments to pass to the command\n * @returns {*} The result of the command execution, or false if the command was not found or disabled\n */\n executeCommand(name, ...args) {\n return this.commands.execute(name, ...args)\n }\n\n /**\n * Subscribes to an editor event.\n * @param {string} event - The event name to listen for\n * @param {Function} handler - The callback function to invoke when the event fires\n * @returns {Function} An unsubscribe function that removes the listener when called\n */\n on(event, handler) {\n return this.eventBus.on(event, handler)\n }\n\n /**\n * Unsubscribes from an editor event.\n * @param {string} event - The event name to stop listening for\n * @param {Function} handler - The handler to remove\n * @returns {void}\n */\n off(event, handler) {\n this.eventBus.off(event, handler)\n }\n\n /**\n * Returns the word count of the editor content.\n * Task 273: Shares getText() call with getCharCount() via cached _textCache.\n * @returns {number} The number of words in the editor\n */\n getWordCount() {\n const text = this.getText().trim()\n if (!text) return 0\n return text.split(/\\s+/).length\n }\n\n /**\n * Returns the character count of the editor content.\n * Task 273: Shares getText() call with getWordCount() via cached _textCache.\n * @returns {number} The number of characters in the editor\n */\n getCharCount() {\n return this.getText().length\n }\n\n /**\n * Handles input events on the editor element. Ensures at least one\n * block element exists and emits a content:change event.\n * @private\n * @returns {void}\n */\n _handleInput() {\n // Task 272: Mark HTML as dirty on input\n this._htmlDirty = true\n // Task 273: Invalidate text cache on input\n this._textCacheDirty = true\n\n // Ensure we always have at least one block element\n if (this.element.innerHTML === '' || this.element.innerHTML === '<br>') {\n this.element.innerHTML = '<p><br></p>'\n const range = document.createRange()\n range.setStart(this.element.firstChild, 0)\n range.collapse(true)\n this.selection.setRange(range)\n }\n this.eventBus.emit('content:change')\n }\n\n /**\n * Handles document selectionchange events. Checks if the selection is\n * within the editor and emits selection:change with active formats.\n * @private\n * @returns {void}\n */\n _handleSelectionChange() {\n if (!this.selection.isWithinEditor(document.activeElement)) return\n const range = this.selection.getRange()\n if (!range) return\n const formats = this.selection.getActiveFormats()\n this.eventBus.emit('selection:change', formats)\n }\n\n /**\n * Handles focus events on the editor element.\n * @private\n * @returns {void}\n */\n _handleFocus() {\n this.eventBus.emit('focus')\n }\n\n /**\n * Handles blur events on the editor element.\n * @private\n * @returns {void}\n */\n _handleBlur() {\n this.eventBus.emit('blur')\n }\n\n /**\n * Handles click events on the editor element.\n * Toggles task list checkboxes on click.\n * @private\n * @param {MouseEvent} e - The click event\n * @returns {void}\n */\n _handleClick(e) {\n // Handle task list checkbox clicks\n if (e.target.type === 'checkbox' && e.target.classList.contains('rmx-task-checkbox')) {\n e.target.checked = !e.target.checked\n this._htmlDirty = true\n this._textCacheDirty = true\n this.eventBus.emit('content:change')\n }\n }\n}\n","import { EventBus } from './EventBus.js'\n\n/**\n * A global inter-editor communication bus.\n *\n * EditorBus is a process-wide singleton that lets multiple EditorEngine\n * instances on the same page communicate without direct references to\n * each other.\n *\n * Typical use-cases:\n * - Source + preview linked editors (content sync)\n * - Master/detail layouts where selecting in one editor loads content in another\n * - Broadcasting \"save\" or \"theme change\" to all editors at once\n *\n * @example\n * import { EditorBus } from '@remyxjs/core';\n *\n * // In editor A — broadcast content on every change\n * engineA.on('content:change', () => {\n * EditorBus.emit('sync:content', { id: 'editor-a', html: engineA.getHTML() });\n * });\n *\n * // In editor B — listen for updates from A\n * EditorBus.on('sync:content', ({ id, html }) => {\n * if (id !== 'editor-b') engineB.setHTML(html);\n * });\n */\nclass EditorBusImpl {\n constructor() {\n /** @private */\n this._bus = new EventBus()\n /** @private @type {Map<string, import('./EditorEngine.js').EditorEngine>} */\n this._registry = new Map()\n }\n\n // ── Registry ──────────────────────────────────────────────────────\n\n /**\n * Register an editor instance so other editors can look it up by ID.\n * @param {string} id - A unique identifier for the editor\n * @param {import('./EditorEngine.js').EditorEngine} engine - The editor engine instance\n * @returns {void}\n */\n register(id, engine) {\n this._registry.set(id, engine)\n this._bus.emit('editor:registered', { id, engine })\n }\n\n /**\n * Unregister an editor (typically called during destroy).\n * @param {string} id - The identifier of the editor to remove\n * @returns {void}\n */\n unregister(id) {\n this._registry.delete(id)\n this._bus.emit('editor:unregistered', { id })\n }\n\n /**\n * Get a registered editor by ID.\n * @param {string} id - The editor identifier\n * @returns {import('./EditorEngine.js').EditorEngine | undefined}\n */\n getEditor(id) {\n return this._registry.get(id)\n }\n\n /**\n * Get all registered editor IDs.\n * @returns {string[]}\n */\n getEditorIds() {\n return [...this._registry.keys()]\n }\n\n /**\n * Get the count of currently registered editors.\n * @returns {number}\n */\n get editorCount() {\n return this._registry.size\n }\n\n // ── Pub/Sub (delegates to EventBus) ───────────────────────────────\n\n /**\n * Subscribe to an inter-editor event.\n * @param {string} event - Event name (e.g. 'sync:content', 'theme:change')\n * @param {Function} handler - Callback\n * @param {Object} [options] - Options forwarded to EventBus.on\n * @returns {Function} Unsubscribe function\n */\n on(event, handler, options) {\n return this._bus.on(event, handler, options)\n }\n\n /**\n * Unsubscribe from an inter-editor event.\n * @param {string} event\n * @param {Function} handler\n */\n off(event, handler) {\n this._bus.off(event, handler)\n }\n\n /**\n * Subscribe to an event that fires at most once.\n * @param {string} event\n * @param {Function} handler\n * @returns {Function} Unsubscribe function\n */\n once(event, handler) {\n return this._bus.once(event, handler)\n }\n\n /**\n * Emit an event to all subscribers.\n * @param {string} event\n * @param {*} [data]\n */\n emit(event, data) {\n this._bus.emit(event, data)\n }\n\n /**\n * Broadcast an event to all registered editors by calling engine.eventBus.emit\n * on each one. Useful for pushing a global event (e.g. theme change) into every\n * editor's local event loop.\n *\n * @param {string} event - Event name to emit on each editor's eventBus\n * @param {*} [data] - Data to pass\n * @param {Object} [options]\n * @param {string} [options.exclude] - Editor ID to skip (e.g. the sender)\n */\n broadcast(event, data, options) {\n for (const [id, engine] of this._registry) {\n if (options?.exclude && id === options.exclude) continue\n engine.eventBus.emit(event, data)\n }\n }\n\n /**\n * Remove all listeners and clear the registry.\n * Primarily useful for test teardown.\n */\n reset() {\n this._bus.removeAllListeners()\n this._registry.clear()\n }\n}\n\n/**\n * Singleton inter-editor communication bus.\n * Import this directly — all editors on the page share the same instance.\n *\n * @type {EditorBusImpl}\n */\nexport const EditorBus = new EditorBusImpl()\n","import { DEFAULT_TOOLBAR } from '../constants/defaults.js'\n\n/**\n * Pre-built toolbar presets for common use cases.\n */\nexport const TOOLBAR_PRESETS = {\n /** All available toolbar items */\n full: DEFAULT_TOOLBAR,\n\n /** Standard editing toolbar without advanced features */\n standard: [\n ['undo', 'redo'],\n ['headings', 'fontFamily', 'fontSize'],\n ['bold', 'italic', 'underline', 'strikethrough'],\n ['foreColor', 'backColor'],\n ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'],\n ['orderedList', 'unorderedList', 'taskList'],\n ['outdent', 'indent'],\n ['link', 'image', 'table', 'blockquote', 'codeBlock', 'horizontalRule'],\n ['fullscreen'],\n ],\n\n /** Minimal toolbar for simple text editing */\n minimal: [\n ['headings'],\n ['bold', 'italic', 'underline'],\n ['orderedList', 'unorderedList'],\n ['link', 'image', 'blockquote'],\n ],\n\n /** Bare-bones formatting only */\n bare: [\n ['bold', 'italic', 'underline'],\n ],\n\n /** Rich document editing with all plugin features */\n rich: [\n ['undo', 'redo'],\n ['headings', 'fontFamily', 'fontSize', 'typography'],\n ['bold', 'italic', 'underline', 'strikethrough'],\n ['foreColor', 'backColor'],\n ['alignLeft', 'alignCenter', 'alignRight'],\n ['orderedList', 'unorderedList', 'taskList'],\n ['outdent', 'indent'],\n ['link', 'image', 'attachment', 'table', 'embedMedia', 'blockquote', 'codeBlock', 'horizontalRule'],\n ['insertCallout', 'insertMath', 'insertToc', 'insertBookmark', 'insertMergeTag'],\n ['subscript', 'superscript', 'removeFormat'],\n ['findReplace', 'toggleMarkdown', 'sourceMode', 'toggleAnalytics', 'distractionFree', 'toggleSplitView', 'export', 'commandPalette', 'fullscreen'],\n ],\n}\n\n/**\n * Remove specific items from a toolbar config.\n *\n * @param {string[][]} config - Toolbar config (array of groups)\n * @param {string[]} itemsToRemove - Command names to remove\n * @returns {string[][]} New toolbar config with items removed (empty groups are excluded)\n *\n * @example\n * removeToolbarItems(DEFAULT_TOOLBAR, ['image', 'table', 'embedMedia'])\n */\nexport function removeToolbarItems(config, itemsToRemove) {\n const removeSet = new Set(itemsToRemove)\n return config\n .map(group => group.filter(item => !removeSet.has(item)))\n .filter(group => group.length > 0)\n}\n\n/**\n * Add items to a toolbar config.\n *\n * @param {string[][]} config - Toolbar config (array of groups)\n * @param {string|string[]} items - Command name(s) to add\n * @param {Object} [options]\n * @param {number} [options.group] - Group index to insert into. If omitted, appends a new group.\n * @param {string} [options.after] - Insert after this command within the group\n * @param {string} [options.before] - Insert before this command within the group\n * @returns {string[][]} New toolbar config with items added\n *\n * @example\n * // Add 'export' to the last group\n * addToolbarItems(myToolbar, 'export', { group: -1 })\n *\n * // Add items as a new group at the end\n * addToolbarItems(myToolbar, ['subscript', 'superscript'])\n *\n * // Insert after a specific item\n * addToolbarItems(myToolbar, 'taskList', { after: 'unorderedList' })\n */\nexport function addToolbarItems(config, items, options = {}) {\n const itemsArray = Array.isArray(items) ? items : [items]\n const result = config.map(group => [...group])\n\n if (options.after || options.before) {\n const target = options.after || options.before\n for (let i = 0; i < result.length; i++) {\n const idx = result[i].indexOf(target)\n if (idx !== -1) {\n const insertAt = options.after ? idx + 1 : idx\n result[i].splice(insertAt, 0, ...itemsArray)\n return result\n }\n }\n // Target not found — fall through to append as new group\n }\n\n if (options.group !== undefined) {\n const gi = options.group < 0 ? result.length + options.group : options.group\n if (gi >= 0 && gi < result.length) {\n result[gi].push(...itemsArray)\n return result\n }\n }\n\n // Default: append as a new group\n result.push(itemsArray)\n return result\n}\n\n/**\n * Create a toolbar config from a flat list of command names.\n * Commands are automatically grouped by category.\n *\n * @param {string[]} items - Command names to include\n * @returns {string[][]} Toolbar config grouped by category\n *\n * @example\n * createToolbar(['bold', 'italic', 'underline', 'link', 'orderedList', 'unorderedList'])\n */\nexport function createToolbar(items) {\n const CATEGORY_ORDER = [\n { commands: ['undo', 'redo'] },\n { commands: ['headings', 'fontFamily', 'fontSize'] },\n { commands: ['bold', 'italic', 'underline', 'strikethrough'] },\n { commands: ['foreColor', 'backColor'] },\n { commands: ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'] },\n { commands: ['orderedList', 'unorderedList', 'taskList'] },\n { commands: ['outdent', 'indent'] },\n { commands: ['link', 'image', 'attachment', 'importDocument', 'table', 'embedMedia', 'blockquote', 'codeBlock', 'horizontalRule'] },\n { commands: ['subscript', 'superscript'] },\n { commands: ['findReplace', 'toggleMarkdown', 'sourceMode', 'export', 'fullscreen'] },\n ]\n\n const itemSet = new Set(items)\n const result = []\n\n for (const category of CATEGORY_ORDER) {\n const group = category.commands.filter(cmd => itemSet.has(cmd))\n if (group.length > 0) {\n result.push(group)\n group.forEach(cmd => itemSet.delete(cmd))\n }\n }\n\n // Any remaining items that didn't match a category go in their own group\n if (itemSet.size > 0) {\n result.push([...itemSet])\n }\n\n return result\n}\n","import { isMac } from '../utils/platform.js'\n\n/**\n * Item 20: IMPORTANT — BUTTON_COMMANDS and TOOLTIP_MAP must be kept in sync manually.\n * When adding a new command to BUTTON_COMMANDS, also add a corresponding entry to TOOLTIP_MAP below.\n * When adding a new entry to TOOLTIP_MAP for a button command, ensure it is also in BUTTON_COMMANDS.\n * SHORTCUT_MAP should also be updated if the command has a keyboard shortcut.\n * MODAL_COMMANDS should be updated if the command triggers a modal instead of direct execution.\n */\nexport const BUTTON_COMMANDS = new Set([\n 'bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript',\n 'alignLeft', 'alignCenter', 'alignRight', 'alignJustify',\n 'orderedList', 'unorderedList', 'taskList',\n 'blockquote', 'codeBlock', 'horizontalRule',\n 'undo', 'redo', 'removeFormat',\n 'indent', 'outdent',\n 'fullscreen', 'sourceMode', 'toggleMarkdown',\n 'distractionFree', 'toggleSplitView',\n // Plugin commands exposed as toolbar buttons\n 'insertCallout', 'insertMath', 'insertToc', 'insertBookmark', 'insertMergeTag',\n 'toggleAnalytics', 'toggleSpellcheck', 'checkGrammar',\n 'startCollaboration', 'stopCollaboration',\n 'addComment', 'removeFormat',\n 'lineHeight', 'letterSpacing', 'paragraphSpacing',\n])\n\nexport const TOOLTIP_MAP = {\n bold: 'Bold', italic: 'Italic', underline: 'Underline', strikethrough: 'Strikethrough',\n subscript: 'Subscript', superscript: 'Superscript',\n alignLeft: 'Align Left', alignCenter: 'Align Center', alignRight: 'Align Right', alignJustify: 'Justify',\n orderedList: 'Numbered List', unorderedList: 'Bulleted List', taskList: 'Task List',\n blockquote: 'Blockquote', codeBlock: 'Code Block', horizontalRule: 'Horizontal Rule',\n undo: 'Undo', redo: 'Redo', removeFormat: 'Remove Formatting',\n indent: 'Indent', outdent: 'Outdent',\n link: 'Insert Link', image: 'Insert Image', attachment: 'Attach File', table: 'Insert Table',\n importDocument: 'Import Document',\n embedMedia: 'Embed Media', findReplace: 'Find & Replace',\n fullscreen: 'Fullscreen', sourceMode: 'Source Code',\n toggleMarkdown: 'Toggle Markdown', export: 'Export Document',\n distractionFree: 'Distraction-Free Mode', toggleSplitView: 'Split View',\n lineHeight: 'Line Height', letterSpacing: 'Letter Spacing', paragraphSpacing: 'Paragraph Spacing',\n saveColorPreset: 'Save Color Preset', typography: 'Typography',\n foreColor: 'Text Color', backColor: 'Background Color',\n headings: 'Block Type', fontFamily: 'Font Family', fontSize: 'Font Size',\n commandPalette: 'Command Palette',\n // Plugin commands\n insertCallout: 'Insert Callout',\n insertMath: 'Insert Math',\n insertToc: 'Insert Table of Contents',\n insertBookmark: 'Insert Bookmark',\n insertMergeTag: 'Insert Merge Tag',\n addComment: 'Add Comment',\n scanBrokenLinks: 'Scan Broken Links',\n getAnalytics: 'Content Analytics',\n toggleAnalytics: 'Toggle Analytics',\n toggleSpellcheck: 'Toggle Spellcheck',\n checkGrammar: 'Check Grammar',\n startCollaboration: 'Start Collaboration',\n stopCollaboration: 'Stop Collaboration',\n}\n\nexport const SHORTCUT_MAP = {\n bold: 'mod+B', italic: 'mod+I', underline: 'mod+U',\n strikethrough: 'mod+Shift+X', undo: 'mod+Z', redo: 'mod+Shift+Z',\n insertLink: 'mod+K', findReplace: 'mod+F',\n fullscreen: 'mod+Shift+F', sourceMode: 'mod+Shift+U',\n commandPalette: 'mod+Shift+P',\n distractionFree: 'mod+Shift+D', toggleSplitView: 'mod+Shift+V',\n}\n\n/** Commands that trigger modals instead of direct execution */\nexport const MODAL_COMMANDS = {\n link: 'link',\n image: 'image',\n attachment: 'attachment',\n importDocument: 'importDocument',\n table: 'table',\n embedMedia: 'embed',\n findReplace: 'findReplace',\n export: 'export',\n}\n\nexport function getShortcutLabel(command) {\n const shortcut = SHORTCUT_MAP[command]\n if (!shortcut) return ''\n return shortcut.replace(/mod/g, isMac() ? '⌘' : 'Ctrl')\n}\n\n/**\n * Get the active state for a command from selectionState + engine.\n */\nexport function getCommandActiveState(command, selectionState, engine) {\n switch (command) {\n case 'bold': return selectionState.bold\n case 'italic': return selectionState.italic\n case 'underline': return selectionState.underline\n case 'strikethrough': return selectionState.strikethrough\n case 'subscript': return selectionState.subscript\n case 'superscript': return selectionState.superscript\n case 'alignLeft': return selectionState.alignment === 'left'\n case 'alignCenter': return selectionState.alignment === 'center'\n case 'alignRight': return selectionState.alignment === 'right'\n case 'alignJustify': return selectionState.alignment === 'justify'\n case 'orderedList': return selectionState.orderedList\n case 'unorderedList': return selectionState.unorderedList\n case 'blockquote': return selectionState.blockquote\n case 'codeBlock': return selectionState.codeBlock\n case 'sourceMode': return engine?.isSourceMode\n case 'toggleMarkdown': return engine?.isMarkdownMode\n case 'fullscreen': return engine?.element?.closest('.rmx-editor')?.classList.contains('rmx-fullscreen')\n case 'distractionFree': return engine?.element?.closest('.rmx-editor')?.classList.contains('rmx-distraction-free')\n case 'toggleSplitView': return engine?.element?.closest('.rmx-editor')?.classList.contains('rmx-split-view')\n default: return false\n }\n}\n","export const DEFAULT_KEYBINDINGS = {\n bold: 'mod+b',\n italic: 'mod+i',\n underline: 'mod+u',\n strikethrough: 'mod+shift+x',\n undo: 'mod+z',\n redo: 'mod+shift+z',\n selectAll: 'mod+a',\n insertLink: 'mod+k',\n findReplace: 'mod+f',\n orderedList: 'mod+shift+7',\n unorderedList: 'mod+shift+8',\n blockquote: 'mod+shift+9',\n codeBlock: 'mod+shift+c',\n indent: 'tab',\n outdent: 'shift+tab',\n fullscreen: 'mod+shift+f',\n sourceMode: 'mod+shift+u',\n subscript: 'mod+,',\n superscript: 'mod+.',\n}\n","import { ALLOWED_TAGS, ALLOWED_STYLES } from '../constants/schema.js'\nimport { TOOLBAR_PRESETS } from '../utils/toolbarConfig.js'\nimport { DEFAULT_TOOLBAR, DEFAULT_MENU_BAR, DEFAULT_FONTS, DEFAULT_FONT_SIZES, DEFAULT_COLORS, HEADING_OPTIONS } from '../constants/defaults.js'\nimport { BUTTON_COMMANDS, TOOLTIP_MAP, SHORTCUT_MAP, MODAL_COMMANDS } from '../constants/commands.js'\nimport { DEFAULT_KEYBINDINGS } from '../constants/keybindings.js'\n\n/**\n * A memory-efficient shared resource singleton.\n *\n * When running 10+ EditorEngine instances on a single page, each engine\n * would normally allocate its own copy of the sanitizer schema, toolbar\n * presets, icon map, default config objects, and keybinding tables.\n * SharedResources provides a single frozen copy of these large, immutable\n * data structures so every engine can reference the same objects in memory.\n *\n * The singleton is lazily initialized on first access and the frozen data\n * is reused for the lifetime of the page.\n *\n * @example\n * import { SharedResources } from '@remyxjs/core';\n *\n * // Access shared sanitizer schema (same object for all editors)\n * const tags = SharedResources.sanitizerSchema.allowedTags;\n *\n * // Access shared toolbar presets\n * const fullToolbar = SharedResources.toolbarPresets.full;\n *\n * // Register a custom icon once, available to all editors\n * SharedResources.registerIcon('myIcon', '<svg>...</svg>');\n */\nclass SharedResourcesImpl {\n constructor() {\n /** @private */\n this._initialized = false\n /** @private */\n this._sanitizerSchema = null\n /** @private */\n this._toolbarPresets = null\n /** @private */\n this._defaults = null\n /** @private */\n this._keybindings = null\n /** @private */\n this._commands = null\n /** @private @type {Map<string, string>} */\n this._icons = new Map()\n }\n\n /** @private */\n _ensureInit() {\n if (this._initialized) return\n this._initialized = true\n\n // Freeze the sanitizer schema so all engines share the same immutable copy\n this._sanitizerSchema = Object.freeze({\n allowedTags: Object.freeze(\n Object.fromEntries(\n Object.entries(ALLOWED_TAGS).map(([tag, attrs]) => [tag, Object.freeze([...attrs])])\n )\n ),\n allowedStyles: Object.freeze([...ALLOWED_STYLES]),\n })\n\n // Freeze toolbar presets (deep-freeze the nested arrays)\n this._toolbarPresets = Object.freeze(\n Object.fromEntries(\n Object.entries(TOOLBAR_PRESETS).map(([name, groups]) => [\n name,\n Object.freeze(groups.map(g => Object.freeze([...g]))),\n ])\n )\n )\n\n // Freeze default configuration objects\n this._defaults = Object.freeze({\n toolbar: Object.freeze(DEFAULT_TOOLBAR.map(g => Object.freeze([...g]))),\n menuBar: Object.freeze(DEFAULT_MENU_BAR),\n fonts: Object.freeze([...DEFAULT_FONTS]),\n fontSizes: Object.freeze([...DEFAULT_FONT_SIZES]),\n colors: Object.freeze([...DEFAULT_COLORS]),\n headingOptions: Object.freeze([...HEADING_OPTIONS]),\n })\n\n // Freeze keybinding table\n this._keybindings = Object.freeze({ ...DEFAULT_KEYBINDINGS })\n\n // Freeze command metadata maps\n this._commands = Object.freeze({\n buttons: Object.freeze({ ...BUTTON_COMMANDS }),\n tooltips: Object.freeze({ ...TOOLTIP_MAP }),\n shortcuts: Object.freeze({ ...SHORTCUT_MAP }),\n modals: Object.freeze({ ...MODAL_COMMANDS }),\n })\n }\n\n // ── Accessors ──────────────────────────────────────────────────────\n\n /**\n * Shared, frozen sanitizer schema.\n * @returns {{ allowedTags: Object, allowedStyles: string[] }}\n */\n get sanitizerSchema() {\n this._ensureInit()\n return this._sanitizerSchema\n }\n\n /**\n * Shared, frozen toolbar presets (full, standard, minimal, bare).\n * @returns {Object}\n */\n get toolbarPresets() {\n this._ensureInit()\n return this._toolbarPresets\n }\n\n /**\n * Shared, frozen default configuration values.\n * @returns {{ toolbar, menuBar, fonts, fontSizes, colors, headingOptions }}\n */\n get defaults() {\n this._ensureInit()\n return this._defaults\n }\n\n /**\n * Shared, frozen keybinding table.\n * @returns {Object}\n */\n get keybindings() {\n this._ensureInit()\n return this._keybindings\n }\n\n /**\n * Shared, frozen command metadata maps.\n * @returns {{ buttons, tooltips, shortcuts, modals }}\n */\n get commands() {\n this._ensureInit()\n return this._commands\n }\n\n // ── Icon registry ──────────────────────────────────────────────────\n\n /**\n * Register a shared icon (SVG string or URL) by name.\n * Once registered, any editor on the page can use it via toolbar config.\n *\n * @param {string} name - Icon identifier (e.g. 'myCustomAction')\n * @param {string} svgOrUrl - SVG markup string or URL to icon asset\n */\n registerIcon(name, svgOrUrl) {\n this._icons.set(name, svgOrUrl)\n }\n\n /**\n * Unregister a previously registered icon.\n * @param {string} name - Icon identifier to remove\n */\n unregisterIcon(name) {\n this._icons.delete(name)\n }\n\n /**\n * Retrieve a registered icon by name.\n * @param {string} name - Icon identifier\n * @returns {string | undefined} SVG markup or URL, or undefined if not registered\n */\n getIcon(name) {\n return this._icons.get(name)\n }\n\n /**\n * Get all registered icon names.\n * @returns {string[]}\n */\n getIconNames() {\n return [...this._icons.keys()]\n }\n\n /**\n * Total approximate memory savings info.\n * @returns {{ registeredIcons: number, frozenSchemas: boolean }}\n */\n get stats() {\n this._ensureInit()\n return {\n registeredIcons: this._icons.size,\n frozenSchemas: true,\n }\n }\n}\n\n/**\n * Singleton shared resource pool.\n * Import this directly — all editors on the page share the same instance.\n *\n * @type {SharedResourcesImpl}\n */\nexport const SharedResources = new SharedResourcesImpl()\n","/**\n * CSP-compatible inline formatting commands.\n *\n * All commands use Selection/Range-based DOM manipulation instead of the\n * deprecated `document.execCommand()` API. This eliminates CSP violations\n * from `unsafe-inline` requirements and ensures forward compatibility.\n */\n\n/**\n * Toggle an inline tag (e.g., <strong>, <em>) on the current selection.\n * If the selection is already wrapped in the tag, remove it. Otherwise wrap it.\n */\nfunction toggleInlineTag(engine, tagName) {\n const sel = engine.selection.getSelection()\n if (!sel || sel.rangeCount === 0) return\n\n const range = sel.getRangeAt(0)\n if (range.collapsed) return\n\n // Check if already wrapped in this tag\n const parent = engine.selection.getParentElement()\n if (parent && parent.tagName === tagName.toUpperCase()) {\n // Unwrap: move children out and remove the tag\n const docFrag = document.createDocumentFragment()\n while (parent.firstChild) {\n docFrag.appendChild(parent.firstChild)\n }\n parent.parentNode.replaceChild(docFrag, parent)\n return\n }\n\n // Also check if selection is inside a nested instance\n let ancestor = parent\n while (ancestor && ancestor !== engine.element) {\n if (ancestor.tagName === tagName.toUpperCase()) {\n const docFrag = document.createDocumentFragment()\n while (ancestor.firstChild) {\n docFrag.appendChild(ancestor.firstChild)\n }\n ancestor.parentNode.replaceChild(docFrag, ancestor)\n return\n }\n ancestor = ancestor.parentNode\n }\n\n // Wrap selection in the tag\n const wrapper = document.createElement(tagName)\n try {\n range.surroundContents(wrapper)\n } catch {\n const fragment = range.extractContents()\n wrapper.appendChild(fragment)\n range.insertNode(wrapper)\n }\n}\n\n/**\n * Check if the selection is currently inside a given tag.\n */\nfunction isInsideTag(engine, tagName) {\n const parent = engine.selection.getParentElement()\n if (!parent) return false\n let el = parent\n while (el && el !== engine.element) {\n if (el.tagName === tagName.toUpperCase()) return true\n el = el.parentNode\n }\n return false\n}\n\nexport function registerFormattingCommands(engine) {\n engine.commands.register('bold', {\n execute(eng) { toggleInlineTag(eng, 'strong') },\n isActive(eng) { return isInsideTag(eng, 'STRONG') || isInsideTag(eng, 'B') },\n shortcut: 'mod+b',\n meta: { icon: 'bold', tooltip: 'Bold' },\n })\n\n engine.commands.register('italic', {\n execute(eng) { toggleInlineTag(eng, 'em') },\n isActive(eng) { return isInsideTag(eng, 'EM') || isInsideTag(eng, 'I') },\n shortcut: 'mod+i',\n meta: { icon: 'italic', tooltip: 'Italic' },\n })\n\n engine.commands.register('underline', {\n execute(eng) { toggleInlineTag(eng, 'u') },\n isActive(eng) { return isInsideTag(eng, 'U') },\n shortcut: 'mod+u',\n meta: { icon: 'underline', tooltip: 'Underline' },\n })\n\n engine.commands.register('strikethrough', {\n execute(eng) { toggleInlineTag(eng, 's') },\n isActive(eng) { return isInsideTag(eng, 'S') || isInsideTag(eng, 'DEL') },\n shortcut: 'mod+shift+x',\n meta: { icon: 'strikethrough', tooltip: 'Strikethrough' },\n })\n\n engine.commands.register('subscript', {\n execute(eng) { toggleInlineTag(eng, 'sub') },\n isActive(eng) { return isInsideTag(eng, 'SUB') },\n shortcut: 'mod+,',\n alternateShortcuts: ['mod+shift+,'],\n meta: { icon: 'subscript', tooltip: 'Subscript' },\n })\n\n engine.commands.register('superscript', {\n execute(eng) { toggleInlineTag(eng, 'sup') },\n isActive(eng) { return isInsideTag(eng, 'SUP') },\n shortcut: 'mod+.',\n alternateShortcuts: ['mod+shift+.'],\n meta: { icon: 'superscript', tooltip: 'Superscript' },\n })\n\n engine.commands.register('highlight', {\n execute(eng, { color = 'yellow' } = {}) {\n const sel = eng.selection.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n if (range.collapsed) return\n\n // Check if already inside a <mark> — toggle off\n const parent = eng.selection.getParentElement()\n let ancestor = parent\n while (ancestor && ancestor !== eng.element) {\n if (ancestor.tagName === 'MARK') {\n const docFrag = document.createDocumentFragment()\n while (ancestor.firstChild) {\n docFrag.appendChild(ancestor.firstChild)\n }\n ancestor.parentNode.replaceChild(docFrag, ancestor)\n return\n }\n ancestor = ancestor.parentNode\n }\n\n // Wrap in <mark> with the specified background color\n const colorMap = {\n yellow: 'rgba(255, 235, 59, 0.4)',\n green: 'rgba(76, 175, 80, 0.3)',\n blue: 'rgba(33, 150, 243, 0.3)',\n pink: 'rgba(233, 30, 99, 0.3)',\n orange: 'rgba(255, 152, 0, 0.3)',\n purple: 'rgba(156, 39, 176, 0.3)',\n }\n const bg = colorMap[color] || colorMap.yellow\n const mark = document.createElement('mark')\n mark.style.backgroundColor = bg\n mark.setAttribute('data-highlight-color', color)\n try {\n range.surroundContents(mark)\n } catch {\n const fragment = range.extractContents()\n mark.appendChild(fragment)\n range.insertNode(mark)\n }\n },\n isActive(eng) { return isInsideTag(eng, 'MARK') },\n meta: { icon: 'highlight', tooltip: 'Highlight' },\n })\n\n engine.commands.register('removeFormat', {\n execute(eng) {\n const sel = eng.selection.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n if (range.collapsed) return\n\n // Walk the selection and remove all inline formatting tags\n const inlineTags = new Set(['STRONG', 'B', 'EM', 'I', 'U', 'S', 'DEL', 'SUB', 'SUP', 'MARK', 'SPAN'])\n const container = range.commonAncestorContainer\n const root = container.nodeType === Node.TEXT_NODE ? container.parentElement : container\n\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT)\n const toUnwrap = []\n let node\n while ((node = walker.nextNode())) {\n if (inlineTags.has(node.tagName) && range.intersectsNode(node)) {\n toUnwrap.push(node)\n }\n }\n\n // Unwrap in reverse order to avoid DOM mutation issues\n for (let i = toUnwrap.length - 1; i >= 0; i--) {\n const el = toUnwrap[i]\n const parent = el.parentNode\n while (el.firstChild) {\n parent.insertBefore(el.firstChild, el)\n }\n parent.removeChild(el)\n }\n },\n shortcut: 'mod+\\\\',\n meta: { icon: 'removeFormat', tooltip: 'Remove Formatting' },\n })\n}\n","/**\n * CSP-compatible heading commands using Range-based DOM manipulation.\n *\n * Compute the effective heading tag, applying a baseHeadingLevel offset.\n * If baseHeadingLevel is 2, then logical H1 renders as <h2>, H2 as <h3>, etc.\n * Levels are clamped to 1-6.\n */\nfunction getEffectiveLevel(logicalLevel, baseHeadingLevel) {\n if (!baseHeadingLevel || baseHeadingLevel <= 1) return logicalLevel\n const offset = baseHeadingLevel - 1\n return Math.min(logicalLevel + offset, 6)\n}\n\n/**\n * Convert the current block element to a different block tag (e.g., <p> → <h2>).\n */\nfunction convertBlock(engine, newTag) {\n const block = engine.selection.getParentBlock()\n if (!block) return\n\n // If already this tag, do nothing\n if (block.tagName === newTag.toUpperCase()) return\n\n const newEl = document.createElement(newTag)\n // Copy over children\n while (block.firstChild) {\n newEl.appendChild(block.firstChild)\n }\n // Copy class attribute if present\n if (block.className) newEl.className = block.className\n\n block.parentNode.replaceChild(newEl, block)\n\n // Restore cursor inside the new element\n const range = document.createRange()\n range.selectNodeContents(newEl)\n range.collapse(false)\n engine.selection.setRange(range)\n}\n\nexport function registerHeadingCommands(engine) {\n const baseLevel = engine.options.baseHeadingLevel || 1\n\n engine.commands.register('heading', {\n execute(eng, level) {\n if (level === 'p') {\n convertBlock(eng, 'p')\n } else {\n const effective = getEffectiveLevel(Number(level), baseLevel)\n convertBlock(eng, `h${effective}`)\n }\n },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return false\n return /^H[1-6]$/.test(block.tagName) ? block.tagName.toLowerCase() : false\n },\n meta: { icon: 'heading', tooltip: 'Heading' },\n })\n\n // Individual heading commands for convenience\n for (let i = 1; i <= 6; i++) {\n const effectiveLevel = getEffectiveLevel(i, baseLevel)\n engine.commands.register(`h${i}`, {\n execute(eng) { convertBlock(eng, `h${effectiveLevel}`) },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n return block && block.tagName === `H${effectiveLevel}`\n },\n meta: { tooltip: `Heading ${i}` },\n })\n }\n\n engine.commands.register('paragraph', {\n execute(eng) { convertBlock(eng, 'p') },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n return block && block.tagName === 'P'\n },\n meta: { tooltip: 'Normal text' },\n })\n}\n","/**\n * CSP-compatible text alignment commands using direct style manipulation.\n * Task 255: isActive() reads block.style.textAlign only (no getComputedStyle).\n */\nexport function registerAlignmentCommands(engine) {\n engine.commands.register('alignLeft', {\n execute(eng) {\n const block = eng.selection.getParentBlock()\n if (block) block.style.textAlign = 'left'\n },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return false\n const align = block.style.textAlign\n return !align || align === 'left' || align === 'start'\n },\n meta: { icon: 'alignLeft', tooltip: 'Align Left' },\n })\n\n engine.commands.register('alignCenter', {\n execute(eng) {\n const block = eng.selection.getParentBlock()\n if (block) block.style.textAlign = 'center'\n },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return false\n const align = block.style.textAlign\n return align === 'center'\n },\n meta: { icon: 'alignCenter', tooltip: 'Align Center' },\n })\n\n engine.commands.register('alignRight', {\n execute(eng) {\n const block = eng.selection.getParentBlock()\n if (block) block.style.textAlign = 'right'\n },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return false\n const align = block.style.textAlign\n return align === 'right' || align === 'end'\n },\n meta: { icon: 'alignRight', tooltip: 'Align Right' },\n })\n\n engine.commands.register('alignJustify', {\n execute(eng) {\n const block = eng.selection.getParentBlock()\n if (block) block.style.textAlign = 'justify'\n },\n isActive(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return false\n const align = block.style.textAlign\n return align === 'justify'\n },\n meta: { icon: 'alignJustify', tooltip: 'Justify' },\n })\n}\n","/**\n * CSP-compatible list commands using Range-based DOM manipulation.\n */\nexport function registerListCommands(engine) {\n engine.commands.register('orderedList', {\n execute(eng) {\n const existingOl = eng.selection.getClosestElement('ol')\n if (existingOl) {\n // Toggle off: unwrap list items to paragraphs\n unwrapList(existingOl)\n return\n }\n // Convert from ul to ol if in an unordered list\n const existingUl = eng.selection.getClosestElement('ul')\n if (existingUl) {\n convertListType(existingUl, 'ol')\n return\n }\n // Wrap current block in <ol><li>\n wrapInList(eng, 'ol')\n },\n isActive(eng) {\n return !!eng.selection.getClosestElement('ol')\n },\n shortcut: 'mod+shift+7',\n meta: { icon: 'orderedList', tooltip: 'Numbered List' },\n })\n\n engine.commands.register('unorderedList', {\n execute(eng) {\n const existingUl = eng.selection.getClosestElement('ul')\n if (existingUl && !existingUl.classList.contains('rmx-task-list')) {\n unwrapList(existingUl)\n return\n }\n const existingOl = eng.selection.getClosestElement('ol')\n if (existingOl) {\n convertListType(existingOl, 'ul')\n return\n }\n wrapInList(eng, 'ul')\n },\n isActive(eng) {\n const ul = eng.selection.getClosestElement('ul')\n if (!ul) return false\n return !ul.classList.contains('rmx-task-list')\n },\n shortcut: 'mod+shift+8',\n meta: { icon: 'unorderedList', tooltip: 'Bulleted List' },\n })\n\n engine.commands.register('taskList', {\n execute(eng) {\n const existingUl = eng.selection.getClosestElement('ul')\n if (existingUl && existingUl.classList.contains('rmx-task-list')) {\n // Remove task list - convert back to regular list\n existingUl.classList.remove('rmx-task-list')\n existingUl.querySelectorAll('.rmx-task-checkbox').forEach((cb) => cb.remove())\n return\n }\n\n // If not in a list, create one\n if (!existingUl) {\n wrapInList(eng, 'ul')\n }\n\n const ul = eng.selection.getClosestElement('ul')\n if (!ul) return\n\n ul.classList.add('rmx-task-list')\n ul.querySelectorAll('li').forEach((li) => {\n if (!li.querySelector('.rmx-task-checkbox')) {\n const checkbox = document.createElement('input')\n checkbox.type = 'checkbox'\n checkbox.className = 'rmx-task-checkbox'\n checkbox.setAttribute('contenteditable', 'false')\n li.insertBefore(checkbox, li.firstChild)\n }\n })\n },\n isActive(eng) {\n const ul = eng.selection.getClosestElement('ul')\n return ul && ul.classList.contains('rmx-task-list')\n },\n meta: { icon: 'taskList', tooltip: 'Task List' },\n })\n\n engine.commands.register('indent', {\n execute(eng) {\n const li = eng.selection.getClosestElement('li')\n if (!li) return\n const prevLi = li.previousElementSibling\n if (!prevLi) return // Can't indent first item\n\n // Check current nesting depth and enforce max\n const maxDepth = eng.options?.maxListNestingDepth ?? 5\n let depth = 0\n let el = li.parentElement\n while (el && el !== eng.element) {\n if (el.tagName === 'UL' || el.tagName === 'OL') depth++\n el = el.parentElement\n }\n if (depth >= maxDepth) return // Already at max nesting depth\n\n // Find or create a sub-list inside the previous li\n const listTag = li.parentElement.tagName.toLowerCase()\n let subList = prevLi.querySelector(listTag)\n if (!subList) {\n subList = document.createElement(listTag)\n prevLi.appendChild(subList)\n }\n subList.appendChild(li)\n },\n meta: { icon: 'indent', tooltip: 'Increase Indent' },\n })\n\n engine.commands.register('outdent', {\n execute(eng) {\n const li = eng.selection.getClosestElement('li')\n if (!li) return\n const parentList = li.parentElement\n if (!parentList) return\n const grandParentLi = parentList.parentElement\n if (!grandParentLi || grandParentLi.tagName !== 'LI') return // Already at top level\n\n // Move the li after the grandparent li\n const grandParentList = grandParentLi.parentElement\n grandParentList.insertBefore(li, grandParentLi.nextSibling)\n\n // Clean up empty sub-list\n if (parentList.children.length === 0) {\n parentList.remove()\n }\n },\n meta: { icon: 'outdent', tooltip: 'Decrease Indent' },\n })\n}\n\nfunction wrapInList(engine, listTag) {\n const block = engine.selection.getParentBlock()\n if (!block) return\n\n const list = document.createElement(listTag)\n const li = document.createElement('li')\n\n // Move block content into the li\n while (block.firstChild) {\n li.appendChild(block.firstChild)\n }\n list.appendChild(li)\n block.parentNode.replaceChild(list, block)\n\n // Restore cursor\n const range = document.createRange()\n range.selectNodeContents(li)\n range.collapse(false)\n engine.selection.setRange(range)\n}\n\nfunction unwrapList(list) {\n const parent = list.parentNode\n const items = Array.from(list.querySelectorAll(':scope > li'))\n for (const li of items) {\n const p = document.createElement('p')\n while (li.firstChild) {\n p.appendChild(li.firstChild)\n }\n parent.insertBefore(p, list)\n }\n parent.removeChild(list)\n}\n\nfunction convertListType(list, newTag) {\n const newList = document.createElement(newTag)\n // Copy attributes\n for (const attr of list.attributes) {\n newList.setAttribute(attr.name, attr.value)\n }\n while (list.firstChild) {\n newList.appendChild(list.firstChild)\n }\n list.parentNode.replaceChild(newList, list)\n}\n","import { escapeHTMLAttr as escapeHTML } from '../utils/escapeHTML.js'\n\nconst DANGEROUS_PROTOCOL = /^\\s*(javascript|vbscript|data\\s*:\\s*text\\/html)\\s*:/i\n\nfunction validateUrl(url) {\n if (!url) return url\n if (DANGEROUS_PROTOCOL.test(url)) return '#'\n return url\n}\n\nexport function registerLinkCommands(engine) {\n engine.commands.register('insertLink', {\n execute(eng, { href, text, target = '_blank' }) {\n if (!href) return\n\n const selection = eng.selection\n const selectedText = selection.getSelectedText()\n\n if (selectedText) {\n const link = selection.wrapWith('a', {\n href,\n target,\n rel: target === '_blank' ? 'noopener noreferrer' : undefined,\n })\n if (link && text && text !== selectedText) {\n link.textContent = text\n }\n } else {\n const displayText = text || href\n const safeHref = validateUrl(href)\n const html = `<a href=\"${escapeAttr(safeHref)}\" target=\"${escapeAttr(target)}\"${target === '_blank' ? ' rel=\"noopener noreferrer\"' : ''}>${escapeHTML(displayText)}</a>`\n selection.insertHTML(html)\n }\n },\n shortcut: 'mod+k',\n meta: { icon: 'link', tooltip: 'Insert Link' },\n })\n\n engine.commands.register('editLink', {\n execute(eng, { href, text, target }) {\n const linkEl = eng.selection.getClosestElement('a')\n if (!linkEl) return\n if (href !== undefined) linkEl.href = validateUrl(href)\n if (text !== undefined) linkEl.textContent = text\n if (target !== undefined) {\n linkEl.target = target\n if (target === '_blank') {\n linkEl.rel = 'noopener noreferrer'\n } else {\n linkEl.removeAttribute('rel')\n }\n }\n },\n meta: { tooltip: 'Edit Link' },\n })\n\n engine.commands.register('removeLink', {\n execute(eng) {\n eng.selection.unwrap('a')\n },\n meta: { icon: 'unlink', tooltip: 'Remove Link' },\n })\n}\n\n// Task 261: escapeHTML imported from shared utils/escapeHTML.js\n\nfunction escapeAttr(str) {\n return str.replace(/\"/g, '"').replace(/'/g, ''')\n}\n","/**\n * Image commands for the editor.\n *\n * **Privacy notice:** Images inserted via external URL (`http://`, `https://`) will\n * make GET requests when rendered in the editor, revealing the viewer's IP address\n * and user-agent to the image host. This can be exploited as a tracking pixel.\n * For privacy-sensitive deployments, use an upload handler (`options.uploadHandler`)\n * to proxy images through your own server, or restrict to data URIs only.\n */\nexport function registerImageCommands(engine) {\n engine.commands.register('insertImage', {\n execute(eng, { src, alt = '', width, height }) {\n if (!src) return\n\n // Block SVG data URIs — can contain executable JavaScript\n if (/^data:image\\/svg/i.test(src)) return\n\n // Block external SVG URLs — SVGs loaded via <img src> can still\n // exploit CSS-based attacks; only raster formats are safe over URL\n try {\n const url = new URL(src, window.location.href)\n if (url.pathname.toLowerCase().endsWith('.svg')) return\n } catch {\n // relative URLs that fail parsing are checked via extension\n if (/\\.svg(\\?|#|$)/i.test(src)) return\n }\n\n // Ensure editor has focus (e.g. when called from a modal)\n eng.element.focus()\n\n // If no selection range exists, place cursor at the end\n if (!eng.selection.getRange()) {\n const range = document.createRange()\n range.selectNodeContents(eng.element)\n range.collapse(false)\n eng.selection.setRange(range)\n }\n\n const img = document.createElement('img')\n img.src = src\n img.alt = alt\n if (width) img.style.width = typeof width === 'number' ? `${width}px` : width\n if (height) img.style.height = typeof height === 'number' ? `${height}px` : height\n img.style.maxWidth = '100%'\n img.className = 'rmx-image'\n\n eng.selection.insertNode(img)\n\n // Add a paragraph after the image if it's the last element\n if (!img.nextSibling || img.parentElement === eng.element) {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n img.parentElement.insertBefore(p, img.nextSibling)\n }\n },\n meta: { icon: 'image', tooltip: 'Insert Image' },\n })\n\n engine.commands.register('resizeImage', {\n execute(eng, { element, width, height }) {\n if (!element || element.tagName !== 'IMG') return\n element.style.width = typeof width === 'number' ? `${width}px` : width\n if (height) {\n element.style.height = typeof height === 'number' ? `${height}px` : height\n } else {\n element.style.height = 'auto'\n }\n },\n meta: { tooltip: 'Resize Image' },\n })\n\n engine.commands.register('alignImage', {\n execute(eng, { element, alignment }) {\n if (!element || element.tagName !== 'IMG') return\n element.style.float = ''\n element.style.margin = ''\n element.style.display = ''\n\n switch (alignment) {\n case 'left':\n element.style.float = 'left'\n element.style.margin = '0 16px 16px 0'\n break\n case 'right':\n element.style.float = 'right'\n element.style.margin = '0 0 16px 16px'\n break\n case 'center':\n element.style.display = 'block'\n element.style.margin = '16px auto'\n break\n default:\n break\n }\n },\n meta: { tooltip: 'Align Image' },\n })\n\n engine.commands.register('removeImage', {\n execute(eng, { element }) {\n if (!element || element.tagName !== 'IMG') return\n element.parentNode.removeChild(element)\n },\n meta: { tooltip: 'Remove Image' },\n })\n}\n","export function registerTableCommands(engine) {\n engine.commands.register('insertTable', {\n execute(eng, { rows = 3, cols = 3 } = {}) {\n let html = '<table class=\"rmx-table\"><thead><tr>'\n for (let c = 0; c < cols; c++) {\n html += '<th><br></th>'\n }\n html += '</tr></thead><tbody>'\n for (let r = 1; r < rows; r++) {\n html += '<tr>'\n for (let c = 0; c < cols; c++) {\n html += '<td><br></td>'\n }\n html += '</tr>'\n }\n html += '</tbody></table><p><br></p>'\n eng.selection.insertHTML(html)\n },\n meta: { icon: 'table', tooltip: 'Insert Table' },\n })\n\n engine.commands.register('toggleHeaderRow', {\n execute(eng) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n const thead = table.querySelector('thead')\n if (thead) {\n // Remove thead — move row to start of tbody, convert th → td\n const headerRow = thead.querySelector('tr')\n if (!headerRow) return\n const newRow = document.createElement('tr')\n for (const th of Array.from(headerRow.cells)) {\n const td = document.createElement('td')\n td.innerHTML = th.innerHTML\n if (th.colSpan > 1) td.colSpan = th.colSpan\n if (th.rowSpan > 1) td.rowSpan = th.rowSpan\n newRow.appendChild(td)\n }\n let tbody = table.querySelector('tbody')\n if (!tbody) {\n tbody = document.createElement('tbody')\n table.appendChild(tbody)\n }\n tbody.insertBefore(newRow, tbody.firstChild)\n thead.remove()\n } else {\n // Create thead from first row of tbody\n let tbody = table.querySelector('tbody')\n if (!tbody) return\n const firstRow = tbody.querySelector('tr')\n if (!firstRow) return\n const newThead = document.createElement('thead')\n const newRow = document.createElement('tr')\n for (const td of Array.from(firstRow.cells)) {\n const th = document.createElement('th')\n th.innerHTML = td.innerHTML\n if (td.colSpan > 1) th.colSpan = td.colSpan\n if (td.rowSpan > 1) th.rowSpan = td.rowSpan\n newRow.appendChild(th)\n }\n newThead.appendChild(newRow)\n table.insertBefore(newThead, table.firstChild)\n firstRow.remove()\n }\n },\n meta: { tooltip: 'Toggle Header Row' },\n })\n\n engine.commands.register('alignCell', {\n execute(eng, { direction = 'left' } = {}) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n if (!['left', 'center', 'right'].includes(direction)) return\n td.style.textAlign = direction\n },\n meta: { tooltip: 'Align Cell Content' },\n })\n\n engine.commands.register('addRowBefore', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const tr = td.parentElement\n const newRow = createEmptyRow(getColumnCount(tr))\n tr.parentElement.insertBefore(newRow, tr)\n },\n meta: { tooltip: 'Insert Row Before' },\n })\n\n engine.commands.register('addRowAfter', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const tr = td.parentElement\n const newRow = createEmptyRow(getColumnCount(tr))\n tr.parentElement.insertBefore(newRow, tr.nextSibling)\n },\n meta: { tooltip: 'Insert Row After' },\n })\n\n engine.commands.register('addColBefore', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const colIndex = getCellIndex(td)\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n table.querySelectorAll('tr').forEach((row) => {\n const cell = document.createElement(row.parentElement.tagName === 'THEAD' ? 'th' : 'td')\n cell.innerHTML = '<br>'\n const refCell = row.cells[colIndex]\n row.insertBefore(cell, refCell)\n })\n },\n meta: { tooltip: 'Insert Column Before' },\n })\n\n engine.commands.register('addColAfter', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const colIndex = getCellIndex(td)\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n table.querySelectorAll('tr').forEach((row) => {\n const cell = document.createElement(row.parentElement.tagName === 'THEAD' ? 'th' : 'td')\n cell.innerHTML = '<br>'\n const refCell = row.cells[colIndex]\n row.insertBefore(cell, refCell ? refCell.nextSibling : null)\n })\n },\n meta: { tooltip: 'Insert Column After' },\n })\n\n engine.commands.register('deleteRow', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const tr = td.parentElement\n const tbody = tr.parentElement\n if (tbody.rows.length <= 1) {\n // If last row, remove the entire table\n const table = eng.selection.getClosestElement('table')\n if (table) {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n table.parentElement.replaceChild(p, table)\n }\n } else {\n tr.remove()\n }\n },\n meta: { tooltip: 'Delete Row' },\n })\n\n engine.commands.register('deleteCol', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const colIndex = getCellIndex(td)\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n\n const firstRow = table.querySelector('tr')\n if (firstRow && firstRow.cells.length <= 1) {\n // If last column, remove the entire table\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n table.parentElement.replaceChild(p, table)\n } else {\n table.querySelectorAll('tr').forEach((row) => {\n if (row.cells[colIndex]) {\n row.cells[colIndex].remove()\n }\n })\n }\n },\n meta: { tooltip: 'Delete Column' },\n })\n\n engine.commands.register('deleteTable', {\n execute(eng) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n table.parentElement.replaceChild(p, table)\n },\n meta: { tooltip: 'Delete Table' },\n })\n\n engine.commands.register('mergeCells', {\n execute(eng, { cells }) {\n if (!cells || cells.length < 2) return\n\n const firstCell = cells[0]\n const rows = new Set()\n const cols = new Set()\n\n cells.forEach((cell) => {\n rows.add(cell.parentElement.rowIndex)\n cols.add(getCellIndex(cell))\n })\n\n const rowSpan = rows.size\n const colSpan = cols.size\n\n // Collect content from all cells, then apply once to avoid repeated innerHTML concat\n const fragments = []\n cells.slice(1).forEach((cell) => {\n if (cell.textContent.trim()) {\n fragments.push(cell.innerHTML)\n }\n cell.remove()\n })\n if (fragments.length > 0) {\n firstCell.innerHTML += '<br>' + fragments.join('<br>')\n }\n\n if (rowSpan > 1) firstCell.rowSpan = rowSpan\n if (colSpan > 1) firstCell.colSpan = colSpan\n },\n meta: { tooltip: 'Merge Cells' },\n })\n\n engine.commands.register('sortTable', {\n execute(eng, { columnIndex, direction, dataType, keys, comparator } = {}) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n const tbody = table.querySelector('tbody')\n if (!tbody) return\n const rows = Array.from(tbody.querySelectorAll('tr'))\n if (rows.length === 0) return\n\n // Support multi-column sort via keys array, or single-column via columnIndex/direction\n const sortKeys = keys || [{ columnIndex, direction: direction || 'asc', dataType }]\n\n // Build sort comparator\n const getCell = (row, idx) => {\n const cells = row.querySelectorAll('td, th')\n return cells[idx]?.textContent?.trim() || ''\n }\n\n const customComparator = comparator || eng.options?.tableSortComparator\n\n const chainedCompare = (a, b) => {\n for (const key of sortKeys) {\n const valA = getCell(a, key.columnIndex)\n const valB = getCell(b, key.columnIndex)\n const type = key.dataType || detectSortDataType(rows, key.columnIndex)\n let result = 0\n\n if (customComparator) {\n result = customComparator(valA, valB, type, key.columnIndex)\n } else if (type === 'numeric') {\n const numA = parseFloat(valA) || 0\n const numB = parseFloat(valB) || 0\n result = numA - numB\n } else if (type === 'date') {\n const dateA = new Date(valA).getTime() || 0\n const dateB = new Date(valB).getTime() || 0\n result = dateA - dateB\n } else {\n result = valA.localeCompare(valB, undefined, { sensitivity: 'base' })\n }\n\n if (key.direction === 'desc') result = -result\n if (result !== 0) return result\n }\n return 0\n }\n\n rows.sort(chainedCompare)\n rows.forEach(row => tbody.appendChild(row))\n\n // Update sort indicators on thead\n const thead = table.querySelector('thead')\n if (thead) {\n const ths = thead.querySelectorAll('th')\n ths.forEach(th => {\n th.removeAttribute('data-sort-dir')\n th.removeAttribute('data-sort-priority')\n })\n sortKeys.forEach((key, idx) => {\n if (ths[key.columnIndex]) {\n ths[key.columnIndex].setAttribute('data-sort-dir', key.direction || 'asc')\n if (sortKeys.length > 1) {\n ths[key.columnIndex].setAttribute('data-sort-priority', String(idx + 1))\n }\n }\n })\n }\n },\n meta: { tooltip: 'Sort Table' },\n })\n\n engine.commands.register('filterTable', {\n execute(eng, { columnIndex, filterValue } = {}) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n const thead = table.querySelector('thead')\n const tbody = table.querySelector('tbody')\n if (!tbody) return\n\n // Store filter value on the header cell\n if (thead) {\n const ths = thead.querySelectorAll('th')\n if (ths[columnIndex]) {\n if (filterValue) {\n ths[columnIndex].setAttribute('data-filter-value', filterValue)\n } else {\n ths[columnIndex].removeAttribute('data-filter-value')\n }\n }\n }\n\n // Collect all active filters\n const filters = []\n if (thead) {\n thead.querySelectorAll('th').forEach((th, idx) => {\n const val = th.getAttribute('data-filter-value')\n if (val) filters.push({ columnIndex: idx, value: val.toLowerCase() })\n })\n }\n\n // Apply filters to rows\n const rows = tbody.querySelectorAll('tr')\n rows.forEach(row => {\n const cells = row.querySelectorAll('td, th')\n const hidden = filters.some(f => {\n const cellText = (cells[f.columnIndex]?.textContent || '').toLowerCase()\n return !cellText.includes(f.value)\n })\n if (hidden) {\n row.classList.add('rmx-row-hidden')\n } else {\n row.classList.remove('rmx-row-hidden')\n }\n })\n },\n meta: { tooltip: 'Filter Table' },\n })\n\n engine.commands.register('clearTableFilters', {\n execute(eng) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n const thead = table.querySelector('thead')\n if (thead) {\n thead.querySelectorAll('th').forEach(th => {\n th.removeAttribute('data-filter-value')\n })\n }\n const tbody = table.querySelector('tbody')\n if (tbody) {\n tbody.querySelectorAll('tr.rmx-row-hidden').forEach(row => {\n row.classList.remove('rmx-row-hidden')\n })\n }\n },\n meta: { tooltip: 'Clear Table Filters' },\n })\n\n engine.commands.register('formatCell', {\n execute(eng, { format, options = {} } = {}) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td || !format) return\n\n const rawValue = td.getAttribute('data-raw-value') || td.textContent.trim()\n td.setAttribute('data-raw-value', rawValue)\n td.setAttribute('data-cell-format', format)\n\n const num = parseFloat(rawValue)\n\n if (format === 'number' && !isNaN(num)) {\n td.textContent = new Intl.NumberFormat(undefined, {\n minimumFractionDigits: options.decimals ?? 2,\n maximumFractionDigits: options.decimals ?? 2,\n }).format(num)\n } else if (format === 'currency' && !isNaN(num)) {\n td.textContent = new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: options.currency || 'USD',\n minimumFractionDigits: options.decimals ?? 2,\n maximumFractionDigits: options.decimals ?? 2,\n }).format(num)\n } else if (format === 'percentage' && !isNaN(num)) {\n const decimals = options.decimals ?? 1\n td.textContent = (num * 100).toFixed(decimals) + '%'\n } else if (format === 'date') {\n const date = new Date(rawValue)\n if (!isNaN(date.getTime())) {\n td.textContent = new Intl.DateTimeFormat(undefined, {\n dateStyle: options.dateStyle || 'short',\n }).format(date)\n }\n }\n },\n meta: { tooltip: 'Format Cell' },\n })\n\n engine.commands.register('evaluateFormulas', {\n execute(eng) {\n const table = eng.selection.getClosestElement('table')\n if (!table) return\n evaluateTableFormulas(table)\n },\n meta: { tooltip: 'Evaluate Formulas' },\n })\n\n engine.commands.register('splitCell', {\n execute(eng) {\n const td = eng.selection.getClosestElement('td') || eng.selection.getClosestElement('th')\n if (!td) return\n const colspan = td.colSpan || 1\n const rowspan = td.rowSpan || 1\n if (colspan <= 1 && rowspan <= 1) return\n\n td.colSpan = 1\n td.rowSpan = 1\n\n // Add missing cells in the same row\n const tr = td.parentElement\n const cellTag = tr.closest('thead') ? 'th' : td.tagName.toLowerCase()\n for (let c = 1; c < colspan; c++) {\n const newCell = document.createElement(cellTag)\n newCell.innerHTML = '<br>'\n tr.insertBefore(newCell, td.nextSibling)\n }\n\n // Add missing cells in subsequent rows\n if (rowspan > 1) {\n // Compute the visual column index once from the original row\n const targetColIndex = getCellIndex(td)\n let currentRow = tr.nextElementSibling\n for (let r = 1; r < rowspan; r++) {\n if (!currentRow) break\n const rowCellTag = currentRow.closest('thead') ? 'th' : 'td'\n // Find the correct insertion point in this row by walking its cells\n let insertRef = null\n let colAccum = 0\n for (const cell of currentRow.cells) {\n if (colAccum >= targetColIndex) {\n insertRef = cell\n break\n }\n colAccum += cell.colSpan || 1\n }\n for (let c = 0; c < colspan; c++) {\n const newCell = document.createElement(rowCellTag)\n newCell.innerHTML = '<br>'\n currentRow.insertBefore(newCell, insertRef)\n }\n currentRow = currentRow.nextElementSibling\n }\n }\n },\n meta: { tooltip: 'Split Cell' },\n })\n}\n\nfunction getColumnCount(tr) {\n let count = 0\n for (const cell of tr.cells) {\n count += cell.colSpan || 1\n }\n return count\n}\n\nfunction getCellIndex(cell) {\n let index = 0\n let prev = cell.previousElementSibling\n while (prev) {\n index += prev.colSpan || 1\n prev = prev.previousElementSibling\n }\n return index\n}\n\nfunction createEmptyRow(colCount) {\n const tr = document.createElement('tr')\n for (let i = 0; i < colCount; i++) {\n const td = document.createElement('td')\n td.innerHTML = '<br>'\n tr.appendChild(td)\n }\n return tr\n}\n\n/**\n * Detect the data type of a column by sampling its values.\n */\nfunction detectSortDataType(rows, columnIndex) {\n let numericCount = 0\n let dateCount = 0\n let total = 0\n\n for (const row of rows) {\n const cells = row.querySelectorAll('td, th')\n const text = (cells[columnIndex]?.textContent || '').trim()\n if (!text) continue\n total++\n if (!isNaN(parseFloat(text)) && isFinite(text)) numericCount++\n else if (!isNaN(new Date(text).getTime()) && text.length > 4) dateCount++\n }\n\n if (total === 0) return 'alphabetical'\n if (numericCount / total > 0.7) return 'numeric'\n if (dateCount / total > 0.7) return 'date'\n return 'alphabetical'\n}\n\n// ── Formula Engine ──\n\nconst FORMULA_FUNCTIONS = {\n SUM: (args) => args.flat().reduce((a, b) => a + (parseFloat(b) || 0), 0),\n AVERAGE: (args) => {\n const flat = args.flat().map(v => parseFloat(v)).filter(v => !isNaN(v))\n return flat.length ? flat.reduce((a, b) => a + b, 0) / flat.length : 0\n },\n COUNT: (args) => args.flat().filter(v => v !== '' && v != null).length,\n MIN: (args) => Math.min(...args.flat().map(v => parseFloat(v)).filter(v => !isNaN(v))),\n MAX: (args) => Math.max(...args.flat().map(v => parseFloat(v)).filter(v => !isNaN(v))),\n IF: (args) => args[0] ? args[1] : (args[2] ?? ''),\n CONCAT: (args) => args.flat().join(''),\n}\n\n/**\n * Parse a cell reference like \"A1\" into { col, row } (0-indexed).\n */\nfunction parseCellRef(ref) {\n const match = ref.match(/^([A-Z]+)(\\d+)$/)\n if (!match) return null\n let col = 0\n for (let i = 0; i < match[1].length; i++) {\n col = col * 26 + (match[1].charCodeAt(i) - 64)\n }\n return { col: col - 1, row: parseInt(match[2], 10) - 1 }\n}\n\n/**\n * Expand a range like \"A1:B3\" into an array of { col, row } refs.\n */\nfunction expandRange(rangeStr) {\n const parts = rangeStr.split(':')\n if (parts.length !== 2) return null\n const start = parseCellRef(parts[0])\n const end = parseCellRef(parts[1])\n if (!start || !end) return null\n const refs = []\n for (let r = start.row; r <= end.row; r++) {\n for (let c = start.col; c <= end.col; c++) {\n refs.push({ col: c, row: r })\n }\n }\n return refs\n}\n\n/**\n * Get cell value from table by col/row indices.\n * Row 0 = first row of thead (if exists) or first row of tbody.\n */\nfunction getTableCellValue(table, col, row) {\n const allRows = table.querySelectorAll('thead tr, tbody tr')\n const tr = allRows[row]\n if (!tr) return ''\n const cells = tr.querySelectorAll('td, th')\n if (!cells[col]) return ''\n // Use raw value if available, otherwise text content\n return cells[col].getAttribute('data-raw-value') || cells[col].textContent.trim()\n}\n\n/**\n * Tokenize a formula string into tokens.\n */\nfunction tokenizeFormula(formula) {\n const tokens = []\n let i = 0\n while (i < formula.length) {\n const ch = formula[i]\n if (/\\s/.test(ch)) { i++; continue }\n if ('+-*/(),'.includes(ch)) {\n tokens.push({ type: 'op', value: ch })\n i++\n continue\n }\n if (ch === '>' || ch === '<' || ch === '=') {\n if (formula[i + 1] === '=') {\n tokens.push({ type: 'op', value: ch + '=' })\n i += 2\n } else {\n tokens.push({ type: 'op', value: ch })\n i++\n }\n continue\n }\n if (/\\d/.test(ch) || (ch === '.' && /\\d/.test(formula[i + 1]))) {\n let num = ''\n while (i < formula.length && (/\\d/.test(formula[i]) || formula[i] === '.')) {\n num += formula[i++]\n }\n tokens.push({ type: 'number', value: parseFloat(num) })\n continue\n }\n if (ch === '\"') {\n let str = ''\n i++\n while (i < formula.length && formula[i] !== '\"') str += formula[i++]\n i++ // skip closing quote\n tokens.push({ type: 'string', value: str })\n continue\n }\n if (/[A-Z]/i.test(ch)) {\n let ident = ''\n while (i < formula.length && /[A-Z0-9]/i.test(formula[i])) {\n ident += formula[i++]\n }\n // Check if it's a range (e.g., A1:B3)\n if (formula[i] === ':') {\n i++ // skip colon\n let end = ''\n while (i < formula.length && /[A-Z0-9]/i.test(formula[i])) {\n end += formula[i++]\n }\n tokens.push({ type: 'range', value: ident.toUpperCase() + ':' + end.toUpperCase() })\n } else if (/^[A-Z]+\\d+$/i.test(ident)) {\n tokens.push({ type: 'cellref', value: ident.toUpperCase() })\n } else {\n tokens.push({ type: 'func', value: ident.toUpperCase() })\n }\n continue\n }\n i++ // skip unknown\n }\n return tokens\n}\n\n/**\n * Recursive-descent parser and evaluator for formulas.\n */\nfunction evaluateFormula(formulaStr, table, evalStack = new Set()) {\n // Strip leading '=' that marks a cell as a formula\n const cleanFormula = formulaStr.startsWith('=') ? formulaStr.slice(1) : formulaStr\n const tokens = tokenizeFormula(cleanFormula)\n let pos = 0\n\n const peek = () => tokens[pos] || null\n const next = () => tokens[pos++]\n\n function parseExpression() {\n let left = parseTerm()\n while (peek() && (peek().value === '+' || peek().value === '-')) {\n const op = next().value\n const right = parseTerm()\n left = op === '+' ? left + right : left - right\n }\n return left\n }\n\n function parseTerm() {\n let left = parseComparison()\n while (peek() && (peek().value === '*' || peek().value === '/')) {\n const op = next().value\n const right = parseComparison()\n left = op === '*' ? left * right : (right !== 0 ? left / right : NaN)\n }\n return left\n }\n\n function parseComparison() {\n let left = parseFactor()\n if (peek() && ['>', '<', '>=', '<=', '=='].includes(peek().value)) {\n const op = next().value\n const right = parseFactor()\n if (op === '>') return left > right ? 1 : 0\n if (op === '<') return left < right ? 1 : 0\n if (op === '>=') return left >= right ? 1 : 0\n if (op === '<=') return left <= right ? 1 : 0\n if (op === '==') return left === right ? 1 : 0\n }\n return left\n }\n\n function parseFactor() {\n const token = peek()\n if (!token) return 0\n\n if (token.type === 'number') {\n next()\n return token.value\n }\n if (token.type === 'string') {\n next()\n return token.value\n }\n if (token.type === 'cellref') {\n next()\n const ref = parseCellRef(token.value)\n if (!ref) return 0\n const cellKey = token.value\n if (evalStack.has(cellKey)) return '#CIRC!'\n evalStack.add(cellKey)\n const raw = getTableCellValue(table, ref.col, ref.row)\n // If the cell itself has a formula, evaluate it\n const allRows = table.querySelectorAll('thead tr, tbody tr')\n const cell = allRows[ref.row]?.querySelectorAll('td, th')[ref.col]\n const cellFormula = cell?.getAttribute('data-formula')\n let val\n if (cellFormula) {\n val = evaluateFormula(cellFormula, table, evalStack)\n } else {\n val = parseFloat(raw)\n if (isNaN(val)) val = raw\n }\n evalStack.delete(cellKey)\n return val\n }\n if (token.type === 'range') {\n next()\n const refs = expandRange(token.value)\n if (!refs) return []\n return refs.map(r => {\n const raw = getTableCellValue(table, r.col, r.row)\n const num = parseFloat(raw)\n return isNaN(num) ? raw : num\n })\n }\n if (token.type === 'func') {\n const funcName = next().value\n const fn = FORMULA_FUNCTIONS[funcName]\n if (!fn) return '#NAME!'\n // Expect '('\n if (peek()?.value === '(') next()\n const args = []\n while (peek() && peek().value !== ')') {\n if (peek().value === ',') { next(); continue }\n const arg = peek()?.type === 'range' ? parseFactor() : parseExpression()\n args.push(arg)\n }\n if (peek()?.value === ')') next()\n return fn(args)\n }\n if (token.value === '(') {\n next()\n const val = parseExpression()\n if (peek()?.value === ')') next()\n return val\n }\n if (token.value === '-') {\n next()\n return -parseFactor()\n }\n\n next() // skip unknown\n return 0\n }\n\n return parseExpression()\n}\n\n/**\n * Evaluate all formula cells in a table.\n */\nexport function evaluateTableFormulas(table) {\n const cells = table.querySelectorAll('td[data-formula], th[data-formula]')\n for (const cell of cells) {\n const formula = cell.getAttribute('data-formula')\n if (!formula) continue\n const result = evaluateFormula(formula, table)\n if (typeof result === 'string' && result.startsWith('#')) {\n cell.textContent = result\n } else if (typeof result === 'number') {\n // Round to 10 decimal places to avoid floating point display artifacts\n cell.textContent = String(Math.round(result * 1e10) / 1e10)\n } else {\n cell.textContent = result\n }\n }\n}\n","/** Block-level tags considered top-level blocks */\nconst BLOCK_TAGS = new Set([\n 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',\n 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'TABLE', 'HR', 'DIV', 'DETAILS',\n])\n\n/**\n * Get the focused top-level block element (direct child of editor root).\n * @param {object} eng - Editor engine\n * @returns {HTMLElement|null}\n */\nfunction getTopLevelBlock(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return null\n let el = block\n while (el && el.parentElement !== eng.element) {\n el = el.parentElement\n }\n return (el && BLOCK_TAGS.has(el.tagName)) ? el : null\n}\n\n/**\n * Ensure the editor always has at least one empty paragraph.\n * @param {object} eng - Editor engine\n */\nfunction ensureMinimumContent(eng) {\n if (eng.element.children.length === 0) {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n eng.element.appendChild(p)\n const range = document.createRange()\n range.selectNodeContents(p)\n range.collapse(true)\n eng.selection.setRange(range)\n }\n}\n\n/**\n * CSP-compatible block-level commands using Range-based DOM manipulation.\n */\nexport function registerBlockCommands(engine) {\n engine.commands.register('blockquote', {\n execute(eng) {\n const existing = eng.selection.getClosestElement('blockquote')\n if (existing) {\n // Toggle off: unwrap blockquote\n const parent = existing.parentNode\n while (existing.firstChild) {\n parent.insertBefore(existing.firstChild, existing)\n }\n parent.removeChild(existing)\n } else {\n // Wrap current block in <blockquote>\n const block = eng.selection.getParentBlock()\n if (!block) return\n const bq = document.createElement('blockquote')\n block.parentNode.replaceChild(bq, block)\n bq.appendChild(block)\n }\n },\n isActive(eng) {\n return !!eng.selection.getClosestElement('blockquote')\n },\n shortcut: 'mod+shift+9',\n meta: { icon: 'blockquote', tooltip: 'Blockquote' },\n })\n\n engine.commands.register('codeBlock', {\n execute(eng, { language } = {}) {\n const existing = eng.selection.getClosestElement('pre')\n if (existing) {\n // Toggle off: unwrap pre/code\n const text = existing.textContent\n const p = document.createElement('p')\n p.textContent = text\n existing.parentNode.replaceChild(p, existing)\n // Move cursor into the new paragraph\n const range = document.createRange()\n range.selectNodeContents(p)\n range.collapse(false)\n eng.selection.setRange(range)\n } else {\n const range = eng.selection.getRange()\n if (!range) return\n const text = range.collapsed ? '\\n' : range.toString()\n const pre = document.createElement('pre')\n const code = document.createElement('code')\n code.textContent = text\n if (language) {\n code.setAttribute('data-language', language)\n pre.setAttribute('data-language', language)\n }\n pre.appendChild(code)\n\n if (!range.collapsed) {\n range.deleteContents()\n }\n range.insertNode(pre)\n\n // Add paragraph after if needed\n if (!pre.nextSibling) {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n pre.parentNode.insertBefore(p, pre.nextSibling)\n }\n\n const newRange = document.createRange()\n newRange.selectNodeContents(code)\n newRange.collapse(false)\n eng.selection.setRange(newRange)\n\n // Emit event so the syntax highlight plugin can pick it up\n eng.eventBus.emit('codeblock:created', { element: pre, language })\n }\n },\n isActive(eng) {\n return !!eng.selection.getClosestElement('pre')\n },\n shortcut: 'mod+shift+c',\n meta: { icon: 'codeBlock', tooltip: 'Code Block' },\n })\n\n engine.commands.register('horizontalRule', {\n execute(eng) {\n const range = eng.selection.getRange()\n if (!range) return\n\n const hr = document.createElement('hr')\n range.deleteContents()\n range.insertNode(hr)\n\n // Add a paragraph after the hr for continued editing\n if (!hr.nextSibling || hr.nextSibling.tagName !== 'P') {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n hr.parentNode.insertBefore(p, hr.nextSibling)\n }\n\n // Move cursor after the hr\n const newRange = document.createRange()\n newRange.setStartAfter(hr)\n newRange.collapse(true)\n eng.selection.setRange(newRange)\n },\n meta: { icon: 'horizontalRule', tooltip: 'Horizontal Rule' },\n })\n\n // ── Block Operations ────────────────────────────────────────────────\n\n engine.commands.register('moveBlockUp', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block || !block.previousElementSibling) return\n eng.history.snapshot()\n block.parentNode.insertBefore(block, block.previousElementSibling)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Move Block Up' },\n })\n\n engine.commands.register('moveBlockDown', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block || !block.nextElementSibling) return\n eng.history.snapshot()\n block.parentNode.insertBefore(block, block.nextElementSibling.nextSibling)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Move Block Down' },\n })\n\n engine.commands.register('duplicateBlock', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n eng.history.snapshot()\n const clone = block.cloneNode(true)\n block.parentNode.insertBefore(clone, block.nextSibling)\n // Place cursor in the cloned block\n const range = document.createRange()\n range.selectNodeContents(clone)\n range.collapse(false)\n eng.selection.setRange(range)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Duplicate Block' },\n })\n\n engine.commands.register('deleteBlock', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n eng.history.snapshot()\n // Determine where to place cursor after deletion\n const next = block.nextElementSibling\n const prev = block.previousElementSibling\n block.parentNode.removeChild(block)\n ensureMinimumContent(eng)\n // Move cursor to adjacent block if available\n const target = next || prev || eng.element.firstElementChild\n if (target) {\n const range = document.createRange()\n range.selectNodeContents(target)\n range.collapse(true)\n eng.selection.setRange(range)\n }\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Delete Block' },\n })\n\n engine.commands.register('selectBlocks', {\n execute(eng, { blocks } = {}) {\n if (!blocks || !Array.isArray(blocks)) return\n blocks.forEach((el) => {\n if (el && el.classList) {\n el.classList.add('rmx-block-selected')\n }\n })\n },\n meta: { tooltip: 'Select Blocks' },\n })\n\n engine.commands.register('clearBlockSelection', {\n execute(eng) {\n const selected = eng.element.querySelectorAll('.rmx-block-selected')\n selected.forEach((el) => el.classList.remove('rmx-block-selected'))\n },\n meta: { tooltip: 'Clear Block Selection' },\n })\n\n // ── Nested Blocks: Collapsible Sections ─────────────────────────────\n\n engine.commands.register('toggleCollapse', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n eng.history.snapshot()\n\n if (block.tagName === 'DETAILS') {\n // Unwrap: move children out of details, remove the summary\n const parent = block.parentNode\n const summary = block.querySelector('summary')\n // Move all children except summary before the details element\n const children = Array.from(block.childNodes).filter((n) => n !== summary)\n children.forEach((child) => parent.insertBefore(child, block))\n parent.removeChild(block)\n } else {\n // Wrap in <details><summary>\n const details = document.createElement('details')\n details.className = 'rmx-collapsible'\n details.setAttribute('open', '')\n const summary = document.createElement('summary')\n summary.textContent = block.textContent.slice(0, 50) || 'Section'\n block.parentNode.replaceChild(details, block)\n details.appendChild(summary)\n details.appendChild(block)\n }\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Toggle Collapsible Section' },\n })\n\n // ── Block Grouping ──────────────────────────────────────────────────\n\n engine.commands.register('groupBlocks', {\n execute(eng) {\n const selected = Array.from(eng.element.querySelectorAll('.rmx-block-selected'))\n if (selected.length === 0) return\n eng.history.snapshot()\n\n const group = document.createElement('div')\n group.className = 'rmx-block-group'\n\n // Insert group before the first selected block\n selected[0].parentNode.insertBefore(group, selected[0])\n\n // Move all selected blocks into the group\n selected.forEach((el) => {\n el.classList.remove('rmx-block-selected')\n group.appendChild(el)\n })\n\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Group Selected Blocks' },\n })\n\n engine.commands.register('ungroupBlocks', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n\n // Find the closest group div\n let group = block\n if (!group.classList.contains('rmx-block-group')) {\n group = block.closest('.rmx-block-group')\n }\n if (!group || !group.classList.contains('rmx-block-group')) return\n\n eng.history.snapshot()\n const parent = group.parentNode\n while (group.firstChild) {\n parent.insertBefore(group.firstChild, group)\n }\n parent.removeChild(group)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Ungroup Blocks' },\n })\n\n engine.commands.register('moveGroup', {\n execute(eng, { direction } = {}) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n let group = block.classList.contains('rmx-block-group')\n ? block\n : block.closest('.rmx-block-group')\n if (!group || !group.classList.contains('rmx-block-group')) return\n // Ensure group is a direct child of editor\n while (group.parentElement !== eng.element) {\n group = group.parentElement\n if (!group) return\n }\n\n eng.history.snapshot()\n if (direction === 'up' && group.previousElementSibling) {\n group.parentNode.insertBefore(group, group.previousElementSibling)\n } else if (direction === 'down' && group.nextElementSibling) {\n group.parentNode.insertBefore(group, group.nextElementSibling.nextSibling)\n }\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Move Group' },\n })\n\n engine.commands.register('duplicateGroup', {\n execute(eng) {\n const block = getTopLevelBlock(eng)\n if (!block) return\n let group = block.classList.contains('rmx-block-group')\n ? block\n : block.closest('.rmx-block-group')\n if (!group || !group.classList.contains('rmx-block-group')) return\n // Ensure group is a direct child of editor\n while (group.parentElement !== eng.element) {\n group = group.parentElement\n if (!group) return\n }\n\n eng.history.snapshot()\n const clone = group.cloneNode(true)\n group.parentNode.insertBefore(clone, group.nextSibling)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Duplicate Group' },\n })\n}\n","/**\n * Block type conversion commands.\n * Converts block-level elements between types (paragraph, headings, blockquote, code block, lists)\n * while preserving text content.\n */\n\n/** Map of conversion target names to their corresponding tag info */\nconst BLOCK_TYPE_MAP = {\n paragraph: { tag: 'P' },\n heading1: { tag: 'H1' },\n heading2: { tag: 'H2' },\n heading3: { tag: 'H3' },\n heading4: { tag: 'H4' },\n heading5: { tag: 'H5' },\n heading6: { tag: 'H6' },\n blockquote: { tag: 'BLOCKQUOTE' },\n codeBlock: { tag: 'PRE' },\n unorderedList: { tag: 'UL' },\n orderedList: { tag: 'OL' },\n}\n\n/**\n * Get the focused block element in the editor.\n * @param {object} eng - Editor engine\n * @returns {HTMLElement|null}\n */\nfunction getFocusedBlock(eng) {\n const block = eng.selection.getParentBlock()\n if (!block) return null\n // Walk up to find the direct child of the editor element\n let el = block\n while (el && el.parentElement !== eng.element) {\n el = el.parentElement\n }\n return el || null\n}\n\n/**\n * Extract the inner text/HTML content from a block, stripping wrappers.\n * For lists, extracts LI contents. For pre>code, extracts text.\n * @param {HTMLElement} block\n * @returns {string} innerHTML suitable for insertion into a new block\n */\nfunction extractBlockContent(block) {\n const tag = block.tagName\n\n if (tag === 'UL' || tag === 'OL') {\n // Collect text from all list items\n const items = Array.from(block.querySelectorAll('li'))\n if (items.length === 1) {\n return items[0].innerHTML\n }\n // Join multiple items with line breaks\n return items.map((li) => li.innerHTML).join('<br>')\n }\n\n if (tag === 'PRE') {\n const code = block.querySelector('code')\n return code ? code.textContent : block.textContent\n }\n\n if (tag === 'BLOCKQUOTE') {\n // If blockquote wraps a single element, use its innerHTML\n if (block.children.length === 1) {\n return block.children[0].innerHTML\n }\n return block.innerHTML\n }\n\n return block.innerHTML\n}\n\n/**\n * Create the new block element for the target type.\n * @param {string} to - Target type name\n * @param {string} content - Inner HTML content\n * @returns {HTMLElement}\n */\nfunction createTargetBlock(to, content) {\n const info = BLOCK_TYPE_MAP[to]\n if (!info) return null\n\n if (to === 'unorderedList' || to === 'orderedList') {\n const list = document.createElement(info.tag)\n const li = document.createElement('li')\n li.innerHTML = content || '<br>'\n list.appendChild(li)\n return list\n }\n\n if (to === 'codeBlock') {\n const pre = document.createElement('pre')\n const code = document.createElement('code')\n // For code blocks, use text content only (strip HTML)\n const temp = document.createElement('div')\n temp.innerHTML = content\n code.textContent = temp.textContent || '\\n'\n pre.appendChild(code)\n return pre\n }\n\n if (to === 'blockquote') {\n const bq = document.createElement('blockquote')\n const p = document.createElement('p')\n p.innerHTML = content || '<br>'\n bq.appendChild(p)\n return bq\n }\n\n // Simple block: p, h1-h6\n const el = document.createElement(info.tag)\n el.innerHTML = content || '<br>'\n return el\n}\n\nexport function registerBlockConvertCommands(engine) {\n engine.commands.register('convertBlock', {\n execute(eng, { to } = {}) {\n if (!to || !BLOCK_TYPE_MAP[to]) return\n\n const block = getFocusedBlock(eng)\n if (!block) return\n\n // Don't convert if already the target type\n const targetTag = BLOCK_TYPE_MAP[to].tag\n if (block.tagName === targetTag) return\n\n eng.history.snapshot()\n\n const content = extractBlockContent(block)\n const newBlock = createTargetBlock(to, content)\n if (!newBlock) return\n\n block.parentNode.replaceChild(newBlock, block)\n\n // Place cursor in the new block\n const range = document.createRange()\n let cursorTarget = newBlock\n\n if (to === 'unorderedList' || to === 'orderedList') {\n cursorTarget = newBlock.querySelector('li') || newBlock\n } else if (to === 'codeBlock') {\n cursorTarget = newBlock.querySelector('code') || newBlock\n } else if (to === 'blockquote') {\n cursorTarget = newBlock.querySelector('p') || newBlock\n }\n\n range.selectNodeContents(cursorTarget)\n range.collapse(false)\n eng.selection.setRange(range)\n\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Convert Block Type' },\n })\n}\n","/**\n * CSP-compatible font formatting commands.\n *\n * All commands use Selection/Range-based span wrapping instead of the\n * deprecated `document.execCommand()` API. This eliminates CSP violations\n * and ensures forward compatibility with browsers removing execCommand.\n */\n\n/**\n * Wraps the current selection in a span with the given style property/value.\n * If already wrapped in a span with that property, updates the value.\n */\nfunction wrapWithStyle(engine, property, value) {\n if (!value) return\n const sel = engine.selection.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n if (range.collapsed) return\n\n // Check if already wrapped in a styled span\n const parent = engine.selection.getParentElement()\n if (parent && parent.tagName === 'SPAN' && parent.style[property]) {\n parent.style[property] = value\n return\n }\n\n const span = document.createElement('span')\n span.style[property] = value\n\n try {\n range.surroundContents(span)\n } catch {\n const fragment = range.extractContents()\n span.appendChild(fragment)\n range.insertNode(span)\n }\n}\n\n/**\n * Get the computed style value for a CSS property from the current selection.\n */\nfunction getSelectionStyle(engine, property) {\n const parent = engine.selection.getParentElement()\n if (!parent) return false\n try {\n return window.getComputedStyle(parent)[property] || false\n } catch {\n return false\n }\n}\n\nexport function registerFontCommands(engine) {\n engine.commands.register('fontFamily', {\n execute(eng, family) {\n wrapWithStyle(eng, 'fontFamily', family)\n },\n isActive(eng) {\n return getSelectionStyle(eng, 'fontFamily')\n },\n meta: { icon: 'fontFamily', tooltip: 'Font Family' },\n })\n\n engine.commands.register('fontSize', {\n execute(eng, size) {\n if (!size) return\n // Validate numeric value\n if (!/^\\d+(\\.\\d+)?(px|pt|em|rem|%)$/.test(size)) return\n wrapWithStyle(eng, 'fontSize', size)\n },\n meta: { icon: 'fontSize', tooltip: 'Font Size' },\n })\n\n engine.commands.register('foreColor', {\n execute(eng, color) {\n wrapWithStyle(eng, 'color', color)\n },\n isActive(eng) {\n return getSelectionStyle(eng, 'color')\n },\n meta: { icon: 'foreColor', tooltip: 'Text Color' },\n })\n\n engine.commands.register('backColor', {\n execute(eng, color) {\n wrapWithStyle(eng, 'backgroundColor', color)\n },\n isActive(eng) {\n return getSelectionStyle(eng, 'backgroundColor')\n },\n meta: { icon: 'backColor', tooltip: 'Background Color' },\n })\n\n // Typography controls — line height, letter spacing, paragraph spacing\n engine.commands.register('lineHeight', {\n execute(eng, value) {\n if (!value) return\n wrapWithStyle(eng, 'lineHeight', value)\n },\n isActive(eng) {\n return getSelectionStyle(eng, 'lineHeight')\n },\n meta: { icon: 'lineHeight', tooltip: 'Line Height' },\n })\n\n engine.commands.register('letterSpacing', {\n execute(eng, value) {\n if (!value) return\n wrapWithStyle(eng, 'letterSpacing', value)\n },\n isActive(eng) {\n return getSelectionStyle(eng, 'letterSpacing')\n },\n meta: { icon: 'letterSpacing', tooltip: 'Letter Spacing' },\n })\n\n engine.commands.register('paragraphSpacing', {\n execute(eng, value) {\n if (!value) return\n // Apply margin-bottom to the parent block element\n const parent = eng.selection.getParentElement()\n if (!parent) return\n const block = parent.closest('p, h1, h2, h3, h4, h5, h6, li, blockquote, pre, div')\n if (block) {\n block.style.marginBottom = value\n }\n },\n isActive(eng) {\n const parent = eng.selection.getParentElement()\n if (!parent) return false\n const block = parent.closest('p, h1, h2, h3, h4, h5, h6, li, blockquote, pre, div')\n if (!block) return false\n return block.style.marginBottom || false\n },\n meta: { icon: 'paragraphSpacing', tooltip: 'Paragraph Spacing' },\n })\n}\n","export function registerMediaCommands(engine) {\n engine.commands.register('embedMedia', {\n execute(eng, { url }) {\n if (!url) return\n\n const embedUrl = getEmbedUrl(url)\n if (!embedUrl) return\n\n const wrapper = document.createElement('div')\n wrapper.className = 'rmx-embed-wrapper'\n wrapper.setAttribute('contenteditable', 'false')\n wrapper.setAttribute('data-embed-url', url)\n\n const iframe = document.createElement('iframe')\n iframe.src = embedUrl\n iframe.setAttribute('frameborder', '0')\n iframe.setAttribute('allowfullscreen', 'true')\n iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-presentation allow-popups')\n iframe.setAttribute('allow', 'autoplay; picture-in-picture')\n iframe.style.width = '100%'\n iframe.style.height = '100%'\n\n wrapper.appendChild(iframe)\n eng.selection.insertNode(wrapper)\n\n // Add paragraph after embed\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n wrapper.parentNode.insertBefore(p, wrapper.nextSibling)\n },\n meta: { icon: 'embedMedia', tooltip: 'Embed Media' },\n })\n\n engine.commands.register('removeEmbed', {\n execute(eng, { element }) {\n if (!element) return\n const wrapper = element.closest('.rmx-embed-wrapper') || element\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n wrapper.parentNode.replaceChild(p, wrapper)\n },\n meta: { tooltip: 'Remove Embed' },\n })\n}\n\nfunction getEmbedUrl(url) {\n // YouTube\n let match = url.match(/(?:youtube\\.com\\/(?:watch\\?v=|embed\\/|v\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/)\n if (match) {\n return `https://www.youtube.com/embed/${match[1]}`\n }\n\n // Vimeo\n match = url.match(/(?:vimeo\\.com\\/)(\\d+)/)\n if (match) {\n return `https://player.vimeo.com/video/${match[1]}`\n }\n\n // Dailymotion\n match = url.match(/dailymotion\\.com\\/video\\/([a-zA-Z0-9]+)/)\n if (match) {\n return `https://www.dailymotion.com/embed/video/${match[1]}`\n }\n\n return null\n}\n","export function registerFindReplaceCommands(engine) {\n let currentMatches = []\n let currentIndex = -1\n\n function clearHighlights(editorEl) {\n editorEl.querySelectorAll('mark.rmx-find-highlight').forEach((mark) => {\n const parent = mark.parentNode\n parent.replaceChild(document.createTextNode(mark.textContent), mark)\n parent.normalize()\n })\n }\n\n function findMatches(editorEl, searchText, caseSensitive = false) {\n clearHighlights(editorEl)\n if (!searchText) return []\n\n const matches = []\n const walker = document.createTreeWalker(editorEl, NodeFilter.SHOW_TEXT, null)\n const textNodes = []\n\n while (walker.nextNode()) {\n textNodes.push(walker.currentNode)\n }\n\n const flags = caseSensitive ? 'g' : 'gi'\n const regex = new RegExp(escapeRegex(searchText), flags)\n\n // Process text nodes in reverse to avoid offset issues\n for (let i = textNodes.length - 1; i >= 0; i--) {\n const node = textNodes[i]\n const text = node.textContent\n const nodeMatches = []\n let m\n while ((m = regex.exec(text)) !== null) {\n nodeMatches.push({ index: m.index, length: m[0].length })\n }\n\n // Wrap matches in reverse order within this node\n for (let j = nodeMatches.length - 1; j >= 0; j--) {\n const match = nodeMatches[j]\n const range = document.createRange()\n range.setStart(node, match.index)\n range.setEnd(node, match.index + match.length)\n\n const mark = document.createElement('mark')\n mark.className = 'rmx-find-highlight'\n range.surroundContents(mark)\n matches.push(mark)\n }\n }\n\n // Reverse to restore document order (collected in reverse for DOM safety)\n matches.reverse()\n return matches\n }\n\n engine.commands.register('find', {\n execute(eng, { text, caseSensitive }) {\n currentMatches = findMatches(eng.element, text, caseSensitive)\n currentIndex = currentMatches.length > 0 ? 0 : -1\n if (currentIndex >= 0) {\n highlightCurrent()\n }\n eng.eventBus.emit('find:results', {\n total: currentMatches.length,\n current: currentIndex + 1,\n })\n return { total: currentMatches.length, current: currentIndex + 1 }\n },\n shortcut: 'mod+f',\n meta: { icon: 'findReplace', tooltip: 'Find & Replace' },\n })\n\n engine.commands.register('findNext', {\n execute(eng) {\n if (currentMatches.length === 0) return\n currentIndex = (currentIndex + 1) % currentMatches.length\n highlightCurrent()\n eng.eventBus.emit('find:results', {\n total: currentMatches.length,\n current: currentIndex + 1,\n })\n },\n meta: { tooltip: 'Find Next' },\n })\n\n engine.commands.register('findPrev', {\n execute(eng) {\n if (currentMatches.length === 0) return\n currentIndex = (currentIndex - 1 + currentMatches.length) % currentMatches.length\n highlightCurrent()\n eng.eventBus.emit('find:results', {\n total: currentMatches.length,\n current: currentIndex + 1,\n })\n },\n meta: { tooltip: 'Find Previous' },\n })\n\n engine.commands.register('replace', {\n execute(eng, { replaceText }) {\n pruneStaleMatches()\n if (currentMatches.length === 0 || currentIndex < 0 || currentIndex >= currentMatches.length) return\n const mark = currentMatches[currentIndex]\n const textNode = document.createTextNode(replaceText)\n mark.parentNode.replaceChild(textNode, mark)\n currentMatches.splice(currentIndex, 1)\n if (currentMatches.length > 0) {\n currentIndex = Math.min(currentIndex, currentMatches.length - 1)\n highlightCurrent()\n } else {\n currentIndex = -1\n }\n eng.eventBus.emit('find:results', {\n total: currentMatches.length,\n current: currentIndex + 1,\n })\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Replace' },\n })\n\n engine.commands.register('replaceAll', {\n execute(eng, { replaceText }) {\n pruneStaleMatches()\n currentMatches.forEach((mark) => {\n const textNode = document.createTextNode(replaceText)\n mark.parentNode.replaceChild(textNode, mark)\n })\n const count = currentMatches.length\n currentMatches = []\n currentIndex = -1\n eng.eventBus.emit('find:results', { total: 0, current: 0 })\n eng.eventBus.emit('content:change')\n return count\n },\n meta: { tooltip: 'Replace All' },\n })\n\n engine.commands.register('clearFind', {\n execute(eng) {\n clearHighlights(eng.element)\n currentMatches = []\n currentIndex = -1\n },\n meta: { tooltip: 'Clear Find' },\n })\n\n /** Remove stale mark references that are no longer in the document */\n function pruneStaleMatches() {\n currentMatches = currentMatches.filter((m) => m.isConnected)\n if (currentIndex >= currentMatches.length) {\n currentIndex = currentMatches.length > 0 ? currentMatches.length - 1 : -1\n }\n }\n\n function highlightCurrent() {\n pruneStaleMatches()\n currentMatches.forEach((m, i) => {\n m.className = i === currentIndex ? 'rmx-find-highlight rmx-find-current' : 'rmx-find-highlight'\n })\n if (currentMatches[currentIndex]) {\n currentMatches[currentIndex].scrollIntoView({ block: 'center', behavior: 'smooth' })\n }\n }\n}\n\nfunction escapeRegex(str) {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","export function registerSourceModeCommands(engine) {\n engine.commands.register('sourceMode', {\n execute(eng) {\n eng.isSourceMode = !eng.isSourceMode\n eng.eventBus.emit('mode:change', { sourceMode: eng.isSourceMode })\n },\n isActive(eng) {\n return eng.isSourceMode\n },\n shortcut: 'mod+shift+u',\n meta: { icon: 'sourceMode', tooltip: 'Source Code' },\n })\n}\n","// Track how many editors are currently fullscreen so we only restore\n// document.body.style.overflow when the last one exits.\nlet fullscreenCount = 0\n\nexport function registerFullscreenCommands(engine) {\n engine.commands.register('fullscreen', {\n execute(eng) {\n const root = eng.element.closest('.rmx-editor')\n if (!root) return\n\n const isFullscreen = root.classList.contains('rmx-fullscreen')\n if (isFullscreen) {\n root.classList.remove('rmx-fullscreen')\n fullscreenCount = Math.max(0, fullscreenCount - 1)\n if (fullscreenCount === 0) {\n document.body.style.overflow = ''\n }\n } else {\n root.classList.add('rmx-fullscreen')\n fullscreenCount++\n document.body.style.overflow = 'hidden'\n }\n eng.eventBus.emit('fullscreen:toggle', { fullscreen: !isFullscreen })\n },\n isActive(eng) {\n const root = eng.element.closest('.rmx-editor')\n return root ? root.classList.contains('rmx-fullscreen') : false\n },\n shortcut: 'mod+shift+f',\n meta: { icon: 'fullscreen', tooltip: 'Fullscreen' },\n })\n}\n","/**\n * Distraction-free mode — hides toolbar, status bar, and menu bar.\n * Chrome elements reappear on mouse movement (3s timeout to re-hide).\n * Toggle via command or Mod+Shift+D shortcut.\n */\n\nlet hideTimer = null\n\nexport function registerDistractionFreeCommands(engine) {\n engine.commands.register('distractionFree', {\n execute(eng) {\n const root = eng.element.closest('.rmx-editor')\n if (!root) return\n\n const isActive = root.classList.contains('rmx-distraction-free')\n if (isActive) {\n root.classList.remove('rmx-distraction-free')\n root.classList.remove('rmx-df-show-chrome')\n root.removeEventListener('mousemove', showChrome)\n clearTimeout(hideTimer)\n } else {\n root.classList.add('rmx-distraction-free')\n root.addEventListener('mousemove', showChrome)\n }\n eng.eventBus.emit('distractionFree:toggle', { active: !isActive })\n },\n isActive(eng) {\n const root = eng.element.closest('.rmx-editor')\n return root ? root.classList.contains('rmx-distraction-free') : false\n },\n shortcut: 'mod+shift+d',\n meta: { icon: 'distractionFree', tooltip: 'Distraction-Free Mode' },\n })\n}\n\nfunction showChrome(e) {\n const root = e.currentTarget\n if (!root.classList.contains('rmx-distraction-free')) return\n\n root.classList.add('rmx-df-show-chrome')\n clearTimeout(hideTimer)\n hideTimer = setTimeout(() => {\n root.classList.remove('rmx-df-show-chrome')\n }, 3000)\n}\n","/**\n * Split view — side-by-side preview pane showing rendered HTML or markdown.\n * Toggle via command or Mod+Shift+V shortcut.\n */\n\nexport function registerSplitViewCommands(engine) {\n engine.commands.register('toggleSplitView', {\n execute(eng) {\n const root = eng.element.closest('.rmx-editor')\n if (!root) return\n\n const isActive = root.classList.contains('rmx-split-view')\n if (isActive) {\n root.classList.remove('rmx-split-view')\n } else {\n root.classList.add('rmx-split-view')\n }\n eng.eventBus.emit('splitView:toggle', { active: !isActive })\n },\n isActive(eng) {\n const root = eng.element.closest('.rmx-editor')\n return root ? root.classList.contains('rmx-split-view') : false\n },\n shortcut: 'mod+shift+v',\n meta: { icon: 'splitView', tooltip: 'Split View' },\n })\n}\n","/**\n * Color palette presets — save and load named color palettes in localStorage.\n */\n\nconst STORAGE_KEY = 'rmx-color-presets'\n\n/**\n * Get all saved color presets from localStorage.\n * @returns {{ name: string, colors: string[] }[]}\n */\nexport function loadColorPresets() {\n try {\n const raw = localStorage.getItem(STORAGE_KEY)\n if (!raw) return []\n const parsed = JSON.parse(raw)\n return Array.isArray(parsed) ? parsed : []\n } catch {\n return []\n }\n}\n\n/**\n * Save a named color preset to localStorage.\n * @param {string} name\n * @param {string[]} colors\n */\nexport function saveColorPreset(name, colors) {\n if (!name || !Array.isArray(colors)) return\n try {\n const presets = loadColorPresets().filter(p => p.name !== name)\n presets.push({ name, colors })\n localStorage.setItem(STORAGE_KEY, JSON.stringify(presets))\n } catch {\n // localStorage unavailable\n }\n}\n\n/**\n * Delete a named color preset from localStorage.\n * @param {string} name\n */\nexport function deleteColorPreset(name) {\n try {\n const presets = loadColorPresets().filter(p => p.name !== name)\n localStorage.setItem(STORAGE_KEY, JSON.stringify(presets))\n } catch {\n // noop\n }\n}\n\nexport function registerColorPresetCommands(engine) {\n engine.commands.register('saveColorPreset', {\n execute(_eng, name, colors) {\n saveColorPreset(name, colors)\n engine.eventBus.emit('colorPresets:change', { presets: loadColorPresets() })\n },\n meta: { icon: 'colorPreset', tooltip: 'Save Color Preset' },\n })\n\n engine.commands.register('loadColorPresets', {\n execute() {\n return loadColorPresets()\n },\n meta: { icon: 'colorPreset', tooltip: 'Load Color Presets' },\n })\n\n engine.commands.register('deleteColorPreset', {\n execute(_eng, name) {\n deleteColorPreset(name)\n engine.eventBus.emit('colorPresets:change', { presets: loadColorPresets() })\n },\n meta: { icon: 'colorPreset', tooltip: 'Delete Color Preset' },\n })\n}\n","import { htmlToMarkdown, markdownToHtml } from '../utils/markdownConverter.js'\n\nexport function registerMarkdownToggleCommands(engine) {\n engine.commands.register('toggleMarkdown', {\n execute(eng) {\n eng.history.snapshot()\n\n if (eng.isMarkdownMode) {\n // Markdown → HTML: get raw text, convert to rich HTML\n const markdown = eng.element.textContent\n const html = markdownToHtml(markdown)\n eng.setHTML(html)\n eng.isMarkdownMode = false\n } else {\n // HTML → Markdown: get HTML, convert to markdown, show as text\n const html = eng.getHTML()\n const markdown = htmlToMarkdown(html)\n eng.element.textContent = markdown\n eng.isMarkdownMode = true\n }\n\n eng.element.classList.toggle('rmx-markdown-mode', eng.isMarkdownMode)\n eng.eventBus.emit('mode:change:markdown', { markdownMode: eng.isMarkdownMode })\n eng.eventBus.emit('content:change')\n },\n isActive(eng) {\n return eng.isMarkdownMode\n },\n meta: { icon: 'toggleMarkdown', tooltip: 'Toggle Markdown' },\n })\n}\n","const DANGEROUS_PROTOCOL = /^\\s*(javascript|vbscript|data\\s*:\\s*text\\/html)\\s*:/i\n\n/**\n * Format bytes into a human-readable file size string.\n */\nfunction formatFileSize(bytes) {\n if (!bytes || bytes === 0) return ''\n const units = ['B', 'KB', 'MB', 'GB']\n let size = bytes\n let unitIndex = 0\n while (size >= 1024 && unitIndex < units.length - 1) {\n size /= 1024\n unitIndex++\n }\n return `${size % 1 === 0 ? size : size.toFixed(1)} ${units[unitIndex]}`\n}\n\nexport function registerAttachmentCommands(engine) {\n engine.commands.register('insertAttachment', {\n execute(eng, { url, filename = 'file', filesize }) {\n if (!url) return\n\n // Ensure editor has focus (e.g. when called from a modal)\n eng.element.focus()\n\n // If no selection range exists, place cursor at the end\n if (!eng.selection.getRange()) {\n const range = document.createRange()\n range.selectNodeContents(eng.element)\n range.collapse(false)\n eng.selection.setRange(range)\n }\n\n const a = document.createElement('a')\n a.href = DANGEROUS_PROTOCOL.test(url) ? '#' : url\n a.className = 'rmx-attachment'\n a.setAttribute('data-attachment', 'true')\n a.setAttribute('data-filename', filename)\n if (filesize) a.setAttribute('data-filesize', String(filesize))\n a.target = '_blank'\n a.rel = 'noopener noreferrer'\n\n const sizeLabel = filesize ? ` (${formatFileSize(filesize)})` : ''\n a.textContent = `\\u{1F4CE} ${filename}${sizeLabel}`\n\n eng.selection.insertNode(a)\n\n // Add a paragraph after the attachment if it's the last element\n if (!a.nextSibling || a.parentElement === eng.element) {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n a.parentElement.insertBefore(p, a.nextSibling)\n }\n },\n meta: { icon: 'attachment', tooltip: 'Insert Attachment' },\n })\n\n engine.commands.register('removeAttachment', {\n execute(eng, { element }) {\n if (!element || !element.classList?.contains('rmx-attachment')) return\n element.parentNode.removeChild(element)\n },\n meta: { tooltip: 'Remove Attachment' },\n })\n}\n","export function registerImportDocumentCommands(engine) {\n engine.commands.register('importDocument', {\n execute(eng, { html, mode = 'insert' }) {\n if (!html) return\n\n eng.element.focus()\n\n if (mode === 'replace') {\n eng.setHTML(html)\n } else {\n // Insert at cursor\n if (!eng.selection.getRange()) {\n const range = document.createRange()\n range.selectNodeContents(eng.element)\n range.collapse(false)\n eng.selection.setRange(range)\n }\n eng.selection.insertHTML(eng.sanitizer.sanitize(html))\n }\n\n eng.eventBus.emit('content:change')\n },\n meta: { icon: 'importDocument', tooltip: 'Import Document' },\n })\n}\n","/**\n * Slash Commands — inline command palette triggered by Mod+/ (Cmd+/ on Mac, Ctrl+/ on Windows/Linux)\n *\n * Provides a default catalog of slash command items, a filter function,\n * recently-used command tracking, custom command registration,\n * and a registration function that hooks into the editor's keyboard events.\n */\n\nimport { isMac } from '../utils/platform.js'\n\n/** @typedef {{ id: string, label: string, description: string, icon: string, keywords: string[], category: string, action: (engine: any, openModal?: (name: string, data?: any) => void) => void }} SlashCommandItem */\n\n// ---------------------------------------------------------------------------\n// Recently-used commands\n// ---------------------------------------------------------------------------\n\nconst RECENT_STORAGE_KEY = 'rmx-recent-commands'\nconst MAX_RECENT = 5\n\n/**\n * Get the list of recently-used command IDs (most recent first).\n * @returns {string[]}\n */\nexport function getRecentCommands() {\n try {\n const raw = localStorage.getItem(RECENT_STORAGE_KEY)\n if (!raw) return []\n const parsed = JSON.parse(raw)\n return Array.isArray(parsed) ? parsed.slice(0, MAX_RECENT) : []\n } catch {\n return []\n }\n}\n\n/**\n * Record a command execution so it appears in the recently-used list.\n * @param {string} commandId\n */\nexport function recordRecentCommand(commandId) {\n try {\n const recent = getRecentCommands().filter(id => id !== commandId)\n recent.unshift(commandId)\n localStorage.setItem(RECENT_STORAGE_KEY, JSON.stringify(recent.slice(0, MAX_RECENT)))\n } catch {\n // localStorage unavailable — silently ignore\n }\n}\n\n/**\n * Clear the recently-used command history.\n */\nexport function clearRecentCommands() {\n try {\n localStorage.removeItem(RECENT_STORAGE_KEY)\n } catch {\n // noop\n }\n}\n\n// ---------------------------------------------------------------------------\n// Custom command items registry\n// ---------------------------------------------------------------------------\n\n/** @type {SlashCommandItem[]} */\nconst _customItems = []\n\n/**\n * Register one or more custom command items that appear in the command palette.\n * Items are deduplicated by `id` — re-registering the same id replaces the previous item.\n * @param {SlashCommandItem | SlashCommandItem[]} items\n */\nexport function registerCommandItems(items) {\n const arr = Array.isArray(items) ? items : [items]\n for (const item of arr) {\n const idx = _customItems.findIndex(i => i.id === item.id)\n if (idx >= 0) {\n _customItems[idx] = item\n } else {\n _customItems.push(item)\n }\n }\n}\n\n/**\n * Unregister a custom command item by id.\n * @param {string} id\n * @returns {boolean} true if an item was removed\n */\nexport function unregisterCommandItem(id) {\n const idx = _customItems.findIndex(i => i.id === id)\n if (idx >= 0) {\n _customItems.splice(idx, 1)\n return true\n }\n return false\n}\n\n/**\n * Get all registered custom command items.\n * @returns {SlashCommandItem[]}\n */\nexport function getCustomCommandItems() {\n return [..._customItems]\n}\n\n/**\n * Default catalog of slash command items.\n * Each item defines: id, label, description, icon (emoji), keywords for search,\n * category for grouping, and an action function that receives the engine.\n * @type {SlashCommandItem[]}\n */\nexport const SLASH_COMMAND_ITEMS = [\n // Text\n { id: 'heading1', label: 'Heading 1', description: 'Large section heading', icon: 'H1', keywords: ['h1', 'title', 'heading', 'large'], category: 'Text', action: (engine) => engine.executeCommand('heading', 1) },\n { id: 'heading2', label: 'Heading 2', description: 'Medium section heading', icon: 'H2', keywords: ['h2', 'subtitle', 'heading', 'medium'], category: 'Text', action: (engine) => engine.executeCommand('heading', 2) },\n { id: 'heading3', label: 'Heading 3', description: 'Small section heading', icon: 'H3', keywords: ['h3', 'heading', 'small'], category: 'Text', action: (engine) => engine.executeCommand('heading', 3) },\n { id: 'paragraph', label: 'Paragraph', description: 'Plain text block', icon: '\\u00b6', keywords: ['text', 'normal', 'body', 'plain'], category: 'Text', action: (engine) => engine.executeCommand('heading', 'p') },\n { id: 'blockquote', label: 'Blockquote', description: 'Indented quote block', icon: '\\u201c', keywords: ['quote', 'citation', 'indent'], category: 'Text', action: (engine) => engine.executeCommand('blockquote') },\n { id: 'codeBlock', label: 'Code Block', description: 'Monospace code snippet', icon: '<>', keywords: ['code', 'pre', 'monospace', 'snippet', 'programming'], category: 'Text', action: (engine) => engine.executeCommand('codeBlock') },\n\n // Lists\n { id: 'unorderedList', label: 'Bulleted List', description: 'Unordered bullet list', icon: '\\u2022', keywords: ['bullet', 'ul', 'list', 'unordered'], category: 'Lists', action: (engine) => engine.executeCommand('unorderedList') },\n { id: 'orderedList', label: 'Numbered List', description: 'Ordered numbered list', icon: '1.', keywords: ['number', 'ol', 'list', 'ordered'], category: 'Lists', action: (engine) => engine.executeCommand('orderedList') },\n { id: 'taskList', label: 'Task List', description: 'Checklist with toggleable items', icon: '\\u2611', keywords: ['todo', 'checkbox', 'checklist', 'task'], category: 'Lists', action: (engine) => engine.executeCommand('taskList') },\n\n // Media\n { id: 'image', label: 'Image', description: 'Insert an image', icon: '\\ud83d\\uddbc', keywords: ['img', 'photo', 'picture', 'upload'], category: 'Media', action: (_engine, openModal) => openModal?.('image') },\n { id: 'attachment', label: 'Attachment', description: 'Attach a file', icon: '\\ud83d\\udcce', keywords: ['file', 'attach', 'upload', 'document'], category: 'Media', action: (_engine, openModal) => openModal?.('attachment') },\n { id: 'embedMedia', label: 'Embed Media', description: 'Embed a YouTube or Vimeo video', icon: '\\u25b6', keywords: ['video', 'youtube', 'vimeo', 'embed', 'media'], category: 'Media', action: (_engine, openModal) => openModal?.('embed') },\n\n // Layout\n { id: 'horizontalRule', label: 'Horizontal Rule', description: 'Visual divider line', icon: '\\u2015', keywords: ['hr', 'divider', 'line', 'separator', 'rule'], category: 'Layout', action: (engine) => engine.executeCommand('horizontalRule') },\n { id: 'table', label: 'Table', description: 'Insert a data table', icon: '\\u2637', keywords: ['table', 'grid', 'spreadsheet', 'cells'], category: 'Layout', action: (_engine, openModal) => openModal?.('table') },\n\n // Insert — plugin features\n { id: 'insertCallout', label: 'Callout', description: 'Insert a styled callout block', icon: '\\u2139\\ufe0f', keywords: ['callout', 'alert', 'admonition', 'info', 'warning', 'tip', 'note'], category: 'Insert', action: (engine) => engine.commands.has('insertCallout') && engine.executeCommand('insertCallout') },\n { id: 'insertMath', label: 'Math Equation', description: 'Insert a LaTeX math expression', icon: '\\u{1D453}', keywords: ['math', 'equation', 'latex', 'formula', 'katex'], category: 'Insert', action: (engine) => engine.commands.has('insertMath') && engine.executeCommand('insertMath', { latex: '', displayMode: true }) },\n { id: 'insertToc', label: 'Table of Contents', description: 'Insert an auto-generated table of contents', icon: '\\ud83d\\udcdc', keywords: ['toc', 'contents', 'outline', 'navigation', 'index'], category: 'Insert', action: (engine) => engine.commands.has('insertToc') && engine.executeCommand('insertToc') },\n { id: 'insertBookmark', label: 'Bookmark', description: 'Insert a bookmark anchor for linking', icon: '\\ud83d\\udd16', keywords: ['bookmark', 'anchor', 'link', 'target', 'reference'], category: 'Insert', action: (engine) => engine.commands.has('insertBookmark') && engine.executeCommand('insertBookmark', { name: 'bookmark' }) },\n { id: 'insertMergeTag', label: 'Merge Tag', description: 'Insert a template merge tag', icon: '\\ud83c\\udff7\\ufe0f', keywords: ['merge', 'tag', 'template', 'variable', 'placeholder', 'dynamic'], category: 'Insert', action: (engine) => engine.commands.has('insertMergeTag') && engine.executeCommand('insertMergeTag', { tag: 'name' }) },\n { id: 'addComment', label: 'Comment', description: 'Add a comment to the selected text', icon: '\\ud83d\\udcac', keywords: ['comment', 'annotate', 'annotation', 'note', 'review', 'feedback'], category: 'Insert', action: (engine) => engine.commands.has('addComment') && engine.executeCommand('addComment') },\n\n // Advanced\n { id: 'findReplace', label: 'Find & Replace', description: 'Search and replace text', icon: '\\ud83d\\udd0d', keywords: ['find', 'search', 'replace'], category: 'Advanced', action: (_engine, openModal) => openModal?.('findReplace') },\n { id: 'sourceMode', label: 'Source Code', description: 'View and edit raw HTML', icon: '</>', keywords: ['html', 'source', 'code', 'raw'], category: 'Advanced', action: (engine) => engine.executeCommand('sourceMode') },\n { id: 'export', label: 'Export', description: 'Export as PDF, DOCX, or Markdown', icon: '\\ud83d\\udce4', keywords: ['export', 'download', 'pdf', 'docx', 'save'], category: 'Advanced', action: (_engine, openModal) => openModal?.('export') },\n { id: 'importDocument', label: 'Import Document', description: 'Import from file', icon: '\\ud83d\\udce5', keywords: ['import', 'upload', 'open', 'file'], category: 'Advanced', action: (_engine, openModal) => openModal?.('importDocument') },\n { id: 'toggleAnalytics', label: 'Toggle Analytics', description: 'Show or hide content analytics panel', icon: '\\ud83d\\udcca', keywords: ['analytics', 'readability', 'stats', 'word count', 'reading time'], category: 'Advanced', action: (engine) => engine.commands.has('toggleAnalytics') && engine.executeCommand('toggleAnalytics') },\n { id: 'toggleSpellcheck', label: 'Toggle Spellcheck', description: 'Enable or disable spelling and grammar checking', icon: '\\u2713', keywords: ['spellcheck', 'spelling', 'grammar', 'proofread', 'writing'], category: 'Advanced', action: (engine) => engine.commands.has('toggleSpellcheck') && engine.executeCommand('toggleSpellcheck') },\n { id: 'checkGrammar', label: 'Check Grammar', description: 'Run grammar and style analysis on content', icon: 'Aa', keywords: ['grammar', 'check', 'proofread', 'style', 'writing', 'passive'], category: 'Advanced', action: (engine) => engine.commands.has('checkGrammar') && engine.executeCommand('checkGrammar') },\n { id: 'startCollaboration', label: 'Start Collaboration', description: 'Connect to collaboration server and start co-editing', icon: '\\ud83d\\udc65', keywords: ['collaborate', 'realtime', 'share', 'multiplayer', 'co-edit'], category: 'Advanced', action: (engine) => engine.commands.has('startCollaboration') && engine.executeCommand('startCollaboration') },\n { id: 'stopCollaboration', label: 'Stop Collaboration', description: 'Disconnect from collaboration session', icon: '\\ud83d\\udeab', keywords: ['disconnect', 'leave', 'stop', 'collaboration'], category: 'Advanced', action: (engine) => engine.commands.has('stopCollaboration') && engine.executeCommand('stopCollaboration') },\n { id: 'toggleMarkdown', label: 'Toggle Markdown', description: 'Switch between WYSIWYG and Markdown editing', icon: 'M\\u2193', keywords: ['markdown', 'toggle', 'wysiwyg', 'mode'], category: 'Advanced', action: (engine) => engine.executeCommand('toggleMarkdown') },\n { id: 'fullscreen', label: 'Fullscreen', description: 'Enter distraction-free fullscreen mode', icon: '\\u26f6', keywords: ['fullscreen', 'expand', 'zen', 'focus', 'distraction'], category: 'Advanced', action: (engine) => engine.executeCommand('fullscreen') },\n { id: 'removeFormat', label: 'Clear Formatting', description: 'Remove all formatting from selection', icon: '\\ud83d\\udeab', keywords: ['clear', 'remove', 'format', 'plain', 'reset'], category: 'Advanced', action: (engine) => engine.executeCommand('removeFormat') },\n { id: 'distractionFree', label: 'Distraction-Free', description: 'Hide toolbar and status bar, show on mouse move', icon: '\\ud83e\\uddd8', keywords: ['distraction', 'zen', 'focus', 'minimal', 'clean'], category: 'Advanced', action: (engine) => engine.executeCommand('distractionFree') },\n { id: 'toggleSplitView', label: 'Split View', description: 'Side-by-side edit and preview pane', icon: '\\u25eb', keywords: ['split', 'preview', 'side', 'dual', 'pane'], category: 'Advanced', action: (engine) => engine.executeCommand('toggleSplitView') },\n]\n\n/**\n * Filter slash command items by query string.\n * Matches against label, description, and keywords. Label matches are prioritized.\n *\n * When `options.pinRecent` is true (default) and there is no query, recently-used\n * commands are placed at the top under a \"Recent\" category.\n *\n * @param {SlashCommandItem[]} items\n * @param {string} query\n * @param {{ pinRecent?: boolean }} [options]\n * @returns {SlashCommandItem[]}\n */\nexport function filterSlashItems(items, query, options = {}) {\n const { pinRecent = true } = options\n\n if (!query) {\n if (!pinRecent) return items\n const recentIds = getRecentCommands()\n if (recentIds.length === 0) return items\n\n const recentItems = []\n for (const id of recentIds) {\n const item = items.find(i => i.id === id)\n if (item) {\n recentItems.push({ ...item, category: 'Recent' })\n }\n }\n if (recentItems.length === 0) return items\n return [...recentItems, ...items]\n }\n\n const q = query.toLowerCase()\n const labelMatches = []\n const otherMatches = []\n for (const item of items) {\n if (item.label.toLowerCase().includes(q)) {\n labelMatches.push(item)\n } else if (\n item.description.toLowerCase().includes(q) ||\n item.keywords.some(k => k.includes(q))\n ) {\n otherMatches.push(item)\n }\n }\n return [...labelMatches, ...otherMatches]\n}\n\n/**\n * Register slash command support on the engine.\n *\n * Triggered by Mod+/ (Cmd+/ on Mac, Ctrl+/ on Windows/Linux). Opens a floating\n * palette at the caret. While active, typing filters the list without inserting\n * text into the editor. The React layer handles the palette UI.\n *\n * @param {import('../core/EditorEngine.js').EditorEngine} engine\n */\nexport function registerSlashCommands(engine) {\n let slashActive = false\n let query = ''\n\n function getCaretRect() {\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return null\n const range = sel.getRangeAt(0).cloneRange()\n range.collapse(true)\n const rect = range.getBoundingClientRect()\n if (rect.width === 0 && rect.height === 0) {\n // Fallback: insert a zero-width space to measure\n const span = document.createElement('span')\n span.textContent = '\\u200b'\n range.insertNode(span)\n const spanRect = span.getBoundingClientRect()\n span.parentNode.removeChild(span)\n sel.getRangeAt(0).startContainer.parentNode?.normalize()\n return spanRect\n }\n return rect\n }\n\n function open() {\n const rect = getCaretRect()\n if (!rect) return\n slashActive = true\n query = ''\n engine.eventBus.emit('slash:open', { rect, query: '' })\n }\n\n function close() {\n if (slashActive) {\n slashActive = false\n query = ''\n engine.eventBus.emit('slash:close')\n }\n }\n\n function handleKeyDown(e) {\n // Mod+/ toggles the slash palette\n const mod = isMac() ? e.metaKey : e.ctrlKey\n if (mod && e.key === '/') {\n e.preventDefault()\n e.stopPropagation()\n if (slashActive) {\n close()\n } else {\n open()\n }\n return\n }\n\n if (!slashActive) return\n\n // Navigation and selection keys\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === 'Tab') {\n e.preventDefault()\n e.stopPropagation()\n engine.eventBus.emit('slash:keydown', { key: e.key })\n return\n }\n\n if (e.key === 'Escape') {\n e.preventDefault()\n e.stopPropagation()\n close()\n return\n }\n\n // Backspace removes last query character\n if (e.key === 'Backspace') {\n e.preventDefault()\n e.stopPropagation()\n if (query.length > 0) {\n query = query.slice(0, -1)\n engine.eventBus.emit('slash:query', { query })\n } else {\n close()\n }\n return\n }\n\n // Printable characters → filter query (don't insert into editor)\n if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {\n e.preventDefault()\n e.stopPropagation()\n query += e.key\n engine.eventBus.emit('slash:query', { query })\n }\n }\n\n // Listen for execute events from the React layer\n const unsubExecute = engine.eventBus.on('slash:execute', ({ item, openModal }) => {\n slashActive = false\n query = ''\n recordRecentCommand(item.id)\n item.action(engine, openModal)\n engine.eventBus.emit('content:change')\n })\n\n engine.element.addEventListener('keydown', handleKeyDown, true)\n\n // Register cleanup on engine destroy event\n const cleanup = () => {\n engine.element?.removeEventListener('keydown', handleKeyDown, true)\n unsubExecute()\n }\n engine.eventBus.on('destroy', cleanup)\n}\n","/**\n * Autosave Storage Providers\n *\n * Pluggable adapters for persisting editor content to different backends.\n * Each provider implements: save(key, content, metadata), load(key), clear(key).\n */\n\nconst ENVELOPE_VERSION = 1\n\n/** Validates that a URL from a callback uses http or https protocol */\nfunction validateUrl(url) {\n try {\n const parsed = new URL(url)\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error('URL must use http or https protocol')\n }\n return url\n } catch (err) {\n throw new Error(`Invalid URL from callback: ${err.message}`)\n }\n}\n\n/** Wraps content in a versioned JSON envelope with timestamp */\nfunction createEnvelope(content, metadata = {}) {\n return JSON.stringify({\n content,\n timestamp: Date.now(),\n version: ENVELOPE_VERSION,\n ...metadata,\n })\n}\n\n/** Parses a JSON envelope, returns null on failure */\nfunction parseEnvelope(json) {\n if (!json) return null\n try {\n const data = JSON.parse(json)\n if (!data || typeof data.content !== 'string') return null\n return data\n } catch {\n return null\n }\n}\n\n// ── LocalStorageProvider ─────────────────────────────────────────\n\nexport class LocalStorageProvider {\n constructor({ prefix = 'rmx-autosave' } = {}) {\n this.prefix = prefix\n }\n\n _key(key) {\n return `${this.prefix}:${key}`\n }\n\n async save(key, content, metadata) {\n try {\n localStorage.setItem(this._key(key), createEnvelope(content, metadata))\n } catch (err) {\n throw new Error(`LocalStorage save failed: ${err.message}`)\n }\n }\n\n async load(key) {\n try {\n const raw = localStorage.getItem(this._key(key))\n return parseEnvelope(raw)\n } catch {\n return null\n }\n }\n\n async clear(key) {\n try {\n localStorage.removeItem(this._key(key))\n } catch {\n // Ignore removal errors\n }\n }\n\n /**\n * Synchronous save for beforeunload — localStorage is synchronous.\n * Returns true on success, false on failure.\n */\n saveSync(key, content, metadata) {\n try {\n localStorage.setItem(this._key(key), createEnvelope(content, metadata))\n return true\n } catch {\n return false\n }\n }\n}\n\n// ── SessionStorageProvider ───────────────────────────────────────\n\nexport class SessionStorageProvider {\n constructor({ prefix = 'rmx-autosave' } = {}) {\n this.prefix = prefix\n }\n\n _key(key) {\n return `${this.prefix}:${key}`\n }\n\n async save(key, content, metadata) {\n try {\n sessionStorage.setItem(this._key(key), createEnvelope(content, metadata))\n } catch (err) {\n throw new Error(`SessionStorage save failed: ${err.message}`)\n }\n }\n\n async load(key) {\n try {\n const raw = sessionStorage.getItem(this._key(key))\n return parseEnvelope(raw)\n } catch {\n return null\n }\n }\n\n async clear(key) {\n try {\n sessionStorage.removeItem(this._key(key))\n } catch {\n // Ignore removal errors\n }\n }\n\n saveSync(key, content, metadata) {\n try {\n sessionStorage.setItem(this._key(key), createEnvelope(content, metadata))\n return true\n } catch {\n return false\n }\n }\n}\n\n// ── FileSystemProvider ──────────────────────────────────────────\n\nexport class FileSystemProvider {\n /**\n * @param {Object} opts\n * @param {(key: string, data: string) => Promise<void>} opts.writeFn\n * @param {(key: string) => Promise<string|null>} opts.readFn\n * @param {(key: string) => Promise<void>} opts.deleteFn\n */\n constructor({ writeFn, readFn, deleteFn }) {\n if (typeof writeFn !== 'function') throw new Error('FileSystemProvider requires a writeFn')\n if (typeof readFn !== 'function') throw new Error('FileSystemProvider requires a readFn')\n if (typeof deleteFn !== 'function') throw new Error('FileSystemProvider requires a deleteFn')\n this.writeFn = writeFn\n this.readFn = readFn\n this.deleteFn = deleteFn\n }\n\n async save(key, content, metadata) {\n const envelope = createEnvelope(content, metadata)\n await this.writeFn(key, envelope)\n }\n\n async load(key) {\n const raw = await this.readFn(key)\n return parseEnvelope(raw)\n }\n\n async clear(key) {\n await this.deleteFn(key)\n }\n}\n\n// ── CloudProvider ───────────────────────────────────────────────\n\nexport class CloudProvider {\n /**\n * @param {Object} opts\n * @param {string} opts.endpoint - Base URL for the cloud API\n * @param {Record<string, string>} [opts.headers] - Auth/content headers\n * @param {string} [opts.method='PUT'] - HTTP method for save\n * @param {typeof fetch} [opts.fetchFn] - Custom fetch implementation\n * @param {(key: string) => string} [opts.buildUrl] - Custom URL builder (e.g. for S3 presigned URLs)\n * @param {(key: string, content: string) => string|FormData} [opts.buildBody] - Custom body builder\n * @param {(key: string) => string} [opts.buildLoadUrl] - Custom URL for load/GET requests\n * @param {(key: string) => string} [opts.buildDeleteUrl] - Custom URL for delete requests\n *\n * **CSRF Protection:** When using cloud autosave with session-based authentication,\n * include a CSRF token in the `headers` option. For example:\n * ```js\n * new CloudProvider({\n * endpoint: 'https://api.example.com/autosave',\n * headers: {\n * 'X-CSRF-Token': getCsrfToken(),\n * 'Authorization': 'Bearer ...',\n * },\n * })\n * ```\n * For token-based auth (Bearer tokens), CSRF protection is typically not needed\n * since the token itself proves the request origin.\n */\n constructor({\n endpoint,\n headers = {},\n method = 'PUT',\n fetchFn,\n buildUrl,\n buildBody,\n buildLoadUrl,\n buildDeleteUrl,\n }) {\n if (!endpoint && !buildUrl) throw new Error('CloudProvider requires an endpoint or buildUrl')\n // Validate endpoint URL to prevent injection via user-supplied strings\n if (endpoint) {\n try {\n const parsed = new URL(endpoint)\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n throw new Error('CloudProvider endpoint must use http or https protocol')\n }\n } catch (err) {\n if (err.message.includes('protocol')) throw err\n throw new Error(`CloudProvider endpoint is not a valid URL: ${endpoint}`)\n }\n }\n this.endpoint = endpoint\n this.headers = headers\n this.method = method\n this._fetch = fetchFn || (typeof fetch !== 'undefined' ? fetch.bind(globalThis) : null)\n this.buildUrl = buildUrl\n this.buildBody = buildBody\n this.buildLoadUrl = buildLoadUrl || buildUrl\n this.buildDeleteUrl = buildDeleteUrl || buildUrl\n if (!this._fetch) throw new Error('CloudProvider requires fetch or a custom fetchFn')\n }\n\n _saveUrl(key) {\n return this.buildUrl ? validateUrl(this.buildUrl(key)) : this.endpoint\n }\n\n _loadUrl(key) {\n if (this.buildLoadUrl) return validateUrl(this.buildLoadUrl(key))\n const sep = this.endpoint.includes('?') ? '&' : '?'\n return `${this.endpoint}${sep}key=${encodeURIComponent(key)}`\n }\n\n _deleteUrl(key) {\n return this.buildDeleteUrl ? validateUrl(this.buildDeleteUrl(key)) : this._loadUrl(key)\n }\n\n async _fetchWithRetry(url, opts, retries = 1) {\n try {\n const res = await this._fetch(url, opts)\n if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`)\n return res\n } catch (err) {\n if (retries > 0) {\n await new Promise(r => setTimeout(r, 1000))\n return this._fetchWithRetry(url, opts, retries - 1)\n }\n throw err\n }\n }\n\n async save(key, content, metadata) {\n const body = this.buildBody\n ? this.buildBody(key, content)\n : createEnvelope(content, metadata)\n\n const headers = { ...this.headers }\n if (!this.buildBody && !headers['Content-Type']) {\n headers['Content-Type'] = 'application/json'\n }\n\n await this._fetchWithRetry(this._saveUrl(key), {\n method: this.method,\n headers,\n body,\n })\n }\n\n async load(key) {\n try {\n const res = await this._fetch(this._loadUrl(key), {\n method: 'GET',\n headers: this.headers,\n })\n if (!res.ok) {\n if (res.status === 404) return null\n throw new Error(`HTTP ${res.status}`)\n }\n const text = await res.text()\n return parseEnvelope(text)\n } catch {\n return null\n }\n }\n\n async clear(key) {\n try {\n await this._fetch(this._deleteUrl(key), {\n method: 'DELETE',\n headers: this.headers,\n })\n } catch {\n // Best-effort delete\n }\n }\n}\n\n// ── CustomProvider ──────────────────────────────────────────────\n\nexport class CustomProvider {\n /**\n * @param {Object} opts\n * @param {(key: string, content: string, metadata?: object) => Promise<void>} opts.save\n * @param {(key: string) => Promise<{content: string, timestamp: number}|null>} opts.load\n * @param {(key: string) => Promise<void>} opts.clear\n */\n constructor({ save, load, clear }) {\n if (typeof save !== 'function') throw new Error('CustomProvider requires a save function')\n if (typeof load !== 'function') throw new Error('CustomProvider requires a load function')\n if (typeof clear !== 'function') throw new Error('CustomProvider requires a clear function')\n this._save = save\n this._load = load\n this._clear = clear\n }\n\n async save(key, content, metadata) {\n await this._save(key, content, metadata)\n }\n\n async load(key) {\n return this._load(key)\n }\n\n async clear(key) {\n await this._clear(key)\n }\n}\n\n// ── Factory ─────────────────────────────────────────────────────\n\n/**\n * Resolves a provider config shorthand into a StorageProvider instance.\n *\n * @param {string|object|undefined} config\n * - undefined / 'localStorage' → LocalStorageProvider\n * - 'sessionStorage' → SessionStorageProvider\n * - { save, load, clear } → CustomProvider (or already a provider instance)\n * - { endpoint, ... } → CloudProvider\n * - { writeFn, readFn, deleteFn } → FileSystemProvider\n * @returns {StorageProvider}\n */\nexport function createStorageProvider(config) {\n if (!config || config === 'localStorage') {\n return new LocalStorageProvider()\n }\n if (config === 'sessionStorage') {\n return new SessionStorageProvider()\n }\n if (typeof config !== 'object') {\n throw new Error(`Unknown storage provider: ${config}`)\n }\n\n // Already a provider instance (has save/load/clear methods)\n if (typeof config.save === 'function' && typeof config.load === 'function' && typeof config.clear === 'function') {\n // Check if it's a plain config object (CustomProvider) vs an existing instance\n if (config.constructor === Object) {\n return new CustomProvider(config)\n }\n // Already instantiated provider\n return config\n }\n\n // Cloud provider (has endpoint or buildUrl)\n if (config.endpoint || config.buildUrl) {\n return new CloudProvider(config)\n }\n\n // FileSystem provider (has writeFn)\n if (config.writeFn) {\n return new FileSystemProvider(config)\n }\n\n // LocalStorageProvider with custom prefix\n if (config.prefix) {\n return new LocalStorageProvider(config)\n }\n\n throw new Error('Unable to resolve storage provider from config')\n}\n","/**\n * AutosaveManager — framework-agnostic autosave engine.\n *\n * Subscribes to `content:change` events, debounces saves, runs periodic\n * interval saves, and checks for crash-recoverable content on init.\n *\n * Emits via engine.eventBus:\n * - autosave:saving — save started\n * - autosave:saved — save succeeded, payload: { timestamp }\n * - autosave:error — save failed, payload: { error }\n * - autosave:recovered — recovery data found, payload: { recoveredContent, timestamp }\n */\n\nimport { createStorageProvider } from '../autosave/providers.js'\n\n/**\n * Module-level registry of active AutosaveManager instances, keyed by config.key.\n * Prevents duplicate managers for the same storage key.\n * @type {Map<string, AutosaveManager>}\n */\nconst _managerRegistry = new Map()\n\nexport class AutosaveManager {\n /**\n * @param {import('./EditorEngine.js').EditorEngine} engine\n * @param {Object} [options]\n * @param {string|object} [options.provider] - Storage provider config (see createStorageProvider)\n * @param {string} [options.key='rmx-default'] - Storage key for this editor instance\n * @param {number} [options.interval=30000] - Periodic save interval in ms\n * @param {number} [options.debounce=2000] - Debounce delay after content change in ms\n * @param {boolean} [options.enabled=true] - Whether autosave is active\n */\n constructor(engine, options = {}) {\n this.engine = engine\n this.provider = createStorageProvider(options.provider)\n this.key = options.key || 'rmx-default'\n\n // Deduplication: destroy existing manager for the same key\n const existing = _managerRegistry.get(this.key)\n if (existing) {\n console.warn(`[Remyx] AutosaveManager for key \"${this.key}\" already exists. Destroying old instance.`)\n existing.destroy()\n }\n _managerRegistry.set(this.key, this)\n this.interval = options.interval ?? 30000\n this.debounceMs = options.debounce ?? 2000\n this.enabled = options.enabled !== false\n\n this._debounceTimer = null\n this._intervalTimer = null\n this._isSaving = false\n this._pendingSave = false\n this._lastSavedContent = null\n this._contentChangeHandler = null\n this._beforeUnloadHandler = null\n this._destroyed = false\n this._consecutiveErrors = 0\n this._maxRetries = options.maxRetries ?? 5\n }\n\n /**\n * Start listening for content changes and begin periodic saves.\n * Subscribes to `content:change` events on the engine's EventBus,\n * starts a periodic save interval, and registers a `beforeunload`\n * handler for last-chance saves. Call this after the engine is fully initialized.\n * @returns {void}\n */\n init() {\n if (!this.enabled || this._destroyed) return\n\n // Subscribe to content changes\n this._contentChangeHandler = () => {\n this._scheduleDebouncedSave()\n }\n this._unsubContentChange = this.engine.eventBus.on('content:change', this._contentChangeHandler)\n\n // Start periodic interval saves\n if (this.interval > 0) {\n this._intervalTimer = setInterval(() => {\n this.save()\n }, this.interval)\n }\n\n // Register beforeunload for last-chance save\n if (typeof window !== 'undefined') {\n this._beforeUnloadHandler = () => {\n this._attemptSyncSave()\n }\n window.addEventListener('beforeunload', this._beforeUnloadHandler)\n }\n }\n\n /**\n * Stop all timers, remove event listeners, and perform a final save.\n * Clears the debounce timer, periodic interval, content:change subscription,\n * and beforeunload handler. A fire-and-forget save is attempted before the\n * instance is marked as destroyed.\n * @returns {void}\n */\n destroy() {\n // Clear timers\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer)\n this._debounceTimer = null\n }\n if (this._intervalTimer) {\n clearInterval(this._intervalTimer)\n this._intervalTimer = null\n }\n\n // Remove content:change listener\n if (this._unsubContentChange) {\n this._unsubContentChange()\n this._unsubContentChange = null\n }\n\n // Remove beforeunload\n if (this._beforeUnloadHandler && typeof window !== 'undefined') {\n window.removeEventListener('beforeunload', this._beforeUnloadHandler)\n this._beforeUnloadHandler = null\n }\n\n // Remove from registry\n if (_managerRegistry.get(this.key) === this) {\n _managerRegistry.delete(this.key)\n }\n\n // Final save attempt (fire-and-forget), then mark as destroyed\n this.save().catch(() => {}).finally(() => { this._destroyed = true })\n }\n\n /**\n * Save the current editor content to the storage provider.\n * Deduplicates by comparing against the last saved content to avoid\n * redundant writes. Prevents concurrent saves; if a save is requested\n * while one is in progress, it is queued and retried with exponential\n * backoff on failure.\n * @returns {Promise<void>}\n */\n async save() {\n if (!this.engine || this._destroyed) return\n\n const content = this.engine.getHTML()\n\n // Skip if content hasn't changed since last save\n if (content === this._lastSavedContent) return\n\n // Prevent concurrent saves\n if (this._isSaving) {\n this._pendingSave = true\n return\n }\n\n this._isSaving = true\n this.engine.eventBus.emit('autosave:saving')\n\n try {\n await this.provider.save(this.key, content)\n this._lastSavedContent = content\n this._consecutiveErrors = 0\n\n const timestamp = Date.now()\n this.engine.eventBus.emit('autosave:saved', { timestamp })\n } catch (error) {\n this._consecutiveErrors++\n this.engine.eventBus.emit('autosave:error', { error, retryCount: this._consecutiveErrors })\n } finally {\n this._isSaving = false\n\n // If a save was requested while we were saving, retry with exponential backoff\n if (this._pendingSave) {\n this._pendingSave = false\n if (this._consecutiveErrors >= this._maxRetries) {\n this.engine.eventBus.emit('autosave:error', {\n error: new Error(`Autosave failed after ${this._maxRetries} consecutive attempts`),\n fatal: true,\n })\n } else if (this._consecutiveErrors > 0) {\n // Exponential backoff: 1s, 2s, 4s, 8s, 16s\n const delay = Math.min(1000 * Math.pow(2, this._consecutiveErrors - 1), 30000)\n setTimeout(() => this.save(), delay)\n } else {\n this.save()\n }\n }\n }\n }\n\n /**\n * Check the storage provider for recoverable content from a previous session.\n * Compares stored content against the current editor HTML and returns\n * recovery data only when the stored version differs meaningfully.\n * @param {string} currentContent - The editor's current HTML content to compare against\n * @returns {Promise<{recoveredContent: string, timestamp: number}|null>} Recovery data if available, or null\n */\n async checkRecovery(currentContent) {\n try {\n const stored = await this.provider.load(this.key)\n if (!stored || !stored.content) return null\n\n // Normalize for comparison (trim whitespace)\n const storedNorm = stored.content.trim()\n const currentNorm = (currentContent || '').trim()\n\n // Only offer recovery if content actually differs and stored content is non-empty\n if (storedNorm && storedNorm !== currentNorm) {\n return {\n recoveredContent: stored.content,\n timestamp: stored.timestamp || Date.now(),\n }\n }\n\n return null\n } catch {\n return null\n }\n }\n\n /**\n * Clear the stored recovery content from the provider.\n * Typically called after the user dismisses a recovery banner\n * or restores the recovered content.\n * @returns {Promise<void>}\n */\n async clearRecovery() {\n try {\n await this.provider.clear(this.key)\n } catch {\n // Best-effort clear\n }\n }\n\n /**\n * Schedule a debounced save after content changes.\n * @private\n */\n _scheduleDebouncedSave() {\n if (this._debounceTimer) {\n clearTimeout(this._debounceTimer)\n }\n this._debounceTimer = setTimeout(() => {\n this._debounceTimer = null\n this.save()\n }, this.debounceMs)\n }\n\n /**\n * Attempt a synchronous save for beforeunload.\n * Only works for providers that support saveSync (localStorage/sessionStorage).\n * For async providers, uses navigator.sendBeacon as fallback.\n * @private\n */\n _attemptSyncSave() {\n if (!this.engine || this._destroyed) return\n\n const content = this.engine.getHTML()\n if (content === this._lastSavedContent) return\n\n // Try sync save (localStorage/sessionStorage providers)\n if (typeof this.provider.saveSync === 'function') {\n this.provider.saveSync(this.key, content)\n return\n }\n\n // Fallback: sendBeacon for async providers\n if (typeof navigator !== 'undefined' && navigator.sendBeacon && this.provider.endpoint) {\n try {\n const body = JSON.stringify({\n key: this.key,\n content,\n timestamp: Date.now(),\n version: 1,\n })\n navigator.sendBeacon(this.provider.endpoint, body)\n } catch {\n // Best-effort\n }\n }\n }\n}\n","/**\n * @typedef {Object} PluginSettingsSchema\n * @property {string} key - Setting key name\n * @property {string} type - Value type: 'string' | 'number' | 'boolean' | 'select'\n * @property {string} label - Human-readable label\n * @property {*} defaultValue - Default value\n * @property {string} [description] - Description for documentation/UI\n * @property {Array<{label: string, value: *}>} [options] - Options for 'select' type\n * @property {Function} [validate] - Custom validation function: (value) => boolean\n */\n\n/**\n * @typedef {Object} CreatePluginDefinition\n * @property {string} name - Unique plugin name\n * @property {string} [version] - Plugin version string (e.g., '1.0.0')\n * @property {string} [description] - Human-readable description\n * @property {string} [author] - Plugin author\n * @property {string[]} [dependencies] - Plugin names this plugin depends on (loaded first)\n * @property {Function} [init] - Called with the plugin API (or full engine if requiresFullAccess) on initialization\n * @property {Function} [destroy] - Called on cleanup with the same API as init\n * @property {Function} [onContentChange] - Called on every content:change event (debounced by the manager)\n * @property {Function} [onSelectionChange] - Called on every selectionchange event\n * @property {boolean} [requiresFullAccess=false] - If true, receives the full engine reference\n * instead of the restricted API facade. Only set this for trusted plugins that need\n * direct DOM/sanitizer/history access.\n * @property {boolean} [lazy=false] - If true, defers initialization until first command use\n * @property {Array<import('../core/CommandRegistry.js').CommandDefinition>} [commands] - Commands to register with the editor\n * @property {Array} [toolbarItems] - Toolbar item definitions for the UI layer\n * @property {Array} [statusBarItems] - Status bar item definitions for the UI layer\n * @property {Array} [contextMenuItems] - Context menu item definitions for the UI layer\n * @property {PluginSettingsSchema[]} [settingsSchema] - Schema for plugin-specific settings\n * @property {Object} [defaultSettings] - Default values for plugin settings\n */\n\n/**\n * Creates a normalized plugin definition with default values for optional properties.\n *\n * Enhanced plugin architecture supports:\n * - **Lifecycle hooks**: `onContentChange` and `onSelectionChange` in addition to `init`/`destroy`\n * - **Dependencies**: declare `dependencies: ['otherPlugin']` to ensure load ordering\n * - **Scoped settings**: `settingsSchema` + `defaultSettings` for per-plugin configuration\n * - **Metadata**: `version`, `description`, `author` for registry/marketplace\n *\n * @param {CreatePluginDefinition} definition - Plugin configuration\n * @returns {import('./PluginManager.js').PluginDefinition} A fully normalized plugin definition object\n */\nexport function createPlugin(definition) {\n return {\n name: definition.name,\n version: definition.version || '0.0.0',\n description: definition.description || '',\n author: definition.author || '',\n dependencies: definition.dependencies || [],\n requiresFullAccess: definition.requiresFullAccess || false,\n lazy: definition.lazy || false,\n init: definition.init || (() => {}),\n destroy: definition.destroy || (() => {}),\n onContentChange: definition.onContentChange || null,\n onSelectionChange: definition.onSelectionChange || null,\n commands: definition.commands || [],\n toolbarItems: definition.toolbarItems || [],\n statusBarItems: definition.statusBarItems || [],\n contextMenuItems: definition.contextMenuItems || [],\n settingsSchema: definition.settingsSchema || [],\n defaultSettings: definition.defaultSettings || {},\n }\n}\n","import { createPlugin } from '../createPlugin.js'\n\nexport function WordCountPlugin() {\n let debounceTimer = null\n let unsubContentChange = null\n\n return createPlugin({\n name: 'wordCount',\n requiresFullAccess: true, // Writes to engine._wordCount\n init(engine) {\n const update = () => {\n const text = engine.getText().trim()\n const wordCount = text ? text.split(/\\s+/).length : 0\n const charCount = engine.getText().length\n const data = { wordCount, charCount }\n engine._wordCount = data\n engine.eventBus.emit('wordcount:update', data)\n }\n\n // Task 248: Removed MutationObserver. Debounce content:change handler with 100ms timeout.\n const debouncedUpdate = () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(update, 100)\n }\n\n // Listen for content:change events from the engine (debounced)\n unsubContentChange = engine.eventBus.on('content:change', debouncedUpdate)\n\n update()\n },\n\n destroy() {\n clearTimeout(debounceTimer)\n if (unsubContentChange) {\n unsubContentChange()\n unsubContentChange = null\n }\n },\n })\n}\n","import { createPlugin } from '../createPlugin.js'\n\n// Maximum length for a URL match to prevent ReDoS on extremely long strings\nconst MAX_URL_LENGTH = 2048\n\n// Combined regex that matches all three URL patterns in a single pass:\n// 1. Protocol URLs: https://example.com/path\n// 2. www. prefixed: www.example.com/path\n// 3. Bare domains: example.com/path\n// Uses alternation groups to eliminate redundant text scans\nconst COMBINED_URL_REGEX = /(?:https?:\\/\\/[^\\s<]{1,2000}[^\\s<.,:;\"')\\]!?])|(?:www\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?(?:\\.[a-zA-Z]{2,20})+(?:\\/[^\\s<]{0,2000}[^\\s<.,:;\"')\\]!?])?)|(?:(?<![a-zA-Z0-9@/:.])(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?\\.){1,10}[a-zA-Z]{2,20}(?:\\/[^\\s<]{0,2000}[^\\s<.,:;\"')\\]!?])?)/g\n\n// Pre-compiled regexes for checking if a match is part of a larger URL\nconst PROTOCOL_BEFORE_REGEX = /https?:\\/\\/$/\nconst PROTOCOL_OR_WWW_BEFORE_REGEX = /https?:\\/\\/[^\\s]*$/\n\n// Common TLDs to validate bare domains against (avoids false positives like \"file.txt\")\nconst COMMON_TLDS = new Set([\n 'com', 'org', 'net', 'edu', 'gov', 'mil', 'int',\n 'io', 'co', 'us', 'uk', 'ca', 'au', 'de', 'fr', 'es', 'it', 'nl', 'be', 'at', 'ch',\n 'jp', 'cn', 'kr', 'in', 'br', 'mx', 'ru', 'za', 'nz', 'se', 'no', 'fi', 'dk', 'pl',\n 'dev', 'app', 'ai', 'me', 'tv', 'cc', 'info', 'biz', 'pro', 'name', 'museum',\n 'xyz', 'online', 'site', 'tech', 'store', 'blog', 'cloud', 'design', 'agency',\n])\n\n/**\n * Extract the TLD from a matched domain string.\n * e.g. \"example.com/path\" → \"com\", \"docs.github.io\" → \"io\"\n */\nfunction extractTLD(domain) {\n // Remove any path component\n const hostPart = domain.split('/')[0]\n const parts = hostPart.split('.')\n return parts[parts.length - 1].toLowerCase()\n}\n\nexport function AutolinkPlugin() {\n // Store handler reference for cleanup\n let _handler = null\n\n return createPlugin({\n name: 'autolink',\n requiresFullAccess: true, // Needs direct DOM event listener access\n init(engine) {\n _handler = (e) => {\n if (e.key !== ' ' && e.key !== 'Enter') return\n\n const sel = window.getSelection()\n if (!sel || !sel.focusNode) return\n const textNode = sel.focusNode\n if (textNode.nodeType !== Node.TEXT_NODE) return\n\n // Check if already inside a link\n if (textNode.parentElement.closest('a')) return\n\n const text = textNode.textContent\n const match = findLastURLMatch(text)\n if (!match) return\n\n const { href, startIdx, endIdx } = match\n\n // Ensure the matched range is before the cursor position\n const cursorOffset = sel.focusOffset\n if (endIdx > cursorOffset) return\n\n // Create the link\n const range = document.createRange()\n range.setStart(textNode, startIdx)\n range.setEnd(textNode, endIdx)\n\n const link = document.createElement('a')\n link.href = href\n link.target = '_blank'\n link.rel = 'noopener noreferrer'\n range.surroundContents(link)\n\n // Move cursor after the link\n const newRange = document.createRange()\n newRange.setStartAfter(link)\n newRange.collapse(true)\n sel.removeAllRanges()\n sel.addRange(newRange)\n }\n\n engine.element.addEventListener('keydown', _handler)\n },\n destroy(engine) {\n if (_handler) {\n engine.element.removeEventListener('keydown', _handler)\n _handler = null\n }\n },\n })\n}\n\n/**\n * Find the last URL-like match in a text string.\n * Checks protocol URLs first, then www. domains, then bare domains.\n * Returns { url, href, startIdx, endIdx } or null.\n */\nfunction findLastURLMatch(text) {\n // Guard against extremely long text nodes that could cause regex performance issues\n if (!text || text.length > MAX_URL_LENGTH * 2) return null\n\n // Task 270: Limit search to the last 200 characters of the text node\n const searchStart = Math.max(0, text.length - 200)\n if (searchStart > 0) {\n text = text.slice(searchStart)\n }\n\n let best = null\n\n // Single pass using combined regex with alternation groups\n for (const m of text.matchAll(COMBINED_URL_REGEX)) {\n const url = m[0]\n const isProtocol = url.startsWith('http://') || url.startsWith('https://')\n const isWww = !isProtocol && url.startsWith('www.')\n const isBare = !isProtocol && !isWww\n\n let href = url\n let candidate = null\n\n if (isProtocol) {\n candidate = { url, href: url, startIdx: m.index, endIdx: m.index + url.length }\n } else if (isWww) {\n // Make sure this isn't part of a protocol URL already matched\n const before = text.slice(Math.max(0, m.index - 8), m.index)\n if (PROTOCOL_BEFORE_REGEX.test(before)) continue\n candidate = { url, href: 'https://' + url, startIdx: m.index, endIdx: m.index + url.length }\n } else if (isBare) {\n const tld = extractTLD(url)\n // Only auto-link if the TLD is a known one (avoids false positives)\n if (!COMMON_TLDS.has(tld)) continue\n // Make sure this isn't part of a www. or protocol URL already matched\n const before = text.slice(Math.max(0, m.index - 12), m.index)\n if (PROTOCOL_OR_WWW_BEFORE_REGEX.test(before) || /www\\.$/.test(before)) continue\n candidate = { url, href: 'https://' + url, startIdx: m.index, endIdx: m.index + url.length }\n }\n\n if (candidate && (!best || candidate.startIdx >= best.startIdx)) {\n best = candidate\n }\n }\n\n // Task 270: Adjust indices back to original text positions\n if (best && searchStart > 0) {\n best.startIdx += searchStart\n best.endIdx += searchStart\n }\n\n return best\n}\n","import { createPlugin } from '../createPlugin.js'\n\nexport function PlaceholderPlugin(placeholderText = 'Start typing...') {\n return createPlugin({\n name: 'placeholder',\n requiresFullAccess: true, // Needs direct eventBus and element access\n init(engine) {\n const update = () => {\n if (engine.isEmpty()) {\n engine.element.setAttribute('data-placeholder', placeholderText)\n engine.element.classList.add('rmx-empty')\n } else {\n engine.element.classList.remove('rmx-empty')\n }\n }\n engine.eventBus.on('content:change', update)\n engine.eventBus.on('focus', update)\n engine.eventBus.on('blur', update)\n update()\n },\n })\n}\n","/**\n * Syntax highlighting tokenizers for common programming languages.\n *\n * Each tokenizer takes a source string and returns an array of\n * { text, className } tokens where className uses the `rmx-syn-` prefix.\n * Tokens with no syntactic meaning have className: null.\n */\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Run a list of ordered regex rules against `code` in a single linear pass.\n * Each rule is [RegExp, className | null]. The first match at the current\n * position wins. Unmatched characters are collected into a plain-text token.\n */\nfunction runRules(code, rules) {\n const tokens = [];\n let pos = 0;\n let plain = '';\n\n const flush = () => {\n if (plain) {\n tokens.push({ text: plain, className: null });\n plain = '';\n }\n };\n\n while (pos < code.length) {\n let matched = false;\n for (const [re, cls] of rules) {\n re.lastIndex = pos;\n const m = re.exec(code);\n if (m && m.index === pos) {\n flush();\n tokens.push({ text: m[0], className: cls });\n pos += m[0].length;\n matched = true;\n break;\n }\n }\n if (!matched) {\n plain += code[pos];\n pos++;\n }\n }\n flush();\n return tokens;\n}\n\n/** Build a keyword regex from an array of words (bounded, case-sensitive). */\nfunction kw(words) {\n return new RegExp(`\\\\b(?:${words.join('|')})\\\\b`, 'g');\n}\n\n/** Same as kw but case-insensitive. */\nfunction kwi(words) {\n return new RegExp(`\\\\b(?:${words.join('|')})\\\\b`, 'gi');\n}\n\n/**\n * Set-based keyword matcher for large keyword lists (50+ words).\n * More efficient than regex alternation for large word sets.\n * @param {string[]} keywords - Array of keywords\n * @returns {(word: string) => boolean}\n */\nfunction keywordMatcher(keywords) {\n const set = new Set(keywords)\n return (word) => set.has(word)\n}\n\n/** Case-insensitive Set-based keyword matcher. */\nfunction keywordMatcherI(keywords) {\n const set = new Set(keywords.map(w => w.toLowerCase()))\n return (word) => set.has(word.toLowerCase())\n}\n\n/**\n * Post-process tokens from runRules: split plain-text tokens on word boundaries\n * and classify words using Set-based matchers. Each entry in `matchers` is\n * { match: (word) => boolean, className: string }.\n */\nfunction applyKeywordSets(tokens, matchers) {\n const result = []\n const WORD_RE = /\\b[a-zA-Z_$]\\w*\\b/g\n for (const tok of tokens) {\n if (tok.className !== null) {\n result.push(tok)\n continue\n }\n // Split plain text on word boundaries and check against matchers\n let lastIndex = 0\n let m\n WORD_RE.lastIndex = 0\n while ((m = WORD_RE.exec(tok.text)) !== null) {\n if (m.index > lastIndex) {\n result.push({ text: tok.text.slice(lastIndex, m.index), className: null })\n }\n let cls = null\n for (const { match, className } of matchers) {\n if (match(m[0])) {\n cls = className\n break\n }\n }\n result.push({ text: m[0], className: cls })\n lastIndex = WORD_RE.lastIndex\n }\n if (lastIndex < tok.text.length) {\n result.push({ text: tok.text.slice(lastIndex), className: null })\n }\n }\n return result\n}\n\n// ---------------------------------------------------------------------------\n// JavaScript / TypeScript\n// ---------------------------------------------------------------------------\n\nconst JS_KEYWORDS = [\n 'await', 'break', 'case', 'catch', 'class', 'const', 'continue',\n 'debugger', 'default', 'delete', 'do', 'else', 'enum', 'export',\n 'extends', 'finally', 'for', 'from', 'function', 'if', 'implements',\n 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'of',\n 'package', 'return', 'static', 'super', 'switch', 'this', 'throw',\n 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield', 'async',\n];\n\nconst JS_BUILTINS = [\n 'Array', 'Boolean', 'console', 'Date', 'Error', 'JSON', 'Map',\n 'Math', 'Number', 'Object', 'Promise', 'Proxy', 'RegExp', 'Set',\n 'String', 'Symbol', 'WeakMap', 'WeakSet', 'parseInt', 'parseFloat',\n 'undefined', 'null', 'true', 'false', 'NaN', 'Infinity',\n];\n\nconst JS_TYPES = [\n 'any', 'boolean', 'never', 'number', 'string', 'unknown', 'void',\n 'type', 'keyof', 'readonly', 'infer',\n];\n\n// Set-based matchers for JS (73 combined keywords — avoids large regex alternation)\nconst _jsTypeMatch = keywordMatcher(JS_TYPES);\nconst _jsKeywordMatch = keywordMatcher(JS_KEYWORDS);\nconst _jsBuiltinMatch = keywordMatcher(JS_BUILTINS);\nconst _jsMatchers = [\n { match: _jsTypeMatch, className: 'rmx-syn-type' },\n { match: _jsKeywordMatch, className: 'rmx-syn-keyword' },\n { match: _jsBuiltinMatch, className: 'rmx-syn-builtin' },\n];\n\nconst JS_RULES = [\n [/\\/\\/[^\\n]{0,500}/g, 'rmx-syn-comment'],\n [/\\/\\*[^]*?(?:\\*\\/|$)/g, 'rmx-syn-comment'],\n [/\\/(?:[^/\\\\*\\n]|\\\\.){1,200}\\/[gimsuy]{0,6}/g, 'rmx-syn-regex'],\n [/`(?:[^`\\\\]|\\\\.|\\$\\{[^}]{0,200}\\}){0,2000}`/g, 'rmx-syn-string'],\n [/\"(?:[^\"\\\\]|\\\\.){0,1000}\"/g, 'rmx-syn-string'],\n [/'(?:[^'\\\\]|\\\\.){0,1000}'/g, 'rmx-syn-string'],\n [/@\\w{1,50}/g, 'rmx-syn-decorator'],\n [/\\b\\d[\\d_]{0,30}(?:\\.\\d[\\d_]{0,30})?(?:[eE][+-]?\\d{1,10})?\\b/g, 'rmx-syn-number'],\n [/0[xX][\\da-fA-F_]{1,20}/g, 'rmx-syn-number'],\n [/0[bB][01_]{1,64}/g, 'rmx-syn-number'],\n [/\\b[a-zA-Z_$]\\w{0,50}(?=\\s{0,5}\\()/g, 'rmx-syn-function'],\n [/(?<=\\.)[a-zA-Z_$]\\w{0,50}/g, 'rmx-syn-property'],\n [/=>|[+\\-*/%=!<>&|^~?:]{1,3}/g, 'rmx-syn-operator'],\n [/[{}()[\\];,.]/g, 'rmx-syn-punctuation'],\n];\n\nexport function tokenizeJavaScript(code) {\n return applyKeywordSets(runRules(code, JS_RULES), _jsMatchers);\n}\n\n// ---------------------------------------------------------------------------\n// Python\n// ---------------------------------------------------------------------------\n\nconst PY_KEYWORDS = [\n 'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',\n 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',\n 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',\n 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try',\n 'while', 'with', 'yield',\n];\n\nconst PY_BUILTINS = [\n 'abs', 'all', 'any', 'bin', 'bool', 'bytes', 'callable', 'chr',\n 'dict', 'dir', 'enumerate', 'eval', 'filter', 'float', 'format',\n 'frozenset', 'getattr', 'hasattr', 'hash', 'hex', 'id', 'input',\n 'int', 'isinstance', 'issubclass', 'iter', 'len', 'list', 'map',\n 'max', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow',\n 'print', 'property', 'range', 'repr', 'reversed', 'round', 'set',\n 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super',\n 'tuple', 'type', 'vars', 'zip',\n];\n\n// Set-based matchers for Python (78 combined keywords)\nconst _pyKeywordMatch = keywordMatcher(PY_KEYWORDS);\nconst _pyBuiltinMatch = keywordMatcher(PY_BUILTINS);\nconst _pyMatchers = [\n { match: _pyKeywordMatch, className: 'rmx-syn-keyword' },\n { match: _pyBuiltinMatch, className: 'rmx-syn-builtin' },\n];\n\nconst PY_RULES = [\n [/#[^\\n]{0,500}/g, 'rmx-syn-comment'],\n [/\"\"\"[^]*?(?:\"\"\"|$)/g, 'rmx-syn-string'],\n [/'''[^]*?(?:'''|$)/g, 'rmx-syn-string'],\n [/f\"(?:[^\"\\\\]|\\\\.|\\{[^}]{0,200}\\}){0,1000}\"/g, 'rmx-syn-string'],\n [/f'(?:[^'\\\\]|\\\\.|\\{[^}]{0,200}\\}){0,1000}'/g, 'rmx-syn-string'],\n [/\"(?:[^\"\\\\]|\\\\.){0,1000}\"/g, 'rmx-syn-string'],\n [/'(?:[^'\\\\]|\\\\.){0,1000}'/g, 'rmx-syn-string'],\n [/@\\w{1,50}/g, 'rmx-syn-decorator'],\n [/\\b\\d[\\d_]{0,30}(?:\\.\\d[\\d_]{0,30})?(?:[eE][+-]?\\d{1,10})?\\b/g, 'rmx-syn-number'],\n [/0[xX][\\da-fA-F_]{1,20}/g, 'rmx-syn-number'],\n [/0[bBoO][\\d_]{1,64}/g, 'rmx-syn-number'],\n [/\\b[a-zA-Z_]\\w{0,50}(?=\\s{0,5}\\()/g, 'rmx-syn-function'],\n [/(?<=\\.)[a-zA-Z_]\\w{0,50}/g, 'rmx-syn-property'],\n [/[+\\-*/%=!<>&|^~@:]{1,3}/g, 'rmx-syn-operator'],\n [/[{}()[\\];,.]/g, 'rmx-syn-punctuation'],\n];\n\nexport function tokenizePython(code) {\n return applyKeywordSets(runRules(code, PY_RULES), _pyMatchers);\n}\n\n// ---------------------------------------------------------------------------\n// CSS\n// ---------------------------------------------------------------------------\n\nconst CSS_AT_RULES = [\n 'charset', 'import', 'namespace', 'media', 'supports', 'keyframes',\n 'font-face', 'layer', 'container', 'property', 'scope',\n];\n\nconst CSS_RULES = [\n [/\\/\\*[^]*?(?:\\*\\/|$)/g, 'rmx-syn-comment'],\n [new RegExp(`@(?:${CSS_AT_RULES.join('|')})\\\\b`, 'g'), 'rmx-syn-keyword'],\n [/\"(?:[^\"\\\\]|\\\\.){0,1000}\"/g, 'rmx-syn-string'],\n [/'(?:[^'\\\\]|\\\\.){0,1000}'/g, 'rmx-syn-string'],\n [/#[\\da-fA-F]{3,8}\\b/g, 'rmx-syn-number'],\n [/\\b\\d[\\d.]{0,20}(?:%|px|rem|em|vh|vw|ch|ex|s|ms|deg|fr|vmin|vmax)?\\b/g, 'rmx-syn-number'],\n [/(?:^|\\s|[{;])\\s{0,10}[a-zA-Z-]{1,40}(?=\\s{0,5}:)/gm, 'rmx-syn-attr-name'],\n [/[.#][\\w-]{1,60}/g, 'rmx-syn-tag'],\n [/::?[\\w-]{1,40}/g, 'rmx-syn-entity'],\n [/\\b(?:rgb|rgba|hsl|hsla|var|calc|min|max|clamp)(?=\\()/g, 'rmx-syn-builtin'],\n [/!important\\b/g, 'rmx-syn-keyword'],\n [/[{}();:,]/g, 'rmx-syn-punctuation'],\n];\n\nexport function tokenizeCSS(code) {\n return runRules(code, CSS_RULES);\n}\n\n// ---------------------------------------------------------------------------\n// SQL\n// ---------------------------------------------------------------------------\n\nconst SQL_KEYWORDS = [\n 'SELECT', 'FROM', 'WHERE', 'INSERT', 'INTO', 'UPDATE', 'DELETE',\n 'CREATE', 'DROP', 'ALTER', 'TABLE', 'INDEX', 'VIEW', 'JOIN',\n 'INNER', 'LEFT', 'RIGHT', 'OUTER', 'FULL', 'CROSS', 'ON', 'AS',\n 'AND', 'OR', 'NOT', 'IN', 'BETWEEN', 'LIKE', 'IS', 'NULL',\n 'ORDER', 'BY', 'GROUP', 'HAVING', 'LIMIT', 'OFFSET', 'UNION',\n 'ALL', 'DISTINCT', 'SET', 'VALUES', 'EXISTS', 'CASE', 'WHEN',\n 'THEN', 'ELSE', 'END', 'ASC', 'DESC', 'PRIMARY', 'KEY',\n 'FOREIGN', 'REFERENCES', 'CONSTRAINT', 'DEFAULT', 'WITH',\n 'RECURSIVE', 'RETURNING', 'BEGIN', 'COMMIT', 'ROLLBACK',\n 'TRANSACTION', 'GRANT', 'REVOKE', 'TRUNCATE',\n];\n\nconst SQL_TYPES = [\n 'INT', 'INTEGER', 'BIGINT', 'SMALLINT', 'TINYINT', 'FLOAT',\n 'DOUBLE', 'DECIMAL', 'NUMERIC', 'CHAR', 'VARCHAR', 'TEXT',\n 'BLOB', 'BOOLEAN', 'DATE', 'TIMESTAMP', 'SERIAL',\n];\n\nconst SQL_BUILTINS = [\n 'COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'COALESCE', 'IFNULL',\n 'NULLIF', 'CAST', 'CONVERT', 'SUBSTRING', 'TRIM', 'UPPER',\n 'LOWER', 'LENGTH', 'CONCAT', 'NOW', 'CURRENT_TIMESTAMP',\n];\n\n// Set-based matchers for SQL (92 combined keywords, case-insensitive)\nconst _sqlTypeMatch = keywordMatcherI(SQL_TYPES);\nconst _sqlKeywordMatch = keywordMatcherI(SQL_KEYWORDS);\nconst _sqlBuiltinMatch = keywordMatcherI([...SQL_BUILTINS, 'TRUE', 'FALSE']);\nconst _sqlMatchers = [\n { match: _sqlTypeMatch, className: 'rmx-syn-type' },\n { match: _sqlKeywordMatch, className: 'rmx-syn-keyword' },\n { match: _sqlBuiltinMatch, className: 'rmx-syn-builtin' },\n];\n\nconst SQL_RULES = [\n [/--[^\\n]{0,500}/g, 'rmx-syn-comment'],\n [/\\/\\*[^]*?(?:\\*\\/|$)/g, 'rmx-syn-comment'],\n [/'(?:[^'\\\\]|\\\\.){0,1000}'/g, 'rmx-syn-string'],\n [/\"(?:[^\"\\\\]|\\\\.){0,1000}\"/g, 'rmx-syn-string'],\n [/\\b\\d[\\d.]{0,20}\\b/g, 'rmx-syn-number'],\n [/[+\\-*/%=!<>]{1,3}/g, 'rmx-syn-operator'],\n [/[{}()[\\];,.]/g, 'rmx-syn-punctuation'],\n];\n\nexport function tokenizeSQL(code) {\n return applyKeywordSets(runRules(code, SQL_RULES), _sqlMatchers);\n}\n\n// ---------------------------------------------------------------------------\n// JSON\n// ---------------------------------------------------------------------------\n\nconst JSON_RULES = [\n [/\"(?:[^\"\\\\]|\\\\.){0,1000}\"(?=\\s{0,10}:)/g, 'rmx-syn-attr-name'],\n [/\"(?:[^\"\\\\]|\\\\.){0,1000}\"/g, 'rmx-syn-string'],\n [/\\b(?:true|false)\\b/g, 'rmx-syn-builtin'],\n [/\\bnull\\b/g, 'rmx-syn-builtin'],\n [/-?\\b\\d[\\d.]{0,20}(?:[eE][+-]?\\d{1,10})?\\b/g, 'rmx-syn-number'],\n [/[{}[\\]:,]/g, 'rmx-syn-punctuation'],\n];\n\nexport function tokenizeJSON(code) {\n return runRules(code, JSON_RULES);\n}\n\n// ---------------------------------------------------------------------------\n// Bash / Shell\n// ---------------------------------------------------------------------------\n\nconst BASH_KEYWORDS = [\n 'if', 'then', 'else', 'elif', 'fi', 'for', 'while', 'until', 'do',\n 'done', 'case', 'esac', 'in', 'function', 'select', 'return',\n 'exit', 'break', 'continue', 'local', 'export', 'declare',\n 'readonly', 'unset', 'shift', 'source', 'trap',\n];\n\nconst BASH_BUILTINS = [\n 'echo', 'printf', 'cd', 'pwd', 'ls', 'cp', 'mv', 'rm', 'mkdir',\n 'cat', 'grep', 'sed', 'awk', 'find', 'sort', 'uniq', 'wc',\n 'head', 'tail', 'chmod', 'chown', 'curl', 'wget', 'tar', 'git',\n 'docker', 'npm', 'yarn', 'pip', 'sudo', 'apt', 'brew', 'test',\n 'read', 'eval', 'exec', 'set',\n];\n\nconst BASH_RULES = [\n [/#[^\\n]{0,500}/g, 'rmx-syn-comment'],\n [/\"(?:[^\"\\\\]|\\\\.|\\$\\{[^}]{0,200}\\}|\\$\\w{1,50}){0,1000}\"/g, 'rmx-syn-string'],\n [/'[^']{0,2000}'/g, 'rmx-syn-string'],\n [/\\$\\{[^}]{0,200}\\}/g, 'rmx-syn-entity'],\n [/\\$[\\w?!#@*]{1,50}/g, 'rmx-syn-entity'],\n [kw(BASH_KEYWORDS), 'rmx-syn-keyword'],\n [kw(BASH_BUILTINS), 'rmx-syn-builtin'],\n [/\\b\\d[\\d.]{0,20}\\b/g, 'rmx-syn-number'],\n [/[|&;<>]{1,3}/g, 'rmx-syn-operator'],\n [/[{}()[\\]]/g, 'rmx-syn-punctuation'],\n];\n\nexport function tokenizeBash(code) {\n return runRules(code, BASH_RULES);\n}\n\n// ---------------------------------------------------------------------------\n// Rust\n// ---------------------------------------------------------------------------\n\nconst RUST_KEYWORDS = [\n 'as', 'async', 'await', 'break', 'const', 'continue', 'crate',\n 'dyn', 'else', 'enum', 'extern', 'fn', 'for', 'if', 'impl',\n 'in', 'let', 'loop', 'match', 'mod', 'move', 'mut', 'pub',\n 'ref', 'return', 'self', 'static', 'struct', 'super', 'trait',\n 'type', 'unsafe', 'use', 'where', 'while', 'yield',\n];\n\nconst RUST_TYPES = [\n 'bool', 'char', 'f32', 'f64', 'i8', 'i16', 'i32', 'i64', 'i128',\n 'isize', 'str', 'u8', 'u16', 'u32', 'u64', 'u128', 'usize',\n 'Self', 'Box', 'Vec', 'String', 'Option', 'Result', 'Rc', 'Arc',\n 'HashMap', 'HashSet', 'BTreeMap', 'BTreeSet', 'Cow',\n];\n\nconst RUST_BUILTINS = [\n 'Some', 'None', 'Ok', 'Err', 'true', 'false', 'println', 'eprintln',\n 'format', 'panic', 'assert', 'assert_eq', 'assert_ne', 'todo',\n 'unimplemented', 'unreachable', 'dbg', 'cfg', 'derive',\n];\n\n// Set-based matchers for Rust (72 combined keywords)\nconst _rustTypeMatch = keywordMatcher(RUST_TYPES);\nconst _rustKeywordMatch = keywordMatcher(RUST_KEYWORDS);\nconst _rustBuiltinMatch = keywordMatcher(RUST_BUILTINS);\nconst _rustMatchers = [\n { match: _rustTypeMatch, className: 'rmx-syn-type' },\n { match: _rustKeywordMatch, className: 'rmx-syn-keyword' },\n { match: _rustBuiltinMatch, className: 'rmx-syn-builtin' },\n];\n\nconst RUST_RULES = [\n [/\\/\\/[^\\n]{0,500}/g, 'rmx-syn-comment'],\n [/\\/\\*[^]*?(?:\\*\\/|$)/g, 'rmx-syn-comment'],\n [/\"(?:[^\"\\\\]|\\\\.){0,1000}\"/g, 'rmx-syn-string'],\n [/r#*\"[^]*?\"#*/g, 'rmx-syn-string'],\n [/'[a-zA-Z_]\\w{0,30}/g, 'rmx-syn-entity'],\n [/b?'(?:[^'\\\\]|\\\\.){1,4}'/g, 'rmx-syn-string'],\n [/#!\\[[\\w:]{1,50}/g, 'rmx-syn-decorator'],\n [/#\\[[\\w:]{1,50}/g, 'rmx-syn-decorator'],\n [/\\b\\w{1,30}!/g, 'rmx-syn-builtin'],\n [/\\b\\d[\\d_]{0,30}(?:\\.\\d[\\d_]{0,30})?(?:[eE][+-]?\\d{1,10})?(?:_?(?:f32|f64|i8|i16|i32|i64|i128|u8|u16|u32|u64|u128|isize|usize))?\\b/g, 'rmx-syn-number'],\n [/0[xX][\\da-fA-F_]{1,20}/g, 'rmx-syn-number'],\n [/0[bB][01_]{1,64}/g, 'rmx-syn-number'],\n [/\\b[a-zA-Z_]\\w{0,50}(?=\\s{0,5}\\()/g, 'rmx-syn-function'],\n [/(?<=\\.)[a-zA-Z_]\\w{0,50}/g, 'rmx-syn-property'],\n [/[+\\-*/%=!<>&|^]{1,3}|::|=>/g, 'rmx-syn-operator'],\n [/[{}()[\\];,.]/g, 'rmx-syn-punctuation'],\n];\n\nexport function tokenizeRust(code) {\n return applyKeywordSets(runRules(code, RUST_RULES), _rustMatchers);\n}\n\n// ---------------------------------------------------------------------------\n// Go\n// ---------------------------------------------------------------------------\n\nconst GO_KEYWORDS = [\n 'break', 'case', 'chan', 'const', 'continue', 'default', 'defer',\n 'else', 'fallthrough', 'for', 'func', 'go', 'goto', 'if', 'import',\n 'interface', 'map', 'package', 'range', 'return', 'select', 'struct',\n 'switch', 'type', 'var',\n];\n\nconst GO_TYPES = [\n 'bool', 'byte', 'complex64', 'complex128', 'error', 'float32',\n 'float64', 'int', 'int8', 'int16', 'int32', 'int64', 'rune',\n 'string', 'uint', 'uint8', 'uint16', 'uint32', 'uint64', 'uintptr',\n];\n\nconst GO_BUILTINS = [\n 'append', 'cap', 'close', 'complex', 'copy', 'delete', 'imag',\n 'len', 'make', 'new', 'panic', 'print', 'println', 'real',\n 'recover', 'true', 'false', 'nil', 'iota',\n];\n\nconst GO_RULES = [\n [/\\/\\/[^\\n]{0,500}/g, 'rmx-syn-comment'],\n [/\\/\\*[^]*?(?:\\*\\/|$)/g, 'rmx-syn-comment'],\n [/`[^`]{0,5000}`/g, 'rmx-syn-string'],\n [/\"(?:[^\"\\\\]|\\\\.){0,1000}\"/g, 'rmx-syn-string'],\n [kw(GO_TYPES), 'rmx-syn-type'],\n [kw(GO_KEYWORDS), 'rmx-syn-keyword'],\n [kw(GO_BUILTINS), 'rmx-syn-builtin'],\n [/\\b\\d[\\d_]{0,30}(?:\\.\\d[\\d_]{0,30})?(?:[eE][+-]?\\d{1,10})?\\b/g, 'rmx-syn-number'],\n [/0[xX][\\da-fA-F_]{1,20}/g, 'rmx-syn-number'],\n [/\\b[a-zA-Z_]\\w{0,50}(?=\\s{0,5}\\()/g, 'rmx-syn-function'],\n [/(?<=\\.)[a-zA-Z_]\\w{0,50}/g, 'rmx-syn-property'],\n [/:=|[+\\-*/%=!<>&|^]{1,3}/g, 'rmx-syn-operator'],\n [/[{}()[\\];,.]/g, 'rmx-syn-punctuation'],\n];\n\nexport function tokenizeGo(code) {\n return runRules(code, GO_RULES);\n}\n\n// ---------------------------------------------------------------------------\n// Java\n// ---------------------------------------------------------------------------\n\nconst JAVA_KEYWORDS = [\n 'abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch',\n 'char', 'class', 'const', 'continue', 'default', 'do', 'double',\n 'else', 'enum', 'extends', 'final', 'finally', 'float', 'for',\n 'goto', 'if', 'implements', 'import', 'instanceof', 'int',\n 'interface', 'long', 'native', 'new', 'package', 'private',\n 'protected', 'public', 'return', 'short', 'static', 'strictfp',\n 'super', 'switch', 'synchronized', 'this', 'throw', 'throws',\n 'transient', 'try', 'void', 'volatile', 'while', 'var', 'record',\n 'sealed', 'permits', 'yield',\n];\n\nconst JAVA_TYPES = [\n 'String', 'Integer', 'Long', 'Double', 'Float', 'Boolean', 'Byte',\n 'Character', 'Short', 'Object', 'List', 'Map', 'Set', 'Collection',\n 'ArrayList', 'HashMap', 'HashSet', 'Optional', 'Stream',\n];\n\nconst JAVA_BUILTINS = [\n 'true', 'false', 'null', 'System', 'Math', 'Arrays', 'Collections',\n];\n\n// Set-based matchers for Java (71 combined keywords)\nconst _javaTypeMatch = keywordMatcher(JAVA_TYPES);\nconst _javaKeywordMatch = keywordMatcher(JAVA_KEYWORDS);\nconst _javaBuiltinMatch = keywordMatcher(JAVA_BUILTINS);\nconst _javaMatchers = [\n { match: _javaTypeMatch, className: 'rmx-syn-type' },\n { match: _javaKeywordMatch, className: 'rmx-syn-keyword' },\n { match: _javaBuiltinMatch, className: 'rmx-syn-builtin' },\n];\n\nconst JAVA_RULES = [\n [/\\/\\/[^\\n]{0,500}/g, 'rmx-syn-comment'],\n [/\\/\\*[^]*?(?:\\*\\/|$)/g, 'rmx-syn-comment'],\n [/\"(?:[^\"\\\\]|\\\\.){0,1000}\"/g, 'rmx-syn-string'],\n [/'(?:[^'\\\\]|\\\\.){1,4}'/g, 'rmx-syn-string'],\n [/@\\w{1,50}/g, 'rmx-syn-decorator'],\n [/\\b\\d[\\d_]{0,30}(?:\\.\\d[\\d_]{0,30})?(?:[eE][+-]?\\d{1,10})?[lLfFdD]?\\b/g, 'rmx-syn-number'],\n [/0[xX][\\da-fA-F_]{1,20}[lL]?/g, 'rmx-syn-number'],\n [/0[bB][01_]{1,64}[lL]?/g, 'rmx-syn-number'],\n [/\\b[a-zA-Z_]\\w{0,50}(?=\\s{0,5}\\()/g, 'rmx-syn-function'],\n [/(?<=\\.)[a-zA-Z_]\\w{0,50}/g, 'rmx-syn-property'],\n [/[+\\-*/%=!<>&|^~?:]{1,3}|->/g, 'rmx-syn-operator'],\n [/[{}()[\\];,.]/g, 'rmx-syn-punctuation'],\n];\n\nexport function tokenizeJava(code) {\n return applyKeywordSets(runRules(code, JAVA_RULES), _javaMatchers);\n}\n\n// ---------------------------------------------------------------------------\n// HTML\n// ---------------------------------------------------------------------------\n\nconst HTML_RULES = [\n [/<!--[^]*?(?:-->|$)/g, 'rmx-syn-comment'],\n [/<!DOCTYPE[^>]{0,200}>/gi, 'rmx-syn-entity'],\n [/<\\/?[a-zA-Z][\\w.-]{0,50}/g, 'rmx-syn-tag'],\n [/\\/?>/g, 'rmx-syn-tag'],\n [/\\b[a-zA-Z_:][\\w:.-]{0,50}(?=\\s{0,5}=)/g, 'rmx-syn-attr-name'],\n [/=\\s{0,5}\"[^\"]{0,1000}\"/g, 'rmx-syn-attr-value'],\n [/=\\s{0,5}'[^']{0,1000}'/g, 'rmx-syn-attr-value'],\n [/&\\w{1,20};/g, 'rmx-syn-entity'],\n];\n\nexport function tokenizeHTML(code) {\n return runRules(code, HTML_RULES);\n}\n\n// ---------------------------------------------------------------------------\n// Plain Text\n// ---------------------------------------------------------------------------\n\nexport function tokenizePlainText(code) {\n return [{ text: code, className: null }];\n}\n\n// ---------------------------------------------------------------------------\n// Language map & supported languages list\n// ---------------------------------------------------------------------------\n\nexport const LANGUAGE_MAP = {\n javascript: tokenizeJavaScript,\n js: tokenizeJavaScript,\n jsx: tokenizeJavaScript,\n typescript: tokenizeJavaScript,\n ts: tokenizeJavaScript,\n tsx: tokenizeJavaScript,\n python: tokenizePython,\n py: tokenizePython,\n css: tokenizeCSS,\n sql: tokenizeSQL,\n json: tokenizeJSON,\n bash: tokenizeBash,\n sh: tokenizeBash,\n shell: tokenizeBash,\n zsh: tokenizeBash,\n rust: tokenizeRust,\n rs: tokenizeRust,\n go: tokenizeGo,\n golang: tokenizeGo,\n java: tokenizeJava,\n html: tokenizeHTML,\n htm: tokenizeHTML,\n xml: tokenizeHTML,\n svg: tokenizeHTML,\n plaintext: tokenizePlainText,\n text: tokenizePlainText,\n txt: tokenizePlainText,\n};\n\nexport const SUPPORTED_LANGUAGES = [\n { id: 'javascript', label: 'JavaScript' },\n { id: 'typescript', label: 'TypeScript' },\n { id: 'python', label: 'Python' },\n { id: 'css', label: 'CSS' },\n { id: 'sql', label: 'SQL' },\n { id: 'json', label: 'JSON' },\n { id: 'bash', label: 'Bash' },\n { id: 'rust', label: 'Rust' },\n { id: 'go', label: 'Go' },\n { id: 'java', label: 'Java' },\n { id: 'html', label: 'HTML' },\n { id: 'plaintext', label: 'Plain Text' },\n];\n\n// ---------------------------------------------------------------------------\n// Extensible Language Registry\n// ---------------------------------------------------------------------------\n\n/**\n * Register a custom language tokenizer.\n *\n * @param {string} id - Language identifier (e.g. 'ruby', 'swift')\n * @param {string} label - Display label (e.g. 'Ruby', 'Swift')\n * @param {function} tokenizer - Function that takes source code string and\n * returns an array of { text: string, className: string | null } tokens.\n * className should use the `rmx-syn-` prefix (e.g. 'rmx-syn-keyword').\n * @param {string[]} [aliases] - Additional identifiers that map to this\n * tokenizer (e.g. ['rb'] for Ruby).\n */\nexport function registerLanguage(id, label, tokenizer, aliases = []) {\n if (!id || typeof id !== 'string') throw new Error('registerLanguage: id is required')\n if (!label || typeof label !== 'string') throw new Error('registerLanguage: label is required')\n if (typeof tokenizer !== 'function') throw new Error('registerLanguage: tokenizer must be a function')\n\n LANGUAGE_MAP[id.toLowerCase()] = tokenizer\n for (const alias of aliases) {\n LANGUAGE_MAP[alias.toLowerCase()] = tokenizer\n }\n\n // Add to SUPPORTED_LANGUAGES if not already present\n if (!SUPPORTED_LANGUAGES.find(l => l.id === id)) {\n SUPPORTED_LANGUAGES.push({ id, label })\n }\n}\n\n/**\n * Unregister a previously registered language tokenizer.\n * Built-in languages can also be removed.\n *\n * @param {string} id - Language identifier to remove\n * @param {string[]} [aliases] - Aliases to also remove\n */\nexport function unregisterLanguage(id, aliases = []) {\n delete LANGUAGE_MAP[id.toLowerCase()]\n for (const alias of aliases) {\n delete LANGUAGE_MAP[alias.toLowerCase()]\n }\n const idx = SUPPORTED_LANGUAGES.findIndex(l => l.id === id)\n if (idx !== -1) SUPPORTED_LANGUAGES.splice(idx, 1)\n}\n\n// ---------------------------------------------------------------------------\n// Public tokenize(code, language) API\n// ---------------------------------------------------------------------------\n\n/**\n * Tokenize source code using the appropriate language tokenizer.\n * Returns an array of { type, value } tokens where `type` is either\n * 'plain' or a token kind (e.g. 'keyword', 'string', 'comment').\n * Returns null if no tokenizer is available for the language.\n *\n * @param {string} code - Source code to tokenize\n * @param {string} language - Language identifier (e.g. 'javascript', 'python')\n * @returns {{ type: string, value: string }[] | null}\n */\nexport function tokenize(code, language) {\n const tokenizer = LANGUAGE_MAP[language?.toLowerCase()]\n if (!tokenizer || tokenizer === tokenizePlainText) return null\n\n const rawTokens = tokenizer(code)\n // Convert internal { text, className } format to public { type, value } format\n return rawTokens.map(({ text, className }) => ({\n type: className ? className.replace('rmx-syn-', '') : 'plain',\n value: text,\n }))\n}\n\n/**\n * Helper exported for consumers who want to build custom tokenizers using\n * the same regex-rule engine used by all built-in tokenizers.\n *\n * @param {string} code - Source code to tokenize\n * @param {Array<[RegExp, string|null]>} rules - Ordered regex rules\n * @returns {{ text: string, className: string|null }[]}\n */\nexport { runRules }\n\n// ---------------------------------------------------------------------------\n// Language detection heuristic\n// ---------------------------------------------------------------------------\n\n/**\n * Guess the language of a code snippet by checking for distinctive patterns.\n * Returns a language id string (a key in LANGUAGE_MAP) or 'plaintext'.\n */\nexport function detectLanguage(code) {\n if (!code || typeof code !== 'string') return 'plaintext';\n\n const trimmed = code.trimStart();\n const first200 = trimmed.slice(0, 200);\n const lower = first200.toLowerCase();\n\n // JSON — starts with { or [, try to parse\n if (/^\\s*[{\\[]/.test(trimmed)) {\n try {\n JSON.parse(trimmed);\n return 'json';\n } catch {\n // Not valid JSON — could still be JS/other\n }\n }\n\n // HTML — starts with a tag or doctype\n if (/^\\s*<!doctype\\s+html/i.test(trimmed) || /^\\s*<(?:html|head|body|div|span|p|a|img|ul|ol|li|table|form|section|header|footer|nav|main|article)\\b/i.test(trimmed)) {\n return 'html';\n }\n\n // SQL — starts with common SQL keywords\n if (/^\\s*(?:SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|WITH|GRANT|EXPLAIN)\\b/i.test(trimmed)) {\n return 'sql';\n }\n\n // Python — def, import os/sys/re, class Foo:, elif, #!.*python\n if (/^#!.*python/i.test(trimmed) || /^\\s*(?:def |class \\w+[:(]|from \\w+ import|import (?:os|sys|re|json|math|typing|collections|pathlib|django|flask|numpy|pandas))\\b/.test(trimmed)) {\n return 'python';\n }\n\n // Rust — fn main, use std, let mut, impl, #[derive\n if (/^\\s*(?:fn |use std|pub fn|impl |#\\[derive|mod \\w+;|extern crate)/.test(trimmed)) {\n return 'rust';\n }\n\n // Go — package main, import (, func main\n if (/^\\s*(?:package \\w+|func (?:main|\\w+)\\(|import \\()/.test(trimmed)) {\n return 'go';\n }\n\n // Java — public class, import java, @Override\n if (/^\\s*(?:(?:public|private|protected)\\s+(?:class|interface|enum|abstract)|import java|@Override|package [a-z]+\\.)/.test(trimmed)) {\n return 'java';\n }\n\n // Bash — #!/bin/bash, #!/bin/sh, common shell patterns\n if (/^#!\\/(?:bin\\/(?:ba)?sh|usr\\/bin\\/env (?:ba)?sh)/.test(trimmed) || /^\\s*(?:export |alias |source |if \\[)/.test(trimmed)) {\n return 'bash';\n }\n\n // CSS — selectors, @media, @import url\n if (/^\\s*(?:@(?:media|import|charset|keyframes|font-face)|[.#][\\w-]+\\s*\\{|:root\\s*\\{|\\*\\s*\\{|body\\s*\\{)/.test(trimmed)) {\n return 'css';\n }\n\n // JavaScript/TypeScript — import/export/const/let/var, arrow functions, React\n if (/^\\s*(?:import |export |const |let |var |function |class |async |'use strict')/.test(trimmed) || /=>/.test(first200) || /(?:React|require\\(|module\\.exports)/.test(first200)) {\n return 'javascript';\n }\n\n return 'plaintext';\n}\n","import { createPlugin } from '../../createPlugin.js'\nimport { detectLanguage, tokenize, LANGUAGE_MAP } from './tokenizers.js'\nimport { escapeHTMLAttr as escapeHTML } from '../../../utils/escapeHTML.js'\n\nconst HIGHLIGHT_DEBOUNCE_MS = 150\nconst COPY_FEEDBACK_MS = 1500\n\n/**\n * Finds the closest `<pre>` ancestor of the currently focused element,\n * or null if the focus is outside any `<pre>` block.\n */\nfunction getFocusedPre() {\n const active = document.activeElement\n if (!active) return null\n // activeElement may be the contenteditable div itself; check the selection\n const sel = window.getSelection()\n if (!sel || !sel.focusNode) return null\n const node = sel.focusNode.nodeType === Node.TEXT_NODE\n ? sel.focusNode.parentElement\n : sel.focusNode\n return node?.closest?.('pre') ?? null\n}\n\n/**\n * Returns the `<pre>` element containing the current selection's anchor,\n * or null if the selection is not inside a code block.\n */\nfunction getCurrentCodeBlock(engine) {\n const sel = engine.selection.getSelection()\n if (!sel || !sel.anchorNode) return null\n const node = sel.anchorNode.nodeType === Node.TEXT_NODE\n ? sel.anchorNode.parentElement\n : sel.anchorNode\n return node?.closest?.('pre') ?? null\n}\n\n/**\n * SyntaxHighlightPlugin - Automatically highlights `<pre><code>` blocks\n * in the editor using language-specific tokenizers.\n *\n * Watches for DOM mutations to detect new or changed code blocks and\n * applies syntax highlighting via `<span class=\"rmx-syn-{type}\">` wrappers.\n *\n * Avoids disrupting contenteditable behavior by skipping blocks that are\n * currently focused (the user is typing in them). Re-highlights on blur.\n */\nexport function SyntaxHighlightPlugin() {\n let observer = null\n let debounceTimer = null\n let blurHandler = null\n let languageChangeUnsub = null\n let copyClickHandler = null\n let lineNumberClickHandler = null\n\n /**\n * Highlights a single `<code>` element inside a `<pre>`.\n * Detects (or reads) the language, tokenizes, and replaces innerHTML.\n * Preserves cursor position if the code block is inside the editor.\n */\n function highlightCodeElement(codeEl, engine) {\n const pre = codeEl.closest('pre')\n if (!pre) return\n\n // Determine language\n let language = codeEl.getAttribute('data-language')\n if (!language) {\n language = detectLanguage(codeEl.textContent)\n if (language) {\n codeEl.setAttribute('data-language', language)\n }\n }\n\n // Tokenize and build highlighted HTML\n const source = codeEl.textContent\n const tokens = tokenize(source, language)\n if (!tokens) {\n // No tokenizer available — mark as highlighted to skip next time\n pre.classList.add('rmx-highlighted')\n return\n }\n\n let html = ''\n for (const token of tokens) {\n if (token.type === 'plain') {\n html += escapeHTML(token.value)\n } else {\n html += `<span class=\"rmx-syn-${token.type}\">${escapeHTML(token.value)}</span>`\n }\n }\n\n // Save cursor, replace content, restore cursor\n const bookmark = engine.selection.save()\n codeEl.innerHTML = html\n pre.classList.add('rmx-highlighted')\n if (bookmark) {\n engine.selection.restore(bookmark)\n }\n }\n\n /**\n * Injects or removes line number gutter for a `<pre>` block.\n * Line numbers are rendered as a `<span class=\"rmx-line-numbers\">` inside\n * the `<pre>`, with one `<span>` per line.\n */\n function updateLineNumbers(pre) {\n const show = pre.hasAttribute('data-line-numbers')\n let gutter = pre.querySelector('.rmx-line-numbers')\n\n if (!show) {\n if (gutter) gutter.remove()\n pre.classList.remove('rmx-has-line-numbers')\n return\n }\n\n const code = pre.querySelector('code')\n if (!code) return\n\n const lineCount = (code.textContent.match(/\\n/g) || []).length + 1\n\n if (!gutter) {\n gutter = document.createElement('span')\n gutter.className = 'rmx-line-numbers'\n gutter.setAttribute('aria-hidden', 'true')\n gutter.contentEditable = 'false'\n pre.insertBefore(gutter, pre.firstChild)\n }\n\n // Build line number spans\n let nums = ''\n for (let i = 1; i <= lineCount; i++) {\n nums += `<span class=\"rmx-line-number\">${i}</span>`\n }\n gutter.innerHTML = nums\n pre.classList.add('rmx-has-line-numbers')\n }\n\n /**\n * Injects a copy-to-clipboard button into a `<pre>` block if one doesn't\n * already exist.\n */\n function ensureCopyButton(pre) {\n if (pre.querySelector('.rmx-code-copy-btn')) return\n const btn = document.createElement('button')\n btn.className = 'rmx-code-copy-btn'\n btn.type = 'button'\n btn.setAttribute('aria-label', 'Copy code')\n btn.contentEditable = 'false'\n btn.textContent = '⧉'\n pre.appendChild(btn)\n }\n\n /**\n * Highlights inline `<code>` elements (not inside `<pre>`) with\n * mini syntax highlighting when they have a `data-language` attribute.\n */\n function highlightInlineCode(engine) {\n const codes = engine.element.querySelectorAll('code[data-language]:not(pre code)')\n for (const code of codes) {\n if (code.classList.contains('rmx-inline-highlighted')) continue\n const language = code.getAttribute('data-language')\n const tokens = tokenize(code.textContent, language)\n if (!tokens) continue\n\n let html = ''\n for (const token of tokens) {\n if (token.type === 'plain') {\n html += escapeHTML(token.value)\n } else {\n html += `<span class=\"rmx-syn-${token.type}\">${escapeHTML(token.value)}</span>`\n }\n }\n code.innerHTML = html\n code.classList.add('rmx-inline-highlighted')\n }\n }\n\n /**\n * Highlights all `<pre><code>` blocks in the editor that are not\n * currently focused and have not already been highlighted.\n */\n function highlightAll(engine) {\n const focusedPre = getFocusedPre()\n const pres = engine.element.querySelectorAll('pre')\n\n for (const pre of pres) {\n // Always ensure copy button and line numbers are present\n ensureCopyButton(pre)\n updateLineNumbers(pre)\n\n // Skip the block the user is actively editing\n if (pre === focusedPre) continue\n // Skip already-highlighted blocks that haven't changed\n if (pre.classList.contains('rmx-highlighted')) continue\n\n const code = pre.querySelector('code')\n if (!code) continue\n\n highlightCodeElement(code, engine)\n }\n\n // Highlight inline code spans\n highlightInlineCode(engine)\n }\n\n /**\n * Debounced wrapper around highlightAll to prevent excessive processing\n * during rapid typing or paste operations.\n */\n function scheduleHighlight(engine) {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => highlightAll(engine), HIGHLIGHT_DEBOUNCE_MS)\n }\n\n return createPlugin({\n name: 'syntaxHighlight',\n requiresFullAccess: true,\n\n commands: [\n {\n name: 'setCodeLanguage',\n execute(engine, { language } = {}) {\n if (!language) return false\n const pre = getCurrentCodeBlock(engine)\n if (!pre) return false\n const code = pre.querySelector('code')\n if (!code) return false\n\n code.setAttribute('data-language', language)\n // Remove highlighted flag so it gets re-processed\n pre.classList.remove('rmx-highlighted')\n highlightCodeElement(code, engine)\n engine.eventBus.emit('codeblock:language-change', { language, element: code })\n return true\n },\n },\n {\n name: 'getCodeLanguage',\n execute(engine) {\n const pre = getCurrentCodeBlock(engine)\n if (!pre) return null\n const code = pre.querySelector('code')\n if (!code) return null\n return code.getAttribute('data-language') || null\n },\n },\n {\n name: 'toggleLineNumbers',\n execute(engine, { element } = {}) {\n const pre = element || getCurrentCodeBlock(engine)\n if (!pre) return false\n if (pre.hasAttribute('data-line-numbers')) {\n pre.removeAttribute('data-line-numbers')\n } else {\n pre.setAttribute('data-line-numbers', '')\n }\n updateLineNumbers(pre)\n return true\n },\n meta: { icon: 'lineNumbers', tooltip: 'Toggle Line Numbers' },\n },\n ],\n\n init(engine) {\n // Highlight existing code blocks immediately\n highlightAll(engine)\n\n // Watch for DOM changes that might add or modify code blocks\n observer = new MutationObserver((mutations) => {\n let needsHighlight = false\n\n for (const mutation of mutations) {\n // New nodes added (paste, programmatic insert)\n if (mutation.type === 'childList') {\n for (const node of mutation.addedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n if (node.matches?.('pre') || node.querySelector?.('pre')) {\n needsHighlight = true\n break\n }\n }\n }\n }\n\n // Text changed inside a code block (typing)\n if (mutation.type === 'characterData') {\n const pre = mutation.target.parentElement?.closest?.('pre')\n if (pre) {\n // Mark as needing re-highlight\n pre.classList.remove('rmx-highlighted')\n needsHighlight = true\n }\n }\n\n // Attribute changed (e.g. data-language set externally)\n if (mutation.type === 'attributes') {\n const target = mutation.target\n if (target.matches?.('code') && mutation.attributeName === 'data-language') {\n const pre = target.closest('pre')\n if (pre) {\n pre.classList.remove('rmx-highlighted')\n needsHighlight = true\n }\n }\n }\n\n if (needsHighlight) break\n }\n\n if (needsHighlight) {\n scheduleHighlight(engine)\n }\n })\n\n observer.observe(engine.element, {\n childList: true,\n subtree: true,\n characterData: true,\n attributes: true,\n attributeFilter: ['data-language'],\n })\n\n // Copy-to-clipboard click handler (delegated)\n copyClickHandler = async (e) => {\n const btn = e.target.closest('.rmx-code-copy-btn')\n if (!btn) return\n const pre = btn.closest('pre')\n if (!pre) return\n const code = pre.querySelector('code')\n if (!code) return\n\n e.preventDefault()\n e.stopPropagation()\n\n try {\n await navigator.clipboard.writeText(code.textContent)\n btn.textContent = '✓'\n btn.classList.add('rmx-code-copy-success')\n setTimeout(() => {\n btn.textContent = '⧉'\n btn.classList.remove('rmx-code-copy-success')\n }, COPY_FEEDBACK_MS)\n } catch {\n // Fallback for insecure contexts\n const textarea = document.createElement('textarea')\n textarea.value = code.textContent\n textarea.style.position = 'fixed'\n textarea.style.opacity = '0'\n document.body.appendChild(textarea)\n textarea.select()\n document.execCommand('copy')\n document.body.removeChild(textarea)\n btn.textContent = '✓'\n btn.classList.add('rmx-code-copy-success')\n setTimeout(() => {\n btn.textContent = '⧉'\n btn.classList.remove('rmx-code-copy-success')\n }, COPY_FEEDBACK_MS)\n }\n }\n engine.element.addEventListener('click', copyClickHandler, true)\n\n // Re-highlight the block the user was typing in once they leave it\n blurHandler = (e) => {\n const pre = e.target?.closest?.('pre')\n if (pre && engine.element.contains(pre)) {\n pre.classList.remove('rmx-highlighted')\n const code = pre.querySelector('code')\n if (code) {\n highlightCodeElement(code, engine)\n }\n updateLineNumbers(pre)\n }\n }\n engine.element.addEventListener('focusout', blurHandler, true)\n\n // Listen for programmatic language changes via the eventBus\n languageChangeUnsub = engine.eventBus.on('codeblock:language-change', ({ language, element }) => {\n if (!element) return\n const pre = element.closest('pre')\n if (pre && engine.element.contains(pre)) {\n pre.classList.remove('rmx-highlighted')\n highlightCodeElement(element, engine)\n }\n })\n },\n\n destroy(engine) {\n // Clear debounce timer\n clearTimeout(debounceTimer)\n debounceTimer = null\n\n // Disconnect MutationObserver\n if (observer) {\n observer.disconnect()\n observer = null\n }\n\n // Remove blur listener\n if (blurHandler) {\n engine.element.removeEventListener('focusout', blurHandler, true)\n blurHandler = null\n }\n\n // Remove copy click handler\n if (copyClickHandler) {\n engine.element.removeEventListener('click', copyClickHandler, true)\n copyClickHandler = null\n }\n\n // Unsubscribe from eventBus\n if (languageChangeUnsub) {\n languageChangeUnsub()\n languageChangeUnsub = null\n }\n },\n })\n}\n\n/**\n * Escapes HTML special characters to prevent injection when\n * building highlighted markup from token values.\n */\n// Task 261: escapeHTML imported from shared utils/escapeHTML.js\n","/**\n * Table column filtering.\n * Injects filter buttons into header cells and manages filter dropdowns.\n */\n\nconst FILTER_DEBOUNCE_MS = 200\n\n/**\n * Attach filter UI to a table's header cells.\n * Returns a cleanup function.\n */\nexport function attachFilterUI(table, engine) {\n const filterBtns = []\n let activeDropdown = null\n let debounceTimer = null\n\n function createFilterButtons() {\n removeFilterButtons()\n const thead = table.querySelector('thead')\n if (!thead) return\n\n const ths = thead.querySelectorAll('th')\n ths.forEach((th, idx) => {\n const btn = document.createElement('span')\n btn.className = 'rmx-filter-btn'\n btn.textContent = '\\u25BD' // small down triangle\n btn.title = 'Filter column'\n btn.setAttribute('data-col-index', String(idx))\n btn.addEventListener('mousedown', (e) => {\n e.preventDefault()\n e.stopPropagation()\n toggleFilterDropdown(th, idx)\n })\n th.style.position = 'relative'\n th.appendChild(btn)\n filterBtns.push(btn)\n\n // Mark active filters\n if (th.getAttribute('data-filter-value')) {\n btn.classList.add('rmx-filter-active')\n }\n })\n }\n\n function toggleFilterDropdown(th, colIndex) {\n if (activeDropdown && activeDropdown.parentElement === th) {\n closeDropdown()\n return\n }\n closeDropdown()\n\n const dropdown = document.createElement('div')\n dropdown.className = 'rmx-filter-dropdown'\n dropdown.addEventListener('mousedown', e => e.stopPropagation())\n\n const input = document.createElement('input')\n input.type = 'text'\n input.className = 'rmx-filter-input'\n input.placeholder = 'Filter...'\n input.value = th.getAttribute('data-filter-value') || ''\n\n input.addEventListener('input', () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n applyFilter(colIndex, input.value)\n }, FILTER_DEBOUNCE_MS)\n })\n\n const clearBtn = document.createElement('button')\n clearBtn.className = 'rmx-filter-clear-btn'\n clearBtn.textContent = 'Clear'\n clearBtn.type = 'button'\n clearBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n input.value = ''\n applyFilter(colIndex, '')\n closeDropdown()\n })\n\n dropdown.appendChild(input)\n dropdown.appendChild(clearBtn)\n th.appendChild(dropdown)\n activeDropdown = dropdown\n\n // Focus the input after appending\n setTimeout(() => input.focus(), 0)\n\n // Close on click outside\n const closeOnOutsideClick = (e) => {\n if (!dropdown.contains(e.target) && e.target !== th.querySelector('.rmx-filter-btn')) {\n closeDropdown()\n document.removeEventListener('mousedown', closeOnOutsideClick)\n }\n }\n setTimeout(() => document.addEventListener('mousedown', closeOnOutsideClick), 0)\n }\n\n function applyFilter(columnIndex, filterValue) {\n const thead = table.querySelector('thead')\n const tbody = table.querySelector('tbody')\n if (!tbody) return\n\n // Store filter value on header\n if (thead) {\n const ths = thead.querySelectorAll('th')\n if (ths[columnIndex]) {\n if (filterValue) {\n ths[columnIndex].setAttribute('data-filter-value', filterValue)\n const btn = ths[columnIndex].querySelector('.rmx-filter-btn')\n if (btn) btn.classList.add('rmx-filter-active')\n } else {\n ths[columnIndex].removeAttribute('data-filter-value')\n const btn = ths[columnIndex].querySelector('.rmx-filter-btn')\n if (btn) btn.classList.remove('rmx-filter-active')\n }\n }\n }\n\n // Collect all active filters\n const filters = []\n if (thead) {\n thead.querySelectorAll('th').forEach((th, idx) => {\n const val = th.getAttribute('data-filter-value')\n if (val) filters.push({ columnIndex: idx, value: val.toLowerCase() })\n })\n }\n\n // Apply filters\n const rows = tbody.querySelectorAll('tr')\n rows.forEach(row => {\n const cells = row.querySelectorAll('td, th')\n const hidden = filters.some(f => {\n const cellText = (cells[f.columnIndex]?.textContent || '').toLowerCase()\n return !cellText.includes(f.value)\n })\n if (hidden) {\n row.classList.add('rmx-row-hidden')\n } else {\n row.classList.remove('rmx-row-hidden')\n }\n })\n\n if (engine?.eventBus) {\n engine.eventBus.emit('table:filter-change', { table, filters })\n }\n }\n\n function closeDropdown() {\n if (activeDropdown) {\n activeDropdown.remove()\n activeDropdown = null\n }\n }\n\n function removeFilterButtons() {\n filterBtns.forEach(btn => btn.remove())\n filterBtns.length = 0\n closeDropdown()\n }\n\n createFilterButtons()\n\n return {\n update: createFilterButtons,\n destroy: () => {\n clearTimeout(debounceTimer)\n removeFilterButtons()\n },\n }\n}\n","import { createPlugin } from '../../createPlugin.js'\nimport { attachResizeHandles } from './resize.js'\nimport { attachFilterUI } from './filter.js'\nimport { evaluateTableFormulas } from '../../../commands/tables.js'\n\nconst FORMULA_DEBOUNCE_MS = 200\n\n/**\n * TablePlugin — Enhanced table features including:\n * - Column/row resize handles\n * - Click-to-sort on header cells (single + multi-column with Shift)\n * - Filter UI on header cells\n * - Formula evaluation (cells starting with '=')\n * - Freeze header row (via CSS sticky, enabled by default for thead)\n *\n * Follows the SyntaxHighlightPlugin pattern: MutationObserver for\n * auto-detection, event-driven lifecycle, createPlugin API.\n */\nexport function TablePlugin() {\n let observer = null\n let tableMap = new Map() // table element -> { resize, filter } cleanup objects\n let formulaTimer = null\n let sortClickHandler = null\n let formulaFocusHandler = null\n let formulaBlurHandler = null\n let unsubContentChange = null\n\n function setupTable(table, engine) {\n if (tableMap.has(table)) return\n const entry = {}\n\n // Resize handles\n entry.resize = attachResizeHandles(table, engine)\n\n // Filter UI (only if table has thead)\n if (table.querySelector('thead')) {\n entry.filter = attachFilterUI(table, engine)\n }\n\n // Mark header cells as sortable\n const thead = table.querySelector('thead')\n if (thead) {\n thead.querySelectorAll('th').forEach(th => {\n th.classList.add('rmx-sortable')\n })\n }\n\n // Detect cells with formula text (starting with =) but no data-formula attribute\n // and convert them to formula cells\n table.querySelectorAll('td, th').forEach(cell => {\n const text = cell.textContent.trim()\n if (text.startsWith('=') && !cell.hasAttribute('data-formula')) {\n cell.setAttribute('data-formula', text)\n }\n })\n\n // Evaluate any existing formulas\n evaluateTableFormulas(table)\n\n tableMap.set(table, entry)\n }\n\n function teardownTable(table) {\n const entry = tableMap.get(table)\n if (!entry) return\n entry.resize?.destroy()\n entry.filter?.destroy()\n tableMap.delete(table)\n }\n\n function setupAllTables(engine) {\n const tables = engine.element.querySelectorAll('table.rmx-table')\n tables.forEach(table => setupTable(table, engine))\n }\n\n return createPlugin({\n name: 'tableFeatures',\n requiresFullAccess: true,\n\n init(engine) {\n // Setup existing tables\n setupAllTables(engine)\n\n // Watch for new tables or table mutations\n observer = new MutationObserver((mutations) => {\n let needsSetup = false\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n for (const node of mutation.addedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n if (node.matches?.('table.rmx-table') || node.querySelector?.('table.rmx-table')) {\n needsSetup = true\n break\n }\n }\n }\n for (const node of mutation.removedNodes) {\n if (node.nodeType === Node.ELEMENT_NODE && node.matches?.('table.rmx-table')) {\n teardownTable(node)\n }\n }\n }\n if (needsSetup) break\n }\n if (needsSetup) {\n setupAllTables(engine)\n }\n })\n\n observer.observe(engine.element, {\n childList: true,\n subtree: true,\n })\n\n // Sort click handler (delegated)\n sortClickHandler = (e) => {\n const th = e.target.closest('th')\n if (!th) return\n const thead = th.closest('thead')\n if (!thead) return\n const table = thead.closest('table.rmx-table')\n if (!table || !engine.element.contains(table)) return\n\n // Don't sort if clicking filter button\n if (e.target.closest('.rmx-filter-btn') || e.target.closest('.rmx-filter-dropdown')) return\n\n const ths = Array.from(thead.querySelectorAll('th'))\n const columnIndex = ths.indexOf(th)\n if (columnIndex < 0) return\n\n const currentDir = th.getAttribute('data-sort-dir')\n let newDir\n if (!currentDir) newDir = 'asc'\n else if (currentDir === 'asc') newDir = 'desc'\n else newDir = null // remove sort\n\n if (e.shiftKey) {\n // Multi-column sort: build keys from existing sorted columns + this one\n const existingKeys = []\n ths.forEach((t, idx) => {\n const dir = t.getAttribute('data-sort-dir')\n if (dir && idx !== columnIndex) {\n existingKeys.push({ columnIndex: idx, direction: dir })\n }\n })\n if (newDir) {\n existingKeys.push({ columnIndex, direction: newDir })\n }\n if (existingKeys.length > 0) {\n engine.executeCommand('sortTable', { keys: existingKeys })\n } else {\n // Clear all sort indicators\n ths.forEach(t => {\n t.removeAttribute('data-sort-dir')\n t.removeAttribute('data-sort-priority')\n })\n }\n } else {\n if (newDir) {\n engine.executeCommand('sortTable', { columnIndex, direction: newDir })\n } else {\n // Clear sort\n ths.forEach(t => {\n t.removeAttribute('data-sort-dir')\n t.removeAttribute('data-sort-priority')\n })\n }\n }\n }\n engine.element.addEventListener('click', sortClickHandler)\n\n // Formula handlers\n formulaFocusHandler = (e) => {\n const cell = e.target.closest('td, th')\n if (!cell || !engine.element.contains(cell)) return\n const formula = cell.getAttribute('data-formula')\n if (formula) {\n // Show formula text on focus\n cell.textContent = '=' + formula\n }\n }\n\n formulaBlurHandler = (e) => {\n const cell = e.target.closest('td, th')\n if (!cell || !engine.element.contains(cell)) return\n const text = cell.textContent.trim()\n\n if (text.startsWith('=') && text.length > 1) {\n // Store formula and evaluate\n const formula = text.slice(1)\n cell.setAttribute('data-formula', formula)\n const table = cell.closest('table.rmx-table')\n if (table) {\n evaluateTableFormulas(table)\n }\n } else if (cell.hasAttribute('data-formula') && !text.startsWith('=')) {\n // User cleared the formula\n cell.removeAttribute('data-formula')\n }\n }\n\n engine.element.addEventListener('focusin', formulaFocusHandler, true)\n engine.element.addEventListener('focusout', formulaBlurHandler, true)\n\n // Re-evaluate formulas on content change (debounced)\n unsubContentChange = engine.eventBus.on('content:change', () => {\n clearTimeout(formulaTimer)\n formulaTimer = setTimeout(() => {\n const tables = engine.element.querySelectorAll('table.rmx-table')\n tables.forEach(table => evaluateTableFormulas(table))\n }, FORMULA_DEBOUNCE_MS)\n })\n },\n\n destroy(engine) {\n clearTimeout(formulaTimer)\n formulaTimer = null\n\n // Item 4: Remove content:change listener\n unsubContentChange?.()\n unsubContentChange = null\n\n if (observer) {\n observer.disconnect()\n observer = null\n }\n\n if (sortClickHandler) {\n engine.element.removeEventListener('click', sortClickHandler)\n sortClickHandler = null\n }\n\n if (formulaFocusHandler) {\n engine.element.removeEventListener('focusin', formulaFocusHandler, true)\n formulaFocusHandler = null\n }\n\n if (formulaBlurHandler) {\n engine.element.removeEventListener('focusout', formulaBlurHandler, true)\n formulaBlurHandler = null\n }\n\n for (const table of tableMap.keys()) {\n teardownTable(table)\n }\n tableMap.clear()\n },\n })\n}\n","/**\n * Column and row resize handles for tables.\n * Attaches invisible drag handles at column borders and row borders.\n */\n\nconst HANDLE_WIDTH = 6\n\n/**\n * Attach resize handles to a table element.\n * Returns a cleanup function.\n */\nexport function attachResizeHandles(table, engine) {\n const handles = []\n let activeHandle = null\n let startX = 0\n let startY = 0\n let startWidth = 0\n let startHeight = 0\n let targetCol = -1\n let targetRow = null\n\n function createColumnHandles() {\n removeHandles()\n const thead = table.querySelector('thead')\n const headerRow = thead?.querySelector('tr') || table.querySelector('tr')\n if (!headerRow) return\n\n const cells = headerRow.querySelectorAll('th, td')\n cells.forEach((cell, idx) => {\n if (idx === cells.length - 1) return // skip last column (resize from left border of next)\n\n const handle = document.createElement('div')\n handle.className = 'rmx-col-resize-handle'\n handle.setAttribute('data-col-index', String(idx))\n handle.addEventListener('mousedown', onColMouseDown)\n table.appendChild(handle)\n handles.push(handle)\n })\n\n // Row resize handles\n const rows = table.querySelectorAll('tbody tr')\n rows.forEach((row, idx) => {\n if (idx === rows.length - 1) return\n const handle = document.createElement('div')\n handle.className = 'rmx-row-resize-handle'\n handle.setAttribute('data-row-index', String(idx))\n handle.addEventListener('mousedown', onRowMouseDown)\n table.appendChild(handle)\n handles.push(handle)\n })\n\n positionHandles()\n }\n\n function positionHandles() {\n const tableRect = table.getBoundingClientRect()\n const thead = table.querySelector('thead')\n const headerRow = thead?.querySelector('tr') || table.querySelector('tr')\n if (!headerRow) return\n\n const cells = headerRow.querySelectorAll('th, td')\n handles.forEach(handle => {\n if (handle.classList.contains('rmx-col-resize-handle')) {\n const idx = parseInt(handle.getAttribute('data-col-index'))\n const cell = cells[idx]\n if (!cell) return\n const cellRect = cell.getBoundingClientRect()\n handle.style.position = 'absolute'\n handle.style.top = '0'\n handle.style.left = (cellRect.right - tableRect.left - HANDLE_WIDTH / 2) + 'px'\n handle.style.width = HANDLE_WIDTH + 'px'\n handle.style.height = table.offsetHeight + 'px'\n } else if (handle.classList.contains('rmx-row-resize-handle')) {\n const idx = parseInt(handle.getAttribute('data-row-index'))\n const rows = table.querySelectorAll('tbody tr')\n const row = rows[idx]\n if (!row) return\n const rowRect = row.getBoundingClientRect()\n handle.style.position = 'absolute'\n handle.style.left = '0'\n handle.style.top = (rowRect.bottom - tableRect.top - HANDLE_WIDTH / 2) + 'px'\n handle.style.width = table.offsetWidth + 'px'\n handle.style.height = HANDLE_WIDTH + 'px'\n }\n })\n }\n\n function onColMouseDown(e) {\n e.preventDefault()\n e.stopPropagation()\n targetCol = parseInt(e.target.getAttribute('data-col-index'))\n const thead = table.querySelector('thead')\n const headerRow = thead?.querySelector('tr') || table.querySelector('tr')\n const cell = headerRow?.querySelectorAll('th, td')[targetCol]\n if (!cell) return\n startX = e.clientX\n startWidth = cell.offsetWidth\n activeHandle = e.target\n table.classList.add('rmx-table-resizing')\n document.addEventListener('mousemove', onColMouseMove)\n document.addEventListener('mouseup', onColMouseUp)\n }\n\n function onColMouseMove(e) {\n if (targetCol < 0) return\n const delta = e.clientX - startX\n const newWidth = Math.max(40, startWidth + delta)\n // Apply width to all cells in this column\n const rows = table.querySelectorAll('tr')\n rows.forEach(row => {\n const cell = row.cells[targetCol]\n if (cell && cell.colSpan <= 1) {\n cell.style.width = newWidth + 'px'\n }\n })\n }\n\n function onColMouseUp() {\n document.removeEventListener('mousemove', onColMouseMove)\n document.removeEventListener('mouseup', onColMouseUp)\n table.classList.remove('rmx-table-resizing')\n targetCol = -1\n activeHandle = null\n if (engine?.history) engine.history.snapshot()\n positionHandles()\n }\n\n function onRowMouseDown(e) {\n e.preventDefault()\n e.stopPropagation()\n const idx = parseInt(e.target.getAttribute('data-row-index'))\n const rows = table.querySelectorAll('tbody tr')\n targetRow = rows[idx]\n if (!targetRow) return\n startY = e.clientY\n startHeight = targetRow.offsetHeight\n table.classList.add('rmx-table-resizing')\n document.addEventListener('mousemove', onRowMouseMove)\n document.addEventListener('mouseup', onRowMouseUp)\n }\n\n function onRowMouseMove(e) {\n if (!targetRow) return\n const delta = e.clientY - startY\n const newHeight = Math.max(24, startHeight + delta)\n targetRow.style.height = newHeight + 'px'\n }\n\n function onRowMouseUp() {\n document.removeEventListener('mousemove', onRowMouseMove)\n document.removeEventListener('mouseup', onRowMouseUp)\n table.classList.remove('rmx-table-resizing')\n targetRow = null\n if (engine?.history) engine.history.snapshot()\n positionHandles()\n }\n\n function removeHandles() {\n handles.forEach(h => h.remove())\n handles.length = 0\n }\n\n // Make table position relative for absolute handle positioning\n if (getComputedStyle(table).position === 'static') {\n table.style.position = 'relative'\n }\n\n createColumnHandles()\n\n return {\n update: positionHandles,\n destroy: () => {\n removeHandles()\n document.removeEventListener('mousemove', onColMouseMove)\n document.removeEventListener('mouseup', onColMouseUp)\n document.removeEventListener('mousemove', onRowMouseMove)\n document.removeEventListener('mouseup', onRowMouseUp)\n },\n }\n}\n","import { createPlugin } from '../createPlugin.js'\n\n/**\n * Built-in block templates for common content patterns.\n */\nconst BUILT_IN_TEMPLATES = {\n 'Feature Card': `<div class=\"rmx-template-feature-card\" style=\"border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin:8px 0;\">\n <img src=\"\" alt=\"Feature image\" style=\"width:100%;height:auto;border-radius:4px;margin-bottom:12px;\" />\n <h3>Feature Title</h3>\n <p>Describe the feature here. Highlight key benefits and value propositions.</p>\n</div>`,\n\n 'Two-Column': `<div class=\"rmx-template-two-column\" style=\"display:flex;gap:16px;margin:8px 0;\">\n <div style=\"flex:1;padding:12px;border:1px solid #e2e8f0;border-radius:4px;\">\n <p>Left column content</p>\n </div>\n <div style=\"flex:1;padding:12px;border:1px solid #e2e8f0;border-radius:4px;\">\n <p>Right column content</p>\n </div>\n</div>`,\n\n 'Call to Action': `<div class=\"rmx-template-cta\" style=\"text-align:center;padding:24px;margin:8px 0;background:#f8fafc;border-radius:8px;\">\n <h2>Ready to Get Started?</h2>\n <p>Take the next step and join thousands of satisfied users today.</p>\n <p><span style=\"display:inline-block;padding:10px 24px;background:#6366f1;color:#fff;border-radius:6px;font-weight:600;\">Get Started</span></p>\n</div>`,\n}\n\n/**\n * BlockTemplatePlugin - Manages reusable block templates.\n *\n * Provides registerTemplate, insertTemplate, getTemplates, and removeTemplate\n * methods on the engine via engine._blockTemplates.\n */\nexport function BlockTemplatePlugin() {\n /** @type {Map<string, string>} */\n const templates = new Map()\n\n return createPlugin({\n name: 'blockTemplates',\n requiresFullAccess: true,\n\n init(engine) {\n // Load built-in templates\n for (const [name, html] of Object.entries(BUILT_IN_TEMPLATES)) {\n templates.set(name, html)\n }\n\n /**\n * Register a custom block template.\n * @param {string} name - Template name\n * @param {string} htmlString - HTML content for the template\n */\n function registerTemplate(name, htmlString) {\n if (!name || typeof htmlString !== 'string') return\n templates.set(name, htmlString)\n }\n\n /**\n * Insert a template by name at the current caret position.\n * @param {string} name - Template name\n */\n function insertTemplate(name) {\n const html = templates.get(name)\n if (!html) return\n\n engine.history.snapshot()\n\n const range = engine.selection.getRange()\n if (!range) return\n\n // Parse the template HTML\n const temp = document.createElement('div')\n temp.innerHTML = engine.sanitizer.sanitize(html)\n\n // Insert each child node at the caret\n const frag = document.createDocumentFragment()\n while (temp.firstChild) {\n frag.appendChild(temp.firstChild)\n }\n\n if (!range.collapsed) {\n range.deleteContents()\n }\n range.insertNode(frag)\n\n // Ensure there's a paragraph after for continued editing\n const editorEl = engine.element\n if (!editorEl.lastElementChild || editorEl.lastElementChild.tagName !== 'P') {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n editorEl.appendChild(p)\n }\n\n engine.eventBus.emit('content:change')\n }\n\n /**\n * Get all registered template names and their HTML.\n * @returns {Array<{ name: string, html: string }>}\n */\n function getTemplates() {\n return Array.from(templates.entries()).map(([name, html]) => ({ name, html }))\n }\n\n /**\n * Remove a template by name.\n * @param {string} name - Template name\n * @returns {boolean} true if removed\n */\n function removeTemplate(name) {\n return templates.delete(name)\n }\n\n // Expose API on engine\n engine._blockTemplates = {\n registerTemplate,\n insertTemplate,\n getTemplates,\n removeTemplate,\n }\n },\n\n destroy() {\n templates.clear()\n },\n })\n}\n","/**\n * CommentsPlugin — inline comment threads and annotations.\n *\n * Wraps selected text in `<mark class=\"rmx-comment\" data-comment-id=\"…\">` elements,\n * maintains an in-memory thread store, emits lifecycle events, and supports\n * resolved/unresolved state, @mentions, and comment-only mode.\n *\n * @param {object} [options]\n * @param {Function} [options.onComment] — called when a comment thread is created\n * @param {Function} [options.onResolve] — called when a thread is resolved/unresolved\n * @param {Function} [options.onDelete] — called when a thread is deleted\n * @param {Function} [options.onReply] — called when a reply is added to a thread\n * @param {string[]} [options.mentionUsers] — list of usernames for @mention autocomplete\n * @param {boolean} [options.commentOnly] — if true, editor is non-editable but comments can be added\n */\n\nimport { createPlugin } from '../../createPlugin.js'\n\nlet _nextId = 1\n\n/** Generate a unique comment ID */\nfunction generateId() {\n return `rmx-comment-${Date.now()}-${_nextId++}`\n}\n\n/**\n * Parse @mentions from comment body text.\n * @param {string} text\n * @returns {string[]} matched usernames (without the @ prefix)\n */\nexport function parseMentions(text) {\n if (!text) return []\n const matches = text.match(/@([\\w.-]+)/g)\n return matches ? matches.map(m => m.slice(1)) : []\n}\n\n/**\n * @typedef {object} CommentReply\n * @property {string} id\n * @property {string} author\n * @property {string} body\n * @property {string[]} mentions\n * @property {number} createdAt\n */\n\n/**\n * @typedef {object} CommentThread\n * @property {string} id\n * @property {string} author\n * @property {string} body\n * @property {string[]} mentions\n * @property {boolean} resolved\n * @property {number} createdAt\n * @property {number} updatedAt\n * @property {CommentReply[]} replies\n */\n\nexport function CommentsPlugin(options = {}) {\n const {\n onComment,\n onResolve,\n onDelete,\n onReply,\n mentionUsers = [],\n commentOnly = false,\n } = options\n\n /** @type {Map<string, CommentThread>} */\n const threads = new Map()\n\n let engine = null\n let unsubContentChange = null\n let unsubDestroy = null\n let syncTimer = null\n\n /**\n * Get all comment threads as an array (newest first).\n * @returns {CommentThread[]}\n */\n function getAllThreads() {\n return Array.from(threads.values()).sort((a, b) => b.createdAt - a.createdAt)\n }\n\n /**\n * Get a single thread by ID.\n * @param {string} id\n * @returns {CommentThread|undefined}\n */\n function getThread(id) {\n return threads.get(id)\n }\n\n /**\n * Get only unresolved threads (newest first).\n * @returns {CommentThread[]}\n */\n function getUnresolvedThreads() {\n return getAllThreads().filter(t => !t.resolved)\n }\n\n /**\n * Get only resolved threads (newest first).\n * @returns {CommentThread[]}\n */\n function getResolvedThreads() {\n return getAllThreads().filter(t => t.resolved)\n }\n\n /**\n * Create a comment thread on the current selection.\n * @param {object} params\n * @param {string} params.author\n * @param {string} params.body\n * @returns {CommentThread|null}\n */\n function addComment({ author = 'Anonymous', body = '' } = {}) {\n if (!engine) return null\n const sel = window.getSelection()\n if (!sel || sel.isCollapsed || sel.rangeCount === 0) return null\n\n const range = sel.getRangeAt(0)\n // Ensure the selection is inside the editor\n if (!engine.element.contains(range.commonAncestorContainer)) return null\n\n const id = generateId()\n const now = Date.now()\n const mentions = parseMentions(body)\n\n const thread = {\n id,\n author,\n body,\n mentions,\n resolved: false,\n createdAt: now,\n updatedAt: now,\n replies: [],\n }\n\n // Wrap the selected text in a <mark>\n engine.history.snapshot()\n const mark = document.createElement('mark')\n mark.className = 'rmx-comment'\n mark.setAttribute('data-comment-id', id)\n mark.setAttribute('data-comment-resolved', 'false')\n mark.setAttribute('title', `${author}: ${body}`)\n\n try {\n range.surroundContents(mark)\n } catch {\n // surroundContents fails if range crosses element boundaries.\n // Fall back to extracting and re-inserting.\n const fragment = range.extractContents()\n mark.appendChild(fragment)\n range.insertNode(mark)\n }\n\n sel.removeAllRanges()\n threads.set(id, thread)\n\n engine.eventBus.emit('comment:created', { thread })\n engine.eventBus.emit('content:change')\n onComment?.(thread)\n\n return thread\n }\n\n /**\n * Add a reply to an existing thread.\n * @param {string} threadId\n * @param {object} params\n * @param {string} params.author\n * @param {string} params.body\n * @returns {CommentReply|null}\n */\n function replyToComment(threadId, { author = 'Anonymous', body = '' } = {}) {\n const thread = threads.get(threadId)\n if (!thread) return null\n\n const reply = {\n id: generateId(),\n author,\n body,\n mentions: parseMentions(body),\n createdAt: Date.now(),\n }\n\n thread.replies.push(reply)\n thread.updatedAt = Date.now()\n\n engine?.eventBus.emit('comment:replied', { thread, reply })\n onReply?.({ thread, reply })\n\n return reply\n }\n\n /**\n * Resolve or unresolve a thread.\n * @param {string} threadId\n * @param {boolean} [resolved=true]\n */\n function resolveComment(threadId, resolved = true) {\n const thread = threads.get(threadId)\n if (!thread) return\n\n thread.resolved = resolved\n thread.updatedAt = Date.now()\n\n // Update the DOM marker\n const mark = engine?.element.querySelector(`[data-comment-id=\"${threadId}\"]`)\n if (mark) {\n mark.setAttribute('data-comment-resolved', String(resolved))\n if (resolved) {\n mark.classList.add('rmx-comment-resolved')\n } else {\n mark.classList.remove('rmx-comment-resolved')\n }\n }\n\n engine?.eventBus.emit('comment:resolved', { thread, resolved })\n onResolve?.({ thread, resolved })\n }\n\n /**\n * Delete a comment thread and remove its highlight from the DOM.\n * @param {string} threadId\n */\n function deleteComment(threadId) {\n const thread = threads.get(threadId)\n if (!thread) return\n\n // Unwrap the <mark> — keep the text content\n const mark = engine?.element.querySelector(`[data-comment-id=\"${threadId}\"]`)\n if (mark) {\n engine.history.snapshot()\n const parent = mark.parentNode\n while (mark.firstChild) {\n parent.insertBefore(mark.firstChild, mark)\n }\n parent.removeChild(mark)\n parent.normalize()\n engine.eventBus.emit('content:change')\n }\n\n threads.delete(threadId)\n engine?.eventBus.emit('comment:deleted', { thread })\n onDelete?.(thread)\n }\n\n /**\n * Edit a comment thread's body text.\n * @param {string} threadId\n * @param {string} newBody\n */\n function editComment(threadId, newBody) {\n const thread = threads.get(threadId)\n if (!thread) return\n\n thread.body = newBody\n thread.mentions = parseMentions(newBody)\n thread.updatedAt = Date.now()\n\n // Update the title attribute on the mark\n const mark = engine?.element.querySelector(`[data-comment-id=\"${threadId}\"]`)\n if (mark) {\n mark.setAttribute('title', `${thread.author}: ${newBody}`)\n }\n\n engine?.eventBus.emit('comment:updated', { thread })\n }\n\n /**\n * Navigate to (scroll into view and select) the annotated text for a thread.\n * @param {string} threadId\n */\n function navigateToComment(threadId) {\n const mark = engine?.element.querySelector(`[data-comment-id=\"${threadId}\"]`)\n if (!mark) return\n\n mark.scrollIntoView?.({ behavior: 'smooth', block: 'center' })\n\n // Select the annotated text\n const sel = window.getSelection()\n const range = document.createRange()\n range.selectNodeContents(mark)\n sel.removeAllRanges()\n sel.addRange(range)\n\n engine?.eventBus.emit('comment:navigated', { threadId })\n }\n\n /**\n * Import threads from serialized data (e.g., from a server).\n * Matches threads to existing data-comment-id marks in the DOM.\n * @param {CommentThread[]} importedThreads\n */\n function importThreads(importedThreads) {\n for (const t of importedThreads) {\n threads.set(t.id, { ...t })\n }\n engine?.eventBus.emit('comment:imported', { count: importedThreads.length })\n }\n\n /**\n * Export all threads as a plain JSON-serializable array.\n * @returns {CommentThread[]}\n */\n function exportThreads() {\n return getAllThreads().map(t => ({ ...t, replies: [...t.replies] }))\n }\n\n /**\n * Sync threads with DOM — remove threads whose marks no longer exist.\n */\n function syncWithDOM() {\n if (!engine) return\n for (const [id] of threads) {\n const mark = engine.element.querySelector(`[data-comment-id=\"${id}\"]`)\n if (!mark) {\n threads.delete(id)\n }\n }\n }\n\n /**\n * Get the list of available mention users.\n * @returns {string[]}\n */\n function getMentionUsers() {\n return [...mentionUsers]\n }\n\n return createPlugin({\n name: 'comments',\n requiresFullAccess: true,\n\n commands: [\n {\n name: 'addComment',\n execute(eng, params) { return addComment(params) },\n isEnabled(eng) {\n const sel = window.getSelection()\n return sel && !sel.isCollapsed && eng.element.contains(sel.anchorNode)\n },\n meta: { icon: 'comment', tooltip: 'Add Comment' },\n },\n {\n name: 'deleteComment',\n execute(eng, threadId) { deleteComment(threadId) },\n meta: { icon: 'trash', tooltip: 'Delete Comment' },\n },\n {\n name: 'resolveComment',\n execute(eng, threadId, resolved) { resolveComment(threadId, resolved) },\n meta: { icon: 'check', tooltip: 'Resolve Comment' },\n },\n {\n name: 'replyToComment',\n execute(eng, threadId, params) { return replyToComment(threadId, params) },\n meta: { icon: 'reply', tooltip: 'Reply to Comment' },\n },\n {\n name: 'editComment',\n execute(eng, threadId, newBody) { editComment(threadId, newBody) },\n meta: { icon: 'edit', tooltip: 'Edit Comment' },\n },\n {\n name: 'navigateToComment',\n execute(eng, threadId) { navigateToComment(threadId) },\n meta: { icon: 'navigate', tooltip: 'Go to Comment' },\n },\n ],\n\n contextMenuItems: [\n {\n label: 'Add Comment',\n command: 'addComment',\n when: (eng) => {\n const sel = window.getSelection()\n return sel && !sel.isCollapsed\n },\n },\n ],\n\n init(eng) {\n engine = eng\n\n // Expose the comments API on the engine for external access\n engine._comments = {\n addComment,\n deleteComment,\n resolveComment,\n replyToComment,\n editComment,\n navigateToComment,\n getAllThreads,\n getThread,\n getUnresolvedThreads,\n getResolvedThreads,\n importThreads,\n exportThreads,\n syncWithDOM,\n getMentionUsers,\n }\n\n // Comment-only mode: make editor non-editable but keep comment functionality\n if (commentOnly) {\n engine.element.setAttribute('contenteditable', 'false')\n engine.element.classList.add('rmx-comment-only')\n }\n\n // Scan existing marks in the DOM (e.g., from imported HTML)\n const existingMarks = engine.element.querySelectorAll('[data-comment-id]')\n for (const mark of existingMarks) {\n const id = mark.getAttribute('data-comment-id')\n if (!threads.has(id)) {\n threads.set(id, {\n id,\n author: 'Imported',\n body: mark.getAttribute('title') || '',\n mentions: [],\n resolved: mark.getAttribute('data-comment-resolved') === 'true',\n createdAt: Date.now(),\n updatedAt: Date.now(),\n replies: [],\n })\n }\n }\n\n // Item 10: Remove MutationObserver, rely on content:change with 100ms debounce\n unsubContentChange = engine.eventBus.on('content:change', () => {\n clearTimeout(syncTimer)\n syncTimer = setTimeout(syncWithDOM, 100)\n })\n\n // Click handler — emit event when a comment mark is clicked\n engine.element.addEventListener('click', handleClick)\n\n // Cleanup on engine destroy\n unsubDestroy = engine.eventBus.on('destroy', cleanup)\n },\n\n destroy() {\n cleanup()\n },\n })\n\n function handleClick(e) {\n const mark = e.target.closest?.('[data-comment-id]')\n if (mark) {\n const threadId = mark.getAttribute('data-comment-id')\n const thread = threads.get(threadId)\n if (thread) {\n engine.eventBus.emit('comment:clicked', { thread, element: mark })\n }\n }\n }\n\n function cleanup() {\n clearTimeout(syncTimer)\n syncTimer = null\n unsubContentChange?.()\n unsubDestroy?.()\n engine?.element?.removeEventListener('click', handleClick)\n if (commentOnly && engine?.element) {\n engine.element.removeAttribute('contenteditable')\n engine.element.classList.remove('rmx-comment-only')\n }\n engine = null\n }\n}\n","/**\n * CalloutPlugin — styled callout/alert/admonition blocks.\n *\n * Provides 7 built-in callout types (info, warning, error, success, tip, note, question)\n * with custom type registration, collapsible toggle, nested content support,\n * and GitHub-flavored alert syntax (`> [!NOTE]`, `> [!WARNING]`).\n *\n * DOM structure:\n * <div class=\"rmx-callout rmx-callout-{type}\" data-callout=\"{type}\" [data-callout-collapsible]>\n * <div class=\"rmx-callout-header\">\n * <span class=\"rmx-callout-icon\">{icon}</span>\n * <span class=\"rmx-callout-title\">{Title}</span>\n * [<button class=\"rmx-callout-toggle\">▶/▼</button>]\n * </div>\n * <div class=\"rmx-callout-body\">{nested content}</div>\n * </div>\n */\n\nimport { createPlugin } from '../../createPlugin.js'\n\n// ---------------------------------------------------------------------------\n// Built-in callout types\n// ---------------------------------------------------------------------------\n\n/** @typedef {{ type: string, label: string, icon: string, color: string }} CalloutType */\n\n/** @type {Map<string, CalloutType>} */\nconst _calloutTypes = new Map()\n\n// Default 7 types\nconst BUILTIN_TYPES = [\n { type: 'info', label: 'Info', icon: 'ℹ️', color: '#3b82f6' },\n { type: 'warning', label: 'Warning', icon: '⚠️', color: '#f59e0b' },\n { type: 'error', label: 'Error', icon: '❌', color: '#ef4444' },\n { type: 'success', label: 'Success', icon: '✅', color: '#22c55e' },\n { type: 'tip', label: 'Tip', icon: '💡', color: '#8b5cf6' },\n { type: 'note', label: 'Note', icon: '📝', color: '#6366f1' },\n { type: 'question', label: 'Question', icon: '❓', color: '#ec4899' },\n]\n\n// Initialize defaults\nfor (const t of BUILTIN_TYPES) _calloutTypes.set(t.type, t)\n\n/**\n * Register a custom callout type (or override a built-in).\n * @param {CalloutType} typeDef\n */\nexport function registerCalloutType(typeDef) {\n if (!typeDef || !typeDef.type) return\n _calloutTypes.set(typeDef.type, typeDef)\n}\n\n/**\n * Unregister a callout type.\n * @param {string} type\n * @returns {boolean}\n */\nexport function unregisterCalloutType(type) {\n return _calloutTypes.delete(type)\n}\n\n/**\n * Get all registered callout types.\n * @returns {CalloutType[]}\n */\nexport function getCalloutTypes() {\n return Array.from(_calloutTypes.values())\n}\n\n/**\n * Get a callout type definition by name.\n * @param {string} type\n * @returns {CalloutType|undefined}\n */\nexport function getCalloutType(type) {\n return _calloutTypes.get(type)\n}\n\n// ---------------------------------------------------------------------------\n// GitHub-flavored alert mapping\n// ---------------------------------------------------------------------------\n\n/** Map GFM alert keywords to callout types */\nconst GFM_ALERT_MAP = {\n NOTE: 'note',\n TIP: 'tip',\n IMPORTANT: 'info',\n WARNING: 'warning',\n CAUTION: 'error',\n}\n\n/**\n * Detect GitHub-flavored alert syntax in a blockquote.\n * Pattern: `> [!NOTE]` or `> [!WARNING]` as the first line.\n * @param {string} text — the text content of a blockquote\n * @returns {{ type: string, body: string }|null}\n */\nexport function parseGFMAlert(text) {\n if (!text) return null\n const match = text.match(/^\\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\]\\s*\\n?([\\s\\S]*)/)\n if (!match) return null\n const keyword = match[1]\n const type = GFM_ALERT_MAP[keyword] || keyword.toLowerCase()\n return { type, body: match[2].trim() }\n}\n\n// ---------------------------------------------------------------------------\n// DOM helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Create a callout DOM element.\n * @param {string} type\n * @param {string} [content='']\n * @param {object} [options]\n * @param {boolean} [options.collapsible=false]\n * @param {boolean} [options.collapsed=false]\n * @param {string} [options.title]\n * @returns {HTMLDivElement}\n */\nfunction createCalloutElement(type, content = '', options = {}) {\n const { collapsible = false, collapsed = false, title } = options\n const typeDef = _calloutTypes.get(type) || { type, label: type, icon: '📌', color: '#64748b' }\n\n const wrapper = document.createElement('div')\n wrapper.className = `rmx-callout rmx-callout-${type}`\n wrapper.setAttribute('data-callout', type)\n if (collapsible) {\n wrapper.setAttribute('data-callout-collapsible', '')\n if (collapsed) wrapper.setAttribute('data-callout-collapsed', '')\n }\n\n // Header\n const header = document.createElement('div')\n header.className = 'rmx-callout-header'\n header.contentEditable = 'false'\n\n const icon = document.createElement('span')\n icon.className = 'rmx-callout-icon'\n icon.textContent = typeDef.icon\n header.appendChild(icon)\n\n const titleEl = document.createElement('span')\n titleEl.className = 'rmx-callout-title'\n titleEl.textContent = title || typeDef.label\n header.appendChild(titleEl)\n\n if (collapsible) {\n const toggle = document.createElement('button')\n toggle.className = 'rmx-callout-toggle'\n toggle.type = 'button'\n toggle.setAttribute('aria-label', collapsed ? 'Expand' : 'Collapse')\n toggle.textContent = collapsed ? '▶' : '▼'\n header.appendChild(toggle)\n }\n\n wrapper.appendChild(header)\n\n // Body\n const body = document.createElement('div')\n body.className = 'rmx-callout-body'\n if (content) {\n body.innerHTML = content\n } else {\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n body.appendChild(p)\n }\n if (collapsible && collapsed) {\n body.style.display = 'none'\n }\n wrapper.appendChild(body)\n\n return wrapper\n}\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nexport function CalloutPlugin() {\n let engine = null\n let observer = null\n let unsubContentChange = null\n\n function handleClick(e) {\n const toggle = e.target.closest?.('.rmx-callout-toggle')\n if (!toggle) return\n\n const callout = toggle.closest('.rmx-callout')\n if (!callout) return\n\n e.preventDefault()\n e.stopPropagation()\n\n const body = callout.querySelector('.rmx-callout-body')\n if (!body) return\n\n const isCollapsed = callout.hasAttribute('data-callout-collapsed')\n if (isCollapsed) {\n callout.removeAttribute('data-callout-collapsed')\n body.style.display = ''\n toggle.textContent = '▼'\n toggle.setAttribute('aria-label', 'Collapse')\n } else {\n callout.setAttribute('data-callout-collapsed', '')\n body.style.display = 'none'\n toggle.textContent = '▶'\n toggle.setAttribute('aria-label', 'Expand')\n }\n }\n\n /**\n * Auto-convert blockquotes with GFM alert syntax to callouts.\n */\n function scanForGFMAlerts() {\n if (!engine) return\n const blockquotes = engine.element.querySelectorAll('blockquote')\n for (const bq of blockquotes) {\n const text = bq.textContent\n const alert = parseGFMAlert(text)\n if (alert) {\n engine.history.snapshot()\n const callout = createCalloutElement(alert.type, `<p>${alert.body || '<br>'}</p>`)\n bq.parentNode.replaceChild(callout, bq)\n engine.eventBus.emit('content:change')\n }\n }\n }\n\n return createPlugin({\n name: 'callouts',\n requiresFullAccess: true,\n version: '1.0.0',\n description: 'Styled callout blocks with 7 built-in types, custom types, collapsible toggle, and GFM alert syntax',\n\n commands: [\n {\n name: 'insertCallout',\n execute(eng, params = {}) {\n const { type = 'info', content = '', collapsible = false, collapsed = false, title } = params\n eng.history.snapshot()\n\n const callout = createCalloutElement(type, content, { collapsible, collapsed, title })\n\n // Insert at current selection or append to editor\n const sel = window.getSelection()\n if (sel && sel.rangeCount > 0 && eng.element.contains(sel.anchorNode)) {\n const range = sel.getRangeAt(0)\n // Find the top-level block to insert after\n let block = range.startContainer\n while (block && block.parentNode !== eng.element) {\n block = block.parentNode\n }\n if (block) {\n block.parentNode.insertBefore(callout, block.nextSibling)\n } else {\n eng.element.appendChild(callout)\n }\n } else {\n eng.element.appendChild(callout)\n }\n\n // Place caret in the body\n const body = callout.querySelector('.rmx-callout-body p')\n if (body) {\n const r = document.createRange()\n r.selectNodeContents(body)\n r.collapse(true)\n sel?.removeAllRanges()\n sel?.addRange(r)\n }\n\n eng.eventBus.emit('content:change')\n return callout\n },\n meta: { icon: 'callout', tooltip: 'Insert Callout' },\n },\n {\n name: 'removeCallout',\n execute(eng) {\n const callout = eng.selection.getClosestElement?.('.rmx-callout')\n || findAncestor(eng, 'rmx-callout')\n if (!callout) return\n eng.history.snapshot()\n\n // Extract body content and replace the callout with it\n const body = callout.querySelector('.rmx-callout-body')\n const parent = callout.parentNode\n if (body) {\n while (body.firstChild) {\n parent.insertBefore(body.firstChild, callout)\n }\n }\n parent.removeChild(callout)\n parent.normalize()\n eng.eventBus.emit('content:change')\n },\n meta: { icon: 'remove', tooltip: 'Remove Callout' },\n },\n {\n name: 'changeCalloutType',\n execute(eng, newType) {\n const callout = findAncestor(eng, 'rmx-callout')\n if (!callout || !newType) return\n const typeDef = _calloutTypes.get(newType)\n if (!typeDef) return\n\n eng.history.snapshot()\n // Update class and data attribute\n const oldType = callout.getAttribute('data-callout')\n callout.classList.remove(`rmx-callout-${oldType}`)\n callout.classList.add(`rmx-callout-${newType}`)\n callout.setAttribute('data-callout', newType)\n\n // Update icon and title\n const icon = callout.querySelector('.rmx-callout-icon')\n const title = callout.querySelector('.rmx-callout-title')\n if (icon) icon.textContent = typeDef.icon\n if (title) title.textContent = typeDef.label\n\n eng.eventBus.emit('content:change')\n },\n meta: { icon: 'swap', tooltip: 'Change Callout Type' },\n },\n {\n name: 'toggleCalloutCollapse',\n execute(eng) {\n const callout = findAncestor(eng, 'rmx-callout')\n if (!callout) return\n\n const body = callout.querySelector('.rmx-callout-body')\n const toggle = callout.querySelector('.rmx-callout-toggle')\n if (!body) return\n\n if (!callout.hasAttribute('data-callout-collapsible')) {\n // Make it collapsible\n callout.setAttribute('data-callout-collapsible', '')\n if (!toggle) {\n const btn = document.createElement('button')\n btn.className = 'rmx-callout-toggle'\n btn.type = 'button'\n btn.textContent = '▼'\n btn.setAttribute('aria-label', 'Collapse')\n callout.querySelector('.rmx-callout-header')?.appendChild(btn)\n }\n } else {\n // Toggle collapsed state\n const isCollapsed = callout.hasAttribute('data-callout-collapsed')\n if (isCollapsed) {\n callout.removeAttribute('data-callout-collapsed')\n body.style.display = ''\n if (toggle) { toggle.textContent = '▼'; toggle.setAttribute('aria-label', 'Collapse') }\n } else {\n callout.setAttribute('data-callout-collapsed', '')\n body.style.display = 'none'\n if (toggle) { toggle.textContent = '▶'; toggle.setAttribute('aria-label', 'Expand') }\n }\n }\n\n eng.eventBus.emit('content:change')\n },\n meta: { icon: 'collapse', tooltip: 'Toggle Callout Collapse' },\n },\n ],\n\n contextMenuItems: [\n {\n label: 'Insert Callout',\n command: 'insertCallout',\n },\n {\n label: 'Remove Callout',\n command: 'removeCallout',\n when: (eng) => !!findAncestor(eng, 'rmx-callout'),\n },\n ],\n\n init(eng) {\n engine = eng\n\n // Expose API on engine\n engine._callouts = {\n getCalloutTypes,\n getCalloutType,\n registerCalloutType,\n unregisterCalloutType,\n parseGFMAlert,\n }\n\n // Click handler for collapse toggles\n engine.element.addEventListener('click', handleClick)\n\n // Scan for GFM alerts on content change (debounced)\n let debounceTimer = null\n unsubContentChange = engine.eventBus.on('content:change', () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(scanForGFMAlerts, 300)\n })\n\n // Initial scan\n scanForGFMAlerts()\n\n // Watch for pasted/inserted callout content\n observer = new MutationObserver(() => {\n // Ensure callout bodies remain editable\n const bodies = engine.element.querySelectorAll('.rmx-callout-body')\n for (const body of bodies) {\n if (!body.hasAttribute('contenteditable')) {\n // Body should be editable (inherits from editor)\n }\n }\n })\n observer.observe(engine.element, { childList: true, subtree: true })\n },\n\n destroy() {\n observer?.disconnect()\n observer = null\n unsubContentChange?.()\n engine?.element?.removeEventListener('click', handleClick)\n engine = null\n },\n })\n}\n\n/**\n * Find the closest ancestor with a given class from the current selection.\n * @param {object} eng\n * @param {string} className\n * @returns {HTMLElement|null}\n */\nfunction findAncestor(eng, className) {\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return null\n let node = sel.anchorNode\n if (node.nodeType === 3) node = node.parentNode\n while (node && node !== eng.element) {\n if (node.classList?.contains(className)) return node\n node = node.parentNode\n }\n return null\n}\n","import { escapeHTMLAttr } from '../../../utils/escapeHTML.js'\n\n/**\n * LinkPlugin — Advanced link management.\n *\n * - Link previews: hover tooltip with title, description, thumbnail (via onUnfurl callback)\n * - Broken link detection: periodic scan with visual indicators\n * - Auto-link: detect and convert raw URLs, emails, phone numbers on typing\n * - Link analytics: onLinkClick callback with metadata\n * - Internal link suggestions: onSuggest callback for document search\n * - Bookmark anchors: named anchors and intra-document linking\n *\n * @param {object} [options]\n * @param {Function} [options.onLinkClick] — (metadata) => void, called on every link click\n * @param {Function} [options.onUnfurl] — (url) => Promise<{ title, description, image }>, for link previews\n * @param {Function} [options.onSuggest] — (query) => Promise<Array<{ title, url }>>, for internal link search\n * @param {Function} [options.onBrokenLink] — (url, element) => void, called when a broken link is detected\n * @param {Function} [options.validateLink] — (url) => Promise<boolean>, check if a link is alive\n * @param {number} [options.scanInterval=60000] — ms between broken link scans (0 = disabled)\n * @param {boolean} [options.autoLink=true] — auto-convert typed URLs/emails/phones to links\n * @param {boolean} [options.showPreviews=true] — show hover previews on links\n */\n\nimport { createPlugin } from '../../createPlugin.js'\n\n// ---------------------------------------------------------------------------\n// URL / email / phone detection patterns\n// ---------------------------------------------------------------------------\n\n/** Match URLs starting with http(s):// or www. */\nconst URL_REGEX = /(?:https?:\\/\\/|www\\.)[^\\s<>\"']+/gi\n\n/** Match email addresses */\nconst EMAIL_REGEX = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g\n\n/** Match phone numbers (common formats) */\nconst PHONE_REGEX = /(?:\\+?1[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}/g\n\n/**\n * Find raw URLs, emails, and phone numbers in a text node.\n * @param {string} text\n * @returns {Array<{ type: 'url'|'email'|'phone', value: string, index: number }>}\n */\nexport function detectLinks(text) {\n const results = []\n let match\n\n URL_REGEX.lastIndex = 0\n while ((match = URL_REGEX.exec(text)) !== null) {\n results.push({ type: 'url', value: match[0], index: match.index })\n }\n\n EMAIL_REGEX.lastIndex = 0\n while ((match = EMAIL_REGEX.exec(text)) !== null) {\n // Skip if already inside a URL match\n const emailStart = match.index\n if (!results.some(r => emailStart >= r.index && emailStart < r.index + r.value.length)) {\n results.push({ type: 'email', value: match[0], index: match.index })\n }\n }\n\n PHONE_REGEX.lastIndex = 0\n while ((match = PHONE_REGEX.exec(text)) !== null) {\n results.push({ type: 'phone', value: match[0], index: match.index })\n }\n\n return results.sort((a, b) => a.index - b.index)\n}\n\n/**\n * Convert a detected link to an href.\n * @param {{ type: string, value: string }} link\n * @returns {string}\n */\nfunction linkToHref(link) {\n if (link.type === 'email') return `mailto:${link.value}`\n if (link.type === 'phone') return `tel:${link.value.replace(/[^\\d+]/g, '')}`\n if (link.value.startsWith('www.')) return `https://${link.value}`\n return link.value\n}\n\n// ---------------------------------------------------------------------------\n// Bookmark anchor helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a slug from text for use as an anchor ID.\n * @param {string} text\n * @returns {string}\n */\nexport function slugify(text) {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n || 'anchor'\n}\n\n// ---------------------------------------------------------------------------\n// Link preview tooltip\n// ---------------------------------------------------------------------------\n\nlet _previewEl = null\n\nfunction showPreview(anchor, data, editorEl) {\n hidePreview()\n const rect = anchor.getBoundingClientRect()\n const editorRect = editorEl.getBoundingClientRect()\n\n _previewEl = document.createElement('div')\n _previewEl.className = 'rmx-link-preview'\n _previewEl.innerHTML = `\n ${data.image ? `<img class=\"rmx-link-preview-img\" src=\"${data.image}\" alt=\"\" />` : ''}\n <div class=\"rmx-link-preview-text\">\n <div class=\"rmx-link-preview-title\">${escapeHTML(data.title || anchor.href)}</div>\n ${data.description ? `<div class=\"rmx-link-preview-desc\">${escapeHTML(data.description)}</div>` : ''}\n <div class=\"rmx-link-preview-url\">${escapeHTML(anchor.href)}</div>\n </div>\n `\n _previewEl.style.position = 'absolute'\n _previewEl.style.left = `${rect.left - editorRect.left}px`\n _previewEl.style.top = `${rect.bottom - editorRect.top + 4}px`\n\n editorEl.style.position = 'relative'\n editorEl.appendChild(_previewEl)\n}\n\nfunction hidePreview() {\n if (_previewEl) {\n _previewEl.remove()\n _previewEl = null\n }\n}\n\n// Task 261: escapeHTML imported from shared utils/escapeHTML.js\nfunction escapeHTML(str) {\n if (!str) return ''\n return escapeHTMLAttr(str)\n}\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nexport function LinkPlugin(options = {}) {\n const {\n onLinkClick,\n onUnfurl,\n onSuggest,\n onBrokenLink,\n validateLink,\n scanInterval = 60000,\n autoLink = true,\n showPreviews = true,\n } = options\n\n let engine = null\n let scanTimer = null\n let hoverTimer = null\n let unsubContentChange = null\n\n /** Cache for unfurl results */\n const _unfurlCache = new Map()\n\n /** Set of URLs marked as broken */\n const _brokenLinks = new Set()\n\n // -----------------------------------------------------------------------\n // Auto-link: convert typed URLs/emails/phones on Space/Enter\n // -----------------------------------------------------------------------\n\n function handleAutoLink(e) {\n if (!autoLink || !engine) return\n if (e.key !== ' ' && e.key !== 'Enter') return\n\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return\n\n const range = sel.getRangeAt(0)\n const node = range.startContainer\n if (node.nodeType !== 3) return // text node only\n\n // Don't auto-link inside existing anchors\n if (node.parentElement?.closest('a')) return\n if (!engine.element.contains(node)) return\n\n const text = node.textContent.substring(0, range.startOffset)\n const links = detectLinks(text)\n if (links.length === 0) return\n\n // Convert the last detected link (most recent typing)\n const last = links[links.length - 1]\n const endPos = last.index + last.value.length\n\n // Only convert if the link ends right at the caret\n if (endPos !== range.startOffset) return\n\n e.preventDefault()\n engine.history.snapshot()\n\n const href = linkToHref(last)\n const linkRange = document.createRange()\n linkRange.setStart(node, last.index)\n linkRange.setEnd(node, endPos)\n\n const anchor = document.createElement('a')\n anchor.href = href\n anchor.target = '_blank'\n anchor.rel = 'noopener noreferrer'\n anchor.textContent = last.value\n\n linkRange.deleteContents()\n linkRange.insertNode(anchor)\n\n // Insert the space/enter after the link\n const space = document.createTextNode(e.key === ' ' ? ' ' : '')\n anchor.after(space)\n if (e.key === 'Enter') {\n const br = document.createElement('br')\n anchor.after(br)\n const afterBr = document.createTextNode('')\n br.after(afterBr)\n const newRange = document.createRange()\n newRange.setStart(afterBr, 0)\n newRange.collapse(true)\n sel.removeAllRanges()\n sel.addRange(newRange)\n } else {\n const newRange = document.createRange()\n newRange.setStartAfter(space)\n newRange.collapse(true)\n sel.removeAllRanges()\n sel.addRange(newRange)\n }\n\n node.parentNode?.normalize()\n engine.eventBus.emit('content:change')\n }\n\n // -----------------------------------------------------------------------\n // Link click tracking\n // -----------------------------------------------------------------------\n\n function handleLinkClick(e) {\n const anchor = e.target.closest?.('a[href]')\n if (!anchor || !engine?.element.contains(anchor)) return\n\n if (onLinkClick) {\n onLinkClick({\n href: anchor.href,\n text: anchor.textContent,\n target: anchor.target,\n timestamp: Date.now(),\n element: anchor,\n })\n }\n }\n\n // -----------------------------------------------------------------------\n // Link hover previews\n // -----------------------------------------------------------------------\n\n function handleMouseOver(e) {\n if (!showPreviews || !onUnfurl) return\n const anchor = e.target.closest?.('a[href]')\n if (!anchor || !engine?.element.contains(anchor)) return\n\n clearTimeout(hoverTimer)\n hoverTimer = setTimeout(async () => {\n const url = anchor.href\n try {\n let data = _unfurlCache.get(url)\n if (!data) {\n data = await onUnfurl(url)\n if (data) _unfurlCache.set(url, data)\n }\n if (data) {\n showPreview(anchor, data, engine.element)\n }\n } catch {\n // unfurl failed — silently skip\n }\n }, 400)\n }\n\n function handleMouseOut(e) {\n const anchor = e.target.closest?.('a[href]')\n if (anchor) {\n clearTimeout(hoverTimer)\n hidePreview()\n }\n }\n\n // -----------------------------------------------------------------------\n // Broken link detection\n // -----------------------------------------------------------------------\n\n /** Item 16: Concurrency limit for broken link scanning */\n const CONCURRENT_LINK_CHECKS = 5\n\n async function scanForBrokenLinks() {\n if (!engine || !validateLink) return\n const anchors = engine.element.querySelectorAll('a[href]')\n const checked = new Set()\n\n // Collect unique URLs with their anchors\n const urlAnchors = []\n for (const anchor of anchors) {\n const url = anchor.href\n if (checked.has(url) || url.startsWith('mailto:') || url.startsWith('tel:') || url.startsWith('#')) continue\n checked.add(url)\n urlAnchors.push({ url, anchor })\n }\n\n // Item 16: Process with concurrency limit of 5\n for (let i = 0; i < urlAnchors.length; i += CONCURRENT_LINK_CHECKS) {\n const batch = urlAnchors.slice(i, i + CONCURRENT_LINK_CHECKS)\n await Promise.all(batch.map(async ({ url, anchor }) => {\n try {\n const alive = await validateLink(url)\n if (!alive) {\n _brokenLinks.add(url)\n anchor.classList.add('rmx-link-broken')\n anchor.setAttribute('data-link-broken', 'true')\n onBrokenLink?.(url, anchor)\n } else {\n _brokenLinks.delete(url)\n anchor.classList.remove('rmx-link-broken')\n anchor.removeAttribute('data-link-broken')\n }\n } catch {\n _brokenLinks.add(url)\n anchor.classList.add('rmx-link-broken')\n anchor.setAttribute('data-link-broken', 'true')\n onBrokenLink?.(url, anchor)\n }\n }))\n }\n\n engine.eventBus.emit('link:scanComplete', {\n total: checked.size,\n broken: _brokenLinks.size,\n })\n }\n\n // -----------------------------------------------------------------------\n // Sync broken link classes on content change\n // -----------------------------------------------------------------------\n\n function syncBrokenLinks() {\n if (!engine || _brokenLinks.size === 0) return\n const anchors = engine.element.querySelectorAll('a[href]')\n for (const anchor of anchors) {\n if (_brokenLinks.has(anchor.href)) {\n anchor.classList.add('rmx-link-broken')\n anchor.setAttribute('data-link-broken', 'true')\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Plugin definition\n // -----------------------------------------------------------------------\n\n return createPlugin({\n name: 'advancedLinks',\n requiresFullAccess: true,\n version: '1.0.0',\n description: 'Link previews, broken link detection, auto-linking, analytics, bookmarks',\n\n commands: [\n {\n name: 'insertBookmark',\n execute(eng, params = {}) {\n const { name, id } = params\n const anchorId = id || slugify(name || 'bookmark')\n\n eng.history.snapshot()\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return\n\n const range = sel.getRangeAt(0)\n const anchor = document.createElement('a')\n anchor.id = anchorId\n anchor.className = 'rmx-bookmark'\n anchor.setAttribute('data-bookmark', anchorId)\n anchor.textContent = name ? `\\u{1F4CC} ${name}` : `\\u{1F4CC} ${anchorId}`\n anchor.contentEditable = 'false'\n\n range.collapse(true)\n range.insertNode(anchor)\n\n // Add a space after the bookmark\n const space = document.createTextNode(' ')\n anchor.after(space)\n\n eng.eventBus.emit('content:change')\n eng.eventBus.emit('bookmark:created', { id: anchorId, name })\n return anchor\n },\n meta: { icon: 'bookmark', tooltip: 'Insert Bookmark Anchor' },\n },\n {\n name: 'linkToBookmark',\n execute(eng, bookmarkId) {\n if (!bookmarkId) return\n const sel = eng.selection\n const text = sel.getSelectedText()\n if (!text) return\n\n eng.history.snapshot()\n const link = sel.wrapWith('a', {\n href: `#${bookmarkId}`,\n })\n if (link) {\n link.classList.add('rmx-internal-link')\n }\n eng.eventBus.emit('content:change')\n },\n meta: { icon: 'link', tooltip: 'Link to Bookmark' },\n },\n {\n name: 'getBookmarks',\n execute(eng) {\n const bookmarks = eng.element.querySelectorAll('[data-bookmark]')\n return Array.from(bookmarks).map(el => ({\n id: el.getAttribute('data-bookmark'),\n name: el.textContent.replace(/^\\u{1F4CC}\\s*/u, ''),\n element: el,\n }))\n },\n meta: { tooltip: 'List Bookmarks' },\n },\n {\n name: 'removeBookmark',\n execute(eng, bookmarkId) {\n const el = eng.element.querySelector(`[data-bookmark=\"${bookmarkId}\"]`)\n if (!el) return\n eng.history.snapshot()\n el.remove()\n eng.eventBus.emit('content:change')\n eng.eventBus.emit('bookmark:deleted', { id: bookmarkId })\n },\n meta: { icon: 'trash', tooltip: 'Remove Bookmark' },\n },\n {\n name: 'scanBrokenLinks',\n execute() {\n scanForBrokenLinks()\n },\n meta: { icon: 'scan', tooltip: 'Scan for Broken Links' },\n },\n {\n name: 'getBrokenLinks',\n execute() {\n return Array.from(_brokenLinks)\n },\n meta: { tooltip: 'Get Broken Links' },\n },\n ],\n\n contextMenuItems: [\n {\n label: 'Insert Bookmark Here',\n command: 'insertBookmark',\n },\n ],\n\n init(eng) {\n engine = eng\n\n // Expose API on engine\n engine._links = {\n detectLinks,\n slugify,\n getBrokenLinks: () => Array.from(_brokenLinks),\n getBookmarks: () => {\n const els = engine.element.querySelectorAll('[data-bookmark]')\n return Array.from(els).map(el => ({\n id: el.getAttribute('data-bookmark'),\n name: el.textContent.replace(/^\\u{1F4CC}\\s*/u, ''),\n }))\n },\n scanForBrokenLinks,\n clearUnfurlCache: () => _unfurlCache.clear(),\n }\n\n // Auto-link on space/enter\n if (autoLink) {\n engine.element.addEventListener('keydown', handleAutoLink)\n }\n\n // Link click tracking\n engine.element.addEventListener('click', handleLinkClick)\n\n // Link hover previews\n if (showPreviews && onUnfurl) {\n engine.element.addEventListener('mouseover', handleMouseOver)\n engine.element.addEventListener('mouseout', handleMouseOut)\n }\n\n // Broken link scanning\n if (validateLink && scanInterval > 0) {\n scanTimer = setInterval(scanForBrokenLinks, scanInterval)\n // Initial scan after a short delay\n setTimeout(scanForBrokenLinks, 2000)\n }\n\n // Sync broken link classes on content changes\n unsubContentChange = engine.eventBus.on('content:change', syncBrokenLinks)\n },\n\n destroy() {\n if (autoLink) {\n engine?.element?.removeEventListener('keydown', handleAutoLink)\n }\n engine?.element?.removeEventListener('click', handleLinkClick)\n engine?.element?.removeEventListener('mouseover', handleMouseOver)\n engine?.element?.removeEventListener('mouseout', handleMouseOut)\n\n hidePreview()\n clearTimeout(hoverTimer)\n clearInterval(scanTimer)\n unsubContentChange?.()\n _unfurlCache.clear()\n _brokenLinks.clear()\n engine = null\n },\n })\n}\n","/**\n * TemplatePlugin — Merge tags, conditional blocks, repeatable sections,\n * live preview with sample data, and pre-built template library.\n *\n * Merge tag syntax: {{variable_name}}\n * Conditionals: {{#if condition}}...{{/if}}\n * Loops: {{#each items}}...{{/each}}\n *\n * Tags are rendered as inline chips: <span class=\"rmx-merge-tag\" data-tag=\"name\">{{name}}</span>\n */\n\nimport { createPlugin } from '../../createPlugin.js'\n\n// ---------------------------------------------------------------------------\n// Template parsing & rendering\n// ---------------------------------------------------------------------------\n\n/** Match merge tags: {{variable}} */\nconst TAG_REGEX = /\\{\\{([^{}]+)\\}\\}/g\n\n/** Match block tags: {{#if x}}...{{/if}} and {{#each x}}...{{/each}} */\nconst BLOCK_IF_REGEX = /\\{\\{#if\\s+(\\w+)\\}\\}([\\s\\S]*?)\\{\\{\\/if\\}\\}/g\nconst BLOCK_EACH_REGEX = /\\{\\{#each\\s+(\\w+)\\}\\}([\\s\\S]*?)\\{\\{\\/each\\}\\}/g\n\n/**\n * Render a template string with data.\n * Processes {{#each}}, {{#if}}, and {{variable}} tags.\n * @param {string} template\n * @param {Object} data\n * @returns {string}\n */\nexport function renderTemplate(template, data) {\n if (!template || !data) return template || ''\n let result = template\n\n // Process {{#each items}}...{{/each}}\n result = result.replace(BLOCK_EACH_REGEX, (_, key, body) => {\n const arr = data[key]\n if (!Array.isArray(arr)) return ''\n return arr.map(item => {\n let rendered = body\n if (typeof item === 'object' && item !== null) {\n rendered = renderTemplate(body, { ...data, ...item })\n } else {\n rendered = rendered.replace(/\\{\\{this\\}\\}/g, String(item))\n }\n return rendered\n }).join('')\n })\n\n // Process {{#if condition}}...{{/if}}\n result = result.replace(BLOCK_IF_REGEX, (_, key, body) => {\n return data[key] ? renderTemplate(body, data) : ''\n })\n\n // Process {{variable}} tags\n result = result.replace(TAG_REGEX, (_, key) => {\n const trimmed = key.trim()\n return trimmed in data ? String(data[trimmed]) : `{{${trimmed}}}`\n })\n\n return result\n}\n\n/**\n * Extract all unique tag names from a template string.\n * @param {string} template\n * @returns {string[]}\n */\nexport function extractTags(template) {\n if (!template) return []\n const tags = new Set()\n const matches = template.matchAll(/\\{\\{(?:#(?:if|each)\\s+)?(\\w+)\\}\\}/g)\n for (const m of matches) tags.add(m[1])\n return Array.from(tags)\n}\n\n// ---------------------------------------------------------------------------\n// Pre-built template library\n// ---------------------------------------------------------------------------\n\n/** @type {Map<string, { name: string, category: string, html: string, sampleData: Object }>} */\nconst _templateLibrary = new Map()\n\nconst BUILTIN_TEMPLATES = [\n {\n id: 'email',\n name: 'Email',\n category: 'Communication',\n html: '<p>Dear {{recipient_name}},</p><p>{{body}}</p><p>Best regards,<br>{{sender_name}}<br>{{sender_title}}</p>',\n sampleData: { recipient_name: 'John Doe', body: 'Thank you for your interest in our product.', sender_name: 'Jane Smith', sender_title: 'Sales Manager' },\n },\n {\n id: 'invoice',\n name: 'Invoice',\n category: 'Business',\n html: '<h2>Invoice #{{invoice_number}}</h2><p>Date: {{date}}</p><p>Bill to: <strong>{{client_name}}</strong></p><p>{{client_address}}</p><table class=\"rmx-table\"><thead><tr><th>Item</th><th>Qty</th><th>Price</th></tr></thead><tbody><tr><td>{{item_1}}</td><td>{{qty_1}}</td><td>{{price_1}}</td></tr></tbody></table><p><strong>Total: {{total}}</strong></p>',\n sampleData: { invoice_number: 'INV-001', date: '2026-03-19', client_name: 'Acme Corp', client_address: '123 Main St', item_1: 'Widget', qty_1: '10', price_1: '$29.99', total: '$299.90' },\n },\n {\n id: 'letter',\n name: 'Letter',\n category: 'Communication',\n html: '<p>{{date}}</p><p>{{recipient_name}}<br>{{recipient_address}}</p><p>Dear {{recipient_name}},</p><p>{{body}}</p><p>Sincerely,<br>{{sender_name}}</p>',\n sampleData: { date: 'March 19, 2026', recipient_name: 'John Doe', recipient_address: '456 Oak Ave', body: 'I am writing to inform you...', sender_name: 'Jane Smith' },\n },\n {\n id: 'report',\n name: 'Report',\n category: 'Business',\n html: '<h1>{{title}}</h1><p><em>Prepared by {{author}} | {{date}}</em></p><h2>Executive Summary</h2><p>{{summary}}</p><h2>Findings</h2><p>{{findings}}</p><h2>Recommendations</h2><p>{{recommendations}}</p>',\n sampleData: { title: 'Q1 2026 Report', author: 'Analytics Team', date: 'March 2026', summary: 'Key findings from Q1...', findings: 'Revenue increased by 15%...', recommendations: 'Continue investing in...' },\n },\n {\n id: 'newsletter',\n name: 'Newsletter',\n category: 'Marketing',\n html: '<h1>{{newsletter_name}}</h1><p><em>{{edition}} | {{date}}</em></p><hr><h2>{{headline}}</h2><p>{{lead_story}}</p>{{#if has_coupon}}<div class=\"rmx-callout rmx-callout-success\" data-callout=\"success\"><div class=\"rmx-callout-header\" contenteditable=\"false\"><span class=\"rmx-callout-icon\">\\u2705</span><span class=\"rmx-callout-title\">Special Offer</span></div><div class=\"rmx-callout-body\"><p>Use code <strong>{{coupon_code}}</strong> for {{discount}} off!</p></div></div>{{/if}}<p>{{closing}}</p>',\n sampleData: { newsletter_name: 'The Weekly Digest', edition: 'Vol. 12', date: 'March 19, 2026', headline: 'Big News This Week', lead_story: 'We are excited to announce...', has_coupon: true, coupon_code: 'SAVE20', discount: '20%', closing: 'Thanks for reading!' },\n },\n]\n\nfor (const t of BUILTIN_TEMPLATES) _templateLibrary.set(t.id, t)\n\n/**\n * Register a custom template in the library.\n * @param {{ id: string, name: string, category: string, html: string, sampleData?: Object }} template\n */\nexport function registerTemplate(template) {\n if (!template?.id) return\n _templateLibrary.set(template.id, template)\n}\n\n/**\n * Remove a template from the library.\n * @param {string} id\n * @returns {boolean}\n */\nexport function unregisterTemplate(id) {\n return _templateLibrary.delete(id)\n}\n\n/**\n * Get all templates in the library.\n * @returns {Array}\n */\nexport function getTemplateLibrary() {\n return Array.from(_templateLibrary.values())\n}\n\n/**\n * Get a template by ID.\n * @param {string} id\n * @returns {object|undefined}\n */\nexport function getTemplate(id) {\n return _templateLibrary.get(id)\n}\n\n// ---------------------------------------------------------------------------\n// DOM: convert {{tags}} to visual chips\n// ---------------------------------------------------------------------------\n\nfunction textToChips(html) {\n return html.replace(TAG_REGEX, (match, key) => {\n const trimmed = key.trim()\n if (trimmed.startsWith('#') || trimmed.startsWith('/')) return match\n return `<span class=\"rmx-merge-tag\" data-tag=\"${trimmed}\" contenteditable=\"false\">{{${trimmed}}}</span>`\n })\n}\n\nfunction chipsToText(html) {\n return html.replace(/<span[^>]*class=\"rmx-merge-tag\"[^>]*data-tag=\"([^\"]*)\"[^>]*>.*?<\\/span>/g,\n (_, tag) => `{{${tag}}}`)\n}\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nexport function TemplatePlugin(options = {}) {\n let engine = null\n let previewMode = false\n let currentData = {}\n\n return createPlugin({\n name: 'templates',\n requiresFullAccess: true,\n version: '1.0.0',\n description: 'Merge tags, conditional blocks, repeatable sections, live preview, template library',\n\n commands: [\n {\n name: 'insertMergeTag',\n execute(eng, tagName) {\n if (!tagName) return\n eng.history.snapshot()\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n const chip = document.createElement('span')\n chip.className = 'rmx-merge-tag'\n chip.setAttribute('data-tag', tagName)\n chip.contentEditable = 'false'\n chip.textContent = `{{${tagName}}}`\n range.deleteContents()\n range.insertNode(chip)\n // Move caret after chip\n const space = document.createTextNode('\\u00A0')\n chip.after(space)\n range.setStartAfter(space)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n eng.eventBus.emit('content:change')\n },\n meta: { icon: 'tag', tooltip: 'Insert Merge Tag' },\n },\n {\n name: 'loadTemplate',\n execute(eng, templateId) {\n const tmpl = _templateLibrary.get(templateId)\n if (!tmpl) return null\n eng.history.snapshot()\n eng.element.innerHTML = textToChips(tmpl.html)\n currentData = { ...(tmpl.sampleData || {}) }\n eng.eventBus.emit('content:change')\n eng.eventBus.emit('template:loaded', { id: templateId, template: tmpl })\n return tmpl\n },\n meta: { icon: 'template', tooltip: 'Load Template' },\n },\n {\n name: 'previewTemplate',\n execute(eng, data) {\n if (!data && Object.keys(currentData).length === 0) return\n const mergedData = data || currentData\n currentData = mergedData\n const rawHtml = chipsToText(eng.getHTML())\n const rendered = renderTemplate(rawHtml, mergedData)\n previewMode = true\n eng._templateBackup = eng.element.innerHTML\n eng.element.innerHTML = rendered\n eng.element.contentEditable = 'false'\n eng.element.classList.add('rmx-template-preview')\n eng.eventBus.emit('template:preview', { data: mergedData })\n },\n meta: { icon: 'preview', tooltip: 'Preview Template' },\n },\n {\n name: 'exitPreview',\n execute(eng) {\n if (!previewMode) return\n previewMode = false\n if (eng._templateBackup) {\n eng.element.innerHTML = eng._templateBackup\n delete eng._templateBackup\n }\n eng.element.contentEditable = 'true'\n eng.element.classList.remove('rmx-template-preview')\n eng.eventBus.emit('template:exitPreview')\n },\n meta: { icon: 'edit', tooltip: 'Exit Preview' },\n },\n {\n name: 'exportTemplate',\n execute(eng) {\n const html = chipsToText(eng.getHTML())\n const tags = extractTags(html)\n return { html, tags, sampleData: { ...currentData } }\n },\n meta: { icon: 'export', tooltip: 'Export Template' },\n },\n {\n name: 'getTemplateTags',\n execute(eng) {\n return extractTags(chipsToText(eng.getHTML()))\n },\n meta: { tooltip: 'Get Template Tags' },\n },\n ],\n\n contextMenuItems: [\n { label: 'Insert Merge Tag', command: 'insertMergeTag' },\n ],\n\n init(eng) {\n engine = eng\n engine._templates = {\n renderTemplate,\n extractTags,\n getTemplateLibrary,\n getTemplate,\n registerTemplate,\n unregisterTemplate,\n textToChips,\n chipsToText,\n setPreviewData: (data) => { currentData = { ...data } },\n getPreviewData: () => ({ ...currentData }),\n isPreviewMode: () => previewMode,\n }\n },\n\n destroy() {\n if (previewMode && engine) {\n engine.element.contentEditable = 'true'\n engine.element.classList.remove('rmx-template-preview')\n }\n engine = null\n },\n })\n}\n","/**\n * KeyboardPlugin — Keyboard-first editing enhancements.\n *\n * - Vim-style keybinding mode (normal, insert, visual)\n * - Emacs keybinding preset\n * - Custom keybinding map\n * - Multi-cursor editing (Cmd+D, Alt+Click)\n * - Smart bracket/quote auto-pairing\n * - Jump-to-heading navigation\n *\n * @param {object} [options]\n * @param {'default'|'vim'|'emacs'} [options.mode='default']\n * @param {Object} [options.keyBindings] — custom keybinding overrides\n * @param {boolean} [options.autoPair=true] — auto-pair brackets/quotes\n * @param {boolean} [options.jumpToHeading=true] — enable Cmd+Shift+G heading nav\n */\n\nimport { createPlugin } from '../../createPlugin.js'\n\n// ---------------------------------------------------------------------------\n// Auto-pairing rules\n// ---------------------------------------------------------------------------\n\nconst PAIR_MAP = {\n '(': ')',\n '[': ']',\n '{': '}',\n '\"': '\"',\n \"'\": \"'\",\n '`': '`',\n}\n\nconst CLOSE_CHARS = new Set(Object.values(PAIR_MAP))\n\n// ---------------------------------------------------------------------------\n// Vim mode state\n// ---------------------------------------------------------------------------\n\nconst VIM_MODES = { NORMAL: 'normal', INSERT: 'insert', VISUAL: 'visual' }\n\nfunction createVimState() {\n return {\n mode: VIM_MODES.NORMAL,\n pending: '', // partial command buffer (e.g., 'd' waiting for motion)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Emacs keybinding map\n// ---------------------------------------------------------------------------\n\nconst EMACS_BINDINGS = {\n 'ctrl+a': 'moveToLineStart',\n 'ctrl+e': 'moveToLineEnd',\n 'ctrl+k': 'killToLineEnd',\n 'ctrl+y': 'yank',\n 'ctrl+w': 'killWord',\n 'ctrl+f': 'moveForward',\n 'ctrl+b': 'moveBackward',\n 'ctrl+n': 'moveDown',\n 'ctrl+p': 'moveUp',\n 'ctrl+d': 'deleteForward',\n 'ctrl+h': 'deleteBackward',\n 'ctrl+space': 'setMark',\n}\n\n// ---------------------------------------------------------------------------\n// Heading navigation\n// ---------------------------------------------------------------------------\n\n/**\n * Get all headings in the editor.\n * @param {HTMLElement} editorEl\n * @returns {Array<{ level: number, text: string, element: HTMLElement }>}\n */\nexport function getHeadings(editorEl) {\n const headings = editorEl.querySelectorAll('h1, h2, h3, h4, h5, h6')\n return Array.from(headings).map(el => ({\n level: parseInt(el.tagName.charAt(1), 10),\n text: el.textContent,\n element: el,\n }))\n}\n\n/**\n * Jump to a heading by index.\n * @param {HTMLElement} editorEl\n * @param {number} index\n */\nfunction jumpToHeading(editorEl, index) {\n const headings = getHeadings(editorEl)\n if (index < 0 || index >= headings.length) return\n const heading = headings[index]\n heading.element.scrollIntoView?.({ behavior: 'smooth', block: 'center' })\n const sel = window.getSelection()\n const range = document.createRange()\n range.selectNodeContents(heading.element)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n}\n\n// ---------------------------------------------------------------------------\n// Multi-cursor (simplified — tracks selections)\n// ---------------------------------------------------------------------------\n\n/**\n * Find the next occurrence of the selected text and add it to the selection.\n * Uses window.find() or manual search.\n * @param {HTMLElement} editorEl\n * @returns {number} number of current selections\n */\nexport function selectNextOccurrence(editorEl) {\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return 0\n const text = sel.toString()\n if (!text) return 0\n\n // Find next occurrence after current selection\n const range = sel.getRangeAt(sel.rangeCount - 1)\n const searchFrom = range.endOffset\n const content = editorEl.textContent\n const after = content.indexOf(text, content.indexOf(text, searchFrom) === -1 ? 0 : searchFrom + 1)\n\n if (after === -1) return sel.rangeCount\n\n // Walk the DOM to find the text node at the target offset\n const walker = document.createTreeWalker(editorEl, NodeFilter.SHOW_TEXT)\n let offset = 0\n while (walker.nextNode()) {\n const node = walker.currentNode\n const nodeLen = node.textContent.length\n if (offset + nodeLen > after) {\n const localOffset = after - offset\n const newRange = document.createRange()\n newRange.setStart(node, localOffset)\n newRange.setEnd(node, Math.min(localOffset + text.length, nodeLen))\n sel.addRange(newRange)\n break\n }\n offset += nodeLen\n }\n\n return sel.rangeCount\n}\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nexport function KeyboardPlugin(options = {}) {\n const {\n mode = 'default',\n keyBindings = {},\n autoPair = true,\n jumpToHeading: enableJumpToHeading = true,\n } = options\n\n let engine = null\n let vimState = null\n let killRing = '' // Emacs kill ring (most recent kill)\n\n function handleKeyDown(e) {\n if (!engine) return\n\n // Vim mode handling\n if (mode === 'vim' && vimState) {\n handleVimKey(e)\n return\n }\n\n // Emacs mode handling\n if (mode === 'emacs') {\n handleEmacsKey(e)\n // Don't return — fall through for auto-pair\n }\n\n // Custom keybindings\n const combo = buildCombo(e)\n if (keyBindings[combo]) {\n e.preventDefault()\n const action = keyBindings[combo]\n if (typeof action === 'function') {\n action(engine)\n } else if (typeof action === 'string') {\n engine.executeCommand(action)\n }\n return\n }\n\n // Auto-pair brackets/quotes\n if (autoPair && mode !== 'vim') {\n handleAutoPair(e)\n }\n\n // Jump-to-heading: Cmd/Ctrl+Shift+G\n if (enableJumpToHeading) {\n const mod = e.metaKey || e.ctrlKey\n if (mod && e.shiftKey && e.key === 'g') {\n e.preventDefault()\n engine.eventBus.emit('keyboard:jumpToHeading')\n return\n }\n }\n\n // Multi-cursor: Cmd+D / Ctrl+D\n if ((e.metaKey || e.ctrlKey) && e.key === 'd' && !e.shiftKey) {\n e.preventDefault()\n selectNextOccurrence(engine.element)\n return\n }\n }\n\n function handleAutoPair(e) {\n if (e.key.length !== 1 || e.metaKey || e.ctrlKey || e.altKey) return\n\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n\n // Opening bracket/quote\n if (PAIR_MAP[e.key]) {\n const closer = PAIR_MAP[e.key]\n\n // If text is selected, wrap it\n if (!range.collapsed) {\n e.preventDefault()\n const selected = range.toString()\n range.deleteContents()\n range.insertNode(document.createTextNode(e.key + selected + closer))\n return\n }\n\n // Otherwise insert pair and place caret between\n e.preventDefault()\n const text = document.createTextNode(e.key + closer)\n range.insertNode(text)\n range.setStart(text, 1)\n range.setEnd(text, 1)\n sel.removeAllRanges()\n sel.addRange(range)\n engine.eventBus.emit('content:change')\n return\n }\n\n // Skip over closing bracket if it's already the next char\n if (CLOSE_CHARS.has(e.key) && range.collapsed) {\n const node = range.startContainer\n if (node.nodeType === 3 && node.textContent.charAt(range.startOffset) === e.key) {\n e.preventDefault()\n range.setStart(node, range.startOffset + 1)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n return\n }\n }\n }\n\n function handleVimKey(e) {\n if (vimState.mode === VIM_MODES.INSERT) {\n // Escape returns to normal mode\n if (e.key === 'Escape') {\n e.preventDefault()\n vimState.mode = VIM_MODES.NORMAL\n engine.element.classList.remove('rmx-vim-insert')\n engine.element.classList.add('rmx-vim-normal')\n engine.eventBus.emit('vim:modeChange', { mode: VIM_MODES.NORMAL })\n return\n }\n // In insert mode, allow normal typing + auto-pair\n if (autoPair) handleAutoPair(e)\n return\n }\n\n // Normal mode\n e.preventDefault()\n const sel = window.getSelection()\n\n switch (e.key) {\n case 'i': // enter insert mode\n vimState.mode = VIM_MODES.INSERT\n engine.element.classList.remove('rmx-vim-normal')\n engine.element.classList.add('rmx-vim-insert')\n engine.eventBus.emit('vim:modeChange', { mode: VIM_MODES.INSERT })\n break\n case 'a': // append (insert after cursor)\n vimState.mode = VIM_MODES.INSERT\n if (sel && sel.rangeCount > 0) {\n const r = sel.getRangeAt(0)\n r.collapse(false)\n }\n engine.element.classList.remove('rmx-vim-normal')\n engine.element.classList.add('rmx-vim-insert')\n engine.eventBus.emit('vim:modeChange', { mode: VIM_MODES.INSERT })\n break\n case 'v': // visual mode\n vimState.mode = VIM_MODES.VISUAL\n engine.element.classList.add('rmx-vim-visual')\n engine.eventBus.emit('vim:modeChange', { mode: VIM_MODES.VISUAL })\n break\n case 'Escape':\n if (vimState.mode === VIM_MODES.VISUAL) {\n vimState.mode = VIM_MODES.NORMAL\n engine.element.classList.remove('rmx-vim-visual')\n sel?.collapseToStart()\n engine.eventBus.emit('vim:modeChange', { mode: VIM_MODES.NORMAL })\n }\n break\n case 'h': sel?.modify('move', 'backward', 'character'); break\n case 'l': sel?.modify('move', 'forward', 'character'); break\n case 'j': sel?.modify('move', 'forward', 'line'); break\n case 'k': sel?.modify('move', 'backward', 'line'); break\n case 'w': sel?.modify('move', 'forward', 'word'); break\n case 'b': sel?.modify('move', 'backward', 'word'); break\n case '0': sel?.modify('move', 'backward', 'lineboundary'); break\n case '$': sel?.modify('move', 'forward', 'lineboundary'); break\n case 'G': // go to end\n if (sel && engine.element.lastChild) {\n const r = document.createRange()\n r.selectNodeContents(engine.element.lastChild)\n r.collapse(false)\n sel.removeAllRanges()\n sel.addRange(r)\n }\n break\n case 'u': // undo\n engine.executeCommand('undo')\n break\n case 'x': // delete character\n engine.history.snapshot()\n document.execCommand('delete')\n break\n case 'd':\n if (vimState.pending === 'd') { // dd = delete line\n vimState.pending = ''\n engine.history.snapshot()\n const block = engine.selection?.getParentBlock?.()\n if (block) block.remove()\n engine.eventBus.emit('content:change')\n } else {\n vimState.pending = 'd'\n }\n break\n case 'o': // open line below\n engine.history.snapshot()\n {\n const block = engine.selection?.getParentBlock?.() || engine.element.lastChild\n const p = document.createElement('p')\n p.innerHTML = '<br>'\n block?.after(p)\n const r = document.createRange()\n r.selectNodeContents(p)\n r.collapse(true)\n sel?.removeAllRanges()\n sel?.addRange(r)\n }\n vimState.mode = VIM_MODES.INSERT\n engine.element.classList.remove('rmx-vim-normal')\n engine.element.classList.add('rmx-vim-insert')\n engine.eventBus.emit('vim:modeChange', { mode: VIM_MODES.INSERT })\n engine.eventBus.emit('content:change')\n break\n default:\n vimState.pending = ''\n break\n }\n }\n\n function handleEmacsKey(e) {\n if (!e.ctrlKey) return\n const combo = `ctrl+${e.key}`\n const action = EMACS_BINDINGS[combo]\n if (!action) return\n\n e.preventDefault()\n const sel = window.getSelection()\n const range = sel?.rangeCount > 0 ? sel.getRangeAt(0) : null\n\n switch (action) {\n case 'moveToLineStart': sel?.modify('move', 'backward', 'lineboundary'); break\n case 'moveToLineEnd': sel?.modify('move', 'forward', 'lineboundary'); break\n case 'moveForward': sel?.modify('move', 'forward', 'character'); break\n case 'moveBackward': sel?.modify('move', 'backward', 'character'); break\n case 'moveDown': sel?.modify('move', 'forward', 'line'); break\n case 'moveUp': sel?.modify('move', 'backward', 'line'); break\n case 'deleteForward': document.execCommand('forwardDelete'); break\n case 'deleteBackward': document.execCommand('delete'); break\n case 'killToLineEnd':\n if (range) {\n engine.history.snapshot()\n sel.modify('extend', 'forward', 'lineboundary')\n killRing = sel.toString()\n range.deleteContents()\n engine.eventBus.emit('content:change')\n }\n break\n case 'yank':\n if (killRing && range) {\n engine.history.snapshot()\n range.insertNode(document.createTextNode(killRing))\n range.collapse(false)\n engine.eventBus.emit('content:change')\n }\n break\n case 'killWord':\n if (range) {\n engine.history.snapshot()\n sel.modify('extend', 'backward', 'word')\n killRing = sel.toString()\n range.deleteContents()\n engine.eventBus.emit('content:change')\n }\n break\n case 'setMark':\n // Mark is the current position — in browser, start of selection\n break\n }\n }\n\n function buildCombo(e) {\n const parts = []\n if (e.ctrlKey) parts.push('ctrl')\n if (e.metaKey) parts.push('cmd')\n if (e.altKey) parts.push('alt')\n if (e.shiftKey) parts.push('shift')\n if (e.key.length === 1) parts.push(e.key.toLowerCase())\n else parts.push(e.key)\n return parts.join('+')\n }\n\n return createPlugin({\n name: 'keyboard',\n requiresFullAccess: true,\n version: '1.0.0',\n description: 'Vim/Emacs modes, auto-pairing, multi-cursor, jump-to-heading',\n\n commands: [\n {\n name: 'setKeyboardMode',\n execute(eng, newMode) {\n if (newMode === 'vim') {\n vimState = createVimState()\n eng.element.classList.add('rmx-vim-normal')\n eng.eventBus.emit('vim:modeChange', { mode: VIM_MODES.NORMAL })\n } else {\n vimState = null\n eng.element.classList.remove('rmx-vim-normal', 'rmx-vim-insert', 'rmx-vim-visual')\n }\n eng.eventBus.emit('keyboard:modeChange', { mode: newMode })\n },\n meta: { tooltip: 'Set Keyboard Mode' },\n },\n {\n name: 'getVimMode',\n execute() { return vimState?.mode || null },\n meta: { tooltip: 'Get Vim Mode' },\n },\n {\n name: 'jumpToHeading',\n execute(eng, index) { jumpToHeading(eng.element, index) },\n meta: { icon: 'heading', tooltip: 'Jump to Heading' },\n },\n {\n name: 'getHeadings',\n execute(eng) { return getHeadings(eng.element) },\n meta: { tooltip: 'Get Headings' },\n },\n {\n name: 'selectNextOccurrence',\n execute(eng) { return selectNextOccurrence(eng.element) },\n shortcut: 'mod+d',\n meta: { tooltip: 'Select Next Occurrence' },\n },\n ],\n\n init(eng) {\n engine = eng\n\n if (mode === 'vim') {\n vimState = createVimState()\n engine.element.classList.add('rmx-vim-normal')\n }\n\n engine.element.addEventListener('keydown', handleKeyDown, true)\n\n engine._keyboard = {\n getHeadings: () => getHeadings(engine.element),\n getVimMode: () => vimState?.mode || null,\n selectNextOccurrence: () => selectNextOccurrence(engine.element),\n }\n },\n\n destroy() {\n engine?.element?.removeEventListener('keydown', handleKeyDown, true)\n engine?.element?.classList.remove('rmx-vim-normal', 'rmx-vim-insert', 'rmx-vim-visual')\n vimState = null\n engine = null\n },\n })\n}\n","/**\n * DragDropPlugin — Enhanced drag-and-drop content management.\n *\n * - Drop zone overlays with visual guides\n * - Drag between multiple Remyx editors\n * - External file/image/rich-text drop support\n * - Drag-to-reorder list items, table rows, block elements\n * - Ghost preview during drag\n *\n * @param {object} [options]\n * @param {Function} [options.onDrop] — (event, data) => void\n * @param {Function} [options.onFileDrop] — (files) => void\n * @param {boolean} [options.allowExternalDrop=true]\n * @param {boolean} [options.showDropZone=true]\n * @param {boolean} [options.enableReorder=true]\n */\n\nimport { createPlugin } from '../../createPlugin.js'\n\n// ---------------------------------------------------------------------------\n// Drop zone overlay\n// ---------------------------------------------------------------------------\n\nconst DROP_ZONE_CLASS = 'rmx-drop-zone'\nconst DROP_ZONE_ACTIVE_CLASS = 'rmx-drop-zone-active'\nconst DROP_INDICATOR_CLASS = 'rmx-drop-indicator'\nconst DRAGGING_CLASS = 'rmx-dragging'\n\nfunction createDropIndicator() {\n const indicator = document.createElement('div')\n indicator.className = DROP_INDICATOR_CLASS\n indicator.style.cssText = 'position:absolute;left:0;right:0;height:3px;background:var(--rmx-primary,#6366f1);border-radius:2px;pointer-events:none;z-index:100;transition:top 0.1s ease;'\n return indicator\n}\n\n// ---------------------------------------------------------------------------\n// Reorder helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get the closest block element to a Y position.\n * @param {HTMLElement} container\n * @param {number} y\n * @returns {{ element: HTMLElement|null, position: 'before'|'after' }}\n */\nfunction getDropTarget(container, y) {\n const children = Array.from(container.children).filter(\n el => !el.classList.contains(DROP_INDICATOR_CLASS) && !el.classList.contains(DROP_ZONE_CLASS)\n )\n let closest = null\n let closestDist = Infinity\n let position = 'after'\n\n for (const child of children) {\n const rect = child.getBoundingClientRect()\n const midY = rect.top + rect.height / 2\n\n const dist = Math.abs(y - midY)\n if (dist < closestDist) {\n closestDist = dist\n closest = child\n position = y < midY ? 'before' : 'after'\n }\n }\n\n return { element: closest, position }\n}\n\n/**\n * Get the draggable block ancestor of an element.\n * @param {HTMLElement} el\n * @param {HTMLElement} editorEl\n * @returns {HTMLElement|null}\n */\nfunction getDraggableBlock(el, editorEl) {\n let node = el\n while (node && node !== editorEl && node.parentNode !== editorEl) {\n node = node.parentNode\n }\n return node !== editorEl ? node : null\n}\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nexport function DragDropPlugin(options = {}) {\n const {\n onDrop,\n onFileDrop,\n allowExternalDrop = true,\n showDropZone = true,\n enableReorder = true,\n } = options\n\n let engine = null\n let dropIndicator = null\n let draggedBlock = null\n let ghostEl = null\n\n function handleDragStart(e) {\n if (!engine || !enableReorder) return\n const block = getDraggableBlock(e.target, engine.element)\n if (!block) return\n\n draggedBlock = block\n block.classList.add(DRAGGING_CLASS)\n\n // Ghost preview\n ghostEl = block.cloneNode(true)\n ghostEl.style.cssText = 'position:fixed;top:-9999px;left:-9999px;opacity:0.6;pointer-events:none;max-width:400px;'\n document.body.appendChild(ghostEl)\n e.dataTransfer.setDragImage(ghostEl, 20, 20)\n\n e.dataTransfer.effectAllowed = 'move'\n e.dataTransfer.setData('text/x-remyx-block', 'true')\n e.dataTransfer.setData('text/html', block.outerHTML)\n }\n\n function handleDragOver(e) {\n if (!engine) return\n e.preventDefault()\n e.dataTransfer.dropEffect = 'move'\n\n if (showDropZone) {\n engine.element.classList.add(DROP_ZONE_ACTIVE_CLASS)\n }\n\n // Show drop indicator\n if (enableReorder && dropIndicator) {\n const { element, position } = getDropTarget(engine.element, e.clientY)\n if (element) {\n const rect = element.getBoundingClientRect()\n const editorRect = engine.element.getBoundingClientRect()\n const top = position === 'before'\n ? rect.top - editorRect.top - 2\n : rect.bottom - editorRect.top + 1\n dropIndicator.style.top = `${top}px`\n dropIndicator.style.display = 'block'\n }\n }\n }\n\n function handleDragLeave(e) {\n if (!engine) return\n // Only remove if leaving the editor entirely\n if (!engine.element.contains(e.relatedTarget)) {\n engine.element.classList.remove(DROP_ZONE_ACTIVE_CLASS)\n if (dropIndicator) dropIndicator.style.display = 'none'\n }\n }\n\n function handleDrop(e) {\n if (!engine) return\n e.preventDefault()\n engine.element.classList.remove(DROP_ZONE_ACTIVE_CLASS)\n if (dropIndicator) dropIndicator.style.display = 'none'\n\n engine.history.snapshot()\n\n // Internal block reorder\n if (draggedBlock && engine.element.contains(draggedBlock)) {\n const { element, position } = getDropTarget(engine.element, e.clientY)\n if (element && element !== draggedBlock) {\n if (position === 'before') {\n element.before(draggedBlock)\n } else {\n element.after(draggedBlock)\n }\n engine.eventBus.emit('content:change')\n engine.eventBus.emit('dragdrop:reorder', { block: draggedBlock })\n }\n cleanupDrag()\n onDrop?.(e, { type: 'reorder' })\n return\n }\n\n // Cross-editor drag (HTML from another Remyx instance)\n const isRemyxBlock = e.dataTransfer.getData('text/x-remyx-block')\n if (isRemyxBlock) {\n const html = e.dataTransfer.getData('text/html')\n if (html) {\n const { element, position } = getDropTarget(engine.element, e.clientY)\n const temp = document.createElement('div')\n temp.innerHTML = html\n const fragment = document.createDocumentFragment()\n while (temp.firstChild) fragment.appendChild(temp.firstChild)\n\n if (element) {\n if (position === 'before') {\n element.before(fragment)\n } else {\n element.after(fragment)\n }\n } else {\n engine.element.appendChild(fragment)\n }\n engine.eventBus.emit('content:change')\n engine.eventBus.emit('dragdrop:crossEditor', {})\n onDrop?.(e, { type: 'crossEditor', html })\n }\n cleanupDrag()\n return\n }\n\n // External file drop\n if (allowExternalDrop && e.dataTransfer.files.length > 0) {\n const files = Array.from(e.dataTransfer.files)\n onFileDrop?.(files)\n engine.eventBus.emit('dragdrop:fileDrop', { files })\n\n // Auto-insert images\n for (const file of files) {\n if (file.type.startsWith('image/')) {\n const reader = new FileReader()\n reader.onload = () => {\n const img = document.createElement('img')\n img.src = reader.result\n img.style.maxWidth = '100%'\n const { element, position } = getDropTarget(engine.element, e.clientY)\n if (element) {\n if (position === 'before') element.before(img)\n else element.after(img)\n } else {\n engine.element.appendChild(img)\n }\n engine.eventBus.emit('content:change')\n }\n reader.readAsDataURL(file)\n }\n }\n\n cleanupDrag()\n onDrop?.(e, { type: 'file', files })\n return\n }\n\n // External rich text / plain text drop\n if (allowExternalDrop) {\n const html = e.dataTransfer.getData('text/html')\n const text = e.dataTransfer.getData('text/plain')\n const content = html || (text ? `<p>${text.replace(/\\n/g, '</p><p>')}</p>` : '')\n\n if (content) {\n const { element, position } = getDropTarget(engine.element, e.clientY)\n const temp = document.createElement('div')\n temp.innerHTML = content\n const fragment = document.createDocumentFragment()\n while (temp.firstChild) fragment.appendChild(temp.firstChild)\n\n if (element) {\n if (position === 'before') element.before(fragment)\n else element.after(fragment)\n } else {\n engine.element.appendChild(fragment)\n }\n engine.eventBus.emit('content:change')\n engine.eventBus.emit('dragdrop:externalDrop', { html: content })\n onDrop?.(e, { type: 'external', html: content })\n }\n }\n\n cleanupDrag()\n }\n\n function handleDragEnd() {\n cleanupDrag()\n }\n\n function cleanupDrag() {\n if (draggedBlock) {\n draggedBlock.classList.remove(DRAGGING_CLASS)\n draggedBlock = null\n }\n if (ghostEl) {\n ghostEl.remove()\n ghostEl = null\n }\n engine?.element?.classList.remove(DROP_ZONE_ACTIVE_CLASS)\n if (dropIndicator) dropIndicator.style.display = 'none'\n }\n\n // Make blocks draggable on mousedown near left edge\n function handleMouseDown(e) {\n if (!engine || !enableReorder) return\n const block = getDraggableBlock(e.target, engine.element)\n if (!block) return\n\n // Only enable drag if clicking near the left edge (first 30px)\n const rect = block.getBoundingClientRect()\n if (e.clientX - rect.left < 30) {\n block.draggable = true\n block.addEventListener('dragend', () => { block.draggable = false }, { once: true })\n }\n }\n\n return createPlugin({\n name: 'dragDrop',\n requiresFullAccess: true,\n version: '1.0.0',\n description: 'Drop zones, cross-editor drag, file drops, block reorder, ghost previews',\n\n commands: [\n {\n name: 'moveBlockUp',\n execute(eng) {\n const block = eng.selection?.getParentBlock?.()\n if (!block) return\n let topBlock = block\n while (topBlock.parentNode !== eng.element) topBlock = topBlock.parentNode\n const prev = topBlock.previousElementSibling\n if (prev) {\n eng.history.snapshot()\n prev.before(topBlock)\n eng.eventBus.emit('content:change')\n }\n },\n shortcut: 'mod+shift+ArrowUp',\n meta: { icon: 'arrow-up', tooltip: 'Move Block Up' },\n },\n {\n name: 'moveBlockDown',\n execute(eng) {\n const block = eng.selection?.getParentBlock?.()\n if (!block) return\n let topBlock = block\n while (topBlock.parentNode !== eng.element) topBlock = topBlock.parentNode\n const next = topBlock.nextElementSibling\n if (next) {\n eng.history.snapshot()\n next.after(topBlock)\n eng.eventBus.emit('content:change')\n }\n },\n shortcut: 'mod+shift+ArrowDown',\n meta: { icon: 'arrow-down', tooltip: 'Move Block Down' },\n },\n ],\n\n init(eng) {\n engine = eng\n\n // Create and attach drop indicator\n if (enableReorder) {\n dropIndicator = createDropIndicator()\n dropIndicator.style.display = 'none'\n engine.element.style.position = 'relative'\n engine.element.appendChild(dropIndicator)\n }\n\n engine.element.addEventListener('dragstart', handleDragStart)\n engine.element.addEventListener('dragover', handleDragOver)\n engine.element.addEventListener('dragleave', handleDragLeave)\n engine.element.addEventListener('drop', handleDrop)\n engine.element.addEventListener('dragend', handleDragEnd)\n engine.element.addEventListener('mousedown', handleMouseDown)\n\n if (showDropZone) {\n engine.element.classList.add(DROP_ZONE_CLASS)\n }\n\n engine._dragDrop = {\n getDropTarget: (y) => getDropTarget(engine.element, y),\n }\n },\n\n destroy() {\n engine?.element?.removeEventListener('dragstart', handleDragStart)\n engine?.element?.removeEventListener('dragover', handleDragOver)\n engine?.element?.removeEventListener('dragleave', handleDragLeave)\n engine?.element?.removeEventListener('drop', handleDrop)\n engine?.element?.removeEventListener('dragend', handleDragEnd)\n engine?.element?.removeEventListener('mousedown', handleMouseDown)\n\n dropIndicator?.remove()\n cleanupDrag()\n engine?.element?.classList.remove(DROP_ZONE_CLASS, DROP_ZONE_ACTIVE_CLASS)\n engine = null\n },\n })\n}\n","import { escapeHTML } from '../../../utils/escapeHTML.js'\n\n/**\n * MathPlugin — LaTeX/KaTeX math rendering with inline and block equations.\n *\n * Inline math: $expression$ or \\(expression\\)\n * Block math: $$expression$$ or \\[expression\\]\n *\n * DOM structure:\n * <span class=\"rmx-math rmx-math-inline\" data-math=\"latex\" data-math-src=\"expression\">rendered</span>\n * <div class=\"rmx-math rmx-math-block\" data-math=\"latex\" data-math-src=\"expression\">rendered</div>\n *\n * Uses a pluggable renderer — provide `renderMath(latex, displayMode)` callback,\n * or defaults to displaying the raw LaTeX in a styled container.\n *\n * @param {object} [options]\n * @param {Function} [options.renderMath] — (latex, displayMode) => HTMLString\n * @param {boolean} [options.autoRender=true] — auto-detect and render $ and $$ syntax\n * @param {boolean} [options.numbering=true] — auto-number block equations\n */\n\nimport { createPlugin } from '../../createPlugin.js'\n\n// ---------------------------------------------------------------------------\n// Symbol palette\n// ---------------------------------------------------------------------------\n\n/** @type {Array<{ category: string, symbols: Array<{ label: string, latex: string }> }>} */\nconst SYMBOL_PALETTE = [\n {\n category: 'Greek',\n symbols: [\n { label: '\\u03B1', latex: '\\\\alpha' }, { label: '\\u03B2', latex: '\\\\beta' },\n { label: '\\u03B3', latex: '\\\\gamma' }, { label: '\\u03B4', latex: '\\\\delta' },\n { label: '\\u03B5', latex: '\\\\epsilon' }, { label: '\\u03B8', latex: '\\\\theta' },\n { label: '\\u03BB', latex: '\\\\lambda' }, { label: '\\u03BC', latex: '\\\\mu' },\n { label: '\\u03C0', latex: '\\\\pi' }, { label: '\\u03C3', latex: '\\\\sigma' },\n { label: '\\u03C6', latex: '\\\\phi' }, { label: '\\u03C9', latex: '\\\\omega' },\n { label: '\\u0394', latex: '\\\\Delta' }, { label: '\\u03A3', latex: '\\\\Sigma' },\n { label: '\\u03A9', latex: '\\\\Omega' },\n ],\n },\n {\n category: 'Operators',\n symbols: [\n { label: '\\u00B1', latex: '\\\\pm' }, { label: '\\u00D7', latex: '\\\\times' },\n { label: '\\u00F7', latex: '\\\\div' }, { label: '\\u2260', latex: '\\\\neq' },\n { label: '\\u2264', latex: '\\\\leq' }, { label: '\\u2265', latex: '\\\\geq' },\n { label: '\\u221E', latex: '\\\\infty' }, { label: '\\u2248', latex: '\\\\approx' },\n { label: '\\u221A', latex: '\\\\sqrt{}' }, { label: '\\u2211', latex: '\\\\sum' },\n { label: '\\u220F', latex: '\\\\prod' }, { label: '\\u222B', latex: '\\\\int' },\n ],\n },\n {\n category: 'Arrows',\n symbols: [\n { label: '\\u2190', latex: '\\\\leftarrow' }, { label: '\\u2192', latex: '\\\\rightarrow' },\n { label: '\\u2194', latex: '\\\\leftrightarrow' }, { label: '\\u21D2', latex: '\\\\Rightarrow' },\n { label: '\\u21D4', latex: '\\\\Leftrightarrow' }, { label: '\\u2191', latex: '\\\\uparrow' },\n { label: '\\u2193', latex: '\\\\downarrow' },\n ],\n },\n {\n category: 'Common',\n symbols: [\n { label: '\\u00B2', latex: '^{2}' }, { label: '\\u00B3', latex: '^{3}' },\n { label: 'x\\u207F', latex: '^{n}' }, { label: 'x\\u2081', latex: '_{1}' },\n { label: '\\u2200', latex: '\\\\forall' }, { label: '\\u2203', latex: '\\\\exists' },\n { label: '\\u2208', latex: '\\\\in' }, { label: '\\u2282', latex: '\\\\subset' },\n { label: '\\u222A', latex: '\\\\cup' }, { label: '\\u2229', latex: '\\\\cap' },\n { label: 'frac', latex: '\\\\frac{}{}' }, { label: '\\u2202', latex: '\\\\partial' },\n ],\n },\n]\n\n/**\n * Get the symbol palette for building UI.\n * @returns {typeof SYMBOL_PALETTE}\n */\nexport function getSymbolPalette() {\n return SYMBOL_PALETTE\n}\n\n// ---------------------------------------------------------------------------\n// LaTeX detection\n// ---------------------------------------------------------------------------\n\n/** Inline: $...$ (not $$) */\nconst INLINE_REGEX = /(?<!\\$)\\$(?!\\$)(.+?)(?<!\\$)\\$(?!\\$)/g\n\n/** Block: $$...$$ */\nconst BLOCK_REGEX = /\\$\\$([\\s\\S]+?)\\$\\$/g\n\n/**\n * Parse math expressions from text.\n * @param {string} text\n * @returns {Array<{ type: 'inline'|'block', src: string, index: number, length: number }>}\n */\nexport function parseMathExpressions(text) {\n if (!text) return []\n const results = []\n\n BLOCK_REGEX.lastIndex = 0\n let match\n while ((match = BLOCK_REGEX.exec(text)) !== null) {\n results.push({ type: 'block', src: match[1].trim(), index: match.index, length: match[0].length })\n }\n\n INLINE_REGEX.lastIndex = 0\n while ((match = INLINE_REGEX.exec(text)) !== null) {\n // Skip if inside a block match\n if (!results.some(r => match.index >= r.index && match.index < r.index + r.length)) {\n results.push({ type: 'inline', src: match[1].trim(), index: match.index, length: match[0].length })\n }\n }\n\n return results.sort((a, b) => a.index - b.index)\n}\n\n/**\n * Convert a LaTeX string to MathML (basic subset).\n * @param {string} latex\n * @returns {string}\n */\nexport function latexToMathML(latex) {\n // Basic conversion for common patterns\n let ml = latex\n .replace(/\\\\frac\\{([^}]*)\\}\\{([^}]*)\\}/g, '<mfrac><mrow>$1</mrow><mrow>$2</mrow></mfrac>')\n .replace(/\\\\sqrt\\{([^}]*)\\}/g, '<msqrt><mrow>$1</mrow></msqrt>')\n .replace(/\\^{([^}]*)}/g, '<msup><mo></mo><mn>$1</mn></msup>')\n .replace(/_{([^}]*)}/g, '<msub><mo></mo><mn>$1</mn></msub>')\n .replace(/\\\\sum/g, '<mo>∑</mo>')\n .replace(/\\\\prod/g, '<mo>∏</mo>')\n .replace(/\\\\int/g, '<mo>∫</mo>')\n .replace(/\\\\infty/g, '<mo>∞</mo>')\n .replace(/\\\\alpha/g, '<mi>α</mi>')\n .replace(/\\\\beta/g, '<mi>β</mi>')\n .replace(/\\\\pi/g, '<mi>π</mi>')\n return `<math xmlns=\"http://www.w3.org/1998/Math/MathML\">${ml}</math>`\n}\n\n// ---------------------------------------------------------------------------\n// Default renderer (styled LaTeX display)\n// ---------------------------------------------------------------------------\n\nfunction defaultRenderMath(latex, displayMode) {\n const escaped = escapeHTML(latex)\n return `<code class=\"rmx-math-latex\">${escaped}</code>`\n}\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nexport function MathPlugin(options = {}) {\n const {\n renderMath = defaultRenderMath,\n autoRender = true,\n numbering = true,\n } = options\n\n let engine = null\n let observer = null\n let equationCounter = 0\n\n function renderMathElement(el) {\n const src = el.getAttribute('data-math-src')\n if (!src) return\n const isBlock = el.classList.contains('rmx-math-block')\n const html = renderMath(src, isBlock)\n el.innerHTML = html\n if (isBlock && numbering) {\n const num = el.getAttribute('data-equation-number')\n if (num) {\n const label = document.createElement('span')\n label.className = 'rmx-equation-number'\n label.textContent = `(${num})`\n el.appendChild(label)\n }\n }\n }\n\n function renderAllMath() {\n if (!engine) return\n const els = engine.element.querySelectorAll('[data-math-src]')\n els.forEach(renderMathElement)\n }\n\n function createMathElement(latex, displayMode, eqNumber) {\n const tag = displayMode ? 'div' : 'span'\n const el = document.createElement(tag)\n el.className = `rmx-math ${displayMode ? 'rmx-math-block' : 'rmx-math-inline'}`\n el.setAttribute('data-math', 'latex')\n el.setAttribute('data-math-src', latex)\n el.contentEditable = 'false'\n if (displayMode && numbering && eqNumber) {\n el.setAttribute('data-equation-number', String(eqNumber))\n }\n el.innerHTML = renderMath(latex, displayMode)\n if (displayMode && numbering && eqNumber) {\n const label = document.createElement('span')\n label.className = 'rmx-equation-number'\n label.textContent = `(${eqNumber})`\n el.appendChild(label)\n }\n return el\n }\n\n return createPlugin({\n name: 'math',\n requiresFullAccess: true,\n version: '1.0.0',\n description: 'LaTeX/KaTeX math rendering, symbol palette, equation numbering',\n\n commands: [\n {\n name: 'insertMath',\n execute(eng, params = {}) {\n const { latex = '', displayMode = false } = params\n eng.history.snapshot()\n equationCounter++\n const el = createMathElement(latex, displayMode, displayMode ? equationCounter : null)\n\n const sel = window.getSelection()\n if (sel && sel.rangeCount > 0) {\n const range = sel.getRangeAt(0)\n range.deleteContents()\n range.insertNode(el)\n const space = document.createTextNode('\\u00A0')\n el.after(space)\n range.setStartAfter(space)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n }\n\n eng.eventBus.emit('content:change')\n return el\n },\n meta: { icon: 'math', tooltip: 'Insert Math Equation' },\n },\n {\n name: 'editMath',\n execute(eng, { element, latex }) {\n if (!element || !latex) return\n eng.history.snapshot()\n element.setAttribute('data-math-src', latex)\n renderMathElement(element)\n eng.eventBus.emit('content:change')\n },\n meta: { tooltip: 'Edit Math Equation' },\n },\n {\n name: 'insertSymbol',\n execute(eng, latex) {\n if (!latex) return\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0) return\n const range = sel.getRangeAt(0)\n range.deleteContents()\n range.insertNode(document.createTextNode(latex))\n range.collapse(false)\n eng.eventBus.emit('content:change')\n },\n meta: { icon: 'symbol', tooltip: 'Insert Symbol' },\n },\n {\n name: 'getSymbolPalette',\n execute() { return getSymbolPalette() },\n meta: { tooltip: 'Get Symbol Palette' },\n },\n {\n name: 'getMathElements',\n execute(eng) {\n const els = eng.element.querySelectorAll('[data-math-src]')\n return Array.from(els).map((el, i) => ({\n index: i,\n src: el.getAttribute('data-math-src'),\n displayMode: el.classList.contains('rmx-math-block'),\n equationNumber: el.getAttribute('data-equation-number'),\n element: el,\n }))\n },\n meta: { tooltip: 'Get Math Elements' },\n },\n {\n name: 'copyMathAs',\n execute(eng, { element, format = 'latex' }) {\n if (!element) return null\n const src = element.getAttribute('data-math-src')\n if (!src) return null\n if (format === 'latex') return src\n if (format === 'mathml') return latexToMathML(src)\n return src\n },\n meta: { tooltip: 'Copy Math As Format' },\n },\n ],\n\n contextMenuItems: [\n { label: 'Insert Inline Math', command: 'insertMath' },\n ],\n\n init(eng) {\n engine = eng\n\n engine._math = {\n getSymbolPalette,\n parseMathExpressions,\n latexToMathML,\n renderAllMath,\n getEquationCount: () => equationCounter,\n }\n\n // Click on math element to edit\n engine.element.addEventListener('dblclick', (e) => {\n const mathEl = e.target.closest('[data-math-src]')\n if (mathEl) {\n engine.eventBus.emit('math:edit', {\n element: mathEl,\n src: mathEl.getAttribute('data-math-src'),\n displayMode: mathEl.classList.contains('rmx-math-block'),\n })\n }\n })\n\n // Re-render on content change\n observer = new MutationObserver(() => renderAllMath())\n observer.observe(engine.element, { childList: true, subtree: true })\n\n renderAllMath()\n },\n\n destroy() {\n observer?.disconnect()\n engine = null\n },\n })\n}\n","import { escapeHTML } from '../../../utils/escapeHTML.js'\n\n/**\n * TocPlugin — Auto-generated table of contents and document outline.\n *\n * - Builds heading hierarchy from H1-H6 elements\n * - Click-to-scroll navigation\n * - Collapsible sections based on heading levels\n * - Numbering options (1.1, 1.2, etc.)\n * - onOutlineChange callback for syncing with external navigation\n * - insertToc command to insert a rendered TOC into the document\n *\n * @param {object} [options]\n * @param {Function} [options.onOutlineChange] — (outline) => void\n * @param {boolean} [options.numbering=true] — show section numbers\n * @param {boolean} [options.collapsible=true] — allow collapsing sections in the sidebar\n */\n\nimport { createPlugin } from '../../createPlugin.js'\n\n// ---------------------------------------------------------------------------\n// Outline builder\n// ---------------------------------------------------------------------------\n\n/**\n * @typedef {object} OutlineItem\n * @property {string} id — auto-generated or existing element ID\n * @property {string} text — heading text content\n * @property {number} level — 1-6\n * @property {string} [number] — section number (e.g., \"1.2.3\")\n * @property {HTMLElement} element — the heading DOM element\n * @property {OutlineItem[]} children — nested items\n */\n\nlet _idCounter = 0\n\n/**\n * Build a hierarchical outline from headings in the editor.\n * @param {HTMLElement} editorEl\n * @param {{ numbering?: boolean }} [options]\n * @returns {OutlineItem[]}\n */\nexport function buildOutline(editorEl, options = {}) {\n const { numbering = true } = options\n const headings = editorEl.querySelectorAll('h1, h2, h3, h4, h5, h6')\n const flat = []\n\n for (const el of headings) {\n // Ensure each heading has an ID for linking\n if (!el.id) {\n const slug = el.textContent.toLowerCase().replace(/[^\\w\\s-]/g, '').replace(/\\s+/g, '-').replace(/-+/g, '-') || `heading-${++_idCounter}`\n el.id = slug\n }\n flat.push({\n id: el.id,\n text: el.textContent,\n level: parseInt(el.tagName.charAt(1), 10),\n element: el,\n children: [],\n number: '',\n })\n }\n\n // Build tree\n const root = []\n const stack = [] // { item, level }\n\n for (const item of flat) {\n while (stack.length > 0 && stack[stack.length - 1].level >= item.level) {\n stack.pop()\n }\n if (stack.length === 0) {\n root.push(item)\n } else {\n stack[stack.length - 1].item.children.push(item)\n }\n stack.push({ item, level: item.level })\n }\n\n // Assign numbers\n if (numbering) {\n assignNumbers(root, '')\n }\n\n return root\n}\n\nfunction assignNumbers(items, prefix) {\n items.forEach((item, i) => {\n item.number = prefix ? `${prefix}.${i + 1}` : String(i + 1)\n assignNumbers(item.children, item.number)\n })\n}\n\n/**\n * Flatten a hierarchical outline into a flat list.\n * @param {OutlineItem[]} outline\n * @returns {OutlineItem[]}\n */\nexport function flattenOutline(outline) {\n const result = []\n function walk(items) {\n for (const item of items) {\n result.push(item)\n walk(item.children)\n }\n }\n walk(outline)\n return result\n}\n\n/**\n * Generate HTML for a table of contents.\n * @param {OutlineItem[]} outline\n * @param {{ numbering?: boolean, linkPrefix?: string }} [options]\n * @returns {string}\n */\nexport function renderTocHTML(outline, options = {}) {\n const { numbering = true, linkPrefix = '#' } = options\n\n function renderItems(items) {\n if (items.length === 0) return ''\n const lis = items.map(item => {\n const num = numbering && item.number ? `<span class=\"rmx-toc-number\">${item.number}</span> ` : ''\n const children = item.children.length > 0 ? `<ul>${renderItems(item.children)}</ul>` : ''\n return `<li><a href=\"${linkPrefix}${item.id}\" class=\"rmx-toc-link\">${num}${escapeHTML(item.text)}</a>${children}</li>`\n }).join('')\n return lis\n }\n\n return `<nav class=\"rmx-toc\" role=\"navigation\" aria-label=\"Table of Contents\"><ul>${renderItems(outline)}</ul></nav>`\n}\n\n\n/**\n * Validate heading hierarchy — check for skipped levels.\n * @param {OutlineItem[]} flatItems\n * @returns {Array<{ message: string, element: HTMLElement }>}\n */\nexport function validateHeadingHierarchy(flatItems) {\n const warnings = []\n for (let i = 1; i < flatItems.length; i++) {\n const prev = flatItems[i - 1].level\n const curr = flatItems[i].level\n if (curr > prev + 1) {\n warnings.push({\n message: `Heading level skipped: H${prev} → H${curr} (expected H${prev + 1})`,\n element: flatItems[i].element,\n })\n }\n }\n return warnings\n}\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nexport function TocPlugin(options = {}) {\n const {\n onOutlineChange,\n numbering = true,\n collapsible = true,\n } = options\n\n let engine = null\n let unsubContentChange = null\n let currentOutline = []\n let debounceTimer = null\n\n function updateOutline() {\n if (!engine) return\n currentOutline = buildOutline(engine.element, { numbering })\n engine.eventBus.emit('toc:change', { outline: currentOutline })\n onOutlineChange?.(currentOutline)\n }\n\n return createPlugin({\n name: 'toc',\n requiresFullAccess: true,\n version: '1.0.0',\n description: 'Auto-generated table of contents, document outline, heading validation',\n\n commands: [\n {\n name: 'getOutline',\n execute(eng) {\n return buildOutline(eng.element, { numbering })\n },\n meta: { tooltip: 'Get Document Outline' },\n },\n {\n name: 'insertToc',\n execute(eng) {\n eng.history.snapshot()\n const outline = buildOutline(eng.element, { numbering })\n const html = renderTocHTML(outline, { numbering })\n const sel = window.getSelection()\n if (sel && sel.rangeCount > 0) {\n const range = sel.getRangeAt(0)\n const temp = document.createElement('div')\n temp.innerHTML = html\n const fragment = document.createDocumentFragment()\n while (temp.firstChild) fragment.appendChild(temp.firstChild)\n range.deleteContents()\n range.insertNode(fragment)\n }\n eng.eventBus.emit('content:change')\n },\n meta: { icon: 'toc', tooltip: 'Insert Table of Contents' },\n },\n {\n name: 'scrollToHeading',\n execute(eng, headingId) {\n const el = eng.element.querySelector(`#${CSS.escape(headingId)}`)\n if (el) {\n el.scrollIntoView?.({ behavior: 'smooth', block: 'start' })\n const sel = window.getSelection()\n const range = document.createRange()\n range.selectNodeContents(el)\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n }\n },\n meta: { icon: 'scroll', tooltip: 'Scroll to Heading' },\n },\n {\n name: 'validateHeadings',\n execute(eng) {\n const outline = buildOutline(eng.element, { numbering: false })\n const flat = flattenOutline(outline)\n return validateHeadingHierarchy(flat)\n },\n meta: { tooltip: 'Validate Heading Hierarchy' },\n },\n ],\n\n init(eng) {\n engine = eng\n\n engine._toc = {\n buildOutline: () => buildOutline(engine.element, { numbering }),\n flattenOutline,\n renderTocHTML,\n validateHeadingHierarchy,\n getOutline: () => currentOutline,\n }\n\n // Update outline on content changes (debounced)\n unsubContentChange = engine.eventBus.on('content:change', () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(updateOutline, 200)\n })\n\n // Handle TOC link clicks (smooth scroll)\n engine.element.addEventListener('click', (e) => {\n const link = e.target.closest('.rmx-toc-link')\n if (link) {\n e.preventDefault()\n const id = link.getAttribute('href')?.replace('#', '')\n if (id) {\n const heading = engine.element.querySelector(`#${CSS.escape(id)}`)\n heading?.scrollIntoView?.({ behavior: 'smooth', block: 'start' })\n }\n }\n })\n\n // Initial outline\n updateOutline()\n },\n\n destroy() {\n clearTimeout(debounceTimer)\n unsubContentChange?.()\n engine = null\n },\n })\n}\n","/**\n * AnalyticsPlugin — Content analytics and readability scoring.\n *\n * - Flesch-Kincaid, Gunning Fog, Coleman-Liau readability scores\n * - Reading time estimate\n * - Sentence and paragraph length analysis\n * - Vocabulary level indicator\n * - Heading hierarchy validation\n * - Goal-based writing (target word count)\n * - SEO hints (keyword density, heading structure)\n *\n * @param {object} [options]\n * @param {number} [options.wordsPerMinute=200] — reading speed for time estimate\n * @param {number} [options.targetWordCount=0] — goal word count (0 = disabled)\n * @param {Function} [options.onAnalytics] — (stats) => void, called on every content change\n * @param {number} [options.maxSentenceLength=30] — warn if sentence exceeds this\n * @param {number} [options.maxParagraphLength=150] — warn if paragraph exceeds this\n */\n\nimport { createPlugin } from '../../createPlugin.js'\n\n// ---------------------------------------------------------------------------\n// Text analysis utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Count syllables in a word (English approximation).\n * @param {string} word\n * @returns {number}\n */\nexport function countSyllables(word) {\n word = word.toLowerCase().replace(/[^a-z]/g, '')\n if (!word) return 0\n if (word.length <= 3) return 1\n\n word = word.replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, '')\n word = word.replace(/^y/, '')\n\n const vowelGroups = word.match(/[aeiouy]{1,2}/g)\n return vowelGroups ? vowelGroups.length : 1\n}\n\n/**\n * Split text into sentences.\n * @param {string} text\n * @returns {string[]}\n */\nexport function splitSentences(text) {\n if (!text) return []\n return text\n .split(/(?<=[.!?])\\s+/)\n .map(s => s.trim())\n .filter(s => s.length > 0)\n}\n\n/**\n * Split text into paragraphs.\n * @param {string} text\n * @returns {string[]}\n */\nfunction splitParagraphs(text) {\n return text.split(/\\n\\s*\\n/).map(p => p.trim()).filter(p => p.length > 0)\n}\n\n/**\n * Get words from text.\n * @param {string} text\n * @returns {string[]}\n */\nfunction getWords(text) {\n return text.trim().split(/\\s+/).filter(w => w.length > 0)\n}\n\n// ---------------------------------------------------------------------------\n// Readability scores\n// ---------------------------------------------------------------------------\n\n/**\n * Calculate Flesch-Kincaid Grade Level.\n * @param {{ words: number, sentences: number, syllables: number }} stats\n * @returns {number}\n */\nexport function fleschKincaid(stats) {\n if (stats.sentences === 0 || stats.words === 0) return 0\n return 0.39 * (stats.words / stats.sentences) + 11.8 * (stats.syllables / stats.words) - 15.59\n}\n\n/**\n * Calculate Flesch Reading Ease score.\n * @param {{ words: number, sentences: number, syllables: number }} stats\n * @returns {number}\n */\nexport function fleschReadingEase(stats) {\n if (stats.sentences === 0 || stats.words === 0) return 0\n return 206.835 - 1.015 * (stats.words / stats.sentences) - 84.6 * (stats.syllables / stats.words)\n}\n\n/**\n * Calculate Gunning Fog Index.\n * @param {{ words: number, sentences: number, complexWords: number }} stats\n * @returns {number}\n */\nexport function gunningFog(stats) {\n if (stats.sentences === 0 || stats.words === 0) return 0\n return 0.4 * ((stats.words / stats.sentences) + 100 * (stats.complexWords / stats.words))\n}\n\n/**\n * Calculate Coleman-Liau Index.\n * @param {{ chars: number, words: number, sentences: number }} stats\n * @returns {number}\n */\nexport function colemanLiau(stats) {\n if (stats.words === 0) return 0\n const L = (stats.chars / stats.words) * 100\n const S = (stats.sentences / stats.words) * 100\n return 0.0588 * L - 0.296 * S - 15.8\n}\n\n/**\n * Determine vocabulary level from readability score.\n * @param {number} gradeLevel\n * @returns {'basic'|'intermediate'|'advanced'}\n */\nexport function vocabularyLevel(gradeLevel) {\n if (gradeLevel <= 6) return 'basic'\n if (gradeLevel <= 12) return 'intermediate'\n return 'advanced'\n}\n\n// ---------------------------------------------------------------------------\n// Full content analysis\n// ---------------------------------------------------------------------------\n\n/**\n * Analyze content and return comprehensive stats.\n * @param {string} text\n * @param {object} [options]\n * @param {number} [options.wordsPerMinute=200]\n * @param {number} [options.targetWordCount=0]\n * @param {number} [options.maxSentenceLength=30]\n * @param {number} [options.maxParagraphLength=150]\n * @returns {object}\n */\nexport function analyzeContent(text, options = {}) {\n const {\n wordsPerMinute = 200,\n targetWordCount = 0,\n maxSentenceLength = 30,\n maxParagraphLength = 150,\n } = options\n\n const words = getWords(text)\n const sentences = splitSentences(text)\n const paragraphs = splitParagraphs(text)\n const chars = text.replace(/\\s/g, '').length\n\n let syllables = 0\n let complexWords = 0\n for (const word of words) {\n const s = countSyllables(word)\n syllables += s\n if (s >= 3) complexWords++\n }\n\n const baseStats = { words: words.length, sentences: sentences.length, syllables, complexWords, chars }\n\n // Readability scores\n const fk = fleschKincaid(baseStats)\n const fre = fleschReadingEase(baseStats)\n const gf = gunningFog(baseStats)\n const cl = colemanLiau(baseStats)\n const level = vocabularyLevel(fk)\n\n // Reading time\n const readingTimeMinutes = words.length / wordsPerMinute\n const readingTimeSeconds = Math.round(readingTimeMinutes * 60)\n\n // Sentence/paragraph warnings\n const longSentences = sentences\n .map((s, i) => ({ index: i, text: s, wordCount: getWords(s).length }))\n .filter(s => s.wordCount > maxSentenceLength)\n\n const longParagraphs = paragraphs\n .map((p, i) => ({ index: i, text: p.substring(0, 100) + (p.length > 100 ? '...' : ''), wordCount: getWords(p).length }))\n .filter(p => p.wordCount > maxParagraphLength)\n\n // Word count goal progress\n const goalProgress = targetWordCount > 0\n ? { target: targetWordCount, current: words.length, percentage: Math.round((words.length / targetWordCount) * 100) }\n : null\n\n return {\n wordCount: words.length,\n charCount: chars,\n sentenceCount: sentences.length,\n paragraphCount: paragraphs.length,\n syllableCount: syllables,\n complexWordCount: complexWords,\n readability: {\n fleschKincaid: Math.round(fk * 10) / 10,\n fleschReadingEase: Math.round(fre * 10) / 10,\n gunningFog: Math.round(gf * 10) / 10,\n colemanLiau: Math.round(cl * 10) / 10,\n vocabularyLevel: level,\n },\n readingTime: {\n minutes: Math.ceil(readingTimeMinutes),\n seconds: readingTimeSeconds,\n wordsPerMinute,\n },\n warnings: {\n longSentences,\n longParagraphs,\n },\n goalProgress,\n }\n}\n\n/**\n * Analyze keyword density in text.\n * @param {string} text\n * @param {string} keyword\n * @returns {{ count: number, density: number, positions: number[] }}\n */\nexport function keywordDensity(text, keyword) {\n if (!text || !keyword) return { count: 0, density: 0, positions: [] }\n const words = getWords(text)\n const kw = keyword.toLowerCase()\n const positions = []\n let count = 0\n words.forEach((w, i) => {\n if (w.toLowerCase() === kw) {\n count++\n positions.push(i)\n }\n })\n return {\n count,\n density: words.length > 0 ? Math.round((count / words.length) * 10000) / 100 : 0,\n positions,\n }\n}\n\n/**\n * Generate SEO hints for the content.\n * @param {string} text\n * @param {HTMLElement} editorEl\n * @param {string} [targetKeyword]\n * @returns {object}\n */\nexport function seoAnalysis(text, editorEl, targetKeyword) {\n const words = getWords(text)\n const headings = editorEl.querySelectorAll('h1, h2, h3, h4, h5, h6')\n const h1Count = editorEl.querySelectorAll('h1').length\n\n const hints = []\n if (h1Count === 0) hints.push('Missing H1 heading')\n if (h1Count > 1) hints.push('Multiple H1 headings — consider using only one')\n if (words.length < 300) hints.push('Content is short — aim for 300+ words for SEO')\n if (headings.length === 0) hints.push('No headings — add headings to improve content structure')\n\n let keywordInfo = null\n if (targetKeyword) {\n keywordInfo = keywordDensity(text, targetKeyword)\n if (keywordInfo.density < 0.5) hints.push(`Keyword \"${targetKeyword}\" density is low (${keywordInfo.density}%) — aim for 1-3%`)\n if (keywordInfo.density > 3) hints.push(`Keyword \"${targetKeyword}\" density is high (${keywordInfo.density}%) — may be over-optimized`)\n }\n\n return {\n wordCount: words.length,\n headingCount: headings.length,\n h1Count,\n keywordInfo,\n hints,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Plugin\n// ---------------------------------------------------------------------------\n\nexport function AnalyticsPlugin(options = {}) {\n const {\n wordsPerMinute = 200,\n targetWordCount = 0,\n onAnalytics,\n maxSentenceLength = 30,\n maxParagraphLength = 150,\n } = options\n\n let engine = null\n let unsubContentChange = null\n let debounceTimer = null\n let currentStats = null\n /** Item 15: Cache the last analyzed text to avoid re-splitting */\n let lastAnalyzedText = null\n\n function updateAnalytics() {\n if (!engine) return\n const text = engine.getText()\n // Item 15: Skip recompute if text hasn't changed\n if (text === lastAnalyzedText && currentStats) return\n lastAnalyzedText = text\n currentStats = analyzeContent(text, { wordsPerMinute, targetWordCount, maxSentenceLength, maxParagraphLength })\n engine.eventBus.emit('analytics:update', currentStats)\n onAnalytics?.(currentStats)\n }\n\n return createPlugin({\n name: 'analytics',\n requiresFullAccess: true,\n version: '1.0.0',\n description: 'Readability scores, reading time, vocabulary level, SEO hints',\n\n commands: [\n {\n name: 'toggleAnalytics',\n execute(eng) {\n eng._analyticsVisible = !eng._analyticsVisible\n eng.eventBus.emit('analytics:toggle', { visible: eng._analyticsVisible })\n return eng._analyticsVisible\n },\n meta: { icon: 'analytics', tooltip: 'Toggle Analytics Panel' },\n },\n {\n name: 'getAnalytics',\n execute(eng) {\n const text = eng.getText()\n return analyzeContent(text, { wordsPerMinute, targetWordCount, maxSentenceLength, maxParagraphLength })\n },\n meta: { tooltip: 'Get Content Analytics' },\n },\n {\n name: 'getSeoAnalysis',\n execute(eng, keyword) {\n return seoAnalysis(eng.getText(), eng.element, keyword)\n },\n meta: { tooltip: 'Get SEO Analysis' },\n },\n {\n name: 'getKeywordDensity',\n execute(eng, keyword) {\n return keywordDensity(eng.getText(), keyword)\n },\n meta: { tooltip: 'Get Keyword Density' },\n },\n ],\n\n init(eng) {\n engine = eng\n\n engine._analytics = {\n analyzeContent: () => analyzeContent(engine.getText(), { wordsPerMinute, targetWordCount, maxSentenceLength, maxParagraphLength }),\n seoAnalysis: (keyword) => seoAnalysis(engine.getText(), engine.element, keyword),\n keywordDensity: (keyword) => keywordDensity(engine.getText(), keyword),\n getStats: () => currentStats,\n countSyllables,\n fleschKincaid,\n gunningFog,\n colemanLiau,\n }\n\n // Update on content changes (debounced)\n unsubContentChange = engine.eventBus.on('content:change', () => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(updateAnalytics, 300)\n })\n\n // Initial analysis\n updateAnalytics()\n },\n\n destroy() {\n clearTimeout(debounceTimer)\n unsubContentChange?.()\n engine = null\n },\n })\n}\n","/**\n * GrammarEngine — Built-in grammar, style, and punctuation rules engine.\n *\n * Provides pattern-based detection for:\n * - Passive voice constructions (was/were/been + past participle)\n * - Wordiness patterns (e.g., \"in order to\" -> \"to\")\n * - Cliche detection (common cliches list)\n * - Punctuation issues (double spaces, missing periods, Oxford comma)\n *\n * Each rule returns an array of issues:\n * { offset, length, message, suggestions, type: 'grammar'|'style'|'spelling' }\n *\n * Writing-style presets control which rules fire:\n * - formal: all rules active, strict grammar\n * - casual: relaxed grammar, skip passive voice + wordiness\n * - technical: jargon OK, skip cliches + wordiness\n * - academic: citation-aware, all grammar rules, skip casual cliches\n */\n\n// ---------------------------------------------------------------------------\n// Passive voice patterns\n// ---------------------------------------------------------------------------\n\nconst PASSIVE_AUXILIARIES = /\\b(was|were|been|being|is|are|am|get|gets|got|gotten)\\b/i\n\nconst PAST_PARTICIPLE_SUFFIXES = /\\b\\w+(ed|en|wn|nt|ht|lt)\\b/i\n\n/**\n * Detect passive voice constructions.\n * Looks for auxiliary verb + past participle patterns.\n * @param {string} text\n * @returns {Array<{offset: number, length: number, message: string, suggestions: string[], type: string}>}\n */\nexport function detectPassiveVoice(text) {\n const issues = []\n const words = text.split(/(\\s+)/)\n let offset = 0\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i]\n if (/^\\s+$/.test(word)) {\n offset += word.length\n continue\n }\n\n if (PASSIVE_AUXILIARIES.test(word)) {\n // Look ahead for a past participle (skip whitespace tokens)\n let nextIdx = i + 1\n while (nextIdx < words.length && /^\\s+$/.test(words[nextIdx])) nextIdx++\n\n if (nextIdx < words.length && PAST_PARTICIPLE_SUFFIXES.test(words[nextIdx])) {\n const passiveStart = offset\n let passiveEnd = offset + word.length\n // Include all tokens up to and including the past participle\n for (let j = i + 1; j <= nextIdx; j++) {\n passiveEnd += words[j].length\n }\n const passiveText = text.slice(passiveStart, passiveEnd)\n issues.push({\n offset: passiveStart,\n length: passiveEnd - passiveStart,\n message: `Passive voice detected: \"${passiveText}\". Consider using active voice.`,\n suggestions: [],\n type: 'grammar',\n rule: 'passive-voice',\n })\n }\n }\n\n offset += word.length\n }\n\n return issues\n}\n\n// ---------------------------------------------------------------------------\n// Wordiness patterns\n// ---------------------------------------------------------------------------\n\n/** @type {Array<{pattern: RegExp, replacement: string, message: string}>} */\nconst WORDINESS_RULES = [\n { pattern: /\\bin order to\\b/gi, replacement: 'to', message: '\"in order to\" can be simplified to \"to\"' },\n { pattern: /\\bat this point in time\\b/gi, replacement: 'now', message: '\"at this point in time\" can be simplified to \"now\"' },\n { pattern: /\\bdue to the fact that\\b/gi, replacement: 'because', message: '\"due to the fact that\" can be simplified to \"because\"' },\n { pattern: /\\bin the event that\\b/gi, replacement: 'if', message: '\"in the event that\" can be simplified to \"if\"' },\n { pattern: /\\bfor the purpose of\\b/gi, replacement: 'to', message: '\"for the purpose of\" can be simplified to \"to\"' },\n { pattern: /\\bin the near future\\b/gi, replacement: 'soon', message: '\"in the near future\" can be simplified to \"soon\"' },\n { pattern: /\\bat the present time\\b/gi, replacement: 'now', message: '\"at the present time\" can be simplified to \"now\"' },\n { pattern: /\\bin spite of the fact that\\b/gi, replacement: 'although', message: '\"in spite of the fact that\" can be simplified to \"although\"' },\n { pattern: /\\bwith regard to\\b/gi, replacement: 'about', message: '\"with regard to\" can be simplified to \"about\"' },\n { pattern: /\\bin close proximity\\b/gi, replacement: 'near', message: '\"in close proximity\" can be simplified to \"near\"' },\n { pattern: /\\ba large number of\\b/gi, replacement: 'many', message: '\"a large number of\" can be simplified to \"many\"' },\n { pattern: /\\bhas the ability to\\b/gi, replacement: 'can', message: '\"has the ability to\" can be simplified to \"can\"' },\n { pattern: /\\bis able to\\b/gi, replacement: 'can', message: '\"is able to\" can be simplified to \"can\"' },\n { pattern: /\\bit is important to note that\\b/gi, replacement: '(remove)', message: '\"it is important to note that\" is unnecessary filler' },\n { pattern: /\\bneedless to say\\b/gi, replacement: '(remove)', message: '\"needless to say\" is unnecessary — just say it' },\n { pattern: /\\beach and every\\b/gi, replacement: 'each', message: '\"each and every\" can be simplified to \"each\" or \"every\"' },\n { pattern: /\\bfirst and foremost\\b/gi, replacement: 'first', message: '\"first and foremost\" can be simplified to \"first\"' },\n { pattern: /\\bbasically\\b/gi, replacement: '(remove)', message: '\"basically\" is often unnecessary filler' },\n { pattern: /\\bvery unique\\b/gi, replacement: 'unique', message: '\"unique\" is absolute — \"very\" is redundant' },\n { pattern: /\\bcompletely eliminate\\b/gi, replacement: 'eliminate', message: '\"eliminate\" is absolute — \"completely\" is redundant' },\n]\n\n/**\n * Detect wordy phrases that can be simplified.\n * @param {string} text\n * @returns {Array<{offset: number, length: number, message: string, suggestions: string[], type: string}>}\n */\nexport function detectWordiness(text) {\n const issues = []\n\n for (const rule of WORDINESS_RULES) {\n let match\n const re = new RegExp(rule.pattern.source, rule.pattern.flags)\n while ((match = re.exec(text)) !== null) {\n issues.push({\n offset: match.index,\n length: match[0].length,\n message: rule.message,\n suggestions: rule.replacement === '(remove)' ? [] : [rule.replacement],\n type: 'style',\n rule: 'wordiness',\n })\n }\n }\n\n return issues\n}\n\n// ---------------------------------------------------------------------------\n// Cliche detection\n// ---------------------------------------------------------------------------\n\nconst CLICHES = [\n 'at the end of the day',\n 'think outside the box',\n 'low-hanging fruit',\n 'move the needle',\n 'take it to the next level',\n 'game changer',\n 'paradigm shift',\n 'synergy',\n 'it goes without saying',\n 'avoid it like the plague',\n 'better late than never',\n 'the bottom line',\n 'by the same token',\n 'crystal clear',\n 'few and far between',\n 'hit the ground running',\n 'in a nutshell',\n 'level playing field',\n 'read between the lines',\n 'reinvent the wheel',\n 'tip of the iceberg',\n 'win-win situation',\n 'back to the drawing board',\n 'bite the bullet',\n 'cutting edge',\n 'pushing the envelope',\n 'raise the bar',\n 'on the same page',\n 'circle back',\n 'deep dive',\n 'best practice',\n 'leverage',\n 'ecosystem',\n 'robust',\n 'scalable solution',\n]\n\n/**\n * Detect cliche phrases in text.\n * @param {string} text\n * @returns {Array<{offset: number, length: number, message: string, suggestions: string[], type: string}>}\n */\nexport function detectCliches(text) {\n const issues = []\n const lower = text.toLowerCase()\n\n for (const cliche of CLICHES) {\n let idx = lower.indexOf(cliche)\n while (idx !== -1) {\n issues.push({\n offset: idx,\n length: cliche.length,\n message: `Cliche detected: \"${text.slice(idx, idx + cliche.length)}\". Consider using more original phrasing.`,\n suggestions: [],\n type: 'style',\n rule: 'cliche',\n })\n idx = lower.indexOf(cliche, idx + cliche.length)\n }\n }\n\n return issues\n}\n\n// ---------------------------------------------------------------------------\n// Punctuation checks\n// ---------------------------------------------------------------------------\n\n/**\n * Detect punctuation issues.\n * @param {string} text\n * @returns {Array<{offset: number, length: number, message: string, suggestions: string[], type: string}>}\n */\nexport function detectPunctuationIssues(text) {\n const issues = []\n\n // Double spaces\n let match\n const doubleSpace = / +/g\n while ((match = doubleSpace.exec(text)) !== null) {\n // Skip if at start of line (indentation)\n if (match.index === 0 || text[match.index - 1] === '\\n') continue\n issues.push({\n offset: match.index,\n length: match[0].length,\n message: 'Multiple consecutive spaces detected.',\n suggestions: [' '],\n type: 'grammar',\n rule: 'double-space',\n })\n }\n\n // Repeated punctuation (e.g., \"..\", \",,\", \"!!\")\n const repeatedPunc = /([.,!?;:])\\1+/g\n while ((match = repeatedPunc.exec(text)) !== null) {\n // Allow \"...\" (ellipsis)\n if (match[0] === '...' || match[0] === '..') continue\n issues.push({\n offset: match.index,\n length: match[0].length,\n message: `Repeated punctuation: \"${match[0]}\"`,\n suggestions: [match[1]],\n type: 'grammar',\n rule: 'repeated-punctuation',\n })\n }\n\n // Missing space after punctuation\n const missingSpace = /[.!?,;:](?=[A-Za-z])/g\n while ((match = missingSpace.exec(text)) !== null) {\n // Skip common abbreviations like \"e.g.\" \"i.e.\" \"Dr.\" \"Mr.\" and URLs\n const before = text.slice(Math.max(0, match.index - 4), match.index + 1)\n if (/\\b[A-Z]\\.$/.test(before) || /[a-z]\\.[a-z]/.test(before) || /https?:/.test(before)) continue\n issues.push({\n offset: match.index,\n length: 2,\n message: 'Missing space after punctuation.',\n suggestions: [text[match.index] + ' ' + text[match.index + 1]],\n type: 'grammar',\n rule: 'missing-space',\n })\n }\n\n return issues\n}\n\n// ---------------------------------------------------------------------------\n// Style preset configurations\n// ---------------------------------------------------------------------------\n\n/**\n * @typedef {'formal'|'casual'|'technical'|'academic'} StylePreset\n */\n\n/** @type {Record<StylePreset, {passiveVoice: boolean, wordiness: boolean, cliches: boolean, punctuation: boolean}>} */\nexport const STYLE_PRESETS = {\n formal: { passiveVoice: true, wordiness: true, cliches: true, punctuation: true },\n casual: { passiveVoice: false, wordiness: false, cliches: true, punctuation: true },\n technical: { passiveVoice: true, wordiness: false, cliches: false, punctuation: true },\n academic: { passiveVoice: true, wordiness: true, cliches: false, punctuation: true },\n}\n\n// ---------------------------------------------------------------------------\n// Main analysis function\n// ---------------------------------------------------------------------------\n\n/**\n * Run all applicable grammar rules on the given text.\n *\n * @param {string} text - The plain text content to analyze\n * @param {object} [options]\n * @param {StylePreset} [options.stylePreset='formal'] - Writing style preset\n * @param {boolean} [options.passiveVoice] - Override: enable/disable passive voice detection\n * @param {boolean} [options.wordiness] - Override: enable/disable wordiness detection\n * @param {boolean} [options.cliches] - Override: enable/disable cliche detection\n * @param {boolean} [options.punctuation] - Override: enable/disable punctuation checks\n * @returns {Array<{offset: number, length: number, message: string, suggestions: string[], type: string, rule: string}>}\n */\nexport function analyzeGrammar(text, options = {}) {\n if (!text) return []\n\n const preset = STYLE_PRESETS[options.stylePreset] || STYLE_PRESETS.formal\n const config = {\n passiveVoice: options.passiveVoice ?? preset.passiveVoice,\n wordiness: options.wordiness ?? preset.wordiness,\n cliches: options.cliches ?? preset.cliches,\n punctuation: options.punctuation ?? preset.punctuation,\n }\n\n const issues = []\n\n if (config.passiveVoice) issues.push(...detectPassiveVoice(text))\n if (config.wordiness) issues.push(...detectWordiness(text))\n if (config.cliches) issues.push(...detectCliches(text))\n if (config.punctuation) issues.push(...detectPunctuationIssues(text))\n\n // Sort by offset\n issues.sort((a, b) => a.offset - b.offset)\n\n return issues\n}\n\n/**\n * Get a human-readable summary of grammar analysis results.\n * @param {Array<{type: string, rule: string}>} issues\n * @returns {{ total: number, grammar: number, style: number, byRule: Record<string, number> }}\n */\nexport function summarizeIssues(issues) {\n const summary = { total: issues.length, grammar: 0, style: 0, byRule: {} }\n for (const issue of issues) {\n if (issue.type === 'grammar') summary.grammar++\n else summary.style++\n summary.byRule[issue.rule] = (summary.byRule[issue.rule] || 0) + 1\n }\n return summary\n}\n","/**\n * SpellcheckPlugin — Spelling & grammar checking with inline underlines.\n *\n * - Uses browser's native `spellcheck` attribute on contenteditable\n * - Built-in grammar rules engine for passive voice, wordiness, cliches, punctuation\n * - Configurable writing-style presets (formal, casual, technical, academic)\n * - Custom service interface for LanguageTool, Grammarly SDK, or other services\n * - Context menu integration: right-click on underlined word shows suggestions\n * - Per-session or persistent \"Ignore\" and \"Add to dictionary\"\n * - Language detection and multi-language support via BCP 47 tags\n *\n * @param {object} [options]\n * @param {string} [options.language='en-US'] — BCP 47 language tag\n * @param {boolean} [options.enabled=true] — enable spellcheck on init\n * @param {boolean} [options.grammarRules=true] — enable built-in grammar checking\n * @param {string} [options.stylePreset='formal'] — 'formal'|'casual'|'technical'|'academic'\n * @param {object} [options.customService] — optional external grammar service\n * @param {Function} [options.customService.check] — async (text) => Array<{offset,length,message,suggestions,type}>\n * @param {string[]} [options.dictionary=[]] — custom words to ignore\n * @param {boolean} [options.persistent=true] — persist dictionary in localStorage\n * @param {Function} [options.onError] — (errors) => void\n * @param {Function} [options.onCorrection] — ({ original, replacement }) => void\n */\n\nimport { createPlugin } from '../../createPlugin.js'\nimport { analyzeGrammar, summarizeIssues, STYLE_PRESETS } from './GrammarEngine.js'\n\nconst DICTIONARY_STORAGE_KEY = 'rmx-spellcheck-dictionary'\nconst IGNORED_STORAGE_KEY = 'rmx-spellcheck-ignored'\n\n/**\n * Load persisted dictionary from localStorage.\n * @returns {string[]}\n */\nfunction loadPersistedDictionary() {\n try {\n const raw = localStorage.getItem(DICTIONARY_STORAGE_KEY)\n if (!raw) return []\n const parsed = JSON.parse(raw)\n return Array.isArray(parsed) ? parsed : []\n } catch {\n return []\n }\n}\n\n/**\n * Save dictionary to localStorage.\n * @param {string[]} words\n */\nfunction persistDictionary(words) {\n try {\n localStorage.setItem(DICTIONARY_STORAGE_KEY, JSON.stringify(words))\n } catch {\n // localStorage unavailable — silently ignore\n }\n}\n\n/**\n * Load persisted ignored words from localStorage.\n * @returns {Set<string>}\n */\nfunction loadPersistedIgnored() {\n try {\n const raw = localStorage.getItem(IGNORED_STORAGE_KEY)\n if (!raw) return new Set()\n const parsed = JSON.parse(raw)\n return new Set(Array.isArray(parsed) ? parsed : [])\n } catch {\n return new Set()\n }\n}\n\n/**\n * Save ignored words to localStorage.\n * @param {Set<string>} words\n */\nfunction persistIgnored(words) {\n try {\n localStorage.setItem(IGNORED_STORAGE_KEY, JSON.stringify([...words]))\n } catch {\n // noop\n }\n}\n\nexport function SpellcheckPlugin(options = {}) {\n const {\n language = 'en-US',\n enabled: enabledOnInit = true,\n grammarRules = true,\n stylePreset = 'formal',\n customService = null,\n dictionary: initialDictionary = [],\n persistent = true,\n onError,\n onCorrection,\n } = options\n\n let engine = null\n let isEnabled = enabledOnInit\n let currentStylePreset = stylePreset\n let currentLanguage = language\n let debounceTimer = null\n let currentErrors = []\n let unsubContentChange = null\n let unsubDestroy = null\n\n // Dictionary: custom words to always accept\n const dictionary = new Set([\n ...initialDictionary,\n ...(persistent ? loadPersistedDictionary() : []),\n ])\n\n // Ignored words: per-session (or persistent) ignore list\n const ignoredWords = persistent ? loadPersistedIgnored() : new Set()\n\n // ---------------------------------------------------------------------------\n // Error overlay management\n // ---------------------------------------------------------------------------\n\n /**\n * Clear all spellcheck/grammar underline marks from the editor.\n */\n function clearMarks() {\n if (!engine) return\n const marks = engine.element.querySelectorAll('.rmx-spelling-error, .rmx-grammar-error, .rmx-style-suggestion')\n for (const mark of marks) {\n const parent = mark.parentNode\n while (mark.firstChild) {\n parent.insertBefore(mark.firstChild, mark)\n }\n parent.removeChild(mark)\n parent.normalize()\n }\n }\n\n /**\n * Apply underline marks for detected errors.\n * @param {Array} errors\n */\n function applyMarks(errors) {\n if (!engine || errors.length === 0) return\n\n const text = engine.getText()\n\n // Walk text nodes and map character offsets to DOM positions\n const walker = document.createTreeWalker(engine.element, NodeFilter.SHOW_TEXT, null)\n const textNodes = []\n let totalOffset = 0\n let node\n while ((node = walker.nextNode())) {\n textNodes.push({ node, start: totalOffset, end: totalOffset + node.textContent.length })\n totalOffset += node.textContent.length\n }\n\n // Apply marks in reverse order to avoid offset shifting\n const sortedErrors = [...errors].sort((a, b) => b.offset - a.offset)\n\n for (const error of sortedErrors) {\n const errorEnd = error.offset + error.length\n\n // Find the text node(s) that contain this error\n let startNode = null, endNode = null\n let startOffset = 0, endOffset = 0\n\n for (const tn of textNodes) {\n if (!startNode && tn.end > error.offset) {\n startNode = tn\n startOffset = error.offset - tn.start\n }\n if (tn.end >= errorEnd) {\n endNode = tn\n endOffset = errorEnd - tn.start\n break\n }\n }\n\n if (!startNode || !endNode) continue\n // Only mark if within a single text node (simplification for reliability)\n if (startNode.node !== endNode.node) continue\n\n try {\n const range = document.createRange()\n range.setStart(startNode.node, startOffset)\n range.setEnd(endNode.node, endOffset)\n\n const className = error.type === 'spelling' ? 'rmx-spelling-error'\n : error.type === 'grammar' ? 'rmx-grammar-error'\n : 'rmx-style-suggestion'\n\n const mark = document.createElement('span')\n mark.className = className\n mark.setAttribute('data-spellcheck-message', error.message)\n mark.setAttribute('data-spellcheck-suggestions', JSON.stringify(error.suggestions || []))\n mark.setAttribute('data-spellcheck-type', error.type)\n mark.setAttribute('title', error.message)\n\n range.surroundContents(mark)\n } catch {\n // surroundContents can fail across element boundaries — skip\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Grammar analysis\n // ---------------------------------------------------------------------------\n\n /**\n * Run grammar analysis on the current editor content.\n * @returns {Promise<Array>}\n */\n async function runCheck() {\n if (!engine || !isEnabled) return []\n\n // Item 13: Use full text but debounce already prevents per-keystroke DOM walks\n const text = engine.getText()\n if (!text.trim()) {\n currentErrors = []\n engine.eventBus.emit('spellcheck:update', { errors: [], stats: summarizeIssues([]) })\n return []\n }\n\n let errors = []\n\n // Built-in grammar rules\n if (grammarRules) {\n const grammarIssues = analyzeGrammar(text, { stylePreset: currentStylePreset })\n errors.push(...grammarIssues)\n }\n\n // Custom service (e.g., LanguageTool, Grammarly)\n if (customService?.check) {\n try {\n const serviceIssues = await customService.check(text)\n if (Array.isArray(serviceIssues)) {\n errors.push(...serviceIssues)\n }\n } catch (err) {\n engine.eventBus.emit('spellcheck:error', { error: err })\n }\n }\n\n // Filter out dictionary and ignored words\n errors = errors.filter(error => {\n const word = text.slice(error.offset, error.offset + error.length).toLowerCase()\n return !dictionary.has(word) && !ignoredWords.has(word)\n })\n\n // Sort by offset\n errors.sort((a, b) => a.offset - b.offset)\n\n currentErrors = errors\n\n // Apply visual marks\n clearMarks()\n applyMarks(errors)\n\n const stats = summarizeIssues(errors)\n engine.eventBus.emit('spellcheck:update', { errors, stats })\n engine.eventBus.emit('grammar:check', { errors, stats })\n onError?.(errors)\n\n return errors\n }\n\n /**\n * Item 13: Debounced check — called on content changes.\n * Uses 500ms debounce to batch rapid keystrokes.\n */\n function debouncedCheck() {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(runCheck, 500)\n }\n\n // ---------------------------------------------------------------------------\n // Dictionary and ignore management\n // ---------------------------------------------------------------------------\n\n /**\n * Add a word to the custom dictionary.\n * @param {string} word\n */\n function addToDictionary(word) {\n if (!word) return\n const lower = word.toLowerCase()\n dictionary.add(lower)\n if (persistent) persistDictionary([...dictionary])\n\n engine?.eventBus.emit('spellcheck:dictionary:add', { word: lower })\n\n // Re-run check to remove this word's errors\n debouncedCheck()\n }\n\n /**\n * Remove a word from the custom dictionary.\n * @param {string} word\n */\n function removeFromDictionary(word) {\n dictionary.delete(word.toLowerCase())\n if (persistent) persistDictionary([...dictionary])\n debouncedCheck()\n }\n\n /**\n * Get all dictionary words.\n * @returns {string[]}\n */\n function getDictionary() {\n return [...dictionary]\n }\n\n /**\n * Ignore a word for this session (or persistently).\n * @param {string} word\n */\n function ignoreWord(word) {\n if (!word) return\n const lower = word.toLowerCase()\n ignoredWords.add(lower)\n if (persistent) persistIgnored(ignoredWords)\n\n engine?.eventBus.emit('spellcheck:ignored:add', { word: lower })\n debouncedCheck()\n }\n\n /**\n * Get all currently ignored words.\n * @returns {string[]}\n */\n function getIgnoredWords() {\n return [...ignoredWords]\n }\n\n // ---------------------------------------------------------------------------\n // Correction\n // ---------------------------------------------------------------------------\n\n /**\n * Apply a correction to a spellcheck mark in the DOM.\n * @param {HTMLElement} mark - The .rmx-spelling-error/.rmx-grammar-error element\n * @param {string} replacement - The text to replace with\n */\n function applyCorrection(mark, replacement) {\n if (!engine || !mark) return\n\n const original = mark.textContent\n engine.history.snapshot()\n\n const textNode = document.createTextNode(replacement)\n mark.parentNode.replaceChild(textNode, mark)\n textNode.parentNode.normalize()\n\n engine.eventBus.emit('content:change')\n engine.eventBus.emit('spellcheck:correction', { original, replacement })\n onCorrection?.({ original, replacement })\n\n // Re-check after correction\n debouncedCheck()\n }\n\n // ---------------------------------------------------------------------------\n // Style preset management\n // ---------------------------------------------------------------------------\n\n /**\n * Set the writing style preset.\n * @param {'formal'|'casual'|'technical'|'academic'} preset\n */\n function setWritingStyle(preset) {\n if (!STYLE_PRESETS[preset]) return\n currentStylePreset = preset\n engine?.eventBus.emit('spellcheck:style:change', { preset })\n debouncedCheck()\n }\n\n /**\n * Get the current writing style preset.\n * @returns {string}\n */\n function getWritingStyle() {\n return currentStylePreset\n }\n\n // ---------------------------------------------------------------------------\n // Language management\n // ---------------------------------------------------------------------------\n\n /**\n * Set the language for spellchecking.\n * @param {string} lang - BCP 47 language tag\n */\n function setLanguage(lang) {\n currentLanguage = lang\n if (engine?.element) {\n engine.element.setAttribute('lang', lang)\n }\n engine?.eventBus.emit('spellcheck:language:change', { language: lang })\n debouncedCheck()\n }\n\n /**\n * Get the current language.\n * @returns {string}\n */\n function getLanguage() {\n return currentLanguage\n }\n\n // ---------------------------------------------------------------------------\n // Stats\n // ---------------------------------------------------------------------------\n\n /**\n * Get current spellcheck statistics.\n * @returns {{ total: number, grammar: number, style: number, byRule: Record<string, number>, enabled: boolean, stylePreset: string, language: string }}\n */\n function getSpellcheckStats() {\n const stats = summarizeIssues(currentErrors)\n return {\n ...stats,\n enabled: isEnabled,\n stylePreset: currentStylePreset,\n language: currentLanguage,\n dictionarySize: dictionary.size,\n ignoredCount: ignoredWords.size,\n }\n }\n\n // ---------------------------------------------------------------------------\n // Context menu handler\n // ---------------------------------------------------------------------------\n\n function handleContextMenu(e) {\n const mark = e.target.closest?.('.rmx-spelling-error, .rmx-grammar-error, .rmx-style-suggestion')\n if (!mark) return\n\n e.preventDefault()\n\n const message = mark.getAttribute('data-spellcheck-message') || ''\n const suggestionsRaw = mark.getAttribute('data-spellcheck-suggestions') || '[]'\n const type = mark.getAttribute('data-spellcheck-type') || 'grammar'\n let suggestions = []\n try { suggestions = JSON.parse(suggestionsRaw) } catch { /* noop */ }\n\n const word = mark.textContent\n const rect = mark.getBoundingClientRect()\n\n engine.eventBus.emit('spellcheck:contextmenu', {\n word,\n message,\n suggestions,\n type,\n rect,\n mark,\n applyCorrection: (replacement) => applyCorrection(mark, replacement),\n ignoreWord: () => ignoreWord(word),\n addToDictionary: () => addToDictionary(word),\n })\n }\n\n // ---------------------------------------------------------------------------\n // Plugin definition\n // ---------------------------------------------------------------------------\n\n return createPlugin({\n name: 'spellcheck',\n requiresFullAccess: true,\n version: '1.0.0',\n description: 'Spelling & grammar checking with inline underlines, writing-style presets, and custom service integration',\n\n commands: [\n {\n name: 'toggleSpellcheck',\n execute(eng) {\n isEnabled = !isEnabled\n if (isEnabled) {\n eng.element.setAttribute('spellcheck', 'true')\n debouncedCheck()\n } else {\n eng.element.setAttribute('spellcheck', 'false')\n clearMarks()\n currentErrors = []\n eng.eventBus.emit('spellcheck:update', { errors: [], stats: summarizeIssues([]) })\n }\n eng.eventBus.emit('spellcheck:toggle', { enabled: isEnabled })\n return isEnabled\n },\n meta: { icon: 'spellcheck', tooltip: 'Toggle Spellcheck' },\n },\n {\n name: 'checkGrammar',\n async execute(eng) {\n return runCheck()\n },\n meta: { icon: 'spellcheck', tooltip: 'Check Grammar' },\n },\n {\n name: 'addToDictionary',\n execute(eng, word) {\n addToDictionary(word)\n },\n meta: { tooltip: 'Add to Dictionary' },\n },\n {\n name: 'ignoreWord',\n execute(eng, word) {\n ignoreWord(word)\n },\n meta: { tooltip: 'Ignore Word' },\n },\n {\n name: 'setWritingStyle',\n execute(eng, preset) {\n setWritingStyle(preset)\n return preset\n },\n meta: { tooltip: 'Set Writing Style' },\n },\n {\n name: 'getSpellcheckStats',\n execute(eng) {\n return getSpellcheckStats()\n },\n meta: { tooltip: 'Get Spellcheck Stats' },\n },\n ],\n\n contextMenuItems: [\n {\n label: 'Check Grammar',\n command: 'checkGrammar',\n when: () => isEnabled,\n },\n ],\n\n init(eng) {\n engine = eng\n\n // Expose the spellcheck API on the engine for external access\n engine._spellcheck = {\n runCheck,\n clearMarks,\n addToDictionary,\n removeFromDictionary,\n getDictionary,\n ignoreWord,\n getIgnoredWords,\n applyCorrection,\n setWritingStyle,\n getWritingStyle,\n setLanguage,\n getLanguage,\n getErrors: () => [...currentErrors],\n getStats: getSpellcheckStats,\n isEnabled: () => isEnabled,\n }\n\n // Set native browser spellcheck\n engine.element.setAttribute('spellcheck', isEnabled ? 'true' : 'false')\n engine.element.setAttribute('lang', currentLanguage)\n\n // Listen for content changes\n unsubContentChange = engine.eventBus.on('content:change', debouncedCheck)\n\n // Context menu for correction suggestions\n engine.element.addEventListener('contextmenu', handleContextMenu)\n\n // Cleanup on engine destroy\n unsubDestroy = engine.eventBus.on('destroy', cleanup)\n\n // Initial check\n if (isEnabled) {\n debouncedCheck()\n }\n },\n\n destroy() {\n cleanup()\n },\n })\n\n function cleanup() {\n clearTimeout(debounceTimer)\n clearMarks()\n unsubContentChange?.()\n unsubDestroy?.()\n engine?.element?.removeEventListener('contextmenu', handleContextMenu)\n engine = null\n }\n}\n","/**\n * CrdtEngine — Lightweight operation-based CRDT with vector clocks.\n *\n * Captures DOM mutations as serializable operations, applies remote operations\n * to the local DOM, resolves conflicts via last-writer-wins (LWW), and\n * maintains an offline queue for replay on reconnect.\n *\n * Position model: character offsets within element.textContent.\n */\n\n// ---------------------------------------------------------------------------\n// Position mapping: char offset ↔ DOM Range\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a character offset + length to a DOM Range.\n * @param {HTMLElement} element - contenteditable root\n * @param {number} offset - start character offset\n * @param {number} length - selection length (0 = caret)\n * @returns {Range|null}\n */\nexport function offsetToRange(element, offset, length = 0) {\n const range = document.createRange()\n const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null)\n let charCount = 0\n let startSet = false\n\n while (walker.nextNode()) {\n const node = walker.currentNode\n const nodeLen = node.textContent.length\n\n if (!startSet && charCount + nodeLen >= offset) {\n range.setStart(node, offset - charCount)\n startSet = true\n if (length === 0) {\n range.setEnd(node, offset - charCount)\n return range\n }\n }\n\n if (startSet && charCount + nodeLen >= offset + length) {\n range.setEnd(node, offset + length - charCount)\n return range\n }\n\n charCount += nodeLen\n }\n\n // Offset beyond content — collapse to end\n if (!startSet) {\n range.selectNodeContents(element)\n range.collapse(false)\n }\n return range\n}\n\n/**\n * Convert a DOM Range to character offset + length.\n * @param {HTMLElement} element - contenteditable root\n * @param {Range} range\n * @returns {{ offset: number, length: number }}\n */\nexport function rangeToOffset(element, range) {\n const preRange = document.createRange()\n preRange.selectNodeContents(element)\n preRange.setEnd(range.startContainer, range.startOffset)\n const offset = preRange.toString().length\n const length = range.toString().length\n return { offset, length }\n}\n\n// ---------------------------------------------------------------------------\n// Operation types\n// ---------------------------------------------------------------------------\n\n/**\n * @typedef {Object} Operation\n * @property {string} id - unique opId (`${userId}-${seqNum}`)\n * @property {'insert'|'delete'|'format'|'replace'} type\n * @property {string} userId\n * @property {Record<string, number>} clock - vector clock snapshot\n * @property {number} timestamp - wall clock for LWW tiebreak\n * @property {number} position - char offset\n * @property {number} [length] - affected length (delete, format, replace)\n * @property {string} [content] - inserted/replaced content (HTML fragment)\n * @property {Object} [format] - format descriptor (format type)\n */\n\n// ---------------------------------------------------------------------------\n// CrdtEngine class\n// ---------------------------------------------------------------------------\n\nexport class CrdtEngine {\n /**\n * @param {string} userId - unique client identifier\n */\n constructor(userId) {\n this.userId = userId\n /** @type {Record<string, number>} */\n this._clock = { [userId]: 0 }\n /** @type {Operation[]} */\n this._pendingOps = []\n /** @type {Set<string>} */\n this._seenOps = new Set()\n /** @type {number} Item 9: Maximum _seenOps size to prevent unbounded growth */\n this._maxSeenOps = 10000\n /** @type {boolean} */\n this._suppressRemote = false\n /** @type {string|null} */\n this._lastTextContent = null\n }\n\n /**\n * Item 9: Add op ID to _seenOps with bounded size.\n * Evicts oldest entries when exceeding max.\n * @param {string} opId\n */\n _trackOp(opId) {\n this._seenOps.add(opId)\n if (this._seenOps.size > this._maxSeenOps) {\n // Evict oldest entries (Set preserves insertion order)\n const excess = this._seenOps.size - this._maxSeenOps\n const iter = this._seenOps.values()\n for (let i = 0; i < excess; i++) {\n this._seenOps.delete(iter.next().value)\n }\n }\n }\n\n // --- Vector Clock ---\n\n /**\n * Increment local clock and return snapshot.\n * @returns {Record<string, number>}\n */\n _tickLocal() {\n this._clock[this.userId] = (this._clock[this.userId] || 0) + 1\n return { ...this._clock }\n }\n\n /**\n * Point-wise max merge of remote clock into local.\n * @param {Record<string, number>} remoteClock\n */\n _merge(remoteClock) {\n for (const [uid, seq] of Object.entries(remoteClock)) {\n this._clock[uid] = Math.max(this._clock[uid] || 0, seq)\n }\n }\n\n // --- Operation Capture ---\n\n /**\n * Capture DOM mutations as operations.\n * Called from MutationObserver callback.\n * @param {MutationRecord[]} mutations\n * @param {HTMLElement} element - contenteditable root\n * @returns {Operation[]}\n */\n captureOperations(mutations, element) {\n if (this._suppressRemote) return []\n\n const currentText = element.textContent\n const previousText = this._lastTextContent\n\n if (previousText === null || currentText === previousText) {\n this._lastTextContent = currentText\n return []\n }\n\n const ops = []\n const clock = this._tickLocal()\n const opId = `${this.userId}-${this._clock[this.userId]}`\n\n // Diff to find insert/delete\n if (currentText.length > previousText.length) {\n // Insert detected\n const { position, content } = this._findInsert(previousText, currentText)\n const op = {\n id: opId,\n type: 'insert',\n userId: this.userId,\n clock,\n timestamp: Date.now(),\n position,\n content,\n }\n ops.push(op)\n this._trackOp(opId)\n } else if (currentText.length < previousText.length) {\n // Delete detected\n const { position, length } = this._findDelete(previousText, currentText)\n const op = {\n id: opId,\n type: 'delete',\n userId: this.userId,\n clock,\n timestamp: Date.now(),\n position,\n length,\n }\n ops.push(op)\n this._trackOp(opId)\n } else {\n // Same length — likely a replace (character substitution)\n const { position, length, content } = this._findReplace(previousText, currentText)\n if (content !== undefined) {\n const op = {\n id: opId,\n type: 'replace',\n userId: this.userId,\n clock,\n timestamp: Date.now(),\n position,\n length,\n content,\n }\n ops.push(op)\n this._trackOp(opId)\n }\n }\n\n this._lastTextContent = currentText\n return ops\n }\n\n /**\n * Find the position and content of an insertion.\n */\n _findInsert(oldText, newText) {\n let start = 0\n while (start < oldText.length && oldText[start] === newText[start]) start++\n const insertedLength = newText.length - oldText.length\n return { position: start, content: newText.substring(start, start + insertedLength) }\n }\n\n /**\n * Find the position and length of a deletion.\n */\n _findDelete(oldText, newText) {\n let start = 0\n while (start < newText.length && oldText[start] === newText[start]) start++\n const deletedLength = oldText.length - newText.length\n return { position: start, length: deletedLength }\n }\n\n /**\n * Find a same-length replacement.\n */\n _findReplace(oldText, newText) {\n let start = 0\n while (start < oldText.length && oldText[start] === newText[start]) start++\n if (start >= oldText.length) return {} // no change\n let end = oldText.length - 1\n let endNew = newText.length - 1\n while (end > start && oldText[end] === newText[endNew]) { end--; endNew-- }\n return {\n position: start,\n length: end - start + 1,\n content: newText.substring(start, endNew + 1),\n }\n }\n\n // --- OT Position Transform ---\n\n /**\n * Transform a position against a prior operation.\n * @param {number} pos\n * @param {Operation} op\n * @returns {number}\n */\n _transformPosition(pos, op) {\n if (op.type === 'insert') {\n if (op.position <= pos) return pos + op.content.length\n } else if (op.type === 'delete') {\n if (op.position + op.length <= pos) return pos - op.length\n if (op.position < pos) return op.position\n } else if (op.type === 'replace') {\n if (op.position + op.length <= pos) return pos + (op.content.length - op.length)\n if (op.position < pos) return op.position + op.content.length\n }\n return pos\n }\n\n // --- Applying Remote Operations ---\n\n /**\n * Apply remote operations to the editor DOM.\n * @param {Operation[]} ops\n * @param {HTMLElement} element - contenteditable root\n */\n applyRemoteOperations(ops, element) {\n // Filter already-seen ops\n const newOps = ops.filter(op => !this._seenOps.has(op.id))\n if (newOps.length === 0) return\n\n // Sort by causal order (clock sum, then timestamp, then userId for determinism)\n newOps.sort((a, b) => {\n const sumA = Object.values(a.clock).reduce((s, v) => s + v, 0)\n const sumB = Object.values(b.clock).reduce((s, v) => s + v, 0)\n if (sumA !== sumB) return sumA - sumB\n if (a.timestamp !== b.timestamp) return a.timestamp - b.timestamp\n return a.userId.localeCompare(b.userId)\n })\n\n this._suppressRemote = true\n\n for (const op of newOps) {\n this._trackOp(op.id)\n this._applyOp(op, element)\n }\n\n this._lastTextContent = element.textContent\n this._suppressRemote = false\n }\n\n /**\n * Apply a single operation to the DOM.\n */\n _applyOp(op, element) {\n try {\n if (op.type === 'insert') {\n const range = offsetToRange(element, op.position)\n if (range) {\n range.collapse(true)\n range.insertNode(document.createTextNode(op.content))\n element.normalize()\n }\n } else if (op.type === 'delete') {\n const range = offsetToRange(element, op.position, op.length)\n if (range) {\n range.deleteContents()\n element.normalize()\n }\n } else if (op.type === 'replace') {\n const range = offsetToRange(element, op.position, op.length)\n if (range) {\n range.deleteContents()\n range.insertNode(document.createTextNode(op.content))\n element.normalize()\n }\n }\n } catch (e) {\n // Silently handle position out-of-bounds (can happen with concurrent edits)\n console.warn('[CrdtEngine] Failed to apply remote op:', e.message)\n }\n }\n\n // --- Offline Queue ---\n\n /**\n * Queue an operation for later transmission.\n * @param {Operation} op\n */\n queueOperation(op) {\n this._pendingOps.push(op)\n }\n\n /**\n * Flush and return all pending operations.\n * @returns {Operation[]}\n */\n flushQueue() {\n const ops = [...this._pendingOps]\n this._pendingOps = []\n return ops\n }\n\n /**\n * @returns {boolean}\n */\n hasPendingOps() {\n return this._pendingOps.length > 0\n }\n\n // --- State ---\n\n /**\n * Initialize text content tracking.\n * @param {HTMLElement} element\n */\n initTextContent(element) {\n this._lastTextContent = element.textContent\n }\n\n /**\n * Get current state for debugging/sync.\n * @returns {{ clock: Record<string, number>, pendingOps: number, seenOps: number }}\n */\n getState() {\n return {\n clock: { ...this._clock },\n pendingOps: this._pendingOps.length,\n seenOps: this._seenOps.size,\n }\n }\n\n destroy() {\n this._pendingOps = []\n this._seenOps.clear()\n this._lastTextContent = null\n }\n}\n","/**\n * AwarenessProtocol — Cursor and presence tracking for collaborative editing.\n *\n * Tracks the local user's cursor position (as character offset), broadcasts\n * it periodically, renders remote users' cursors as colored overlays, and\n * manages presence state (active/idle/offline).\n */\n\nimport { rangeToOffset, offsetToRange } from './CrdtEngine.js'\n\n/**\n * @typedef {Object} AwarenessState\n * @property {{ offset: number, length: number }|null} cursor\n * @property {string} userName\n * @property {string} userColor\n * @property {'active'|'idle'|'offline'} status\n * @property {number} lastActive\n */\n\nconst IDLE_TIMEOUT = 30000 // 30 seconds\n\nexport class AwarenessProtocol {\n /**\n * @param {string} userId\n * @param {string} userName\n * @param {string} userColor\n */\n constructor(userId, userName, userColor) {\n this.userId = userId\n\n /** @type {AwarenessState} */\n this._localState = {\n cursor: null,\n userName,\n userColor,\n status: 'active',\n lastActive: Date.now(),\n }\n\n /** @type {Map<string, AwarenessState>} */\n this._remoteStates = new Map()\n\n /** @type {number|null} */\n this._broadcastInterval = null\n\n /** @type {number|null} */\n this._idleTimer = null\n\n /** @type {HTMLElement|null} */\n this._cursorsContainer = null\n\n /** @type {number|null} */\n this._renderRafId = null\n }\n\n // --- Local Cursor ---\n\n /**\n * Update local cursor position from current browser selection.\n * @param {HTMLElement} element - contenteditable root\n * @param {Object} selection - engine.selection instance\n */\n updateLocalCursor(element, selection) {\n try {\n const sel = window.getSelection()\n if (!sel || sel.rangeCount === 0 || !element.contains(sel.anchorNode)) {\n this._localState.cursor = null\n return\n }\n const range = sel.getRangeAt(0)\n this._localState.cursor = rangeToOffset(element, range)\n this._localState.lastActive = Date.now()\n } catch {\n this._localState.cursor = null\n }\n }\n\n // --- Presence ---\n\n /**\n * Set the local user's status.\n * @param {'active'|'idle'|'offline'} status\n */\n setStatus(status) {\n this._localState.status = status\n }\n\n /**\n * Reset the idle timer (called on user input).\n */\n resetIdleTimer() {\n this._localState.status = 'active'\n this._localState.lastActive = Date.now()\n clearTimeout(this._idleTimer)\n this._idleTimer = setTimeout(() => {\n this._localState.status = 'idle'\n }, IDLE_TIMEOUT)\n }\n\n // --- Remote State ---\n\n /**\n * Apply awareness state from a remote user.\n * @param {string} userId\n * @param {AwarenessState} state\n */\n applyRemoteAwareness(userId, state) {\n if (userId === this.userId) return\n this._remoteStates.set(userId, { ...state })\n }\n\n /**\n * Remove a user (on leave/disconnect).\n * @param {string} userId\n */\n removeUser(userId) {\n this._remoteStates.delete(userId)\n }\n\n /**\n * Get list of all remote collaborators.\n * @returns {Array<{ userId: string, userName: string, userColor: string, cursor: Object|null, status: string }>}\n */\n getCollaborators() {\n return Array.from(this._remoteStates.entries()).map(([userId, state]) => ({\n userId,\n userName: state.userName,\n userColor: state.userColor,\n cursor: state.cursor,\n status: state.status,\n }))\n }\n\n // --- Remote Cursor Rendering ---\n\n /**\n * Render remote cursors as absolutely-positioned overlays.\n * Throttled to requestAnimationFrame.\n * @param {HTMLElement} element - contenteditable root\n */\n renderRemoteCursors(element) {\n if (this._renderRafId) cancelAnimationFrame(this._renderRafId)\n this._renderRafId = requestAnimationFrame(() => {\n this._doRender(element)\n })\n }\n\n /**\n * @private\n */\n _doRender(element) {\n // Ensure container exists\n if (!this._cursorsContainer) {\n this._cursorsContainer = document.createElement('div')\n this._cursorsContainer.className = 'rmx-collab-cursors-container'\n this._cursorsContainer.setAttribute('aria-hidden', 'true')\n element.parentNode?.insertBefore(this._cursorsContainer, element.nextSibling)\n }\n\n // Clear existing cursor elements\n this._cursorsContainer.innerHTML = ''\n\n const editorRect = element.getBoundingClientRect()\n let count = 0\n\n for (const [userId, state] of this._remoteStates) {\n if (!state.cursor || state.status === 'offline') continue\n if (count >= 20) break // max 20 cursors for performance\n count++\n\n try {\n const range = offsetToRange(element, state.cursor.offset, state.cursor.length)\n if (!range) continue\n\n const rects = range.getClientRects()\n if (rects.length === 0) continue\n\n // Caret line (at the start of the range)\n const startRect = rects[0]\n const caret = document.createElement('div')\n caret.className = 'rmx-collab-cursor'\n caret.style.cssText = `\n left: ${startRect.left - editorRect.left}px;\n top: ${startRect.top - editorRect.top}px;\n height: ${startRect.height}px;\n background-color: ${state.userColor};\n `\n\n // Name label\n const label = document.createElement('span')\n label.className = 'rmx-collab-cursor-label'\n label.textContent = state.userName\n label.style.backgroundColor = state.userColor\n caret.appendChild(label)\n\n this._cursorsContainer.appendChild(caret)\n\n // Selection highlight (if range has length)\n if (state.cursor.length > 0) {\n for (const rect of rects) {\n const highlight = document.createElement('div')\n highlight.className = 'rmx-collab-selection'\n highlight.style.cssText = `\n left: ${rect.left - editorRect.left}px;\n top: ${rect.top - editorRect.top}px;\n width: ${rect.width}px;\n height: ${rect.height}px;\n background-color: ${state.userColor};\n `\n this._cursorsContainer.appendChild(highlight)\n }\n }\n } catch {\n // Skip cursors with invalid positions\n }\n }\n }\n\n /**\n * Remove all remote cursor overlays.\n * @param {HTMLElement} element\n */\n clearRemoteCursors(element) {\n if (this._cursorsContainer) {\n this._cursorsContainer.innerHTML = ''\n }\n }\n\n // --- Broadcasting ---\n\n /**\n * Get the local awareness state for transmission.\n * @returns {AwarenessState}\n */\n getLocalState() {\n return { ...this._localState }\n }\n\n /**\n * Start periodic awareness broadcasting.\n * @param {function(AwarenessState): void} sendFn - called with state to broadcast\n * @param {number} [intervalMs=1000]\n */\n startBroadcasting(sendFn, intervalMs = 1000) {\n this.stopBroadcasting()\n this._broadcastInterval = setInterval(() => {\n sendFn(this.getLocalState())\n }, intervalMs)\n }\n\n /**\n * Stop periodic broadcasting.\n */\n stopBroadcasting() {\n if (this._broadcastInterval) {\n clearInterval(this._broadcastInterval)\n this._broadcastInterval = null\n }\n }\n\n /**\n * Clean up all resources.\n */\n destroy() {\n this.stopBroadcasting()\n clearTimeout(this._idleTimer)\n if (this._renderRafId) cancelAnimationFrame(this._renderRafId)\n if (this._cursorsContainer) {\n this._cursorsContainer.remove()\n this._cursorsContainer = null\n }\n this._remoteStates.clear()\n }\n}\n","/**\n * WebSocketTransport — Built-in WebSocket transport with auto-reconnect.\n *\n * Implements the Transport interface:\n * connect(), disconnect(), send(msg), onMessage(cb),\n * onConnect(cb), onDisconnect(cb), isConnected()\n *\n * @param {string} url - WebSocket URL (wss://...)\n * @param {Object} [options]\n * @param {boolean} [options.reconnect=true] - auto-reconnect on disconnect\n * @param {number} [options.reconnectInterval=2000] - base interval (ms)\n * @param {number} [options.maxReconnectAttempts=10] - max retries\n * @param {number} [options.maxReconnectDelay=30000] - max backoff delay (ms)\n */\nexport class WebSocketTransport {\n constructor(url, options = {}) {\n this._url = url\n this._reconnect = options.reconnect !== false\n this._reconnectInterval = options.reconnectInterval || 2000\n this._maxAttempts = options.maxReconnectAttempts || 10\n this._maxDelay = options.maxReconnectDelay || 30000\n\n /** @type {WebSocket|null} */\n this._ws = null\n this._messageHandler = null\n this._connectHandler = null\n this._disconnectHandler = null\n this._reconnectTimer = null\n this._reconnectAttempts = 0\n this._intentionalClose = false\n }\n\n /**\n * Open a WebSocket connection.\n */\n connect() {\n if (this._ws && this._ws.readyState === WebSocket.OPEN) return\n\n this._intentionalClose = false\n this._reconnectAttempts = 0\n\n try {\n this._ws = new WebSocket(this._url)\n } catch (e) {\n console.warn('[WebSocketTransport] Failed to create WebSocket:', e.message)\n this._scheduleReconnect()\n return\n }\n\n this._ws.onopen = () => {\n this._reconnectAttempts = 0\n this._connectHandler?.()\n }\n\n this._ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(event.data)\n this._messageHandler?.(msg)\n } catch (e) {\n console.warn('[WebSocketTransport] Invalid message:', e.message)\n }\n }\n\n this._ws.onclose = () => {\n this._disconnectHandler?.()\n if (!this._intentionalClose && this._reconnect) {\n this._scheduleReconnect()\n }\n }\n\n this._ws.onerror = () => {\n // onclose will fire after onerror\n }\n }\n\n /**\n * Close the connection (no auto-reconnect).\n */\n disconnect() {\n this._intentionalClose = true\n clearTimeout(this._reconnectTimer)\n this._reconnectTimer = null\n if (this._ws) {\n this._ws.onclose = null\n this._ws.onerror = null\n this._ws.onmessage = null\n this._ws.onopen = null\n if (this._ws.readyState === WebSocket.OPEN || this._ws.readyState === WebSocket.CONNECTING) {\n this._ws.close()\n }\n this._ws = null\n }\n }\n\n /**\n * Send a JSON message. Silently drops if disconnected (CrdtEngine queues ops).\n * @param {Object} msg\n */\n send(msg) {\n if (this._ws && this._ws.readyState === WebSocket.OPEN) {\n this._ws.send(JSON.stringify(msg))\n }\n }\n\n /**\n * Register message handler.\n * @param {function(Object): void} cb\n */\n onMessage(cb) {\n this._messageHandler = cb\n }\n\n /**\n * Register connect handler.\n * @param {function(): void} cb\n */\n onConnect(cb) {\n this._connectHandler = cb\n }\n\n /**\n * Register disconnect handler.\n * @param {function(): void} cb\n */\n onDisconnect(cb) {\n this._disconnectHandler = cb\n }\n\n /**\n * @returns {boolean}\n */\n isConnected() {\n return this._ws?.readyState === WebSocket.OPEN\n }\n\n /**\n * Schedule a reconnect with exponential backoff.\n * @private\n */\n _scheduleReconnect() {\n if (this._reconnectAttempts >= this._maxAttempts) {\n console.warn(`[WebSocketTransport] Max reconnect attempts (${this._maxAttempts}) reached`)\n return\n }\n\n const delay = Math.min(\n this._reconnectInterval * Math.pow(2, this._reconnectAttempts),\n this._maxDelay\n )\n this._reconnectAttempts++\n\n this._reconnectTimer = setTimeout(() => {\n this.connect()\n }, delay)\n }\n\n /**\n * Clean up all resources.\n */\n destroy() {\n this.disconnect()\n this._messageHandler = null\n this._connectHandler = null\n this._disconnectHandler = null\n }\n}\n","/**\n * CollaborationPlugin — Real-time collaborative editing.\n *\n * CRDT-inspired conflict resolution with vector clocks, live cursors via the\n * awareness protocol, and configurable transport (built-in WebSocket or custom).\n *\n * @param {Object} [options]\n * @param {string} [options.signalingServer] - WebSocket URL for built-in transport\n * @param {Object} [options.transport] - Custom transport: { connect, disconnect, send, onMessage, onConnect, onDisconnect, isConnected }\n * @param {string} [options.userId] - Unique user ID\n * @param {string} [options.userName='Anonymous'] - Display name\n * @param {string} [options.userColor='#6366f1'] - Cursor/avatar color\n * @param {string} [options.roomId='default'] - Document/room identifier\n * @param {boolean} [options.autoConnect=true] - Connect on plugin init\n * @param {number} [options.broadcastInterval=1000] - Awareness broadcast interval (ms)\n * @param {Function} [options.onUserJoin] - ({ userId, userName }) => void\n * @param {Function} [options.onUserLeave] - ({ userId }) => void\n * @param {Function} [options.onSync] - ({ userId, operations }) => void\n * @param {Function} [options.onConflict] - ({ localOp, remoteOp, resolution }) => void\n */\n\nimport { createPlugin } from '../../createPlugin.js'\nimport { CrdtEngine } from './CrdtEngine.js'\nimport { AwarenessProtocol } from './AwarenessProtocol.js'\nimport { WebSocketTransport } from './transports/WebSocketTransport.js'\n\nexport function CollaborationPlugin(options = {}) {\n const {\n signalingServer,\n transport: customTransport,\n userId = `user-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,\n userName = 'Anonymous',\n userColor = '#6366f1',\n roomId = 'default',\n autoConnect = true,\n broadcastInterval = 1000,\n onUserJoin,\n onUserLeave,\n onSync,\n onConflict,\n } = options\n\n let engine = null\n let transport = null\n let crdtEngine = null\n let awareness = null\n let mutationObserver = null\n let unsubs = []\n let connected = false\n\n // -----------------------------------------------------------------------\n // Message handling\n // -----------------------------------------------------------------------\n\n function handleMessage(msg) {\n if (!engine) return\n\n switch (msg.type) {\n case 'op': {\n if (msg.userId === userId) return // ignore own echoed ops\n // Save selection, apply remote ops, restore selection\n const sel = window.getSelection()\n let bookmark = null\n if (sel && sel.rangeCount > 0 && engine.element.contains(sel.anchorNode)) {\n try {\n const range = sel.getRangeAt(0)\n bookmark = { startOffset: range.startOffset, startContainer: range.startContainer }\n } catch { /* noop */ }\n }\n\n engine._isRemoteOperation = true\n crdtEngine.applyRemoteOperations(msg.operations, engine.element)\n crdtEngine._merge(msg.clock)\n engine._isRemoteOperation = false\n\n // Restore selection\n if (bookmark) {\n try {\n const range = document.createRange()\n range.setStart(bookmark.startContainer, Math.min(bookmark.startOffset, bookmark.startContainer.length || 0))\n range.collapse(true)\n sel.removeAllRanges()\n sel.addRange(range)\n } catch { /* noop */ }\n }\n\n engine.eventBus.emit('collab:sync', { userId: msg.userId, opCount: msg.operations.length })\n engine.eventBus.emit('content:change')\n onSync?.({ userId: msg.userId, operations: msg.operations })\n break\n }\n\n case 'awareness':\n if (msg.userId === userId) return\n awareness.applyRemoteAwareness(msg.userId, msg.state)\n awareness.renderRemoteCursors(engine.element)\n break\n\n case 'join':\n if (msg.userId === userId) return\n awareness.applyRemoteAwareness(msg.userId, {\n userName: msg.userName,\n userColor: msg.userColor,\n status: 'active',\n cursor: null,\n lastActive: Date.now(),\n })\n engine.eventBus.emit('collab:userJoin', { userId: msg.userId, userName: msg.userName })\n onUserJoin?.({ userId: msg.userId, userName: msg.userName })\n break\n\n case 'leave':\n if (msg.userId === userId) return\n awareness.removeUser(msg.userId)\n awareness.renderRemoteCursors(engine.element)\n engine.eventBus.emit('collab:userLeave', { userId: msg.userId })\n onUserLeave?.({ userId: msg.userId })\n break\n\n case 'sync-request':\n if (msg.userId === userId) return\n // Respond with full document state\n transport?.send({\n type: 'sync-response',\n roomId,\n userId,\n html: engine.getHTML(),\n clock: crdtEngine._clock,\n })\n break\n\n case 'sync-response': {\n if (msg.userId === userId) return\n // Apply if remote clock is ahead\n const shouldApply = Object.entries(msg.clock).some(\n ([uid, seq]) => (crdtEngine._clock[uid] || 0) < seq\n )\n if (shouldApply) {\n engine._isRemoteOperation = true\n engine.setHTML(msg.html)\n crdtEngine._merge(msg.clock)\n crdtEngine.initTextContent(engine.element)\n engine._isRemoteOperation = false\n engine.eventBus.emit('content:change')\n }\n break\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Connection management\n // -----------------------------------------------------------------------\n\n function startCollaboration() {\n if (!transport || connected) return\n\n transport.onMessage(handleMessage)\n\n transport.onConnect(() => {\n connected = true\n // Send join\n transport.send({ type: 'join', roomId, userId, userName, userColor })\n // Request sync\n transport.send({ type: 'sync-request', roomId, userId, clock: crdtEngine._clock })\n // Flush offline queue\n const pending = crdtEngine.flushQueue()\n if (pending.length > 0) {\n transport.send({\n type: 'op', roomId, userId,\n clock: crdtEngine._clock,\n operations: pending,\n timestamp: Date.now(),\n })\n }\n // Start awareness\n awareness.setStatus('active')\n awareness.startBroadcasting((state) => {\n transport.send({ type: 'awareness', roomId, userId, state })\n }, broadcastInterval)\n\n engine?.eventBus.emit('collab:connected')\n })\n\n transport.onDisconnect(() => {\n connected = false\n awareness.setStatus('offline')\n awareness.stopBroadcasting()\n if (engine) {\n awareness.clearRemoteCursors(engine.element)\n engine.eventBus.emit('collab:disconnected')\n }\n })\n\n transport.connect()\n }\n\n function stopCollaboration() {\n if (!transport) return\n\n if (connected) {\n transport.send({ type: 'leave', roomId, userId })\n }\n awareness.stopBroadcasting()\n if (engine) {\n awareness.clearRemoteCursors(engine.element)\n }\n transport.disconnect()\n connected = false\n }\n\n function getConnectionStatus() {\n if (!transport) return 'unconfigured'\n return transport.isConnected() ? 'connected' : 'disconnected'\n }\n\n function setUserInfo(info) {\n if (info.userName) awareness._localState.userName = info.userName\n if (info.userColor) awareness._localState.userColor = info.userColor\n }\n\n // -----------------------------------------------------------------------\n // Plugin definition\n // -----------------------------------------------------------------------\n\n return createPlugin({\n name: 'collaboration',\n version: '1.0.0',\n description: 'Real-time collaborative editing with CRDT-inspired conflict resolution, live cursors, and configurable transport',\n requiresFullAccess: true,\n\n commands: [\n {\n name: 'startCollaboration',\n execute() { startCollaboration() },\n meta: { icon: 'collaboration', tooltip: 'Start Collaboration' },\n },\n {\n name: 'stopCollaboration',\n execute() { stopCollaboration() },\n meta: { icon: 'collaboration', tooltip: 'Stop Collaboration' },\n },\n {\n name: 'getCollaborators',\n execute() { return awareness?.getCollaborators() ?? [] },\n meta: { tooltip: 'Get Collaborators' },\n },\n {\n name: 'setUserInfo',\n execute(_eng, info) { setUserInfo(info) },\n meta: { tooltip: 'Set User Info' },\n },\n ],\n\n init(eng) {\n engine = eng\n\n // Create subsystems\n crdtEngine = new CrdtEngine(userId)\n awareness = new AwarenessProtocol(userId, userName, userColor)\n\n // Create transport\n if (customTransport) {\n transport = customTransport\n } else if (signalingServer) {\n transport = new WebSocketTransport(signalingServer)\n }\n\n // Initialize text tracking\n crdtEngine.initTextContent(engine.element)\n\n // Expose API on engine (like CommentsPlugin does with engine._comments)\n engine._collaboration = {\n startCollaboration,\n stopCollaboration,\n getCollaborators: () => awareness.getCollaborators(),\n isConnected: () => transport?.isConnected() ?? false,\n getConnectionStatus,\n setUserInfo,\n getCrdtState: () => crdtEngine.getState(),\n userId,\n userName,\n userColor,\n }\n\n // MutationObserver for local edit capture\n mutationObserver = new MutationObserver((mutations) => {\n if (crdtEngine._suppressRemote || engine._isRemoteOperation) return\n const ops = crdtEngine.captureOperations(mutations, engine.element)\n if (ops.length > 0) {\n if (transport?.isConnected()) {\n transport.send({\n type: 'op',\n roomId,\n userId,\n clock: { ...crdtEngine._clock },\n operations: ops,\n timestamp: Date.now(),\n })\n } else {\n // Queue for later\n ops.forEach(op => crdtEngine.queueOperation(op))\n }\n }\n })\n mutationObserver.observe(engine.element, {\n childList: true,\n subtree: true,\n characterData: true,\n characterDataOldValue: true,\n })\n\n // Track local selection for awareness\n const unsubSelection = engine.eventBus.on('selection:change', () => {\n awareness.updateLocalCursor(engine.element, engine.selection)\n awareness.resetIdleTimer()\n })\n unsubs.push(unsubSelection)\n\n // Re-render remote cursors on content change and scroll\n const unsubContent = engine.eventBus.on('content:change', () => {\n awareness.renderRemoteCursors(engine.element)\n })\n unsubs.push(unsubContent)\n\n // Auto-connect\n if (autoConnect && transport) {\n startCollaboration()\n }\n },\n\n destroy() {\n stopCollaboration()\n mutationObserver?.disconnect()\n mutationObserver = null\n for (const unsub of unsubs) unsub?.()\n unsubs = []\n crdtEngine?.destroy()\n awareness?.destroy()\n if (transport && !customTransport) {\n transport.destroy?.()\n }\n engine = null\n transport = null\n crdtEngine = null\n awareness = null\n },\n })\n}\n","/**\n * Optional Web Worker pool for offloading expensive operations.\n * Falls back to synchronous execution when workers are unavailable.\n *\n * @example\n * const pool = new WorkerPool({ maxWorkers: 4 })\n * const html = await pool.execute('sanitize', dirtyHtml)\n * pool.destroy()\n */\nexport class WorkerPool {\n /**\n * @param {Object} [options]\n * @param {number} [options.maxWorkers] - Maximum number of workers (defaults to hardwareConcurrency or 2)\n * @param {boolean} [options.enabled] - Set false to force synchronous fallback\n * @param {string} [options.workerURL] - URL to the worker script (defaults to editorWorker.js)\n */\n constructor(options = {}) {\n this._maxWorkers = options.maxWorkers || (typeof navigator !== 'undefined' ? navigator.hardwareConcurrency : 2) || 2\n this._workers = []\n this._queue = []\n this._nextId = 1\n this._pending = new Map() // id -> { resolve, reject }\n this._roundRobin = 0\n this._enabled = typeof Worker !== 'undefined' && options.enabled !== false\n this._workerURL = options.workerURL || null\n }\n\n /**\n * Execute a task in a worker (or synchronously if workers are unavailable).\n *\n * @param {string} taskType - One of: 'sanitize', 'markdown', 'convert'\n * @param {*} data - The data to send to the worker\n * @returns {Promise<*>} The result from the worker\n */\n async execute(taskType, data) {\n if (!this._enabled) {\n return this._executeFallback(taskType, data)\n }\n\n // Lazily spawn workers\n if (this._workers.length < this._maxWorkers) {\n this._spawnWorker()\n }\n\n const id = this._nextId++\n const worker = this._workers[this._roundRobin % this._workers.length]\n this._roundRobin++\n\n return new Promise((resolve, reject) => {\n this._pending.set(id, { resolve, reject })\n worker.postMessage({ id, type: taskType, data })\n })\n }\n\n /**\n * Synchronous fallback for environments without Worker support.\n *\n * @private\n * @param {string} taskType\n * @param {*} data\n * @returns {*}\n */\n _executeFallback(taskType, data) {\n switch (taskType) {\n case 'sanitize':\n // Minimal strip for fallback — main-thread sanitizer handles the rest\n return data\n .replace(/<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi, '')\n .replace(/\\s+on\\w+\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)/gi, '')\n case 'markdown':\n // Return as-is; caller should use markdownToHtml from utils\n return data\n case 'convert':\n return { format: data.format, html: null, fallback: true }\n default:\n throw new Error(`Unknown task type: ${taskType}`)\n }\n }\n\n /**\n * Spawn a new worker and wire up the message handler.\n *\n * @private\n */\n _spawnWorker() {\n try {\n const workerURL = this._workerURL || new URL('./editorWorker.js', import.meta.url).href\n const worker = new Worker(workerURL, { type: 'module' })\n\n worker.onmessage = (e) => {\n const { id, result, error } = e.data\n const handler = this._pending.get(id)\n if (handler) {\n this._pending.delete(id)\n if (error) {\n handler.reject(new Error(error))\n } else {\n handler.resolve(result)\n }\n }\n }\n\n worker.onerror = (err) => {\n console.error('[WorkerPool] Worker error:', err)\n }\n\n this._workers.push(worker)\n } catch (err) {\n // Worker creation failed — disable pool and use fallback\n console.warn('[WorkerPool] Failed to spawn worker, falling back to sync:', err.message)\n this._enabled = false\n }\n }\n\n /**\n * Returns whether the pool is using real workers.\n * @returns {boolean}\n */\n get isEnabled() {\n return this._enabled\n }\n\n /**\n * Returns the number of currently active workers.\n * @returns {number}\n */\n get workerCount() {\n return this._workers.length\n }\n\n /**\n * Terminates all workers and rejects any pending tasks.\n */\n destroy() {\n this._workers.forEach(w => w.terminate())\n this._workers = []\n\n // Reject any pending tasks\n this._pending.forEach(({ reject }) => {\n reject(new Error('WorkerPool destroyed'))\n })\n this._pending.clear()\n this._queue = []\n }\n}\n","/**\n * Virtualized rendering for long documents.\n *\n * Uses IntersectionObserver to track which block-level elements are visible.\n * For documents exceeding a configurable threshold of block elements,\n * off-screen blocks are collapsed to lightweight placeholder divs with\n * fixed height, reducing DOM complexity and improving scroll performance.\n *\n * Content is restored when blocks scroll back into view.\n *\n * Opt-in via `engine.options.virtualizeThreshold` (default: 200, set 0 to disable).\n *\n * @example\n * const scroller = new VirtualScroller(editorElement, { threshold: 200 })\n * scroller.init()\n * // ... later\n * scroller.destroy()\n */\n\n/** Block-level tag names that are eligible for virtualization */\nconst BLOCK_TAGS = new Set([\n 'P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',\n 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI', 'TABLE',\n 'FIGURE', 'SECTION', 'ARTICLE', 'ASIDE', 'HR',\n 'DL', 'DD', 'DT', 'DETAILS', 'SUMMARY',\n])\n\nexport class VirtualScroller {\n /**\n * @param {HTMLElement} container - The editor content element\n * @param {Object} [options]\n * @param {number} [options.threshold=200] - Minimum block count to activate virtualization\n * @param {string} [options.rootMargin='500px'] - IntersectionObserver root margin for pre-loading\n */\n constructor(container, options = {}) {\n this._container = container\n this._threshold = options.threshold ?? 200\n this._rootMargin = options.rootMargin || '500px'\n this._observer = null\n this._active = false\n /** @private Map<HTMLElement, { html: string, height: number }> */\n this._collapsed = new Map()\n /** @private Set<HTMLElement> placeholder elements currently in DOM */\n this._placeholders = new Set()\n }\n\n /**\n * Initialize the virtual scroller. Sets up the IntersectionObserver\n * and performs an initial scan of block elements.\n *\n * @returns {void}\n */\n init() {\n if (this._threshold === 0) return\n if (typeof IntersectionObserver === 'undefined') return\n\n this._observer = new IntersectionObserver(\n (entries) => this._handleIntersection(entries),\n {\n root: null, // viewport\n rootMargin: this._rootMargin,\n threshold: 0,\n }\n )\n\n this._scan()\n }\n\n /**\n * Scan the container for block-level elements and begin observing\n * them if the count exceeds the threshold.\n *\n * @private\n * @returns {void}\n */\n _scan() {\n const blocks = this._getBlockElements()\n\n if (blocks.length < this._threshold) {\n this._active = false\n return\n }\n\n this._active = true\n blocks.forEach((block) => {\n this._observer.observe(block)\n })\n }\n\n /**\n * Returns all direct and nested block-level children of the container.\n *\n * @private\n * @returns {HTMLElement[]}\n */\n _getBlockElements() {\n const blocks = []\n const children = this._container.children\n for (let i = 0; i < children.length; i++) {\n const child = children[i]\n if (child.nodeType === 1 && BLOCK_TAGS.has(child.tagName)) {\n blocks.push(child)\n }\n }\n return blocks\n }\n\n /**\n * Handle IntersectionObserver callbacks. Collapse blocks that leave\n * the viewport and restore blocks that enter it.\n *\n * @private\n * @param {IntersectionObserverEntry[]} entries\n */\n _handleIntersection(entries) {\n for (const entry of entries) {\n const el = entry.target\n\n if (entry.isIntersecting) {\n // Restore collapsed block\n this._restore(el)\n } else {\n // Collapse off-screen block\n this._collapse(el)\n }\n }\n }\n\n /**\n * Collapse a block element to a placeholder div with fixed height.\n * Stores the original HTML and measured height for later restoration.\n *\n * @private\n * @param {HTMLElement} el\n */\n _collapse(el) {\n // Don't collapse elements that are already placeholders\n if (this._placeholders.has(el)) return\n // Don't collapse if already tracked as collapsed\n if (this._collapsed.has(el)) return\n\n const rect = el.getBoundingClientRect()\n const height = rect.height\n if (height === 0) return // skip zero-height elements\n\n const html = el.innerHTML\n const tagName = el.tagName\n\n // Store original content\n this._collapsed.set(el, { html, height, tagName })\n\n // Replace content with empty placeholder maintaining height\n el.innerHTML = ''\n el.style.minHeight = `${height}px`\n el.setAttribute('data-virtualized', 'true')\n this._placeholders.add(el)\n }\n\n /**\n * Restore a collapsed block element to its original content.\n *\n * @private\n * @param {HTMLElement} el\n */\n _restore(el) {\n const data = this._collapsed.get(el)\n if (!data) return\n\n el.innerHTML = data.html\n el.style.minHeight = ''\n el.removeAttribute('data-virtualized')\n this._collapsed.delete(el)\n this._placeholders.delete(el)\n }\n\n /**\n * Force-restore all collapsed elements. Call before operations that\n * need to read/write the full document content (e.g., getHTML, save).\n *\n * @returns {void}\n */\n restoreAll() {\n this._collapsed.forEach((data, el) => {\n this._restore(el)\n })\n }\n\n /**\n * Rescan the container for new/removed blocks. Call after major\n * content changes (paste, import, etc.).\n *\n * @returns {void}\n */\n refresh() {\n if (!this._observer) return\n\n // Unobserve everything\n this._observer.disconnect()\n\n // Restore all collapsed blocks first\n this.restoreAll()\n\n // Re-scan\n this._scan()\n }\n\n /**\n * Whether virtualization is currently active (block count exceeds threshold).\n * @returns {boolean}\n */\n get isActive() {\n return this._active\n }\n\n /**\n * Number of currently collapsed (off-screen) blocks.\n * @returns {number}\n */\n get collapsedCount() {\n return this._collapsed.size\n }\n\n /**\n * Destroy the virtual scroller and restore all collapsed content.\n *\n * @returns {void}\n */\n destroy() {\n this.restoreAll()\n\n if (this._observer) {\n this._observer.disconnect()\n this._observer = null\n }\n\n this._collapsed.clear()\n this._placeholders.clear()\n this._active = false\n }\n}\n","import { htmlToMarkdown } from './markdownConverter.js'\nimport { Sanitizer } from '../core/Sanitizer.js'\n\n// Module-level singleton to avoid creating a new Sanitizer on every export call\nconst _sanitizer = new Sanitizer()\n\nfunction triggerDownload(blob, filename) {\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n URL.revokeObjectURL(url)\n}\n\nexport function exportAsMarkdown(html, filename = 'document.md') {\n const md = htmlToMarkdown(html)\n const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' })\n triggerDownload(blob, filename)\n}\n\nexport function exportAsPDF(html, title = 'Document') {\n const iframe = document.createElement('iframe')\n iframe.style.cssText = 'position:fixed;left:-9999px;top:-9999px;width:0;height:0;'\n // Sandbox the iframe but allow scripts (needed for window.print()) and same-origin access\n iframe.setAttribute('sandbox', 'allow-same-origin allow-modals')\n document.body.appendChild(iframe)\n\n // Re-sanitize HTML and escape title to prevent XSS in export iframe\n const safeHtml = _sanitizer.sanitize(html)\n const safeTitle = title.replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\n\n // Build iframe content via srcdoc (CSP-compatible, no document.write)\n const htmlContent = `<!DOCTYPE html>\n<html>\n<head>\n <title>${safeTitle}</title>\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #1a1a1a; max-width: 800px; margin: 0 auto; padding: 20px; }\n h1, h2, h3, h4, h5, h6 { margin-top: 1.2em; margin-bottom: 0.4em; }\n img { max-width: 100%; height: auto; }\n table { border-collapse: collapse; width: 100%; }\n th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }\n pre { background: #f5f5f5; padding: 12px; border-radius: 4px; overflow-x: auto; }\n code { font-family: 'SFMono-Regular', Consolas, monospace; font-size: 0.9em; }\n blockquote { border-left: 3px solid #ccc; margin-left: 0; padding-left: 16px; color: #555; }\n @media print { body { padding: 0; } }\n </style>\n</head>\n<body>${safeHtml}</body>\n</html>`\n iframe.srcdoc = htmlContent\n\n // Guard against double-cleanup from concurrent onafterprint and timeout\n let cleaned = false\n const cleanup = () => {\n if (cleaned) return\n cleaned = true\n clearTimeout(fallbackTimer)\n if (iframe.parentNode) document.body.removeChild(iframe)\n }\n\n iframe.contentWindow.onafterprint = cleanup\n\n // Fallback cleanup if onafterprint doesn't fire\n let fallbackTimer = setTimeout(cleanup, 60000)\n\n iframe.onload = () => {\n iframe.contentWindow.focus()\n iframe.contentWindow.print()\n // Reset fallback to a shorter window after print dialog closes\n clearTimeout(fallbackTimer)\n fallbackTimer = setTimeout(cleanup, 1000)\n }\n}\n\nexport function exportAsDocx(html, filename = 'document.doc') {\n const wordHtml = `<html xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" xmlns=\"http://www.w3.org/TR/REC-html40\">\n<head><meta charset=\"utf-8\"><style>\nbody { font-family: Calibri, sans-serif; line-height: 1.5; color: #1a1a1a; }\nh1, h2, h3, h4, h5, h6 { margin-top: 1em; margin-bottom: 0.4em; }\ntable { border-collapse: collapse; width: 100%; }\nth, td { border: 1px solid #999; padding: 6px 10px; }\npre { background: #f5f5f5; padding: 10px; font-family: Consolas, monospace; font-size: 0.9em; }\ncode { font-family: Consolas, monospace; font-size: 0.9em; }\nblockquote { border-left: 3px solid #ccc; margin-left: 0; padding-left: 14px; color: #555; }\nimg { max-width: 100%; }\n</style></head>\n<body>${html}</body></html>`\n const blob = new Blob(['\\ufeff', wordHtml], { type: 'application/msword' })\n triggerDownload(blob, filename)\n}\n","import { DEFAULT_FONTS } from '../constants/defaults.js'\n\n/**\n * Remove fonts from a font list.\n *\n * @param {string[]} fonts - Font list to modify\n * @param {string[]} fontsToRemove - Font names to remove\n * @returns {string[]} New font list with specified fonts removed\n *\n * @example\n * removeFonts(DEFAULT_FONTS, ['Comic Sans MS', 'Impact'])\n */\nexport function removeFonts(fonts, fontsToRemove) {\n const removeSet = new Set(fontsToRemove.map(f => f.toLowerCase()))\n return fonts.filter(f => !removeSet.has(f.toLowerCase()))\n}\n\n/**\n * Add fonts to a font list.\n *\n * @param {string[]} fonts - Font list to modify\n * @param {string[]} fontsToAdd - Font names to add\n * @param {Object} [options]\n * @param {'start' | 'end'} [options.position='end'] - Where to insert the new fonts\n * @returns {string[]} New font list with fonts added (duplicates are skipped)\n *\n * @example\n * // Append fonts\n * addFonts(DEFAULT_FONTS, ['Lato', 'Poppins'])\n *\n * // Prepend fonts\n * addFonts(DEFAULT_FONTS, ['Lato', 'Poppins'], { position: 'start' })\n */\nexport function addFonts(fonts, fontsToAdd, options = {}) {\n const existing = new Set(fonts.map(f => f.toLowerCase()))\n const newFonts = fontsToAdd.filter(f => !existing.has(f.toLowerCase()))\n\n if (options.position === 'start') {\n return [...newFonts, ...fonts]\n }\n return [...fonts, ...newFonts]\n}\n\n/**\n * Load Google Fonts by injecting a stylesheet link into the document head.\n * Fonts that are already loaded will not be loaded again.\n *\n * **Privacy notice:** This function makes external requests to `fonts.googleapis.com`\n * and `fonts.gstatic.com`, which reveals the user's IP address, browser user-agent,\n * and which fonts are being loaded to Google. For privacy-sensitive deployments,\n * consider self-hosting fonts instead. See: https://google-webfonts-helper.herokuapp.com/\n *\n * **CSP note:** Requires `font-src fonts.gstatic.com` and `style-src fonts.googleapis.com`\n * in your Content-Security-Policy header.\n *\n * @param {string[]} fontFamilies - Google Font family names (e.g. ['Roboto', 'Open Sans', 'Lato:wght@400;700'])\n * @returns {HTMLLinkElement | null} The injected link element, or null if all fonts were already loaded\n *\n * @example\n * // Basic usage\n * loadGoogleFonts(['Roboto', 'Open Sans', 'Lato'])\n *\n * // With specific weights\n * loadGoogleFonts(['Roboto:wght@400;700', 'Open Sans:ital,wght@0,400;1,400'])\n */\n/**\n * @typedef {Object} LoadGoogleFontsOptions\n * @property {string} [integrity] - Subresource Integrity (SRI) hash for the stylesheet\n * (e.g. 'sha384-abc123...'). When provided, the browser verifies the fetched\n * stylesheet matches this hash before applying it. Generate with:\n * `openssl dgst -sha384 -binary <file> | openssl base64 -A`\n * @property {string} [crossOrigin='anonymous'] - CORS attribute for the link element.\n * Must be set when using SRI. Defaults to 'anonymous'.\n */\n\nexport function loadGoogleFonts(fontFamilies, options = {}) {\n if (!fontFamilies || fontFamilies.length === 0) return null\n if (typeof document === 'undefined') return null\n\n // Build the Google Fonts URL\n const families = fontFamilies.map(f => {\n // If the user already provided a spec with weights, use as-is\n if (f.includes(':')) return `family=${f.replace(/ /g, '+')}`\n // Otherwise default to requesting the font family\n return `family=${f.replace(/ /g, '+')}`\n })\n\n const url = `https://fonts.googleapis.com/css2?${families.join('&')}&display=swap`\n\n // Check if this URL is already loaded\n const existing = document.querySelector(`link[href=\"${url}\"]`)\n if (existing) return existing\n\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = url\n link.dataset.remyxFonts = 'true'\n\n // Subresource Integrity (SRI) — verify stylesheet hasn't been tampered with\n if (options.integrity) {\n link.integrity = options.integrity\n link.crossOrigin = options.crossOrigin || 'anonymous'\n } else {\n // Always set crossorigin for Google Fonts (required for CORS font loading)\n link.crossOrigin = options.crossOrigin || 'anonymous'\n }\n\n document.head.appendChild(link)\n\n return link\n}\n","import { escapeHTMLAttr } from './escapeHTML.js'\n\n/**\n * Lightweight HTML formatter / prettifier.\n * Produces indented, readable HTML similar to VS Code's formatting.\n * Zero dependencies — uses the browser's DOMParser for correctness.\n */\n\n// Block-level elements that get their own line\nconst BLOCK_ELEMENTS = new Set([\n 'html', 'head', 'body',\n 'div', 'section', 'article', 'aside', 'nav', 'header', 'footer', 'main',\n 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'ul', 'ol', 'li',\n 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption', 'colgroup', 'col',\n 'blockquote', 'pre', 'figure', 'figcaption',\n 'form', 'fieldset', 'legend',\n 'details', 'summary',\n 'hr', 'br',\n 'iframe', 'video', 'audio', 'canvas',\n])\n\n// Void elements (self-closing, no end tag)\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',\n 'link', 'meta', 'param', 'source', 'track', 'wbr',\n])\n\n// Elements whose content should not be reformatted (preserve whitespace)\nconst PREFORMATTED = new Set(['pre', 'code', 'script', 'style', 'textarea'])\n\n// Inline elements that should stay on the same line as their parent text\nconst INLINE_ELEMENTS = new Set([\n 'a', 'abbr', 'b', 'bdi', 'bdo', 'cite', 'code', 'data', 'del', 'dfn',\n 'em', 'i', 'ins', 'kbd', 'mark', 'q', 's', 'samp', 'small', 'span',\n 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr', 'label', 'input',\n])\n\n/**\n * Format raw HTML into a prettified, indented string.\n *\n * @param {string} html - The raw HTML string\n * @param {number} [indentSize=2] - Number of spaces per indent level\n * @returns {string} Formatted HTML\n */\nexport function formatHTML(html) {\n if (!html || !html.trim()) return ''\n\n const indent = ' ' // 2 spaces\n\n // Parse the HTML into a DOM tree\n const parser = new DOMParser()\n const doc = parser.parseFromString(`<body>${html}</body>`, 'text/html')\n const body = doc.body\n\n const lines = []\n formatNode(body, 0, lines, indent)\n\n return lines.join('\\n').trim()\n}\n\n/**\n * Recursively format a DOM node and its children.\n */\nfunction formatNode(node, level, lines, indent) {\n const children = Array.from(node.childNodes)\n\n for (const child of children) {\n if (child.nodeType === Node.COMMENT_NODE) {\n lines.push(`${indent.repeat(Math.max(0, level))}<!--${child.textContent}-->`)\n continue\n }\n\n if (child.nodeType === Node.TEXT_NODE) {\n const text = child.textContent\n // Skip pure-whitespace text nodes between block elements\n if (!text.trim()) continue\n const trimmed = text.replace(/\\s+/g, ' ').trim()\n if (trimmed) {\n lines.push(`${indent.repeat(Math.max(0, level))}${trimmed}`)\n }\n continue\n }\n\n if (child.nodeType !== Node.ELEMENT_NODE) continue\n\n const tag = child.tagName.toLowerCase()\n\n // Preformatted elements — output as-is, no reformatting\n if (PREFORMATTED.has(tag)) {\n lines.push(`${indent.repeat(Math.max(0, level))}${child.outerHTML}`)\n continue\n }\n\n const isVoid = VOID_ELEMENTS.has(tag)\n const currentIndent = indent.repeat(Math.max(0, level))\n\n // Build the opening tag with attributes\n const openTag = buildOpenTag(child, tag)\n\n if (isVoid) {\n // Self-closing element\n lines.push(`${currentIndent}${openTag}`)\n continue\n }\n\n // Check if this element only contains inline content (text + inline elements)\n const hasOnlyInlineContent = isInlineContent(child)\n\n if (hasOnlyInlineContent) {\n // Render the entire element on one line\n const innerContent = serializeInlineContent(child)\n lines.push(`${currentIndent}${openTag}${innerContent}</${tag}>`)\n } else {\n // Block element with block children — indent children\n lines.push(`${currentIndent}${openTag}`)\n formatNode(child, level + 1, lines, indent)\n lines.push(`${currentIndent}</${tag}>`)\n }\n }\n}\n\n/**\n * Build an opening tag string from a DOM element, including attributes.\n */\nfunction buildOpenTag(el, tag) {\n const attrs = Array.from(el.attributes)\n if (attrs.length === 0) return `<${tag}>`\n\n const attrStr = attrs.map(a => {\n if (a.value === '') return a.name\n return `${a.name}=\"${escapeAttr(a.value)}\"`\n }).join(' ')\n\n return `<${tag} ${attrStr}>`\n}\n\n/**\n * Check if an element only contains inline content\n * (text nodes and inline elements, no block-level children).\n */\nfunction isInlineContent(el) {\n for (const child of el.childNodes) {\n if (child.nodeType === Node.TEXT_NODE) continue\n if (child.nodeType === Node.ELEMENT_NODE) {\n const tag = child.tagName.toLowerCase()\n if (BLOCK_ELEMENTS.has(tag)) return false\n // Recursively check inline children\n if (!isInlineContent(child)) return false\n }\n }\n return true\n}\n\n/**\n * Serialize inline content (text + inline tags) into a single string,\n * preserving inner tags but collapsing whitespace.\n */\nfunction serializeInlineContent(el) {\n let result = ''\n for (const child of el.childNodes) {\n if (child.nodeType === Node.TEXT_NODE) {\n result += child.textContent.replace(/\\s+/g, ' ')\n } else if (child.nodeType === Node.ELEMENT_NODE) {\n const tag = child.tagName.toLowerCase()\n const openTag = buildOpenTag(child, tag)\n if (VOID_ELEMENTS.has(tag)) {\n result += openTag\n } else {\n result += `${openTag}${serializeInlineContent(child)}</${tag}>`\n }\n }\n }\n return result\n}\n\n/**\n * Escape HTML attribute values.\n */\n// Task 261: Uses shared escapeHTMLAttr utility\nfunction escapeAttr(val) {\n return escapeHTMLAttr(val)\n}\n","/**\n * RTL (right-to-left) language support utilities.\n *\n * Provides automatic text direction detection and helpers for\n * bidirectional content within the editor.\n */\n\n// Unicode ranges for RTL scripts\nconst RTL_REGEX = /[\\u0591-\\u07FF\\u200F\\u202B\\u202E\\uFB1D-\\uFDFD\\uFE70-\\uFEFC]/\n\n// Unicode ranges for LTR scripts (Latin, CJK, etc.)\nconst LTR_REGEX = /[A-Za-z\\u00C0-\\u00FF\\u0100-\\u024F\\u0400-\\u04FF\\u1100-\\u11FF\\u3040-\\u309F\\u30A0-\\u30FF\\u3400-\\u4DBF\\u4E00-\\u9FFF]/\n\n/**\n * Detect the dominant text direction of a string.\n *\n * Counts RTL vs LTR character occurrences and returns the direction\n * of whichever has more characters. Returns 'auto' if the string\n * contains no directional characters (e.g., numbers only).\n *\n * @param {string} text - The text to analyze\n * @returns {'ltr' | 'rtl' | 'auto'} The detected text direction\n */\nexport function detectTextDirection(text) {\n if (!text || typeof text !== 'string') return 'auto'\n\n let rtlCount = 0\n let ltrCount = 0\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i]\n if (RTL_REGEX.test(char)) rtlCount++\n else if (LTR_REGEX.test(char)) ltrCount++\n }\n\n if (rtlCount === 0 && ltrCount === 0) return 'auto'\n return rtlCount > ltrCount ? 'rtl' : 'ltr'\n}\n\n/**\n * Apply the `dir` attribute to a block element based on its text content.\n *\n * This function is designed to be called on each block-level element\n * (paragraph, heading, list item, etc.) to set the appropriate\n * text direction.\n *\n * @param {HTMLElement} element - The block element to update\n */\nexport function applyAutoDirection(element) {\n if (!element || !element.textContent) return\n const dir = detectTextDirection(element.textContent)\n if (dir === 'auto') {\n element.removeAttribute('dir')\n } else {\n element.setAttribute('dir', dir)\n }\n}\n\n/**\n * Apply auto-direction to all block-level elements in a container.\n *\n * @param {HTMLElement} container - The container element (typically the editor root)\n */\nexport function applyAutoDirectionAll(container) {\n if (!container) return\n const blocks = container.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, blockquote, td, th')\n for (const block of blocks) {\n applyAutoDirection(block)\n }\n}\n\n/**\n * RTL-aware CSS logical property mappings.\n * Use these constants to reference CSS logical properties in JavaScript\n * for consistent layout in both LTR and RTL contexts.\n */\nexport const LOGICAL_PROPERTIES = {\n marginStart: 'margin-inline-start',\n marginEnd: 'margin-inline-end',\n paddingStart: 'padding-inline-start',\n paddingEnd: 'padding-inline-end',\n borderStart: 'border-inline-start',\n borderEnd: 'border-inline-end',\n insetStart: 'inset-inline-start',\n insetEnd: 'inset-inline-end',\n}\n","/**\n * English (default) locale pack for Remyx Editor.\n *\n * This file defines all translatable strings in the editor.\n * Keys are flat dot-separated paths for simple lookup.\n */\nexport const en = {\n // ── Toolbar tooltips ──\n 'toolbar.bold': 'Bold',\n 'toolbar.italic': 'Italic',\n 'toolbar.underline': 'Underline',\n 'toolbar.strikethrough': 'Strikethrough',\n 'toolbar.subscript': 'Subscript',\n 'toolbar.superscript': 'Superscript',\n 'toolbar.removeFormat': 'Remove Formatting',\n 'toolbar.heading': 'Heading',\n 'toolbar.paragraph': 'Normal text',\n 'toolbar.orderedList': 'Numbered List',\n 'toolbar.unorderedList': 'Bulleted List',\n 'toolbar.taskList': 'Task List',\n 'toolbar.indent': 'Increase Indent',\n 'toolbar.outdent': 'Decrease Indent',\n 'toolbar.alignLeft': 'Align Left',\n 'toolbar.alignCenter': 'Align Center',\n 'toolbar.alignRight': 'Align Right',\n 'toolbar.alignJustify': 'Justify',\n 'toolbar.blockquote': 'Blockquote',\n 'toolbar.codeBlock': 'Code Block',\n 'toolbar.horizontalRule': 'Horizontal Rule',\n 'toolbar.link': 'Insert Link',\n 'toolbar.image': 'Insert Image',\n 'toolbar.table': 'Insert Table',\n 'toolbar.fontFamily': 'Font Family',\n 'toolbar.fontSize': 'Font Size',\n 'toolbar.foreColor': 'Text Color',\n 'toolbar.backColor': 'Background Color',\n 'toolbar.undo': 'Undo',\n 'toolbar.redo': 'Redo',\n 'toolbar.source': 'Source Code',\n 'toolbar.markdown': 'Markdown',\n 'toolbar.fullscreen': 'Fullscreen',\n 'toolbar.commandPalette': 'Command Palette',\n 'toolbar.print': 'Print',\n 'toolbar.findReplace': 'Find & Replace',\n 'toolbar.lineNumbers': 'Toggle Line Numbers',\n 'toolbar.copyCode': 'Copy Code',\n\n // ── Menu bar ──\n 'menu.file': 'File',\n 'menu.edit': 'Edit',\n 'menu.view': 'View',\n 'menu.insert': 'Insert',\n 'menu.format': 'Format',\n\n // ── Context menu ──\n 'context.cut': 'Cut',\n 'context.copy': 'Copy',\n 'context.paste': 'Paste',\n 'context.selectAll': 'Select All',\n 'context.toggleHeaderRow': 'Toggle Header Row',\n 'context.formatCell': 'Format Cell',\n 'context.clearFilters': 'Clear Filters',\n\n // ── Modals ──\n 'modal.link.title': 'Insert Link',\n 'modal.link.url': 'URL',\n 'modal.link.text': 'Link Text',\n 'modal.link.newTab': 'Open in new tab',\n 'modal.link.insert': 'Insert',\n 'modal.link.cancel': 'Cancel',\n 'modal.image.title': 'Insert Image',\n 'modal.image.url': 'Image URL',\n 'modal.image.alt': 'Alt Text',\n 'modal.image.insert': 'Insert',\n 'modal.image.cancel': 'Cancel',\n 'modal.source.title': 'Edit Source',\n 'modal.source.apply': 'Apply',\n 'modal.source.cancel': 'Cancel',\n 'modal.table.title': 'Insert Table',\n 'modal.table.rows': 'Rows',\n 'modal.table.cols': 'Columns',\n 'modal.table.insert': 'Insert',\n 'modal.table.cancel': 'Cancel',\n\n // ── Find & Replace ──\n 'find.placeholder': 'Find...',\n 'find.replace': 'Replace...',\n 'find.replaceAll': 'Replace All',\n 'find.matchCase': 'Match Case',\n 'find.noResults': 'No results',\n 'find.results': '{{current}} of {{total}}',\n\n // ── Command Palette ──\n 'palette.placeholder': 'Type a command...',\n 'palette.noResults': 'No matching commands',\n\n // ── Autosave ──\n 'autosave.saved': 'Saved',\n 'autosave.saving': 'Saving...',\n 'autosave.unsaved': 'Unsaved changes',\n 'autosave.error': 'Save failed',\n 'autosave.recovery': 'Unsaved content was recovered.',\n 'autosave.restore': 'Restore',\n 'autosave.dismiss': 'Dismiss',\n\n // ── Accessibility ──\n 'a11y.skipToEditor': 'Skip to editor',\n 'a11y.toolbar': 'Editor toolbar',\n 'a11y.menuBar': 'Editor menu bar',\n 'a11y.editor': 'Rich text editor',\n\n // ── Headings (for command palette / dropdown) ──\n 'heading.h1': 'Heading 1',\n 'heading.h2': 'Heading 2',\n 'heading.h3': 'Heading 3',\n 'heading.h4': 'Heading 4',\n 'heading.h5': 'Heading 5',\n 'heading.h6': 'Heading 6',\n 'heading.paragraph': 'Normal text',\n\n // ── Cell formatting ──\n 'format.number': 'Number',\n 'format.currency': 'Currency',\n 'format.percentage': 'Percentage',\n 'format.date': 'Date',\n\n // ── Status bar ──\n 'status.words': '{{count}} words',\n 'status.chars': '{{count}} characters',\n}\n","/**\n * Internationalization (i18n) system for the Remyx Editor.\n *\n * Provides a simple, lightweight locale system with:\n * - Default English locale included\n * - Runtime locale switching via setLocale()\n * - String lookup via t() with fallback to key\n * - Interpolation support: t('greeting', { name: 'World' }) → 'Hello, World!'\n * - Custom locale registration via registerLocale()\n */\n\nimport { en } from './locales/en.js'\n\n/** Currently active locale strings */\nlet _currentLocale = { ...en }\n\n/** Registered locale packs keyed by language code */\nconst _locales = { en }\n\n/** Current language code */\nlet _currentLang = 'en'\n\n/**\n * Translate a key using the current locale.\n *\n * Supports interpolation with `{{variable}}` placeholders:\n * ```js\n * t('save.status', { status: 'saved' }) // \"Document saved\"\n * ```\n *\n * Returns the key itself if no translation is found (safe fallback).\n *\n * @param {string} key - Dot-separated translation key (e.g., 'toolbar.bold')\n * @param {Object} [vars] - Interpolation variables\n * @returns {string} The translated string, or the key if not found\n */\nexport function t(key, vars) {\n let str = _currentLocale[key]\n if (str === undefined) return key\n\n if (vars) {\n for (const [k, v] of Object.entries(vars)) {\n str = str.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v))\n }\n }\n\n return str\n}\n\n/**\n * Set the active locale by language code.\n *\n * @param {string} lang - Language code (e.g., 'en', 'ar', 'ja')\n * @throws {Error} If the locale has not been registered\n */\nexport function setLocale(lang) {\n const locale = _locales[lang]\n if (!locale) {\n throw new Error(`Locale \"${lang}\" is not registered. Use registerLocale() first.`)\n }\n _currentLang = lang\n // Merge with English fallback so missing keys fall through\n _currentLocale = { ...en, ...locale }\n}\n\n/**\n * Get the current language code.\n *\n * @returns {string} The active language code\n */\nexport function getLocale() {\n return _currentLang\n}\n\n/**\n * Register a locale pack.\n *\n * A locale pack is a flat object mapping translation keys to strings.\n * Partial packs are supported — missing keys fall back to English.\n *\n * @param {string} lang - Language code (e.g., 'fr', 'de', 'ar')\n * @param {Object} strings - Translation key-value pairs\n *\n * @example\n * registerLocale('fr', {\n * 'toolbar.bold': 'Gras',\n * 'toolbar.italic': 'Italique',\n * 'toolbar.underline': 'Souligné',\n * })\n */\nexport function registerLocale(lang, strings) {\n if (!lang || typeof lang !== 'string') throw new Error('registerLocale: lang is required')\n if (!strings || typeof strings !== 'object') throw new Error('registerLocale: strings object is required')\n _locales[lang] = strings\n}\n\n/**\n * Unregister a locale pack. Cannot unregister 'en' (default).\n *\n * @param {string} lang - Language code to remove\n */\nexport function unregisterLocale(lang) {\n if (lang === 'en') return // Cannot remove default\n delete _locales[lang]\n if (_currentLang === lang) {\n setLocale('en')\n }\n}\n\n/**\n * Get all registered locale codes.\n *\n * @returns {string[]} Array of registered language codes\n */\nexport function getRegisteredLocales() {\n return Object.keys(_locales)\n}\n","/**\n * Performance utilities for the Remyx Editor.\n *\n * Provides DOM mutation batching, idle-time processing, and\n * performance measurement helpers for large-document optimization.\n */\n\n/**\n * Batch multiple DOM mutations into a single requestAnimationFrame callback\n * to prevent layout thrash from rapid sequential changes.\n *\n * @param {Function[]} mutations - Array of functions that perform DOM mutations\n * @returns {Promise<void>} Resolves after all mutations are applied\n *\n * @example\n * batchDOMMutations([\n * () => el.style.width = '100px',\n * () => el.style.height = '200px',\n * () => el.classList.add('active'),\n * ])\n */\nexport function batchDOMMutations(mutations) {\n return new Promise(resolve => {\n requestAnimationFrame(() => {\n for (const fn of mutations) {\n fn()\n }\n resolve()\n })\n })\n}\n\n/**\n * Schedule a non-critical task to run during browser idle time.\n * Falls back to setTimeout(fn, 16) if requestIdleCallback is not available.\n *\n * @param {Function} fn - The task to run during idle time\n * @param {Object} [options]\n * @param {number} [options.timeout=2000] - Maximum wait time in ms before forcing execution\n * @returns {number} The idle callback ID (can be passed to cancelIdleCallback)\n *\n * @example\n * scheduleIdleTask(() => {\n * // Non-critical work: word count, readability, analytics\n * updateWordCount(engine)\n * })\n */\nexport function scheduleIdleTask(fn, options = {}) {\n const timeout = options.timeout || 2000\n if (typeof requestIdleCallback === 'function') {\n return requestIdleCallback(fn, { timeout })\n }\n // Fallback for Safari (no requestIdleCallback)\n return setTimeout(fn, 16)\n}\n\n/**\n * Cancel a previously scheduled idle task.\n *\n * @param {number} id - The ID returned by scheduleIdleTask\n */\nexport function cancelIdleTask(id) {\n if (typeof cancelIdleCallback === 'function') {\n cancelIdleCallback(id)\n } else {\n clearTimeout(id)\n }\n}\n\n/**\n * Create a throttled version of a function that fires at most once per\n * animation frame. Useful for scroll and resize handlers.\n *\n * @param {Function} fn - The function to throttle\n * @returns {Function} The throttled function\n */\nexport function rafThrottle(fn) {\n let frameId = null\n return function throttled(...args) {\n if (frameId !== null) return\n frameId = requestAnimationFrame(() => {\n fn.apply(this, args)\n frameId = null\n })\n }\n}\n\n/**\n * Measure the execution time of a function in milliseconds.\n * Uses performance.now() for high-resolution timing.\n *\n * @param {string} label - A label for the measurement (logged to console)\n * @param {Function} fn - The function to measure\n * @returns {*} The return value of the measured function\n *\n * @example\n * const html = measurePerformance('getHTML', () => engine.getHTML())\n */\nexport function measurePerformance(label, fn) {\n const start = performance.now()\n const result = fn()\n const duration = performance.now() - start\n if (typeof console !== 'undefined' && console.debug) {\n console.debug(`[Remyx perf] ${label}: ${duration.toFixed(2)}ms`)\n }\n return result\n}\n\n/**\n * Benchmark a function by running it multiple times and reporting stats.\n *\n * @param {string} label - Label for the benchmark\n * @param {Function} fn - The function to benchmark\n * @param {number} [iterations=100] - Number of iterations\n * @returns {{ label: string, mean: number, median: number, min: number, max: number, p95: number }}\n */\nexport function benchmark(label, fn, iterations = 100) {\n const times = []\n for (let i = 0; i < iterations; i++) {\n const start = performance.now()\n fn()\n times.push(performance.now() - start)\n }\n\n times.sort((a, b) => a - b)\n const sum = times.reduce((a, b) => a + b, 0)\n const mean = sum / times.length\n const median = times[Math.floor(times.length / 2)]\n const min = times[0]\n const max = times[times.length - 1]\n const p95 = times[Math.floor(times.length * 0.95)]\n\n return { label, mean, median, min, max, p95 }\n}\n\n/**\n * Creates an input batcher that coalesces rapid DOM updates.\n * Collects mutations during a frame and applies them in a single rAF.\n *\n * @param {Function} applyFn - Function that receives an array of batched mutations\n * @param {Object} [options]\n * @param {number} [options.flushMs=16] - Flush interval (one frame by default)\n * @returns {{ queue: Function, flush: Function, destroy: Function }}\n *\n * @example\n * const batcher = createInputBatcher((mutations) => {\n * mutations.forEach(m => applyMutation(m))\n * })\n * batcher.queue({ type: 'insert', text: 'a' })\n * batcher.queue({ type: 'insert', text: 'b' })\n * // Both applied in a single rAF\n */\nexport function createInputBatcher(applyFn, options = {}) {\n const flushMs = options.flushMs || 16 // one frame\n let pending = []\n let timer = null\n\n return {\n /**\n * Queue a mutation to be applied in the next batch.\n * @param {*} mutation - The mutation data to queue\n */\n queue(mutation) {\n pending.push(mutation)\n if (!timer) {\n timer = (typeof requestAnimationFrame === 'function')\n ? requestAnimationFrame(() => {\n const batch = pending\n pending = []\n timer = null\n applyFn(batch)\n })\n : setTimeout(() => {\n const batch = pending\n pending = []\n timer = null\n applyFn(batch)\n }, flushMs)\n }\n },\n\n /**\n * Immediately flush any pending mutations without waiting for rAF.\n */\n flush() {\n if (timer) {\n if (typeof cancelAnimationFrame === 'function') {\n cancelAnimationFrame(timer)\n } else {\n clearTimeout(timer)\n }\n timer = null\n }\n if (pending.length) {\n const batch = pending\n pending = []\n applyFn(batch)\n }\n },\n\n /**\n * Destroy the batcher, cancelling any pending flush.\n */\n destroy() {\n if (timer) {\n if (typeof cancelAnimationFrame === 'function') {\n cancelAnimationFrame(timer)\n } else {\n clearTimeout(timer)\n }\n }\n pending = []\n timer = null\n }\n }\n}\n","/**\n * All available theme variables with descriptions.\n * Keys are the camelCase names used with createTheme().\n * Values are objects with the CSS variable name and a description.\n */\nexport const THEME_VARIABLES = {\n // Color Palette\n bg: { var: '--rmx-bg', description: 'Editor background color' },\n text: { var: '--rmx-text', description: 'Primary text color' },\n textSecondary: { var: '--rmx-text-secondary', description: 'Secondary/muted text color' },\n border: { var: '--rmx-border', description: 'Border color' },\n borderSubtle: { var: '--rmx-border-subtle', description: 'Subtle/light border color' },\n\n // Toolbar\n toolbarBg: { var: '--rmx-toolbar-bg', description: 'Toolbar background color' },\n toolbarBorder: { var: '--rmx-toolbar-border', description: 'Toolbar border color' },\n toolbarButtonHover: { var: '--rmx-toolbar-button-hover', description: 'Toolbar button hover background' },\n toolbarButtonActive: { var: '--rmx-toolbar-button-active', description: 'Toolbar button active background' },\n toolbarIcon: { var: '--rmx-toolbar-icon', description: 'Toolbar icon color' },\n toolbarIconActive: { var: '--rmx-toolbar-icon-active', description: 'Toolbar icon active color' },\n\n // Accent / Primary\n primary: { var: '--rmx-primary', description: 'Primary accent color' },\n primaryHover: { var: '--rmx-primary-hover', description: 'Primary color on hover' },\n primaryLight: { var: '--rmx-primary-light', description: 'Light variant of primary (backgrounds)' },\n focusRing: { var: '--rmx-focus-ring', description: 'Focus ring outline color' },\n selection: { var: '--rmx-selection', description: 'Text selection highlight color' },\n\n // Feedback\n danger: { var: '--rmx-danger', description: 'Error/danger color' },\n dangerLight: { var: '--rmx-danger-light', description: 'Light variant of danger' },\n\n // UI Elements\n placeholder: { var: '--rmx-placeholder', description: 'Placeholder text color' },\n modalBg: { var: '--rmx-modal-bg', description: 'Modal background color' },\n modalOverlay: { var: '--rmx-modal-overlay', description: 'Modal overlay background' },\n statusbarBg: { var: '--rmx-statusbar-bg', description: 'Status bar background' },\n statusbarText: { var: '--rmx-statusbar-text', description: 'Status bar text color' },\n hoverBg: { var: '--rmx-hover-bg', description: 'Generic hover background' },\n\n // Shadows\n shadowSm: { var: '--rmx-shadow-sm', description: 'Small shadow' },\n shadowMd: { var: '--rmx-shadow-md', description: 'Medium shadow' },\n shadowLg: { var: '--rmx-shadow-lg', description: 'Large shadow' },\n shadowFloat: { var: '--rmx-shadow-float', description: 'Floating element shadow' },\n\n // Typography\n fontFamily: { var: '--rmx-font-family', description: 'UI font stack' },\n fontSize: { var: '--rmx-font-size', description: 'UI font size' },\n contentFontSize: { var: '--rmx-content-font-size', description: 'Content area font size' },\n contentLineHeight: { var: '--rmx-content-line-height', description: 'Content area line height' },\n\n // Spacing & Shape\n radius: { var: '--rmx-radius', description: 'Standard border radius' },\n radiusSm: { var: '--rmx-radius-sm', description: 'Small border radius' },\n radiusInner: { var: '--rmx-radius-inner', description: 'Inner element border radius' },\n spacingXs: { var: '--rmx-spacing-xs', description: 'Extra small spacing (4px)' },\n spacingSm: { var: '--rmx-spacing-sm', description: 'Small spacing (8px)' },\n spacingMd: { var: '--rmx-spacing-md', description: 'Medium spacing (12px)' },\n\n // Transitions\n transitionFast: { var: '--rmx-transition-fast', description: 'Fast transition duration' },\n transitionNormal: { var: '--rmx-transition-normal', description: 'Normal transition duration' },\n}\n\n// Build a quick lookup: camelCase key → CSS variable name\nconst KEY_TO_VAR = {}\nfor (const [key, meta] of Object.entries(THEME_VARIABLES)) {\n KEY_TO_VAR[key] = meta.var\n}\n\n/**\n * Create a theme object from friendly camelCase overrides.\n * The returned object can be passed directly to the `customTheme` prop.\n *\n * Accepts either camelCase keys (e.g. `bg`, `primary`, `toolbarBg`) or\n * raw CSS variable names (e.g. `--rmx-bg`). Both formats can be mixed.\n *\n * @param {Object} overrides - Theme variable overrides\n * @returns {Object} CSS custom property object for use as `customTheme`\n *\n * @example\n * const myTheme = createTheme({\n * bg: '#1a1a2e',\n * primary: '#e94560',\n * toolbarBg: '#16213e',\n * radius: '12px',\n * })\n *\n * <RemyxEditor customTheme={myTheme} />\n */\nexport function createTheme(overrides) {\n const result = {}\n for (const [key, value] of Object.entries(overrides)) {\n if (key.startsWith('--rmx-')) {\n // Raw CSS variable — pass through\n result[key] = value\n } else if (KEY_TO_VAR[key]) {\n // camelCase key → CSS variable\n result[KEY_TO_VAR[key]] = value\n } else {\n // Unknown key — try as a CSS variable anyway\n result[`--rmx-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`] = value\n }\n }\n return result\n}\n","import { createTheme } from './themeConfig.js'\n\n/**\n * Built-in theme presets.\n *\n * **Recommended:** Use the `theme` prop directly instead of `customTheme`:\n * ```jsx\n * <RemyxEditor theme=\"ocean\" />\n * <RemyxEditor theme=\"forest\" />\n * ```\n *\n * These presets are also available as CSS custom property objects for use with\n * `customTheme` when you need to override individual variables on top of a theme:\n * ```jsx\n * <RemyxEditor theme=\"ocean\" customTheme={createTheme({ primary: '#ff0000' })} />\n * ```\n */\nexport const THEME_PRESETS = {\n /** Deep blue ocean palette */\n ocean: createTheme({\n bg: '#0f172a',\n text: '#e2e8f0',\n textSecondary: '#94a3b8',\n border: '#1e3a5f',\n borderSubtle: '#1e3a5f',\n toolbarBg: '#0c1426',\n toolbarBorder: '#1e3a5f',\n toolbarButtonHover: '#1e3a5f',\n toolbarButtonActive: '#1e4976',\n toolbarIcon: '#94a3b8',\n toolbarIconActive: '#38bdf8',\n primary: '#0ea5e9',\n primaryHover: '#0284c7',\n primaryLight: 'rgba(14, 165, 233, 0.15)',\n focusRing: '#0ea5e9',\n selection: '#0c4a6e',\n placeholder: '#475569',\n modalBg: '#0c1426',\n modalOverlay: 'rgba(0, 0, 0, 0.6)',\n statusbarBg: '#0c1426',\n statusbarText: '#64748b',\n }),\n\n /** Green earth-tone forest palette */\n forest: createTheme({\n bg: '#14201a',\n text: '#d1e7dd',\n textSecondary: '#8fbc8f',\n border: '#2d4a3e',\n borderSubtle: '#2d4a3e',\n toolbarBg: '#0f1a14',\n toolbarBorder: '#2d4a3e',\n toolbarButtonHover: '#2d4a3e',\n toolbarButtonActive: '#1a5c3a',\n toolbarIcon: '#8fbc8f',\n toolbarIconActive: '#4ade80',\n primary: '#22c55e',\n primaryHover: '#16a34a',\n primaryLight: 'rgba(34, 197, 94, 0.15)',\n focusRing: '#22c55e',\n selection: '#14532d',\n placeholder: '#4a7c5f',\n modalBg: '#0f1a14',\n modalOverlay: 'rgba(0, 0, 0, 0.6)',\n statusbarBg: '#0f1a14',\n statusbarText: '#4a7c5f',\n }),\n\n /** Warm orange/amber sunset palette */\n sunset: createTheme({\n bg: '#1c1210',\n text: '#fde8d0',\n textSecondary: '#d4a574',\n border: '#5c3a28',\n borderSubtle: '#5c3a28',\n toolbarBg: '#181010',\n toolbarBorder: '#5c3a28',\n toolbarButtonHover: '#5c3a28',\n toolbarButtonActive: '#7c4a2a',\n toolbarIcon: '#d4a574',\n toolbarIconActive: '#fb923c',\n primary: '#f97316',\n primaryHover: '#ea580c',\n primaryLight: 'rgba(249, 115, 22, 0.15)',\n focusRing: '#f97316',\n selection: '#7c2d12',\n placeholder: '#8b6f47',\n modalBg: '#181010',\n modalOverlay: 'rgba(0, 0, 0, 0.6)',\n statusbarBg: '#181010',\n statusbarText: '#8b6f47',\n }),\n\n /** Soft pink/rose palette */\n rose: createTheme({\n bg: '#1c1018',\n text: '#fde2ee',\n textSecondary: '#d4809f',\n border: '#5c2848',\n borderSubtle: '#5c2848',\n toolbarBg: '#18101a',\n toolbarBorder: '#5c2848',\n toolbarButtonHover: '#5c2848',\n toolbarButtonActive: '#7c2858',\n toolbarIcon: '#d4809f',\n toolbarIconActive: '#fb7185',\n primary: '#f43f5e',\n primaryHover: '#e11d48',\n primaryLight: 'rgba(244, 63, 94, 0.15)',\n focusRing: '#f43f5e',\n selection: '#881337',\n placeholder: '#8b4766',\n modalBg: '#18101a',\n modalOverlay: 'rgba(0, 0, 0, 0.6)',\n statusbarBg: '#18101a',\n statusbarText: '#8b4766',\n }),\n}\n","// ---------------------------------------------------------------------------\n// Per-item toolbar theming\n// ---------------------------------------------------------------------------\n\n/**\n * Maps friendly camelCase keys to CSS custom property names for per-item\n * toolbar button styling. These variables are scoped to individual toolbar\n * elements via inline styles, so they only affect the element they're set on.\n */\nexport const TOOLBAR_ITEM_STYLE_KEYS = {\n color: { var: '--rmx-tb-color', description: 'Icon / text color' },\n background: { var: '--rmx-tb-bg', description: 'Default background' },\n hoverColor: { var: '--rmx-tb-hover-color', description: 'Color on hover' },\n hoverBackground: { var: '--rmx-tb-hover-bg', description: 'Background on hover' },\n activeColor: { var: '--rmx-tb-active-color', description: 'Color when active / pressed' },\n activeBackground: { var: '--rmx-tb-active-bg', description: 'Background when active' },\n border: { var: '--rmx-tb-border', description: 'Border shorthand' },\n borderRadius: { var: '--rmx-tb-radius', description: 'Border radius' },\n size: { var: '--rmx-tb-size', description: 'Button width & height' },\n iconSize: { var: '--rmx-tb-icon-size', description: 'Icon size inside button' },\n padding: { var: '--rmx-tb-padding', description: 'Button padding' },\n opacity: { var: '--rmx-tb-opacity', description: 'Button opacity' },\n}\n\nconst ITEM_KEY_TO_VAR = {}\nfor (const [key, meta] of Object.entries(TOOLBAR_ITEM_STYLE_KEYS)) {\n ITEM_KEY_TO_VAR[key] = meta.var\n}\n\nconst SEPARATOR_KEY_TO_VAR = {\n color: '--rmx-tb-sep-color',\n width: '--rmx-tb-sep-width',\n height: '--rmx-tb-sep-height',\n margin: '--rmx-tb-sep-margin',\n}\n\n/**\n * Convert a per-item overrides object (friendly keys or raw CSS vars) into\n * an inline style object of CSS custom properties.\n *\n * @param {Object} overrides - e.g. { color: '#e11d48', borderRadius: '50%' }\n * @returns {Object|null} Style object or null if empty\n */\nexport function resolveToolbarItemStyle(overrides) {\n if (!overrides || typeof overrides !== 'object') return null\n const style = {}\n let hasKeys = false\n for (const [key, value] of Object.entries(overrides)) {\n if (key.startsWith('--rmx-')) {\n style[key] = value\n hasKeys = true\n } else if (ITEM_KEY_TO_VAR[key]) {\n style[ITEM_KEY_TO_VAR[key]] = value\n hasKeys = true\n }\n }\n return hasKeys ? style : null\n}\n\n/**\n * Convert separator overrides into an inline style object.\n *\n * @param {Object} overrides - e.g. { color: '#ccc', width: '2px' }\n * @returns {Object|null}\n */\nexport function resolveSeparatorStyle(overrides) {\n if (!overrides || typeof overrides !== 'object') return null\n const style = {}\n let hasKeys = false\n for (const [key, value] of Object.entries(overrides)) {\n if (key.startsWith('--rmx-')) {\n style[key] = value\n hasKeys = true\n } else if (SEPARATOR_KEY_TO_VAR[key]) {\n style[SEPARATOR_KEY_TO_VAR[key]] = value\n hasKeys = true\n }\n }\n return hasKeys ? style : null\n}\n\n/**\n * Create a toolbar item theme config. The returned object can be passed\n * directly to the `toolbarItemTheme` prop on `<RemyxEditor>`.\n *\n * Keys are toolbar command names (e.g. `bold`, `italic`, `headings`) or the\n * special `_separator` key. Values are objects of per-item style overrides.\n *\n * @param {Object} config - Map of command → style overrides\n * @returns {Object} Resolved config ready for the prop\n *\n * @example\n * const itemTheme = createToolbarItemTheme({\n * bold: { color: '#e11d48', activeColor: '#be123c', borderRadius: '50%' },\n * italic: { background: '#f0f9ff', hoverBackground: '#e0f2fe' },\n * _separator: { color: '#e2e8f0', width: '2px' },\n * })\n *\n * <RemyxEditor toolbarItemTheme={itemTheme} />\n */\nexport function createToolbarItemTheme(config) {\n if (!config || typeof config !== 'object') return {}\n const resolved = {}\n for (const [command, overrides] of Object.entries(config)) {\n if (command === '_separator') {\n resolved._separator = resolveSeparatorStyle(overrides)\n } else {\n const style = resolveToolbarItemStyle(overrides)\n if (style) resolved[command] = style\n }\n }\n return resolved\n}\n","/**\n * Define a RemyxEditor configuration.\n *\n * Top-level keys act as defaults for all editors.\n * The `editors` key contains named configurations that can be\n * attached to specific <RemyxEditor config=\"name\" /> instances.\n *\n * @param {object} config\n * @returns {object} The validated configuration object\n */\nexport function defineConfig(config) {\n if (!config || typeof config !== 'object') {\n throw new Error('defineConfig expects a configuration object')\n }\n\n if (config.editors && typeof config.editors !== 'object') {\n throw new Error('defineConfig: \"editors\" must be an object mapping names to editor configurations')\n }\n\n return config\n}\n","/**\n * Load a RemyxEditor configuration from a URL (JSON or YAML).\n *\n * Supports:\n * - JSON files (.json or any URL returning application/json)\n * - YAML files (.yml, .yaml) — parsed via a lightweight inline parser\n * - Environment-based config merging via `env` key\n * - Fetching from local file paths or remote URLs\n *\n * @param {string} url - URL or path to a JSON/YAML configuration file\n * @param {object} [options]\n * @param {string} [options.env] - Environment name for config merging (e.g., 'production', 'development')\n * @param {Record<string, string>} [options.headers] - Custom fetch headers (e.g., Authorization)\n * @param {AbortSignal} [options.signal] - AbortController signal for cancellation\n * @returns {Promise<object>} The resolved configuration object\n */\nexport async function loadConfig(url, options = {}) {\n const { env, headers, signal } = options\n\n const response = await fetch(url, {\n headers: { Accept: 'application/json, text/yaml, text/plain', ...headers },\n signal,\n })\n\n if (!response.ok) {\n throw new Error(`loadConfig: Failed to fetch \"${url}\" (HTTP ${response.status})`)\n }\n\n const text = await response.text()\n let config\n\n // Detect format: YAML if extension is .yml/.yaml, or if content doesn't start with { or [\n const isYaml = /\\.ya?ml(\\?.*)?$/i.test(url)\n if (isYaml) {\n config = parseSimpleYaml(text)\n } else {\n try {\n config = JSON.parse(text)\n } catch {\n // Fallback: try YAML parse if JSON fails\n config = parseSimpleYaml(text)\n }\n }\n\n if (!config || typeof config !== 'object') {\n throw new Error('loadConfig: Configuration must be a JSON/YAML object')\n }\n\n // Environment-based merging\n if (env && config.env && typeof config.env === 'object') {\n const envOverrides = config.env[env]\n if (envOverrides && typeof envOverrides === 'object') {\n const { env: _envKey, ...baseConfig } = config\n config = deepMerge(baseConfig, envOverrides)\n } else {\n const { env: _envKey, ...baseConfig } = config\n config = baseConfig\n }\n } else if (config.env) {\n // Strip the env key if no env option was provided\n const { env: _envKey, ...baseConfig } = config\n config = baseConfig\n }\n\n return config\n}\n\n/**\n * Deep merge two objects. Arrays are replaced, not merged.\n * @param {object} target\n * @param {object} source\n * @returns {object}\n */\nfunction deepMerge(target, source) {\n const result = { ...target }\n for (const key of Object.keys(source)) {\n if (\n source[key] &&\n typeof source[key] === 'object' &&\n !Array.isArray(source[key]) &&\n target[key] &&\n typeof target[key] === 'object' &&\n !Array.isArray(target[key])\n ) {\n result[key] = deepMerge(target[key], source[key])\n } else {\n result[key] = source[key]\n }\n }\n return result\n}\n\n/**\n * Lightweight YAML parser for simple key-value and nested object configs.\n * Handles: strings, numbers, booleans, null, arrays (inline [...] and block - items),\n * nested objects via indentation, and quoted strings.\n *\n * This is NOT a full YAML parser — it covers the subset used in editor configs.\n * For complex YAML, use the `js-yaml` library and pass the parsed result to defineConfig().\n *\n * @param {string} text\n * @returns {object}\n */\nfunction parseSimpleYaml(text) {\n const lines = text.split('\\n')\n const root = {}\n const stack = [{ obj: root, indent: -1 }]\n\n for (let i = 0; i < lines.length; i++) {\n const raw = lines[i]\n // Skip empty lines and comments\n if (/^\\s*(#.*)?$/.test(raw)) continue\n\n const match = raw.match(/^(\\s*)(.+)$/)\n if (!match) continue\n\n const indent = match[1].length\n const content = match[2]\n\n // Pop stack to find parent at correct indent level\n while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {\n stack.pop()\n }\n const parent = stack[stack.length - 1].obj\n\n // Array item (block style)\n const arrayMatch = content.match(/^-\\s+(.*)$/)\n if (arrayMatch) {\n // Find the key that this array belongs to — it's the parent\n const val = parseYamlValue(arrayMatch[1])\n if (Array.isArray(parent)) {\n parent.push(val)\n }\n continue\n }\n\n // Key: value pair\n const kvMatch = content.match(/^([^:]+?):\\s*(.*)$/)\n if (kvMatch) {\n const key = kvMatch[1].trim()\n const rawValue = kvMatch[2].trim()\n\n if (rawValue === '' || rawValue === '|' || rawValue === '>') {\n // Could be a nested object or a block array — check next line\n const nextLine = lines[i + 1]\n if (nextLine && /^\\s+-\\s/.test(nextLine)) {\n // Block array\n const arr = []\n parent[key] = arr\n stack.push({ obj: arr, indent })\n } else {\n // Nested object\n const nested = {}\n parent[key] = nested\n stack.push({ obj: nested, indent })\n }\n } else {\n parent[key] = parseYamlValue(rawValue)\n }\n }\n }\n\n return root\n}\n\n/**\n * Parse a simple YAML scalar value.\n */\nfunction parseYamlValue(str) {\n if (str === 'true') return true\n if (str === 'false') return false\n if (str === 'null' || str === '~') return null\n\n // Inline array: [a, b, c]\n if (str.startsWith('[') && str.endsWith(']')) {\n return str.slice(1, -1).split(',').map(s => parseYamlValue(s.trim()))\n }\n\n // Quoted string\n if ((str.startsWith('\"') && str.endsWith('\"')) || (str.startsWith(\"'\") && str.endsWith(\"'\"))) {\n return str.slice(1, -1)\n }\n\n // Number\n const num = Number(str)\n if (!isNaN(num) && str !== '') return num\n\n return str\n}\n","/**\n * Built-in plugin registry for resolving plugin names from config files.\n *\n * Item 21: Uses lazy Map with dynamic import() on first use instead of\n * eagerly importing all 17 plugins at module load time.\n *\n * When plugins are specified as strings in a JSON/YAML config (e.g., `\"plugins\": [\"TablePlugin\", \"MathPlugin\"]`),\n * this resolver maps those names to the actual plugin factory functions.\n *\n * Users can also register custom plugins so they can be referenced by name in config files.\n */\n\n/**\n * Lazy-loaded built-in plugin factories. Each value is an async function\n * that dynamically imports the plugin module and returns the factory result.\n * @type {Map<string, (opts?: object) => Promise<object>>}\n */\nconst BUILTIN_PLUGINS = new Map([\n ['WordCountPlugin', async () => {\n const { WordCountPlugin } = await import('../plugins/builtins/WordCountPlugin.js')\n return WordCountPlugin\n }],\n ['AutolinkPlugin', async () => {\n const { AutolinkPlugin } = await import('../plugins/builtins/AutolinkPlugin.js')\n return AutolinkPlugin\n }],\n ['PlaceholderPlugin', async (opts) => {\n const { PlaceholderPlugin } = await import('../plugins/builtins/PlaceholderPlugin.js')\n return PlaceholderPlugin(opts?.placeholder)\n }],\n ['SyntaxHighlightPlugin', async (opts) => {\n const { SyntaxHighlightPlugin } = await import('../plugins/builtins/syntaxHighlight/index.js')\n return SyntaxHighlightPlugin(opts)\n }],\n ['TablePlugin', async (opts) => {\n const { TablePlugin } = await import('../plugins/builtins/tableFeatures/index.js')\n return TablePlugin(opts)\n }],\n ['BlockTemplatePlugin', async (opts) => {\n const { BlockTemplatePlugin } = await import('../plugins/builtins/BlockTemplatePlugin.js')\n return BlockTemplatePlugin(opts)\n }],\n ['CommentsPlugin', async (opts) => {\n const { CommentsPlugin } = await import('../plugins/builtins/commentsFeatures/index.js')\n return CommentsPlugin(opts)\n }],\n ['CalloutPlugin', async (opts) => {\n const { CalloutPlugin } = await import('../plugins/builtins/calloutFeatures/index.js')\n return CalloutPlugin(opts)\n }],\n ['LinkPlugin', async (opts) => {\n const { LinkPlugin } = await import('../plugins/builtins/linkFeatures/index.js')\n return LinkPlugin(opts)\n }],\n ['TemplatePlugin', async (opts) => {\n const { TemplatePlugin } = await import('../plugins/builtins/templateFeatures/index.js')\n return TemplatePlugin(opts)\n }],\n ['KeyboardPlugin', async (opts) => {\n const { KeyboardPlugin } = await import('../plugins/builtins/keyboardFeatures/index.js')\n return KeyboardPlugin(opts)\n }],\n ['DragDropPlugin', async (opts) => {\n const { DragDropPlugin } = await import('../plugins/builtins/dragDropFeatures/index.js')\n return DragDropPlugin(opts)\n }],\n ['MathPlugin', async (opts) => {\n const { MathPlugin } = await import('../plugins/builtins/mathFeatures/index.js')\n return MathPlugin(opts)\n }],\n ['TocPlugin', async (opts) => {\n const { TocPlugin } = await import('../plugins/builtins/tocFeatures/index.js')\n return TocPlugin(opts)\n }],\n ['AnalyticsPlugin', async (opts) => {\n const { AnalyticsPlugin } = await import('../plugins/builtins/analyticsFeatures/index.js')\n return AnalyticsPlugin(opts)\n }],\n ['SpellcheckPlugin', async (opts) => {\n const { SpellcheckPlugin } = await import('../plugins/builtins/spellcheckFeatures/index.js')\n return SpellcheckPlugin(opts)\n }],\n ['CollaborationPlugin', async (opts) => {\n const { CollaborationPlugin } = await import('../plugins/builtins/collaborationFeatures/index.js')\n return CollaborationPlugin(opts)\n }],\n])\n\n/**\n * Custom plugin registry for user-defined plugins that can be referenced by name.\n * @type {Map<string, Function>}\n */\nconst _customPluginFactories = new Map()\n\n/**\n * Register a custom plugin factory so it can be referenced by name in config files.\n *\n * @param {string} name - The plugin name (used in config files)\n * @param {Function} factory - A factory function that returns a plugin instance.\n * Called with `(options)` from the config. If no options, called with `undefined`.\n *\n * @example\n * ```js\n * import { registerPluginFactory } from '@remyxjs/core';\n *\n * registerPluginFactory('MyCustomPlugin', (opts) => MyCustomPlugin(opts));\n *\n * // Now usable in config files:\n * // { \"plugins\": [{ \"name\": \"MyCustomPlugin\", \"options\": { \"color\": \"red\" } }] }\n * ```\n */\nexport function registerPluginFactory(name, factory) {\n if (typeof name !== 'string' || !name) {\n throw new Error('registerPluginFactory: name must be a non-empty string')\n }\n if (typeof factory !== 'function') {\n throw new Error('registerPluginFactory: factory must be a function')\n }\n _customPluginFactories.set(name, factory)\n}\n\n/**\n * Unregister a previously registered custom plugin factory.\n * @param {string} name - The plugin name to remove\n */\nexport function unregisterPluginFactory(name) {\n _customPluginFactories.delete(name)\n}\n\n/**\n * Resolve a plugins config value into an array of plugin instances.\n *\n * Accepts:\n * - An array of strings: `[\"TablePlugin\", \"MathPlugin\"]`\n * - An array of objects: `[{ \"name\": \"TablePlugin\" }, { \"name\": \"CollaborationPlugin\", \"options\": { \"roomId\": \"doc-1\" } }]`\n * - A mixed array of strings, objects, and already-instantiated plugins\n * - `undefined` or `null` -> returns `undefined` (no plugins override)\n *\n * @param {Array|undefined|null} pluginsConfig - The plugins value from a config file\n * @returns {Promise<Array>|undefined} An array of resolved plugin instances, or undefined\n */\nexport function resolvePlugins(pluginsConfig) {\n if (!pluginsConfig || !Array.isArray(pluginsConfig)) {\n return undefined\n }\n\n return Promise.all(pluginsConfig.map((entry, index) => {\n // Already a plugin instance (function/object with init) -- pass through\n if (typeof entry === 'function' || (typeof entry === 'object' && entry !== null && typeof entry.init === 'function')) {\n return entry\n }\n\n // String name: \"TablePlugin\"\n if (typeof entry === 'string') {\n return resolveByName(entry)\n }\n\n // Object with name and optional options: { name: \"TablePlugin\", options: { ... } }\n if (typeof entry === 'object' && entry !== null && typeof entry.name === 'string') {\n return resolveByName(entry.name, entry.options)\n }\n\n throw new Error(`resolvePlugins: invalid plugin entry at index ${index} -- expected string, { name, options }, or plugin instance`)\n }))\n}\n\n/**\n * Look up a plugin by name and instantiate it with optional options.\n * @param {string} name\n * @param {object} [options]\n * @returns {Promise<*>} The plugin instance\n */\nasync function resolveByName(name, options) {\n // Check custom registry first (allows overriding built-ins)\n const customFactory = _customPluginFactories.get(name)\n if (customFactory) {\n return customFactory(options)\n }\n\n const builtinFactory = BUILTIN_PLUGINS.get(name)\n if (builtinFactory) {\n return builtinFactory(options)\n }\n\n throw new Error(`resolvePlugins: unknown plugin \"${name}\". Register it with registerPluginFactory() or import it directly.`)\n}\n"],"names":["EventBus","constructor","this","_listeners","Map","_keyedHandlers","_emitDepth","on","event","handler","options","has","set","Set","key","compositeKey","existing","get","off","add","handlers","delete","size","once","wrapper","args","emit","data","forEach","err","error","removeAllListeners","clear","BLOCK_LEVEL_TAGS","walkUpToBlock","node","boundary","el","nodeType","Node","TEXT_NODE","parentElement","tagName","closestBlock","root","closestTag","tag","toUpperCase","wrapInTag","range","attrs","document","createElement","Object","entries","value","setAttribute","surroundContents","fragment","extractContents","appendChild","insertNode","unwrapTag","element","parent","parentNode","firstChild","insertBefore","removeChild","generateId","Math","random","toString","substr","isBlockEmpty","block","textContent","trim","querySelector","HEADING_REGEX","FORMAT_TAG_MAP","bold","italic","underline","strikethrough","subscript","superscript","Selection","editorElement","editor","_cachedRange","_cacheGeneration","invalidateCache","getSelection","window","getRange","sel","rangeCount","getRangeAt","isWithinEditor","commonAncestorContainer","gen","Promise","resolve","then","setRange","removeAllRanges","addRange","contains","isCollapsed","getSelectedText","getSelectedHTML","cloneContents","div","innerHTML","save","preRange","createRange","selectNodeContents","setEnd","startContainer","startOffset","length","endOffset","collapsed","restore","bookmark","textWalker","createTreeWalker","NodeFilter","SHOW_TEXT","charCount","startNode","startNodeOffset","endNode","endNodeOffset","nextNode","currentNode","nextCount","setStart","min","collapse","fallbackRange","toEnd","collapseToEnd","collapseToStart","getParentElement","container","getParentBlock","getClosestElement","insertHTML","html","safeHtml","_sanitizer","sanitize","deleteContents","template","content","lastChild","newRange","setStartAfter","setSanitizer","sanitizer","wrapWith","unwrap","getActiveFormats","formats","heading","alignment","orderedList","unorderedList","blockquote","codeBlock","link","fontFamily","fontSize","foreColor","backColor","anchorNode","ELEMENT_NODE","format","toLowerCase","test","align","style","textAlign","getComputedStyle","href","text","target","queryCommandValue","getBoundingRect","getBoundingClientRect","CommandRegistry","engine","_commands","register","name","command","execute","isActive","isEnabled","shortcut","meta","skipSnapshot","keyboard","alternateShortcuts","alt","history","snapshot","result","eventBus","getAll","Array","from","keys","djb2Hash","str","hash","i","len","charCodeAt","applyDiff","baseStr","diff","prefixLen","suffixLen","insert","slice","History","maxSize","debounceMs","coalesceMs","_undoStack","_redoStack","_observer","_debounceTimer","_coalesceTimer","_isPerformingUndoRedo","_isCoalescing","_lastSnapshot","_lastHash","init","_takeSnapshot","MutationObserver","_debouncedSnapshot","observe","childList","characterData","subtree","destroy","disconnect","clearTimeout","setTimeout","_updateTopSnapshot","_createEntry","type","patch","oldStr","newStr","minLen","maxSuffix","computeDiff","_resolveEntry","stack","index","entry","baseIndex","selection","push","shift","_disconnectObserver","_reconnectObserver","undo","canUndo","currentHtml","currentBookmark","stateIndex","stateHtml","stateBookmark","pop","redo","canRedo","_isMac","isMac","navigator","platform","getModKey","KeyboardManager","_shortcuts","_normalizeCache","_handleKeyDown","bind","addEventListener","removeEventListener","commandName","normalized","_normalizeShortcut","unregister","getShortcutForCommand","getShortcutLabel","split","map","p","join","sort","e","parts","metaKey","ctrlKey","shiftKey","altKey","includes","preventDefault","stopPropagation","commands","ALLOWED_TAGS","h1","h2","h3","h4","h5","h6","strong","b","em","u","s","del","sub","sup","a","img","ul","ol","li","table","thead","tbody","tr","th","td","pre","code","hr","br","span","input","label","mark","iframe","ALLOWED_STYLES","JS_PROTOCOL_REGEX","CSS_INJECTION_REGEX","DANGEROUS_REMOVE_TAGS","DEFAULT_IFRAME_ALLOWED_DOMAINS","isAllowedIframeDomain","src","allowedDomains","url","URL","protocol","hostname","some","domain","d","endsWith","Sanitizer","allowedTags","allowedStyles","iframeAllowedDomains","_cache","_parser","DOMParser","cacheKey","cached","output","doc","parseFromString","_cleanNode","body","firstKey","next","childNodes","child","COMMENT_NODE","allowedAttrs","attributes","attr","startsWith","removeAttribute","_cleanStyles","hasAttribute","getAttribute","cleanedStyles","prop","getPropertyValue","BULLET_PATTERN","NUMBER_PATTERN","LETTER_PATTERN","INDENT_PATTERN","LIST_PREFIX_PATTERN","cleanPastedHTML","cleaned","replace","isWord","isGoogleDocs","isGoogleSheets","isLibreOffice","isApplePages","paragraphs","querySelectorAll","inList","listType","listEl","hasIndent","isBullet","isNumber","convertWordListParagraphs","MD_HEADING","MD_UNORDERED","MD_ORDERED","MD_TASK","MD_BLOCKQUOTE","MD_HR","MD_CODE_FENCE","MD_BOLD","MD_BOLD_ALT","MD_ITALIC","MD_LINK","MD_IMAGE","MD_TABLE","MD_TABLE_SEP","MD_INLINE_CODE","looksLikeMarkdown","lines","markdownSignals","totalNonEmptyLines","line","trimmed","escapeHTML","escapeHTMLAttr","getExtension","filename","dot","lastIndexOf","readAsText","file","reject","reader","FileReader","onload","onerror","Error","readAsArrayBuffer","escapeHtml","FORMAT_MAP","EXTENSION_MAP","detectFormat","ext","isImportableFile","getSupportedExtensions","getSupportedFormatNames","async","convertDocument","converters","docx","import","pdf","markdown","csv","rtf","mod","raw","default","DEFAULT_TOOLBAR","DEFAULT_FONTS","DEFAULT_FONT_SIZES","DEFAULT_COLORS","DEFAULT_MENU_BAR","items","HEADING_OPTIONS","exceedsMaxFileSize","maxBytes","context","toFixed","turndownInstance","markedConfigured","SAFE_PROTOCOLS","isSafeUrl","decoded","decodeURIComponent","htmlToMarkdown","TurndownService","headingStyle","bulletListMarker","codeBlockStyle","fence","emDelimiter","strongDelimiter","linkStyle","use","gfm","addRule","filter","replacement","nodeName","classList","checked","turndown","markdownToHtml","md","marked","setOptions","breaks","renderer","Renderer","title","tokens","Parser","parseInline","image","lang","langAttr","ensureMarkedConfigured","parse","insertPlainText","outputFormat","parsedHtml","formatted","para","Clipboard","_handlePaste","_handleCopy","_handleCut","clipboardData","getData","files","imageFile","find","f","_handleImagePaste","importableFiles","validFiles","_exceedsMaxFileSize","chain","sanitized","catch","nonImageFiles","uploadHandler","filesize","caretCell","_getCaretCell","closest","_looksLikeTSV","_pasteIntoTable","tsvFromHtml","_htmlTableToTSV","_handleTextPaste","anchor","focus","focusNode","anchorTable","focusTable","rows","tsvLines","htmlRows","row","cells","tsvCells","htmlCells","cell","tsv","setData","maxFileSize","c","startCell","allRows","startRow","startRowIdx","indexOf","startColIdx","prev","previousElementSibling","colSpan","r","rowIdx","firstRow","colCount","newRow","colIdx","otherRow","idx","otherTd","onprogress","lengthComputable","loaded","total","percent","round","readAsDataURL","REMYX_MIME","DRAGGABLE_BLOCKS","REORDERABLE_CHILDREN","_editorRegistry","WeakMap","_activeInstances","DragDrop","_dragSource","_dragSourceEditor","_dropTarget","_dropPosition","_ghostEl","_dropIndicator","_isExternalDrag","_enterCount","_blockRects","_scrollHandler","_handleDragOver","_handleDrop","_handleDragEnter","_handleDragLeave","_handleDragStart","_handleDragEnd","_cleanupDrag","getDraggableBlock","_closestDraggableBlock","makeBlockDraggable","unmakeBlockDraggable","isDragging","getDragState","isExternalDrag","dropTarget","dropPosition","_getReorderableItem","outerHTML","dataTransfer","effectAllowed","_createGhostPreview","_cacheBlockRects","instance","remove","dropEffect","_updateDropTarget","isExternal","types","_removeDropIndicator","_handleBlockDrop","imageFiles","_handleImageDrop","_handleDocumentDrop","_handleFileDrop","remyxContent","_handleInterEditorDrop","_setCursorAtDropPoint","_handleTextDrop","source","position","sourceEditor","sourceReorderable","targetReorderable","nextSibling","insertionParent","blocks","_getTopLevelBlocks","passive","_getBlockRect","children","mouseY","clientY","closestPosition","closestDistance","Infinity","rect","midY","top","height","distTop","abs","distBottom","bottom","_showDropIndicator","targetBlock","className","editorEl","editorRect","blockRect","scrollTop","ghost","cloneNode","left","width","offsetWidth","pointerEvents","offsetX","clientX","offsetY","setDragImage","_removeGhostPreview","ev","caretRangeFromPoint","createPluginAPI","pluginName","manager","executeCommand","getHTML","getText","isEmpty","getSetting","getPluginSetting","setSetting","setPluginSetting","_getRegistry","_getActiveInstances","_globalRegistry","registerPluginInRegistry","unregisterPluginFromRegistry","listRegisteredPlugins","values","searchPluginRegistry","query","q","description","tags","t","PluginManager","_plugins","_initialized","_commandPluginMap","_settings","_lifecycleUnsubs","settings","plugin","defaultSettings","settingsSchema","schema","o","validate","getPluginSettings","k","v","_resolveDependencyOrder","plugins","nameSet","graph","inDegree","deps","dependencies","queue","degree","sorted","other","circular","remaining","pluginMap","cmd","lazy","originalExecute","self","wrappedCmd","activatePlugin","apply","_initPlugin","api","requiresFullAccess","unsubs","onContentChange","hook","onSelectionChange","dep","initAll","nonLazy","cycle","destroyAll","unsub","isInitialized","_selectionHandlers","_selectionListenerAttached","_sharedSelectionChangeHandler","activeElement","_handleSelectionChange","EditorEngine","isSourceMode","isMarkdownMode","_isDestroyed","_htmlDirty","_lastHTML","_textCache","_textCacheDirty","clipboard","dragDrop","_handleInput","_handleFocus","_handleBlur","_handleClick","phase","setHTML","blur","getWordCount","getCharCount","EditorBus","_bus","_registry","id","getEditor","getEditorIds","editorCount","broadcast","exclude","reset","TOOLBAR_PRESETS","full","standard","minimal","bare","rich","removeToolbarItems","config","itemsToRemove","removeSet","group","item","addToolbarItems","itemsArray","isArray","after","before","insertAt","splice","gi","createToolbar","CATEGORY_ORDER","itemSet","category","BUTTON_COMMANDS","TOOLTIP_MAP","alignLeft","alignCenter","alignRight","alignJustify","taskList","horizontalRule","removeFormat","indent","outdent","attachment","importDocument","embedMedia","findReplace","fullscreen","sourceMode","toggleMarkdown","export","distractionFree","toggleSplitView","lineHeight","letterSpacing","paragraphSpacing","saveColorPreset","typography","headings","commandPalette","insertCallout","insertMath","insertToc","insertBookmark","insertMergeTag","addComment","scanBrokenLinks","getAnalytics","toggleAnalytics","toggleSpellcheck","checkGrammar","startCollaboration","stopCollaboration","SHORTCUT_MAP","insertLink","MODAL_COMMANDS","getCommandActiveState","selectionState","DEFAULT_KEYBINDINGS","selectAll","SharedResources","_sanitizerSchema","_toolbarPresets","_defaults","_keybindings","_icons","_ensureInit","freeze","fromEntries","groups","g","toolbar","menuBar","fonts","fontSizes","colors","headingOptions","buttons","tooltips","shortcuts","modals","sanitizerSchema","toolbarPresets","defaults","keybindings","registerIcon","svgOrUrl","unregisterIcon","getIcon","getIconNames","stats","registeredIcons","frozenSchemas","toggleInlineTag","docFrag","createDocumentFragment","replaceChild","ancestor","isInsideTag","registerFormattingCommands","eng","icon","tooltip","color","colorMap","yellow","green","blue","pink","orange","purple","bg","backgroundColor","inlineTags","walker","SHOW_ELEMENT","toUnwrap","intersectsNode","getEffectiveLevel","logicalLevel","baseHeadingLevel","offset","convertBlock","newTag","newEl","registerHeadingCommands","baseLevel","level","Number","effectiveLevel","registerAlignmentCommands","registerListCommands","existingOl","unwrapList","existingUl","convertListType","wrapInList","cb","checkbox","prevLi","maxDepth","maxListNestingDepth","depth","listTag","subList","parentList","grandParentLi","list","newList","DANGEROUS_PROTOCOL","validateUrl","registerLinkCommands","selectedText","rel","displayText","escapeAttr","linkEl","registerImageCommands","location","pathname","maxWidth","float","margin","display","registerTableCommands","cols","headerRow","rowSpan","newThead","direction","createEmptyRow","getColumnCount","colIndex","getCellIndex","refCell","firstCell","rowIndex","fragments","columnIndex","dataType","comparator","sortKeys","getCell","customComparator","tableSortComparator","valA","valB","detectSortDataType","parseFloat","Date","getTime","localeCompare","sensitivity","ths","String","filterValue","filters","val","rawValue","num","isNaN","date","Intl","DateTimeFormat","dateStyle","decimals","NumberFormat","currency","minimumFractionDigits","maximumFractionDigits","evaluateTableFormulas","colspan","rowspan","cellTag","newCell","targetColIndex","currentRow","nextElementSibling","rowCellTag","insertRef","colAccum","count","numericCount","dateCount","isFinite","FORMULA_FUNCTIONS","SUM","flat","reduce","AVERAGE","COUNT","MIN","MAX","max","IF","CONCAT","parseCellRef","ref","match","col","parseInt","getTableCellValue","evaluateFormula","formulaStr","evalStack","formula","ch","ident","end","tokenizeFormula","pos","peek","parseExpression","parseTerm","op","right","parseComparison","NaN","parseFactor","token","cellKey","cellFormula","refs","rangeStr","start","expandRange","funcName","fn","arg","BLOCK_TAGS","getTopLevelBlock","registerBlockCommands","bq","language","clone","ensureMinimumContent","firstElementChild","summary","n","details","selected","BLOCK_TYPE_MAP","paragraph","heading1","heading2","heading3","heading4","heading5","heading6","registerBlockConvertCommands","to","getFocusedBlock","targetTag","extractBlockContent","newBlock","info","temp","createTargetBlock","cursorTarget","wrapWithStyle","property","getSelectionStyle","registerFontCommands","family","marginBottom","registerMediaCommands","embedUrl","getEmbedUrl","registerFindReplaceCommands","currentMatches","currentIndex","clearHighlights","createTextNode","normalize","pruneStaleMatches","m","isConnected","highlightCurrent","scrollIntoView","behavior","caseSensitive","searchText","matches","textNodes","flags","regex","RegExp","nodeMatches","exec","j","reverse","findMatches","current","replaceText","textNode","registerSourceModeCommands","fullscreenCount","registerFullscreenCommands","isFullscreen","overflow","hideTimer","registerDistractionFreeCommands","showChrome","active","currentTarget","registerSplitViewCommands","STORAGE_KEY","loadColorPresets","localStorage","getItem","parsed","JSON","presets","setItem","stringify","deleteColorPreset","registerColorPresetCommands","_eng","registerMarkdownToggleCommands","toggle","markdownMode","registerAttachmentCommands","sizeLabel","bytes","units","unitIndex","formatFileSize","registerImportDocumentCommands","mode","RECENT_STORAGE_KEY","getRecentCommands","recordRecentCommand","commandId","recent","unshift","clearRecentCommands","removeItem","_customItems","registerCommandItems","arr","findIndex","unregisterCommandItem","getCustomCommandItems","SLASH_COMMAND_ITEMS","keywords","action","_engine","openModal","latex","displayMode","filterSlashItems","pinRecent","recentIds","recentItems","labelMatches","otherMatches","message","createEnvelope","metadata","timestamp","now","version","parseEnvelope","json","LocalStorageProvider","prefix","_key","load","saveSync","SessionStorageProvider","sessionStorage","FileSystemProvider","writeFn","readFn","deleteFn","envelope","CloudProvider","endpoint","headers","method","fetchFn","buildUrl","buildBody","buildLoadUrl","buildDeleteUrl","_fetch","fetch","globalThis","_saveUrl","_loadUrl","sep","encodeURIComponent","_deleteUrl","_fetchWithRetry","opts","retries","res","ok","status","statusText","CustomProvider","_save","_load","_clear","createStorageProvider","_managerRegistry","AutosaveManager","provider","interval","debounce","enabled","_intervalTimer","_isSaving","_pendingSave","_lastSavedContent","_contentChangeHandler","_beforeUnloadHandler","_destroyed","_consecutiveErrors","_maxRetries","maxRetries","_scheduleDebouncedSave","_unsubContentChange","setInterval","_attemptSyncSave","clearInterval","finally","retryCount","fatal","delay","pow","checkRecovery","currentContent","stored","storedNorm","currentNorm","recoveredContent","clearRecovery","sendBeacon","createPlugin","definition","author","toolbarItems","statusBarItems","contextMenuItems","WordCountPlugin","debounceTimer","unsubContentChange","update","wordCount","_wordCount","COMBINED_URL_REGEX","PROTOCOL_BEFORE_REGEX","PROTOCOL_OR_WWW_BEFORE_REGEX","COMMON_TLDS","extractTLD","AutolinkPlugin","_handler","MAX_URL_LENGTH","searchStart","best","matchAll","isProtocol","isWww","isBare","candidate","startIdx","endIdx","tld","findLastURLMatch","focusOffset","PlaceholderPlugin","placeholderText","runRules","rules","plain","flush","matched","re","cls","lastIndex","kw","words","keywordMatcher","word","keywordMatcherI","w","applyKeywordSets","matchers","WORD_RE","tok","_jsMatchers","JS_RULES","tokenizeJavaScript","_pyMatchers","PY_RULES","tokenizePython","CSS_RULES","_sqlMatchers","SQL_RULES","JSON_RULES","BASH_RULES","tokenizeBash","_rustMatchers","RUST_RULES","tokenizeRust","GO_RULES","tokenizeGo","_javaMatchers","JAVA_RULES","HTML_RULES","tokenizeHTML","tokenizePlainText","LANGUAGE_MAP","javascript","js","jsx","typescript","ts","tsx","python","py","css","sql","bash","sh","shell","zsh","rust","rs","go","golang","java","htm","xml","svg","plaintext","txt","SUPPORTED_LANGUAGES","registerLanguage","tokenizer","aliases","alias","l","unregisterLanguage","tokenize","detectLanguage","trimStart","first200","getCurrentCodeBlock","SyntaxHighlightPlugin","observer","blurHandler","languageChangeUnsub","copyClickHandler","highlightCodeElement","codeEl","updateLineNumbers","show","gutter","lineCount","contentEditable","nums","ensureCopyButton","btn","highlightAll","focusedPre","getFocusedPre","pres","codes","highlightInlineCode","mutations","needsHighlight","mutation","addedNodes","attributeName","scheduleHighlight","attributeFilter","writeText","textarea","opacity","select","execCommand","attachFilterUI","filterBtns","activeDropdown","createFilterButtons","removeFilterButtons","closeDropdown","dropdown","placeholder","applyFilter","clearBtn","closeOnOutsideClick","toggleFilterDropdown","TablePlugin","tableMap","formulaTimer","sortClickHandler","formulaFocusHandler","formulaBlurHandler","setupTable","resize","handles","startX","startY","startWidth","startHeight","targetCol","targetRow","positionHandles","tableRect","handle","cellRect","HANDLE_WIDTH","offsetHeight","rowRect","onColMouseDown","onColMouseMove","onColMouseUp","delta","newWidth","onRowMouseDown","onRowMouseMove","onRowMouseUp","newHeight","removeHandles","h","createColumnHandles","attachResizeHandles","teardownTable","setupAllTables","needsSetup","removedNodes","currentDir","newDir","existingKeys","dir","BUILT_IN_TEMPLATES","BlockTemplatePlugin","templates","_blockTemplates","registerTemplate","htmlString","insertTemplate","frag","lastElementChild","getTemplates","removeTemplate","_nextId","parseMentions","CommentsPlugin","onComment","onResolve","onDelete","onReply","mentionUsers","commentOnly","threads","unsubDestroy","syncTimer","getAllThreads","createdAt","getThread","getUnresolvedThreads","resolved","getResolvedThreads","thread","mentions","updatedAt","replies","replyToComment","threadId","reply","resolveComment","deleteComment","editComment","newBody","navigateToComment","importThreads","importedThreads","exportThreads","syncWithDOM","getMentionUsers","params","when","_comments","existingMarks","handleClick","cleanup","_calloutTypes","BUILTIN_TYPES","registerCalloutType","typeDef","unregisterCalloutType","getCalloutTypes","getCalloutType","GFM_ALERT_MAP","NOTE","TIP","IMPORTANT","WARNING","CAUTION","parseGFMAlert","keyword","createCalloutElement","collapsible","header","titleEl","CalloutPlugin","callout","scanForGFMAlerts","blockquotes","alert","findAncestor","newType","oldType","_callouts","bodies","URL_REGEX","EMAIL_REGEX","PHONE_REGEX","detectLinks","results","emailStart","slugify","_previewEl","hidePreview","LinkPlugin","onLinkClick","onUnfurl","onSuggest","onBrokenLink","validateLink","scanInterval","autoLink","showPreviews","scanTimer","hoverTimer","_unfurlCache","_brokenLinks","handleAutoLink","links","substring","last","endPos","linkRange","space","afterBr","handleLinkClick","handleMouseOver","showPreview","handleMouseOut","scanForBrokenLinks","anchors","urlAnchors","batch","all","broken","syncBrokenLinks","anchorId","bookmarkId","bookmarks","_links","getBrokenLinks","getBookmarks","els","clearUnfurlCache","TAG_REGEX","BLOCK_IF_REGEX","BLOCK_EACH_REGEX","renderTemplate","_","rendered","extractTags","_templateLibrary","BUILTIN_TEMPLATES","sampleData","recipient_name","sender_name","sender_title","invoice_number","client_name","client_address","item_1","qty_1","price_1","recipient_address","findings","recommendations","newsletter_name","edition","headline","lead_story","has_coupon","coupon_code","discount","closing","unregisterTemplate","getTemplateLibrary","getTemplate","textToChips","chipsToText","TemplatePlugin","previewMode","currentData","chip","templateId","tmpl","mergedData","_templateBackup","_templates","setPreviewData","getPreviewData","isPreviewMode","PAIR_MAP","CLOSE_CHARS","VIM_MODES","createVimState","pending","EMACS_BINDINGS","getHeadings","charAt","selectNextOccurrence","searchFrom","nodeLen","localOffset","KeyboardPlugin","keyBindings","autoPair","jumpToHeading","enableJumpToHeading","vimState","killRing","handleKeyDown","modify","handleVimKey","combo","handleEmacsKey","buildCombo","handleAutoPair","closer","newMode","_keyboard","getVimMode","DROP_ZONE_CLASS","DROP_ZONE_ACTIVE_CLASS","DROP_INDICATOR_CLASS","DRAGGING_CLASS","getDropTarget","y","closestDist","dist","DragDropPlugin","onDrop","onFileDrop","allowExternalDrop","showDropZone","enableReorder","dropIndicator","draggedBlock","ghostEl","handleDragStart","cssText","handleDragOver","handleDragLeave","relatedTarget","handleDrop","cleanupDrag","handleDragEnd","handleMouseDown","draggable","topBlock","indicator","createDropIndicator","_dragDrop","SYMBOL_PALETTE","symbols","getSymbolPalette","INLINE_REGEX","BLOCK_REGEX","parseMathExpressions","latexToMathML","defaultRenderMath","MathPlugin","renderMath","autoRender","numbering","equationCounter","renderMathElement","isBlock","renderAllMath","eqNumber","createMathElement","equationNumber","_math","getEquationCount","mathEl","_idCounter","buildOutline","slug","number","assignNumbers","flattenOutline","outline","walk","renderTocHTML","linkPrefix","renderItems","validateHeadingHierarchy","flatItems","warnings","curr","TocPlugin","onOutlineChange","currentOutline","updateOutline","headingId","CSS","escape","_toc","getOutline","countSyllables","vowelGroups","splitSentences","getWords","fleschKincaid","sentences","syllables","fleschReadingEase","gunningFog","complexWords","colemanLiau","chars","vocabularyLevel","gradeLevel","analyzeContent","wordsPerMinute","targetWordCount","maxSentenceLength","maxParagraphLength","splitParagraphs","baseStats","fk","fre","gf","cl","readingTimeMinutes","readingTimeSeconds","longSentences","longParagraphs","goalProgress","percentage","sentenceCount","paragraphCount","syllableCount","complexWordCount","readability","readingTime","minutes","ceil","seconds","keywordDensity","density","positions","seoAnalysis","targetKeyword","h1Count","hints","keywordInfo","headingCount","AnalyticsPlugin","onAnalytics","currentStats","lastAnalyzedText","updateAnalytics","_analyticsVisible","visible","_analytics","getStats","PASSIVE_AUXILIARIES","PAST_PARTICIPLE_SUFFIXES","detectPassiveVoice","issues","nextIdx","passiveStart","passiveEnd","passiveText","suggestions","rule","WORDINESS_RULES","pattern","detectWordiness","CLICHES","detectCliches","lower","cliche","detectPunctuationIssues","doubleSpace","repeatedPunc","missingSpace","STYLE_PRESETS","formal","passiveVoice","wordiness","cliches","punctuation","casual","technical","academic","analyzeGrammar","preset","stylePreset","summarizeIssues","grammar","byRule","issue","DICTIONARY_STORAGE_KEY","IGNORED_STORAGE_KEY","loadPersistedDictionary","persistDictionary","SpellcheckPlugin","enabledOnInit","grammarRules","customService","dictionary","initialDictionary","persistent","onError","onCorrection","currentStylePreset","currentLanguage","currentErrors","ignoredWords","loadPersistedIgnored","clearMarks","marks","runCheck","errors","grammarIssues","check","serviceIssues","totalOffset","sortedErrors","errorEnd","tn","applyMarks","debouncedCheck","addToDictionary","removeFromDictionary","getDictionary","ignoreWord","getIgnoredWords","applyCorrection","original","setWritingStyle","getWritingStyle","setLanguage","getLanguage","getSpellcheckStats","dictionarySize","ignoredCount","handleContextMenu","suggestionsRaw","_spellcheck","getErrors","offsetToRange","startSet","rangeToOffset","CrdtEngine","userId","_clock","_pendingOps","_seenOps","_maxSeenOps","_suppressRemote","_lastTextContent","_trackOp","opId","excess","iter","_tickLocal","_merge","remoteClock","uid","seq","captureOperations","currentText","previousText","ops","clock","_findInsert","_findDelete","_findReplace","oldText","newText","insertedLength","endNew","_transformPosition","applyRemoteOperations","newOps","sumA","sumB","_applyOp","queueOperation","flushQueue","hasPendingOps","initTextContent","getState","pendingOps","seenOps","AwarenessProtocol","userName","userColor","_localState","cursor","lastActive","_remoteStates","_broadcastInterval","_idleTimer","_cursorsContainer","_renderRafId","updateLocalCursor","setStatus","resetIdleTimer","applyRemoteAwareness","state","removeUser","getCollaborators","renderRemoteCursors","cancelAnimationFrame","requestAnimationFrame","_doRender","rects","getClientRects","startRect","caret","highlight","clearRemoteCursors","getLocalState","startBroadcasting","sendFn","intervalMs","stopBroadcasting","WebSocketTransport","_url","_reconnect","reconnect","_reconnectInterval","reconnectInterval","_maxAttempts","maxReconnectAttempts","_maxDelay","maxReconnectDelay","_ws","_messageHandler","_connectHandler","_disconnectHandler","_reconnectTimer","_reconnectAttempts","_intentionalClose","connect","readyState","WebSocket","OPEN","_scheduleReconnect","onopen","onmessage","msg","onclose","CONNECTING","close","send","onMessage","onConnect","onDisconnect","CollaborationPlugin","signalingServer","transport","customTransport","roomId","autoConnect","broadcastInterval","onUserJoin","onUserLeave","onSync","onConflict","crdtEngine","awareness","mutationObserver","connected","handleMessage","_isRemoteOperation","operations","opCount","getConnectionStatus","setUserInfo","_collaboration","getCrdtState","characterDataOldValue","unsubSelection","unsubContent","WorkerPool","_maxWorkers","maxWorkers","hardwareConcurrency","_workers","_queue","_pending","_roundRobin","_enabled","Worker","_workerURL","workerURL","taskType","_executeFallback","_spawnWorker","worker","postMessage","fallback","workerCount","terminate","VirtualScroller","_container","_threshold","threshold","_rootMargin","rootMargin","_active","_collapsed","_placeholders","IntersectionObserver","_handleIntersection","_scan","_getBlockElements","isIntersecting","_restore","_collapse","minHeight","restoreAll","refresh","collapsedCount","triggerDownload","blob","createObjectURL","download","click","revokeObjectURL","exportAsMarkdown","Blob","exportAsPDF","htmlContent","srcdoc","fallbackTimer","contentWindow","onafterprint","print","exportAsDocx","removeFonts","fontsToRemove","addFonts","fontsToAdd","newFonts","loadGoogleFonts","fontFamilies","dataset","remyxFonts","integrity","crossOrigin","head","BLOCK_ELEMENTS","VOID_ELEMENTS","PREFORMATTED","formatHTML","formatNode","repeat","isVoid","currentIndent","openTag","buildOpenTag","isInlineContent","innerContent","serializeInlineContent","RTL_REGEX","LTR_REGEX","detectTextDirection","rtlCount","ltrCount","char","applyAutoDirection","applyAutoDirectionAll","LOGICAL_PROPERTIES","marginStart","marginEnd","paddingStart","paddingEnd","borderStart","borderEnd","insetStart","insetEnd","en","_currentLocale","_locales","_currentLang","vars","setLocale","locale","getLocale","registerLocale","strings","unregisterLocale","getRegisteredLocales","batchDOMMutations","scheduleIdleTask","timeout","requestIdleCallback","cancelIdleTask","cancelIdleCallback","rafThrottle","frameId","measurePerformance","performance","console","debug","benchmark","iterations","times","mean","median","floor","p95","createInputBatcher","applyFn","flushMs","timer","THEME_VARIABLES","var","textSecondary","border","borderSubtle","toolbarBg","toolbarBorder","toolbarButtonHover","toolbarButtonActive","toolbarIcon","toolbarIconActive","primary","primaryHover","primaryLight","focusRing","danger","dangerLight","modalBg","modalOverlay","statusbarBg","statusbarText","hoverBg","shadowSm","shadowMd","shadowLg","shadowFloat","contentFontSize","contentLineHeight","radius","radiusSm","radiusInner","spacingXs","spacingSm","spacingMd","transitionFast","transitionNormal","KEY_TO_VAR","createTheme","overrides","THEME_PRESETS","ocean","forest","sunset","rose","TOOLBAR_ITEM_STYLE_KEYS","background","hoverColor","hoverBackground","activeColor","activeBackground","borderRadius","iconSize","padding","ITEM_KEY_TO_VAR","SEPARATOR_KEY_TO_VAR","resolveToolbarItemStyle","hasKeys","resolveSeparatorStyle","createToolbarItemTheme","_separator","defineConfig","editors","loadConfig","env","signal","response","Accept","parseSimpleYaml","envOverrides","_envKey","baseConfig","deepMerge","obj","arrayMatch","parseYamlValue","kvMatch","nextLine","nested","BUILTIN_PLUGINS","WordCountPlugin$1","AutolinkPlugin$1","PlaceholderPlugin$1","BlockTemplatePlugin$1","_customPluginFactories","registerPluginFactory","factory","unregisterPluginFactory","resolvePlugins","pluginsConfig","resolveByName","customFactory","builtinFactory"],"mappings":"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"}
|