@remyxjs/react 1.0.0-beta → 1.0.4-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/dist/{AttachmentModal-D-uxbXvb.cjs → AttachmentModal-BDFFiZJt.cjs} +2 -2
- package/dist/{AttachmentModal-D-uxbXvb.cjs.map → AttachmentModal-BDFFiZJt.cjs.map} +1 -1
- package/dist/{AttachmentModal--6T-vYJt.js → AttachmentModal-Di5V6QX-.js} +2 -2
- package/dist/{AttachmentModal--6T-vYJt.js.map → AttachmentModal-Di5V6QX-.js.map} +1 -1
- package/dist/{ContextMenu-B4K3Zbfp.cjs → ContextMenu-BafbP2Uh.cjs} +2 -2
- package/dist/{ContextMenu-B4K3Zbfp.cjs.map → ContextMenu-BafbP2Uh.cjs.map} +1 -1
- package/dist/{ContextMenu-D8wNSMc3.js → ContextMenu-DsA0YsY7.js} +2 -2
- package/dist/{ContextMenu-D8wNSMc3.js.map → ContextMenu-DsA0YsY7.js.map} +1 -1
- package/dist/{EmbedModal-Bh2Tcow9.cjs → EmbedModal-C73lODIT.cjs} +2 -2
- package/dist/{EmbedModal-Bh2Tcow9.cjs.map → EmbedModal-C73lODIT.cjs.map} +1 -1
- package/dist/{EmbedModal-cxE4maD2.js → EmbedModal-alpHQiAP.js} +2 -2
- package/dist/{EmbedModal-cxE4maD2.js.map → EmbedModal-alpHQiAP.js.map} +1 -1
- package/dist/{ExportModal-Cf1uE4r_.js → ExportModal-BN8LUaSR.js} +2 -2
- package/dist/{ExportModal-Cf1uE4r_.js.map → ExportModal-BN8LUaSR.js.map} +1 -1
- package/dist/{ExportModal-DwQVsrZE.cjs → ExportModal-Cu-cCwa6.cjs} +2 -2
- package/dist/{ExportModal-DwQVsrZE.cjs.map → ExportModal-Cu-cCwa6.cjs.map} +1 -1
- package/dist/{FindReplaceModal-Dgt_MrWb.js → FindReplaceModal-BqNcotUr.js} +2 -2
- package/dist/{FindReplaceModal-Dgt_MrWb.js.map → FindReplaceModal-BqNcotUr.js.map} +1 -1
- package/dist/{FindReplaceModal-DYL_2z8U.cjs → FindReplaceModal-DgysYT04.cjs} +2 -2
- package/dist/{FindReplaceModal-DYL_2z8U.cjs.map → FindReplaceModal-DgysYT04.cjs.map} +1 -1
- package/dist/{ImageModal-D39ywxqI.cjs → ImageModal-BzgYXY9y.cjs} +2 -2
- package/dist/{ImageModal-D39ywxqI.cjs.map → ImageModal-BzgYXY9y.cjs.map} +1 -1
- package/dist/{ImageModal-DqScpPrc.js → ImageModal-bCzSSTmd.js} +2 -2
- package/dist/{ImageModal-DqScpPrc.js.map → ImageModal-bCzSSTmd.js.map} +1 -1
- package/dist/{ImportDocumentModal-BKqMxO3z.js → ImportDocumentModal-BypTEn2i.js} +4 -4
- package/dist/{ImportDocumentModal-BKqMxO3z.js.map → ImportDocumentModal-BypTEn2i.js.map} +1 -1
- package/dist/{ImportDocumentModal-Bev9hp_J.cjs → ImportDocumentModal-gCFept9p.cjs} +2 -2
- package/dist/{ImportDocumentModal-Bev9hp_J.cjs.map → ImportDocumentModal-gCFept9p.cjs.map} +1 -1
- package/dist/{LinkModal-k9IeDtAb.js → LinkModal-CmkK4m-k.js} +3 -3
- package/dist/{LinkModal-k9IeDtAb.js.map → LinkModal-CmkK4m-k.js.map} +1 -1
- package/dist/{LinkModal-B-igSa-g.cjs → LinkModal-DPxg7YCE.cjs} +2 -2
- package/dist/{LinkModal-B-igSa-g.cjs.map → LinkModal-DPxg7YCE.cjs.map} +1 -1
- package/dist/{MenuBar-DWxJNHmb.js → MenuBar-DOv7JPwR.js} +2 -2
- package/dist/{MenuBar-DWxJNHmb.js.map → MenuBar-DOv7JPwR.js.map} +1 -1
- package/dist/{MenuBar-B-ZAX9rH.cjs → MenuBar-Lw_5Jp8u.cjs} +2 -2
- package/dist/{MenuBar-B-ZAX9rH.cjs.map → MenuBar-Lw_5Jp8u.cjs.map} +1 -1
- package/dist/{ModalOverlay-BDsGgv3_.cjs → ModalOverlay-Dh5quv7X.cjs} +2 -2
- package/dist/{ModalOverlay-BDsGgv3_.cjs.map → ModalOverlay-Dh5quv7X.cjs.map} +1 -1
- package/dist/{ModalOverlay-CLvRNHmp.js → ModalOverlay-Dt0JiW3M.js} +2 -2
- package/dist/{ModalOverlay-CLvRNHmp.js.map → ModalOverlay-Dt0JiW3M.js.map} +1 -1
- package/dist/{SourceModal-BNI_i4iW.cjs → SourceModal-CHKC5xcv.cjs} +2 -2
- package/dist/{SourceModal-BNI_i4iW.cjs.map → SourceModal-CHKC5xcv.cjs.map} +1 -1
- package/dist/{SourceModal-MdTGK3Uf.js → SourceModal-ChhNVz7y.js} +2 -2
- package/dist/{SourceModal-MdTGK3Uf.js.map → SourceModal-ChhNVz7y.js.map} +1 -1
- package/dist/{TablePickerModal-DYODWEA1.js → TablePickerModal-BnZHyMsk.js} +2 -2
- package/dist/{TablePickerModal-DYODWEA1.js.map → TablePickerModal-BnZHyMsk.js.map} +1 -1
- package/dist/{TablePickerModal-Do1QyoyM.cjs → TablePickerModal-Dr0wUx_h.cjs} +2 -2
- package/dist/{TablePickerModal-Do1QyoyM.cjs.map → TablePickerModal-Dr0wUx_h.cjs.map} +1 -1
- package/dist/index-Bg_uAWlM.js +3 -0
- package/dist/index-Bg_uAWlM.js.map +1 -0
- package/dist/index-DL-qBZZU.js +357 -0
- package/dist/index-DL-qBZZU.js.map +1 -0
- package/dist/index-OfJxpaev.cjs +2 -0
- package/dist/index-OfJxpaev.cjs.map +1 -0
- package/dist/index-qh1Yzh-l.cjs +2 -0
- package/dist/index-qh1Yzh-l.cjs.map +1 -0
- package/dist/remyx-react.cjs +1 -1
- package/dist/remyx-react.css +1 -1
- package/dist/remyx-react.js +1 -1
- package/package.json +3 -3
- package/dist/index-C720tbJA.js +0 -359
- package/dist/index-C720tbJA.js.map +0 -1
- package/dist/index-Dc63uIP0.cjs +0 -2
- package/dist/index-Dc63uIP0.cjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-Bg_uAWlM.js","sources":["../../../remyxjs/plugins/analytics/AnalyticsPlugin.js","../../../remyxjs/plugins/block-templates/BlockTemplatePlugin.js","../../../remyxjs/plugins/callout/CalloutPlugin.js","../../../remyxjs/plugins/collaboration/CrdtEngine.js","../../../remyxjs/plugins/collaboration/AwarenessProtocol.js","../../../remyxjs/plugins/collaboration/transports/WebSocketTransport.js","../../../remyxjs/plugins/collaboration/CollaborationPlugin.js","../../../remyxjs/plugins/comments/CommentsPlugin.js","../../../remyxjs/plugins/drag-drop/DragDropPlugin.js","../../../remyxjs/plugins/keyboard/KeyboardPlugin.js","../../../remyxjs/plugins/link/LinkPlugin.js","../../../remyxjs/plugins/math/MathPlugin.js","../../../remyxjs/plugins/spellcheck/GrammarEngine.js","../../../remyxjs/plugins/spellcheck/SpellcheckPlugin.js","../../../remyxjs/plugins/syntax-highlight/tokenizers.js","../../../remyxjs/plugins/syntax-highlight/SyntaxHighlightPlugin.js","../../../remyxjs/plugins/table/filter.js","../../../remyxjs/plugins/table/TablePlugin.js","../../../remyxjs/plugins/table/resize.js","../../../remyxjs/plugins/template/TemplatePlugin.js","../../../remyxjs/plugins/toc/TocPlugin.js","../../../remyxjs/plugins/index.js"],"sourcesContent":["/**\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 '@remyxjs/core'\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","import { createPlugin } from '@remyxjs/core'\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 * 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 '@remyxjs/core'\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","/**\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 '@remyxjs/core'\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 * 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 '@remyxjs/core'\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 * 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 '@remyxjs/core'\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","/**\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 '@remyxjs/core'\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","import { escapeHTMLAttr } from '@remyxjs/core'\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 '@remyxjs/core'\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","import { escapeHTML } from '@remyxjs/core'\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 '@remyxjs/core'\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","/**\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 '@remyxjs/core'\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 * 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 '@remyxjs/core'\nimport { detectLanguage, tokenize, LANGUAGE_MAP } from './tokenizers.js'\nimport { escapeHTML } from '@remyxjs/core'\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 '@remyxjs/core'\nimport { attachResizeHandles } from './resize.js'\nimport { attachFilterUI } from './filter.js'\nimport { evaluateTableFormulas } from '@remyxjs/core'\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","/**\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 '@remyxjs/core'\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","import { escapeHTML } from '@remyxjs/core'\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 '@remyxjs/core'\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 * Plugin auto-discovery module.\n *\n * Scans the remyxjs/plugins/ directory for plugin folders and builds a registry.\n * Each plugin directory must have an index.js that exports a factory function\n * whose name ends with \"Plugin\" (e.g. TablePlugin, MathPlugin).\n *\n * Usage from config files:\n * \"plugins\": { \"table\": { \"enabled\": true, \"maxRows\": 100 } }\n *\n * The key (\"table\") matches the directory name under remyxjs/plugins/.\n */\n\nconst pluginModules = import.meta.glob('./**/index.js', { eager: true })\n\nconst pluginRegistry = new Map()\n\nfor (const [path, module] of Object.entries(pluginModules)) {\n // Extract directory name: \"./table/index.js\" → \"table\"\n const segments = path.split('/')\n if (segments.length === 3 && segments[2] === 'index.js') {\n const name = segments[1]\n pluginRegistry.set(name, module)\n }\n}\n\n/**\n * Get the names of all available plugins discovered in the plugins directory.\n * @returns {string[]}\n */\nexport function getAvailablePlugins() {\n return [...pluginRegistry.keys()]\n}\n\n/**\n * Load and instantiate a single plugin by directory name.\n * @param {string} name - Plugin directory name (e.g. \"table\", \"math\")\n * @param {object} [options] - Options passed to the plugin factory\n * @returns {object|null} Plugin instance, or null if not found\n */\nexport function loadPlugin(name, options = {}) {\n const mod = pluginRegistry.get(name)\n if (!mod) {\n console.warn(`[remyxjs] Plugin \"${name}\" not found in remyxjs/plugins/`)\n return null\n }\n const factoryName = Object.keys(mod).find(\n k => k.endsWith('Plugin') && typeof mod[k] === 'function'\n )\n if (!factoryName) {\n console.warn(`[remyxjs] Plugin \"${name}\" has no exported factory function ending in \"Plugin\"`)\n return null\n }\n return mod[factoryName](options)\n}\n\n/**\n * Resolve plugins from a config file's plugins object.\n *\n * @param {object} pluginsConfig - e.g. { \"table\": { \"enabled\": true }, \"math\": false }\n * @returns {object[]} Array of instantiated plugin objects\n *\n * @example\n * resolvePluginsFromConfig({\n * \"table\": { \"enabled\": true, \"maxRows\": 100 },\n * \"math\": { \"enabled\": false },\n * \"comments\": {},\n * \"callout\": true\n * })\n * // Returns: [TablePlugin({ maxRows: 100 }), CommentsPlugin(), CalloutPlugin()]\n */\nexport function resolvePluginsFromConfig(pluginsConfig) {\n if (!pluginsConfig || typeof pluginsConfig !== 'object') return []\n\n return Object.entries(pluginsConfig)\n .filter(([, opts]) => {\n if (opts === false) return false\n if (typeof opts === 'object' && opts !== null && opts.enabled === false) return false\n return true\n })\n .map(([name, opts]) => {\n const options = typeof opts === 'object' && opts !== null ? { ...opts } : {}\n delete options.enabled\n return loadPlugin(name, options)\n })\n .filter(Boolean)\n}\n"],"names":["countSyllables","word","toLowerCase","replace","length","vowelGroups","match","splitSentences","text","split","RegExp","map","s","trim","filter","getWords","w","fleschKincaid","stats","sentences","words","syllables","fleschReadingEase","gunningFog","complexWords","colemanLiau","chars","vocabularyLevel","gradeLevel","analyzeContent","options","wordsPerMinute","targetWordCount","maxSentenceLength","maxParagraphLength","paragraphs","p","splitParagraphs","baseStats","fk","fre","gf","cl","level","readingTimeMinutes","readingTimeSeconds","Math","round","longSentences","i","index","wordCount","longParagraphs","substring","goalProgress","target","current","percentage","charCount","sentenceCount","paragraphCount","syllableCount","complexWordCount","readability","readingTime","minutes","ceil","seconds","warnings","keywordDensity","keyword","count","density","positions","kw","forEach","push","seoAnalysis","editorEl","targetKeyword","headings","querySelectorAll","h1Count","hints","keywordInfo","headingCount","onAnalytics","engine","unsubContentChange","debounceTimer","currentStats","lastAnalyzedText","updateAnalytics","getText","eventBus","emit","createPlugin","name","requiresFullAccess","version","description","commands","execute","eng","_analyticsVisible","visible","meta","icon","tooltip","element","init","_analytics","getStats","on","clearTimeout","setTimeout","destroy","BUILT_IN_TEMPLATES","templates","Map","html","Object","entries","set","_blockTemplates","registerTemplate","htmlString","insertTemplate","get","history","snapshot","range","selection","getRange","temp","document","createElement","innerHTML","sanitizer","sanitize","frag","createDocumentFragment","firstChild","appendChild","collapsed","deleteContents","insertNode","lastElementChild","tagName","getTemplates","Array","from","removeTemplate","delete","clear","_calloutTypes","BUILTIN_TYPES","type","label","color","t","registerCalloutType","typeDef","unregisterCalloutType","getCalloutTypes","values","getCalloutType","GFM_ALERT_MAP","NOTE","TIP","IMPORTANT","WARNING","CAUTION","parseGFMAlert","body","createCalloutElement","content","collapsible","title","wrapper","className","setAttribute","header","contentEditable","textContent","titleEl","toggle","style","display","findAncestor","sel","window","getSelection","rangeCount","node","anchorNode","nodeType","parentNode","classList","contains","observer","handleClick","e","closest","callout","preventDefault","stopPropagation","querySelector","hasAttribute","removeAttribute","scanForGFMAlerts","blockquotes","bq","alert","replaceChild","params","block","getRangeAt","startContainer","insertBefore","nextSibling","r","createRange","selectNodeContents","collapse","removeAllRanges","addRange","getClosestElement","parent","removeChild","normalize","newType","oldType","getAttribute","remove","add","btn","contextMenuItems","command","when","_callouts","addEventListener","MutationObserver","bodies","observe","childList","subtree","disconnect","removeEventListener","offsetToRange","offset","walker","createTreeWalker","NodeFilter","SHOW_TEXT","startSet","nextNode","currentNode","nodeLen","setStart","setEnd","rangeToOffset","preRange","startOffset","toString","CrdtEngine","constructor","userId","this","_clock","_pendingOps","_seenOps","Set","_maxSeenOps","_suppressRemote","_lastTextContent","_trackOp","opId","size","excess","iter","next","value","_tickLocal","_merge","remoteClock","uid","seq","max","captureOperations","mutations","currentText","previousText","ops","clock","position","_findInsert","op","id","timestamp","Date","now","_findDelete","_findReplace","oldText","newText","start","insertedLength","end","endNew","_transformPosition","pos","applyRemoteOperations","newOps","has","sort","a","b","sumA","reduce","v","sumB","localeCompare","_applyOp","createTextNode","queueOperation","flushQueue","hasPendingOps","initTextContent","getState","pendingOps","seenOps","AwarenessProtocol","userName","userColor","_localState","cursor","status","lastActive","_remoteStates","_broadcastInterval","_idleTimer","_cursorsContainer","_renderRafId","updateLocalCursor","setStatus","resetIdleTimer","applyRemoteAwareness","state","removeUser","getCollaborators","renderRemoteCursors","cancelAnimationFrame","requestAnimationFrame","_doRender","editorRect","getBoundingClientRect","rects","getClientRects","startRect","caret","cssText","left","top","height","backgroundColor","rect","highlight","width","clearRemoteCursors","getLocalState","startBroadcasting","sendFn","intervalMs","stopBroadcasting","setInterval","clearInterval","WebSocketTransport","url","_url","_reconnect","reconnect","_reconnectInterval","reconnectInterval","_maxAttempts","maxReconnectAttempts","_maxDelay","maxReconnectDelay","_ws","_messageHandler","_connectHandler","_disconnectHandler","_reconnectTimer","_reconnectAttempts","_intentionalClose","connect","readyState","WebSocket","OPEN","_scheduleReconnect","onopen","onmessage","event","msg","JSON","parse","data","onclose","onerror","CONNECTING","close","send","stringify","onMessage","cb","onConnect","onDisconnect","isConnected","delay","min","pow","signalingServer","transport","customTransport","random","slice","roomId","autoConnect","broadcastInterval","onUserJoin","onUserLeave","onSync","onConflict","crdtEngine","awareness","mutationObserver","unsubs","connected","handleMessage","bookmark","_isRemoteOperation","operations","opCount","getHTML","some","setHTML","startCollaboration","pending","stopCollaboration","getConnectionStatus","setUserInfo","info","_eng","_collaboration","getCrdtState","characterData","characterDataOldValue","unsubSelection","unsubContent","unsub","_nextId","generateId","parseMentions","matches","m","onComment","onResolve","onDelete","onReply","mentionUsers","commentOnly","threads","unsubDestroy","syncTimer","getAllThreads","createdAt","getThread","getUnresolvedThreads","resolved","getResolvedThreads","addComment","author","isCollapsed","commonAncestorContainer","thread","mentions","updatedAt","replies","mark","surroundContents","fragment","extractContents","replyToComment","threadId","reply","resolveComment","String","deleteComment","editComment","newBody","navigateToComment","scrollIntoView","behavior","importThreads","importedThreads","exportThreads","syncWithDOM","getMentionUsers","isEnabled","_comments","existingMarks","cleanup","DROP_ZONE_CLASS","DROP_ZONE_ACTIVE_CLASS","DROP_INDICATOR_CLASS","DRAGGING_CLASS","getDropTarget","container","y","children","el","closestDist","Infinity","child","midY","dist","abs","getDraggableBlock","onDrop","onFileDrop","allowExternalDrop","showDropZone","enableReorder","dropIndicator","draggedBlock","ghostEl","handleDragStart","cloneNode","dataTransfer","setDragImage","effectAllowed","setData","outerHTML","handleDragOver","dropEffect","clientY","bottom","handleDragLeave","relatedTarget","handleDrop","before","after","cleanupDrag","getData","files","file","startsWith","reader","FileReader","onload","img","src","result","maxWidth","readAsDataURL","handleDragEnd","handleMouseDown","clientX","draggable","once","getParentBlock","topBlock","prev","previousElementSibling","shortcut","nextElementSibling","indicator","createDropIndicator","_dragDrop","PAIR_MAP","CLOSE_CHARS","VIM_MODES","createVimState","mode","EMACS_BINDINGS","getHeadings","parseInt","charAt","selectNextOccurrence","searchFrom","endOffset","indexOf","localOffset","newRange","keyBindings","autoPair","jumpToHeading","enableJumpToHeading","vimState","killRing","handleKeyDown","key","collapseToStart","modify","lastChild","executeCommand","execCommand","handleVimKey","ctrlKey","combo","action","handleEmacsKey","parts","metaKey","altKey","shiftKey","join","buildCombo","handleAutoPair","closer","selected","newMode","heading","_keyboard","getVimMode","URL_REGEX","EMAIL_REGEX","PHONE_REGEX","detectLinks","results","lastIndex","exec","emailStart","slugify","_previewEl","hidePreview","escapeHTML","str","escapeHTMLAttr","onLinkClick","onUnfurl","onSuggest","onBrokenLink","validateLink","scanInterval","autoLink","showPreviews","scanTimer","hoverTimer","_unfurlCache","_brokenLinks","handleAutoLink","parentElement","links","last","endPos","href","link","linkRange","anchor","rel","space","br","afterBr","setStartAfter","handleLinkClick","handleMouseOver","async","image","showPreview","handleMouseOut","scanForBrokenLinks","anchors","checked","urlAnchors","batch","Promise","all","total","broken","syncBrokenLinks","anchorId","bookmarkId","getSelectedText","wrapWith","bookmarks","_links","getBrokenLinks","getBookmarks","els","clearUnfurlCache","SYMBOL_PALETTE","category","symbols","latex","getSymbolPalette","INLINE_REGEX","BLOCK_REGEX","parseMathExpressions","latexToMathML","defaultRenderMath","displayMode","renderMath","autoRender","numbering","equationCounter","renderMathElement","isBlock","num","renderAllMath","eqNumber","tag","createMathElement","equationNumber","format","_math","getEquationCount","mathEl","PASSIVE_AUXILIARIES","PAST_PARTICIPLE_SUFFIXES","detectPassiveVoice","issues","test","nextIdx","passiveStart","passiveEnd","j","passiveText","message","suggestions","rule","WORDINESS_RULES","pattern","replacement","detectWordiness","re","source","flags","CLICHES","detectCliches","lower","cliche","idx","detectPunctuationIssues","doubleSpace","repeatedPunc","missingSpace","STYLE_PRESETS","formal","passiveVoice","wordiness","cliches","punctuation","casual","technical","academic","analyzeGrammar","preset","stylePreset","config","summarizeIssues","summary","grammar","byRule","issue","DICTIONARY_STORAGE_KEY","IGNORED_STORAGE_KEY","loadPersistedDictionary","raw","localStorage","getItem","parsed","isArray","persistDictionary","setItem","language","enabled","enabledOnInit","grammarRules","customService","dictionary","initialDictionary","persistent","onError","onCorrection","currentStylePreset","currentLanguage","currentErrors","ignoredWords","loadPersistedIgnored","clearMarks","marks","runCheck","errors","grammarIssues","check","serviceIssues","err","error","textNodes","totalOffset","sortedErrors","errorEnd","startNode","endNode","tn","applyMarks","debouncedCheck","addToDictionary","removeFromDictionary","getDictionary","ignoreWord","getIgnoredWords","applyCorrection","original","textNode","setWritingStyle","getWritingStyle","setLanguage","lang","getLanguage","getSpellcheckStats","dictionarySize","ignoredCount","handleContextMenu","suggestionsRaw","_spellcheck","getErrors","runRules","code","rules","tokens","plain","flush","matched","cls","keywordMatcher","keywords","keywordMatcherI","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","json","bash","sh","shell","zsh","rust","rs","go","golang","java","htm","xml","svg","plaintext","txt","SUPPORTED_LANGUAGES","tokenize","tokenizer","detectLanguage","trimmed","trimStart","first200","getCurrentCodeBlock","Node","TEXT_NODE","blurHandler","languageChangeUnsub","copyClickHandler","highlightCodeElement","codeEl","pre","token","save","restore","updateLineNumbers","show","gutter","lineCount","nums","ensureCopyButton","highlightAll","focusedPre","activeElement","focusNode","getFocusedPre","pres","codes","highlightInlineCode","needsHighlight","mutation","addedNodes","ELEMENT_NODE","attributeName","scheduleHighlight","attributes","attributeFilter","navigator","clipboard","writeText","textarea","opacity","select","aliases","Error","alias","find","l","findIndex","splice","attachFilterUI","table","filterBtns","activeDropdown","createFilterButtons","removeFilterButtons","thead","th","colIndex","closeDropdown","dropdown","input","placeholder","applyFilter","clearBtn","focus","closeOnOutsideClick","toggleFilterDropdown","columnIndex","filterValue","tbody","ths","filters","val","row","cells","f","includes","update","tableMap","formulaTimer","sortClickHandler","formulaFocusHandler","formulaBlurHandler","setupTable","entry","resize","handles","startX","startY","startWidth","startHeight","targetCol","targetRow","positionHandles","tableRect","headerRow","handle","cell","cellRect","right","HANDLE_WIDTH","offsetHeight","rowRect","offsetWidth","onColMouseDown","onColMouseMove","onColMouseUp","delta","newWidth","colSpan","onRowMouseDown","rows","onRowMouseMove","onRowMouseUp","newHeight","removeHandles","h","getComputedStyle","createColumnHandles","attachResizeHandles","evaluateTableFormulas","teardownTable","setupAllTables","needsSetup","removedNodes","currentDir","newDir","existingKeys","dir","direction","keys","formula","TAG_REGEX","BLOCK_IF_REGEX","BLOCK_EACH_REGEX","renderTemplate","template","_","arr","item","rendered","extractTags","tags","matchAll","_templateLibrary","BUILTIN_TEMPLATES","sampleData","recipient_name","sender_name","sender_title","invoice_number","date","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","previewMode","currentData","chip","templateId","tmpl","mergedData","_templateBackup","_templates","setPreviewData","getPreviewData","isPreviewMode","_idCounter","buildOutline","flat","slug","number","root","stack","pop","assignNumbers","items","prefix","flattenOutline","outline","walk","renderTocHTML","linkPrefix","renderItems","validateHeadingHierarchy","flatItems","curr","onOutlineChange","currentOutline","updateOutline","headingId","CSS","escape","_toc","getOutline","pluginModules","assign","__vite_glob_0_0","__vite_glob_0_1","__vite_glob_0_2","__vite_glob_0_3","__vite_glob_0_4","__vite_glob_0_5","__vite_glob_0_6","__vite_glob_0_7","__vite_glob_0_8","__vite_glob_0_9","__vite_glob_0_10","__vite_glob_0_11","__vite_glob_0_12","__vite_glob_0_13","pluginRegistry","path","module","segments","loadPlugin","mod","factoryName","k","endsWith","resolvePluginsFromConfig","pluginsConfig","opts","Boolean"],"mappings":"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"}
|