@oix1987/yjd 2.1.0 → 2.1.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"rich-editor.esm.js","sources":["../lib/core/registry.js","../lib/core/module.js","../lib/utils/exec-command.js","../lib/utils/sanitize.js","../lib/serialize.js","../lib/core/editor.js","../lib/core/format.js","../lib/static.js","../lib/utils/history-helper.js","../lib/formats/bold.js","../lib/formats/italic.js","../lib/formats/underline.js","../lib/formats/strike.js","../lib/formats/subscript.js","../lib/formats/superscript.js","../lib/ui/icons.js","../lib/utils/popup-helper.js","../lib/ui/color-picker.js","../lib/formats/color.js","../lib/formats/background.js","../lib/ui/link-popup.js","../lib/formats/link.js","../lib/ui/table-popup.js","../lib/formats/table.js","../lib/ui/customselect.js","../lib/formats/heading.js","../lib/formats/font-family.js","../lib/formats/line-height.js","../lib/formats/capitalization.js","../lib/ui/text-align-picker.js","../lib/formats/text-align.js","../lib/ui/list-picker.js","../lib/formats/list.js","../lib/formats/indent.js","../lib/ui/emoji-picker.js","../lib/formats/emoji.js","../lib/formats/image.js","../lib/ui/video-popup.js","../lib/formats/video.js","../lib/ui/tag-popup.js","../lib/formats/tag.js","../lib/formats/text-size.js","../lib/ui/import-popup.js","../lib/formats/import.js","../lib/ui/select-button.js","../lib/modules/toolbar.js","../lib/modules/history.js","../lib/modules/block-toolbar.js","../lib/modules/table-toolbar.js","../lib/modules/code-view.js","../lib/modules/find-replace.js","../lib/modules/slash-menu.js","../lib/modules/mention.js","../lib/modules/resize-handles.js","../lib/ui/image-popup.js","../index.js","../lib/styles-loader.js","../lib/styles.css.js"],"sourcesContent":["/**\n * Registry system - Inspired by Quill's registration system\n * Manages registration and retrieval of modules, formats, themes, and UI components\n */\nclass Registry {\n constructor() {\n this.modules = new Map();\n this.formats = new Map();\n this.themes = new Map();\n this.ui = new Map();\n }\n\n /**\n * Register a module, format, theme, or UI component\n * @param {string|object} path - Registration path or object with multiple registrations\n * @param {*} def - Definition to register\n * @param {boolean} suppressWarning - Suppress overwrite warnings\n */\n register(path, def, suppressWarning = false) {\n if (typeof path === 'object') {\n // Bulk registration\n Object.entries(path).forEach(([key, value]) => {\n this.register(key, value, suppressWarning);\n });\n return;\n }\n\n const [type, name] = path.split('/');\n \n // Only warn when genuinely replacing one definition with a DIFFERENT one.\n // Re-registering the same class (common when several editors share the\n // singleton registry) is a no-op, not a mistake.\n if (!suppressWarning) {\n const existing = this.get(path);\n if (existing && existing !== def) {\n console.warn(`Overwriting ${path}`);\n }\n }\n\n switch (type) {\n case 'modules':\n this.modules.set(name, def);\n break;\n case 'formats':\n this.formats.set(name, def);\n break;\n case 'themes':\n this.themes.set(name, def);\n break;\n case 'ui':\n this.ui.set(name, def);\n break;\n default:\n console.warn(`Unknown registry type: ${type}`);\n }\n }\n\n /**\n * Get a registered item\n * @param {string} path - Registration path\n * @returns {*}\n */\n get(path) {\n const [type, name] = path.split('/');\n \n switch (type) {\n case 'modules':\n return this.modules.get(name);\n case 'formats':\n return this.formats.get(name);\n case 'themes':\n return this.themes.get(name);\n case 'ui':\n return this.ui.get(name);\n default:\n return null;\n }\n }\n\n /**\n * Check if an item is registered\n * @param {string} path - Registration path\n * @returns {boolean}\n */\n has(path) {\n return this.get(path) !== null && this.get(path) !== undefined;\n }\n\n /**\n * Get all registered items of a type\n * @param {string} type - Type to get (modules, formats, themes, ui)\n * @returns {Map}\n */\n getAll(type) {\n switch (type) {\n case 'modules':\n return new Map(this.modules);\n case 'formats':\n return new Map(this.formats);\n case 'themes':\n return new Map(this.themes);\n case 'ui':\n return new Map(this.ui);\n default:\n return new Map();\n }\n }\n\n /**\n * Unregister an item\n * @param {string} path - Registration path\n */\n unregister(path) {\n const [type, name] = path.split('/');\n \n switch (type) {\n case 'modules':\n this.modules.delete(name);\n break;\n case 'formats':\n this.formats.delete(name);\n break;\n case 'themes':\n this.themes.delete(name);\n break;\n case 'ui':\n this.ui.delete(name);\n break;\n }\n }\n\n /**\n * Clear all registrations\n */\n clear() {\n this.modules.clear();\n this.formats.clear();\n this.themes.clear();\n this.ui.clear();\n }\n\n /**\n * Get all registered items for debugging\n */\n getAllItems() {\n const items = {};\n items.modules = Array.from(this.modules.keys());\n items.formats = Array.from(this.formats.keys());\n items.themes = Array.from(this.themes.keys());\n items.ui = Array.from(this.ui.keys());\n return items;\n }\n}\n\n// Create singleton instance\nconst registry = new Registry();\n\nexport default registry; ","/**\n * Base Module class - Inspired by Quill's architecture\n * All editor modules should extend this class\n */\nexport default class Module {\n static DEFAULTS = {};\n\n constructor(editor, options = {}) {\n this.editor = editor;\n this.options = { ...this.constructor.DEFAULTS, ...options };\n this.events = new Map();\n }\n\n /**\n * Add event listener\n * @param {string} event - Event name\n * @param {function} handler - Event handler\n */\n on(event, handler) {\n if (!this.events.has(event)) {\n this.events.set(event, []);\n }\n this.events.get(event).push(handler);\n }\n\n /**\n * Remove event listener\n * @param {string} event - Event name\n * @param {function} handler - Event handler\n */\n off(event, handler) {\n if (this.events.has(event)) {\n const handlers = this.events.get(event);\n const index = handlers.indexOf(handler);\n if (index > -1) {\n handlers.splice(index, 1);\n }\n }\n }\n\n /**\n * Emit event\n * @param {string} event - Event name\n * @param {*} data - Event data\n */\n emit(event, data) {\n if (this.events.has(event)) {\n this.events.get(event).forEach(handler => {\n try {\n handler(data);\n } catch (error) {\n console.error(`Error in event handler for ${event}:`, error);\n }\n });\n }\n }\n\n /**\n * Called when module is being destroyed\n * Override this method to cleanup resources\n */\n destroy() {\n this.events.clear();\n }\n\n /**\n * Called when editor content changes\n * Override this method to respond to content changes\n */\n onContentChange() {\n // Override in subclasses\n }\n\n /**\n * Called when selection changes\n * Override this method to respond to selection changes\n */\n onSelectionChange(range) {\n // Override in subclasses\n }\n} ","/**\n * execCommand wrapper — single migration point for the deprecated\n * document.execCommand / queryCommand* family.\n *\n * document.execCommand is deprecated. It still works in every current browser\n * and remains the most reliable way to toggle inline formatting across complex\n * selections, so we keep using it for now — but ONLY through this module.\n * Centralizing it here means:\n * - consistent try/catch (these APIs throw in detached/edge cases),\n * - one place to add feature detection or a fallback,\n * - one place to perform a future Range-API migration without touching every\n * format file.\n *\n * Prefer these helpers over calling document.execCommand directly.\n */\n\n/**\n * Execute a formatting command.\n * @param {string} command - execCommand command name (e.g. 'bold', 'foreColor')\n * @param {string|null} [value] - command value, when applicable\n * @returns {boolean} true if the command ran without throwing\n */\nexport function execFormat(command, value = null) {\n try {\n // Omit the value argument when none is given. Passing null explicitly makes\n // some commands stringify it (e.g. insertHorizontalRule would set id=\"null\").\n return value == null\n ? document.execCommand(command, false)\n : document.execCommand(command, false, value);\n } catch (e) {\n console.warn(`execCommand('${command}') failed:`, e);\n return false;\n }\n}\n\n/**\n * Enable/disable styleWithCSS (so commands emit inline styles instead of\n * deprecated presentational tags like <font>). Safe to call before each\n * styling command.\n * @param {boolean} [enabled=true]\n */\nexport function setStyleWithCSS(enabled = true) {\n return execFormat('styleWithCSS', enabled);\n}\n\n/**\n * Query whether a command is currently active for the selection.\n * @param {string} command\n * @returns {boolean}\n */\nexport function queryFormatState(command) {\n try {\n return document.queryCommandState(command);\n } catch (e) {\n return false;\n }\n}\n\n/**\n * Query the current value of a command for the selection.\n * @param {string} command\n * @returns {string} empty string if unsupported/unavailable\n */\nexport function queryFormatValue(command) {\n try {\n return document.queryCommandValue(command) || '';\n } catch (e) {\n return '';\n }\n}\n\nexport default { execFormat, setStyleWithCSS, queryFormatState, queryFormatValue };\n","/**\n * Sanitize utilities - dependency-free XSS protection helpers.\n *\n * These functions provide a defense-in-depth layer for the few places where\n * the editor turns user-supplied strings into live DOM (links, images, video,\n * HTML import, code view). They are intentionally conservative: anything that\n * is not provably safe is rejected.\n */\n\n// URL schemes that are safe to use as navigable links / resource sources.\nconst SAFE_URL_SCHEMES = ['http:', 'https:', 'mailto:', 'tel:', 'ftp:'];\n\n// Trusted iframe embed prefixes (used by the video feature).\nconst TRUSTED_IFRAME_PREFIXES = [\n 'https://www.youtube.com/embed/',\n 'https://www.youtube-nocookie.com/embed/',\n 'https://player.vimeo.com/video/'\n];\n\n/**\n * Determine whether a URL is safe to assign to href/src.\n *\n * Relative URLs, anchors and path-only references (no scheme) are considered\n * safe. URLs with an explicit scheme are only allowed if the scheme is in the\n * whitelist. `javascript:`, `vbscript:`, `data:text/html`, etc. are rejected.\n *\n * @param {string} url - URL to validate\n * @param {object} [options]\n * @param {boolean} [options.allowDataImage] - allow `data:image/*` (except SVG)\n * @returns {boolean}\n */\nexport function isSafeUrl(url, { allowDataImage = false } = {}) {\n if (typeof url !== 'string') return false;\n\n const trimmed = url.trim();\n if (trimmed === '') return false;\n\n // Strip control/whitespace characters that are commonly used to smuggle\n // schemes past naive validators (e.g. \"java\\tscript:alert(1)\").\n const stripped = trimmed.replace(/[\\u0000-\\u0020\\u007F-\\u009F]/g, '');\n\n // Detect a leading scheme. If there is none it is a relative URL → safe.\n const schemeMatch = stripped.match(/^([a-z][a-z0-9+.-]*):/i);\n if (!schemeMatch) {\n return true;\n }\n\n const scheme = schemeMatch[1].toLowerCase() + ':';\n\n if (scheme === 'data:') {\n if (!allowDataImage) return false;\n // Allow raster image data URIs only. SVG can carry script, so it is denied.\n return /^data:image\\//i.test(stripped) && !/^data:image\\/svg/i.test(stripped);\n }\n\n return SAFE_URL_SCHEMES.includes(scheme);\n}\n\n/**\n * Return the URL if it is safe, otherwise an empty string.\n * @param {string} url\n * @param {object} [options] - same options as isSafeUrl\n * @returns {string}\n */\nexport function sanitizeUrl(url, options) {\n return isSafeUrl(url, options) ? url.trim() : '';\n}\n\n// Tags that are never allowed in sanitized HTML.\nconst FORBIDDEN_TAGS = new Set([\n 'SCRIPT', 'STYLE', 'OBJECT', 'EMBED', 'LINK', 'META', 'BASE',\n 'FORM', 'INPUT', 'BUTTON', 'TEXTAREA', 'SELECT', 'OPTION', 'NOSCRIPT'\n]);\n\n/**\n * Clean a single element node in place: drop event-handler attributes,\n * unsafe href/src URLs and dangerous inline styles.\n * @param {Element} el\n */\nfunction cleanElement(el) {\n const attrs = Array.from(el.attributes);\n for (const attr of attrs) {\n const name = attr.name.toLowerCase();\n const value = attr.value;\n\n // Strip all inline event handlers (onclick, onerror, onload, ...).\n if (name.startsWith('on')) {\n el.removeAttribute(attr.name);\n continue;\n }\n\n // Validate URL-bearing attributes.\n if (name === 'href' || name === 'src' || name === 'xlink:href') {\n const allowDataImage = el.tagName === 'IMG';\n if (!isSafeUrl(value, { allowDataImage })) {\n el.removeAttribute(attr.name);\n }\n continue;\n }\n\n // Reject styles that can execute script (legacy IE expression / url(javascript:)).\n if (name === 'style' && /expression\\s*\\(|javascript:/i.test(value)) {\n el.removeAttribute(attr.name);\n }\n }\n}\n\n/**\n * Sanitize an HTML string and return safe HTML.\n *\n * Parsing is done with DOMParser so that no scripts execute and no network\n * resources load during sanitization (the parsed document is inert).\n *\n * @param {string} html - untrusted HTML\n * @returns {string} sanitized HTML\n */\nexport function sanitizeHtml(html) {\n if (typeof html !== 'string' || html === '') return '';\n\n const doc = new DOMParser().parseFromString(html, 'text/html');\n const elements = Array.from(doc.body.querySelectorAll('*'));\n\n for (const el of elements) {\n const tag = el.tagName;\n\n if (FORBIDDEN_TAGS.has(tag)) {\n el.remove();\n continue;\n }\n\n // Iframes are only allowed when pointing at a trusted embed host.\n if (tag === 'IFRAME') {\n const src = el.getAttribute('src') || '';\n const trusted = TRUSTED_IFRAME_PREFIXES.some(prefix => src.startsWith(prefix));\n if (!trusted) {\n el.remove();\n continue;\n }\n }\n\n cleanElement(el);\n }\n\n return doc.body.innerHTML;\n}\n\n/**\n * Sanitize an already-parsed DOM subtree in place (event handlers + unsafe URLs).\n * Use when content is built via the DOM rather than from an HTML string.\n * @param {Element} root\n */\nexport function sanitizeNode(root) {\n if (!root) return;\n const elements = Array.from(root.querySelectorAll('*'));\n for (const el of elements) {\n if (FORBIDDEN_TAGS.has(el.tagName)) {\n el.remove();\n continue;\n }\n cleanElement(el);\n }\n}\n\nexport default { isSafeUrl, sanitizeUrl, sanitizeHtml, sanitizeNode };\n","/**\n * Serialization for yjd content — HTML <-> Markdown and HTML <-> JSON.\n *\n * Targeted at the HTML yjd emits (headings, inline marks, lists, links,\n * images, blockquote, code, tables, hr, and mention tokens). Browser-only\n * (uses the DOM). Zero dependencies.\n *\n * import { htmlToMarkdown, markdownToHtml, domToJson, jsonToHtml } from '.../serialize.js'\n */\n\n/* ============================ HTML -> Markdown ============================ */\n\nexport function htmlToMarkdown(html) {\n const root = document.createElement('div');\n root.innerHTML = html || '';\n return blocksToMd(root, 0).replace(/\\n{3,}/g, '\\n\\n').trim() + '\\n';\n}\n\nfunction blocksToMd(parent, depth) {\n let out = '';\n parent.childNodes.forEach((node) => { out += nodeBlock(node, depth); });\n return out;\n}\n\nfunction nodeBlock(node, depth) {\n if (node.nodeType === 3) {\n const t = node.textContent.replace(/\\s+/g, ' ');\n return t.trim() ? t + '\\n\\n' : '';\n }\n if (node.nodeType !== 1) return '';\n const tag = node.tagName;\n switch (tag) {\n case 'H1': case 'H2': case 'H3': case 'H4': case 'H5': case 'H6':\n return '#'.repeat(+tag[1]) + ' ' + inline(node) + '\\n\\n';\n case 'P': case 'DIV': {\n const c = inline(node);\n return c.trim() ? c + '\\n\\n' : '';\n }\n case 'BLOCKQUOTE':\n return inline(node).split('\\n').map((l) => '> ' + l).join('\\n') + '\\n\\n';\n case 'PRE':\n return '```\\n' + node.textContent.replace(/\\n$/, '') + '\\n```\\n\\n';\n case 'UL': return listToMd(node, depth, false) + '\\n';\n case 'OL': return listToMd(node, depth, true) + '\\n';\n case 'HR': return '---\\n\\n';\n case 'TABLE': return tableToMd(node) + '\\n';\n case 'FIGURE': return blocksToMd(node, depth);\n case 'IMG': return imgToMd(node) + '\\n\\n';\n case 'BR': return '\\n';\n default:\n return inline(node) + '\\n\\n';\n }\n}\n\nfunction listToMd(node, depth, ordered) {\n let out = '', i = 1;\n node.childNodes.forEach((li) => {\n if (li.nodeType !== 1 || li.tagName !== 'LI') return;\n const marker = ordered ? (i++) + '. ' : '- ';\n const pad = ' '.repeat(depth);\n let text = '', nested = '';\n li.childNodes.forEach((ch) => {\n if (ch.nodeType === 1 && (ch.tagName === 'UL' || ch.tagName === 'OL')) {\n nested += listToMd(ch, depth + 1, ch.tagName === 'OL');\n } else {\n text += inlineNode(ch);\n }\n });\n out += pad + marker + text.trim() + '\\n' + nested;\n });\n return out;\n}\n\nfunction tableToMd(node) {\n const rows = [...node.querySelectorAll('tr')];\n if (!rows.length) return '';\n const cells = (r) => [...r.children].map((c) => inline(c).replace(/\\|/g, '\\\\|').trim());\n const head = cells(rows[0]);\n let out = '| ' + head.join(' | ') + ' |\\n| ' + head.map(() => '---').join(' | ') + ' |\\n';\n rows.slice(1).forEach((r) => { out += '| ' + cells(r).join(' | ') + ' |\\n'; });\n return out;\n}\n\nfunction imgToMd(node) {\n return `![${node.getAttribute('alt') || ''}](${node.getAttribute('src') || ''})`;\n}\n\nfunction inline(node) {\n let out = '';\n node.childNodes.forEach((ch) => { out += inlineNode(ch); });\n return out;\n}\n\nfunction inlineNode(node) {\n if (node.nodeType === 3) return node.textContent;\n if (node.nodeType !== 1) return '';\n const tag = node.tagName;\n if (node.classList && node.classList.contains('mention')) {\n const id = node.getAttribute('data-id') || '';\n const name = (node.textContent || '').replace(/^[@#]/, '');\n const trig = (node.textContent || '@')[0];\n return `${trig}[${name}](${id})`;\n }\n switch (tag) {\n case 'B': case 'STRONG': return '**' + inline(node) + '**';\n case 'I': case 'EM': return '*' + inline(node) + '*';\n case 'S': case 'STRIKE': case 'DEL': return '~~' + inline(node) + '~~';\n case 'U': return '<u>' + inline(node) + '</u>';\n case 'CODE': return '`' + node.textContent + '`';\n case 'A': return '[' + inline(node) + '](' + (node.getAttribute('href') || '') + ')';\n case 'IMG': return imgToMd(node);\n case 'BR': return ' \\n';\n default: return inline(node); // spans (colour/font) → keep text only\n }\n}\n\n/* ============================ Markdown -> HTML ============================ */\n\nexport function markdownToHtml(md) {\n const lines = (md || '').replace(/\\r\\n/g, '\\n').split('\\n');\n let html = '', i = 0;\n const isList = (l) => /^\\s*([-*+]|\\d+\\.)\\s+/.test(l);\n while (i < lines.length) {\n const line = lines[i];\n if (/^\\s*$/.test(line)) { i++; continue; }\n if (/^---+$/.test(line.trim())) { html += '<hr>'; i++; continue; }\n const h = line.match(/^(#{1,6})\\s+(.*)$/);\n if (h) { html += `<h${h[1].length}>${inlineMd(h[2])}</h${h[1].length}>`; i++; continue; }\n if (/^```/.test(line)) {\n i++; let code = '';\n while (i < lines.length && !/^```/.test(lines[i])) { code += lines[i] + '\\n'; i++; }\n i++; html += '<pre>' + escapeHtml(code.replace(/\\n$/, '')) + '</pre>'; continue;\n }\n if (/^>\\s?/.test(line)) {\n const q = [];\n while (i < lines.length && /^>\\s?/.test(lines[i])) { q.push(lines[i].replace(/^>\\s?/, '')); i++; }\n html += '<blockquote>' + inlineMd(q.join(' ')) + '</blockquote>'; continue;\n }\n if (/^\\|.*\\|\\s*$/.test(line) && i + 1 < lines.length && /^\\|[\\s:|-]+\\|\\s*$/.test(lines[i + 1])) {\n const r = parseTable(lines, i); html += r.html; i = r.next; continue;\n }\n if (isList(line)) { const r = parseList(lines, i, 0); html += r.html; i = r.next; continue; }\n const para = [line]; i++;\n while (i < lines.length && !/^\\s*$/.test(lines[i]) && !/^(#{1,6}\\s|>|```)/.test(lines[i]) &&\n !/^---+$/.test(lines[i].trim()) && !isList(lines[i])) { para.push(lines[i]); i++; }\n html += '<p>' + inlineMd(para.join('\\n').trim()) + '</p>';\n }\n return html;\n}\n\nfunction indentOf(l) { return (l.match(/^(\\s*)/)[1] || '').length; }\n\nfunction parseList(lines, start, baseIndent) {\n const ordered = /^\\s*\\d+\\./.test(lines[start]);\n let i = start, html = '<' + (ordered ? 'ol' : 'ul') + '>';\n while (i < lines.length) {\n const l = lines[i];\n if (/^\\s*$/.test(l)) { i++; continue; }\n const ind = indentOf(l);\n const m = l.match(/^\\s*([-*+]|\\d+\\.)\\s+(.*)$/);\n if (!m || ind < baseIndent) break;\n if (ind > baseIndent) { // nested list belongs to previous <li>\n const r = parseList(lines, i, ind);\n html = html.replace(/<\\/li>$/, r.html + '</li>');\n i = r.next; continue;\n }\n html += '<li>' + inlineMd(m[2]) + '</li>';\n i++;\n }\n return { html: html + '</' + (ordered ? 'ol' : 'ul') + '>', next: i };\n}\n\nfunction parseTable(lines, start) {\n const row = (l) => l.trim().replace(/^\\||\\|$/g, '').split('|').map((c) => c.trim());\n const head = row(lines[start]);\n let i = start + 2, body = '';\n while (i < lines.length && /^\\|.*\\|\\s*$/.test(lines[i])) {\n body += '<tr>' + row(lines[i]).map((c) => `<td>${inlineMd(c)}</td>`).join('') + '</tr>';\n i++;\n }\n const thead = '<tr>' + head.map((c) => `<td><b>${inlineMd(c)}</b></td>`).join('') + '</tr>';\n return { html: `<table class=\"rich-editor-table\"><tbody>${thead}${body}</tbody></table>`, next: i };\n}\n\nfunction inlineMd(s) {\n // images, mentions, links, then marks. Order matters.\n return s\n .replace(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/g, (_, a, src) => `<img class=\"inserted-image\" src=\"${attr(src)}\" alt=\"${attr(a)}\" style=\"max-width:100%;height:auto\">`)\n .replace(/([@#])\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_, t, name, id) => `<span class=\"mention\" data-id=\"${attr(id)}\">${t}${escapeHtml(name)}</span>`)\n .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_, t, href) => `<a href=\"${attr(href)}\" target=\"_blank\" rel=\"noopener noreferrer\">${escapeHtml(t)}</a>`)\n .replace(/\\*\\*([^*]+)\\*\\*/g, '<b>$1</b>')\n .replace(/(^|[^*])\\*([^*]+)\\*/g, '$1<i>$2</i>')\n .replace(/~~([^~]+)~~/g, '<s>$1</s>')\n .replace(/`([^`]+)`/g, (_, c) => '<code>' + escapeHtml(c) + '</code>')\n .replace(/\\n/g, '<br>');\n}\n\nfunction escapeHtml(s) { return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }\nfunction attr(s) { return String(s).replace(/\"/g, '&quot;').replace(/</g, '&lt;'); }\n\n/* ============================== HTML <-> JSON ============================= */\n\nexport function domToJson(html) {\n const root = document.createElement('div');\n root.innerHTML = html || '';\n return { type: 'doc', content: [...root.childNodes].map(nodeToJson).filter(Boolean) };\n}\n\nfunction nodeToJson(node) {\n if (node.nodeType === 3) {\n const text = node.textContent;\n return text ? { text } : null;\n }\n if (node.nodeType !== 1) return null;\n const obj = { tag: node.tagName.toLowerCase() };\n if (node.attributes.length) {\n obj.attrs = {};\n for (const a of node.attributes) obj.attrs[a.name] = a.value;\n }\n const kids = [...node.childNodes].map(nodeToJson).filter(Boolean);\n if (kids.length) obj.content = kids;\n return obj;\n}\n\nexport function jsonToHtml(json) {\n const nodes = json && json.content ? json.content : (Array.isArray(json) ? json : []);\n return nodes.map(jsonNodeToHtml).join('');\n}\n\nfunction jsonNodeToHtml(n) {\n if (n == null) return '';\n if (n.text != null) return escapeHtml(n.text);\n if (!n.tag) return '';\n const attrs = n.attrs\n ? Object.entries(n.attrs).map(([k, v]) => ` ${k}=\"${attr(v)}\"`).join('')\n : '';\n const inner = (n.content || []).map(jsonNodeToHtml).join('');\n const VOID = new Set(['img', 'hr', 'br', 'input']);\n if (VOID.has(n.tag)) return `<${n.tag}${attrs}>`;\n return `<${n.tag}${attrs}>${inner}</${n.tag}>`;\n}\n","import registry from './registry.js';\nimport Module from './module.js';\nimport { execFormat, queryFormatState } from '../utils/exec-command.js';\nimport { sanitizeHtml } from '../utils/sanitize.js';\nimport { htmlToMarkdown, markdownToHtml, domToJson, jsonToHtml } from '../serialize.js';\n\n/**\n * Main Editor class - Inspired by Quill's architecture\n * This replaces the monolithic EditorCore class\n */\nexport default class Editor {\n static DEFAULTS = {\n placeholder: 'Start typing...',\n theme: 'light',\n height: 400,\n width: 800,\n maxWidth: 1200,\n maxHeight: 800,\n content: null, // Default content for the editor\n features: {\n emoji: true,\n image: true,\n table: true,\n wordCount: true,\n breadcrumb: true\n }\n };\n\n // Static reference to current editor instance\n static currentInstance = null;\n // Static map to track all editor instances\n static instances = new Map();\n\n constructor(selector, options = {}) {\n this.options = {\n ...Editor.DEFAULTS,\n ...options,\n // Deep-merge `features` so a partial override (e.g. { wordCount: false }\n // to hide the bottom bar) keeps the other defaults instead of wiping them.\n features: { ...Editor.DEFAULTS.features, ...(options.features || {}) }\n };\n this.root = typeof selector === 'string' ? document.querySelector(selector) : selector;\n this.modules = new Map();\n this.formats = new Map();\n this.registry = registry;\n this.events = new Map(); // Add event system\n \n // State management\n this.toolbarBtns = {};\n this.statusbarEls = {};\n this.dropdownMenus = {};\n \n // Popup management - each editor has its own popup instances\n this.popupInstances = new Map();\n \n // Set as current instance\n Editor.currentInstance = this;\n \n // Register this instance\n const instanceId = this.generateInstanceId();\n this.instanceId = instanceId;\n Editor.instances.set(instanceId, this);\n \n this.init();\n }\n\n /**\n * Generate unique instance ID\n */\n generateInstanceId() {\n return 'editor_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);\n }\n\n /**\n * Initialize editor\n */\n init() {\n this.createStructure();\n this.loadModules();\n this.loadFormats();\n this.setupEventListeners();\n this.updateStatusbar();\n }\n\n /**\n * Create basic DOM structure - extracted from EditorCore.init()\n * TODO: Copy implementation from EditorCore.init()\n */\n createStructure() {\n // Create wrapper\n this.wrapper = document.createElement('div');\n this.wrapper.className = 'yjd-rich-editor';\n \n // Apply dynamic sizing. A number is treated as pixels; a string (e.g.\n // '100%') is applied verbatim so the editor can size responsively to its\n // container instead of a fixed width.\n const cssSize = (v) => (typeof v === 'number' ? v + 'px' : v);\n this.wrapper.style.width = cssSize(this.options.width);\n this.wrapper.style.maxWidth = cssSize(this.options.maxWidth);\n this.wrapper.style.minHeight = cssSize(this.options.height);\n this.wrapper.style.maxHeight = cssSize(this.options.maxHeight);\n \n // Set position relative for popup positioning\n this.wrapper.style.position = 'relative';\n\n // Create editor area\n this.editor = document.createElement('div');\n this.editor.className = 'rich-editor-area';\n this.editor.contentEditable = true;\n this.editor.setAttribute('data-placeholder', this.options.placeholder);\n\n // Accessibility: expose the editable region to assistive technology\n this.editor.setAttribute('role', 'textbox');\n this.editor.setAttribute('aria-multiline', 'true');\n this.editor.setAttribute('aria-label', this.options.ariaLabel || this.options.placeholder || 'Rich text editor');\n\n // Text direction (RTL support)\n if (this.options.direction) {\n this.editor.setAttribute('dir', this.options.direction === 'rtl' ? 'rtl' : 'ltr');\n }\n \n // Force browser to create <p> tags instead of <div> when pressing Enter\n execFormat('defaultParagraphSeparator', 'p');\n \n // Add default content\n this.editor.innerHTML = this.getDefaultContent();\n \n this.wrapper.appendChild(this.editor);\n\n // Create popup container\n this.popupContainer = document.createElement('div');\n this.popupContainer.className = 'rich-editor-popup-container';\n this.popupContainer.style.position = 'absolute';\n this.popupContainer.style.top = '0';\n this.popupContainer.style.left = '0';\n this.popupContainer.style.width = '100%';\n this.popupContainer.style.height = '100%';\n this.popupContainer.style.pointerEvents = 'none';\n this.popupContainer.style.zIndex = '1000';\n this.wrapper.appendChild(this.popupContainer);\n\n // Create statusbar if needed\n if (this.options.features.wordCount || this.options.features.breadcrumb) {\n this.createStatusbar();\n }\n\n // Add wrapper to root\n this.root.appendChild(this.wrapper);\n \n // Initialize placeholder visibility\n this.updatePlaceholderVisibility();\n }\n\n /**\n * Check if content is HTML or plain text\n * @param {string} content - Content to check\n * @returns {boolean} True if content appears to be HTML\n */\n isHtmlContent(content) {\n if (!content || typeof content !== 'string') {\n return false;\n }\n \n // Trim whitespace for checking\n const trimmed = content.trim();\n \n // Check for common HTML patterns\n const htmlPatterns = [\n /<[^>]+>/, // Contains HTML tags\n /&[a-zA-Z]+;/, // Contains HTML entities\n /&#\\d+;/, // Contains numeric HTML entities\n ];\n \n return htmlPatterns.some(pattern => pattern.test(trimmed));\n }\n\n /**\n * Wrap plain text content in a paragraph tag\n * @param {string} content - Content to wrap\n * @returns {string} Wrapped content\n */\n wrapTextInParagraph(content) {\n if (!content || typeof content !== 'string') {\n return '<p><br></p>';\n }\n \n const trimmed = content.trim();\n \n // If content is already HTML, return as is\n if (this.isHtmlContent(trimmed)) {\n return trimmed;\n }\n \n // If content is empty, return empty paragraph\n if (trimmed === '') {\n return '<p><br></p>';\n }\n \n // Wrap plain text in paragraph tag\n return `<p>${trimmed}</p>`;\n }\n\n /**\n * Get default content for editor\n */\n getDefaultContent() {\n // If custom content is provided in options, use it\n if (this.options.content) {\n return this.wrapTextInParagraph(this.options.content);\n }\n \n // Restore an autosaved draft if available\n const saved = this._getAutosaved();\n if (saved != null && saved !== '') {\n return saved;\n }\n\n // Return completely empty content to show placeholder\n return '';\n }\n\n /**\n * Create statusbar - extracted from EditorCore\n * TODO: Copy implementation from EditorCore.init()\n */\n createStatusbar() {\n this.statusbar = document.createElement('div');\n this.statusbar.className = 'rich-editor-statusbar';\n\n // Create breadcrumb and word count elements\n this.statusbarEls.breadcrumb = document.createElement('span');\n this.statusbarEls.breadcrumb.className = 'rich-editor-breadcrumb';\n\n this.statusbarEls.wordcount = document.createElement('span');\n this.statusbarEls.wordcount.className = 'wordcount';\n\n this.statusbar.appendChild(this.statusbarEls.breadcrumb);\n this.statusbar.appendChild(this.statusbarEls.wordcount);\n this.wrapper.appendChild(this.statusbar);\n }\n\n /**\n * Load and initialize modules\n */\n loadModules() {\n // Determine which modules to load\n let modulesToLoad;\n \n // Check if user provided toolbar configuration\n const hasToolbarConfig = this.options.toolbar || this.options.toolbar1 || this.options.toolbar2;\n \n if (hasToolbarConfig) {\n // User wants custom toolbar - load only basic modules\n modulesToLoad = this.options.modules || ['toolbar', 'history'];\n } else {\n // No toolbar config - load full feature set\n modulesToLoad = this.options.modules || ['toolbar', 'history', 'block-toolbar', 'table-toolbar', 'code-view', 'theme-switcher', 'resize-handles', 'find-replace', 'slash-menu', 'mention'];\n }\n\n // @mention is inert without a source, so load it whenever configured —\n // even alongside a custom toolbar that otherwise loads only basics.\n if (this.options.mention && !modulesToLoad.includes('mention')) {\n modulesToLoad.push('mention');\n }\n\n \n modulesToLoad.forEach(moduleName => {\n const ModuleClass = this.registry.get(`modules/${moduleName}`);\n if (ModuleClass) {\n // For toolbar module, pass all options so it can detect toolbar config\n const moduleOptions = moduleName === 'toolbar' ? this.options : (this.options[moduleName] || this.options);\n const moduleInstance = new ModuleClass(this, moduleOptions);\n this.modules.set(moduleName, moduleInstance);\n \n // Insert toolbar before editor\n if (moduleName === 'toolbar' && moduleInstance.getContainer) {\n const toolbarContainer = moduleInstance.getContainer();\n this.wrapper.insertBefore(toolbarContainer, this.editor);\n \n // Listen for toolbar events\n moduleInstance.on('toolbar-click', (data) => {\n this.handleToolbarClick(data);\n });\n }\n \n } else {\n }\n });\n }\n\n /**\n * Load and initialize formats\n */\n loadFormats() {\n // Determine which formats to load\n let formatsToLoad;\n \n // Check if user provided toolbar configuration\n const hasToolbarConfig = this.options.toolbar || this.options.toolbar1 || this.options.toolbar2;\n \n if (hasToolbarConfig) {\n // User wants custom toolbar - load only basic formats\n formatsToLoad = this.options.formats || ['bold', 'italic', 'underline', 'strike'];\n } else {\n // No toolbar config - load full feature set\n formatsToLoad = this.options.formats || [\n 'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript',\n 'color', 'background', 'text-align', 'text-size', 'link',\n 'code', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', \n 'paragraph', 'pre'\n ];\n }\n \n \n formatsToLoad.forEach(formatName => {\n const FormatClass = this.registry.get(`formats/${formatName}`);\n if (FormatClass) {\n this.formats.set(formatName, FormatClass);\n } else {\n }\n });\n }\n\n /**\n * Setup event listeners - extracted from EditorCore\n * TODO: Copy implementation from EditorCore.bindEvents()\n */\n setupEventListeners() {\n // Track the active editor: whenever the user interacts with THIS editor\n // (pointer or focus anywhere inside its wrapper — editor area, toolbar,\n // popups), make it the current instance. This fixes multi-instance bugs\n // where helpers resolved to the last-CREATED editor instead of the\n // last-INTERACTED one.\n this._markActive = () => { Editor.currentInstance = this; };\n this.wrapper.addEventListener('pointerdown', this._markActive, true);\n this.wrapper.addEventListener('focusin', this._markActive, true);\n\n // Basic input event. onContentChange() already runs ensureEditorHasContent()\n // and updateStatusbar() (via _emitChange), so we don't duplicate them here.\n this.editor.addEventListener('input', () => {\n this.updatePlaceholderVisibility();\n this.onContentChange();\n });\n\n // Selection changes (caret move, selection) — coalesced to one run per\n // animation frame so rapid events don't each trigger a full toolbar pass.\n this._onDocSelectionChange = () => {\n if (document.activeElement === this.editor || this.editor.contains(document.activeElement)) {\n this._scheduleSelectionUpdate();\n }\n };\n document.addEventListener('selectionchange', this._onDocSelectionChange);\n\n // Mouse up: selectionchange already fires for this; just ensure a refresh\n // (still rAF-throttled, no setTimeout).\n this.editor.addEventListener('mouseup', () => this._scheduleSelectionUpdate());\n\n // Click inside the editor: keep a valid editable block.\n this.editor.addEventListener('click', () => {\n this.ensureEditorHasContent();\n });\n\n // Image context menu (right-click)\n this.editor.addEventListener('contextmenu', (e) => {\n // Image context menu functionality removed - methods don't exist\n });\n\n // Formatting keyboard shortcuts (Ctrl/Cmd + B/I/U, Ctrl/Cmd + K for link)\n this.editor.addEventListener('keydown', (e) => {\n if (!(e.ctrlKey || e.metaKey) || e.altKey) return;\n const shortcuts = { b: 'bold', i: 'italic', u: 'underline', k: 'link' };\n const command = shortcuts[e.key.toLowerCase()];\n if (!command) return;\n // Don't hijack shift-modified combos (e.g. Ctrl+Shift+...) except plain ones\n if (e.shiftKey) return;\n e.preventDefault();\n this.toggleFormat(command);\n });\n\n // Handle keydown events to ensure content structure\n this.editor.addEventListener('keydown', (e) => {\n // Check for delete/backspace operations that might empty the editor\n if (e.key === 'Delete' || e.key === 'Backspace') {\n // Use setTimeout to check after the deletion occurs\n setTimeout(() => {\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n }, 0);\n }\n });\n\n // Handle paste events — sanitize pasted HTML (prevents XSS and strips\n // messy markup from Word/Google Docs). Set options.pasteAsPlainText to\n // always paste as plain text instead.\n this.editor.addEventListener('paste', (e) => {\n this.handlePaste(e);\n });\n\n // Allow dropping (needed for the drop event to fire with files)\n this.editor.addEventListener('dragover', (e) => {\n if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('Files')) {\n e.preventDefault();\n }\n });\n\n // Handle drop events (drag and drop) — insert dropped image files\n this.editor.addEventListener('drop', (e) => {\n const dt = e.dataTransfer;\n const files = dt && dt.files ? Array.from(dt.files) : [];\n const imageFile = files.find(f => f.type && f.type.startsWith('image/'));\n if (imageFile) {\n e.preventDefault();\n this.placeCaretAtPoint(e.clientX, e.clientY);\n this.insertImageFile(imageFile);\n return;\n }\n // Check content after a normal drop operation\n setTimeout(() => {\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n }, 0);\n });\n\n // Enforce character limit (maxLength) on insertion-type input\n if (this.options.maxLength) {\n this.editor.addEventListener('beforeinput', (e) => {\n if (!e.inputType || !e.inputType.startsWith('insert')) return;\n if (e.inputType === 'insertFromPaste') return; // handled in handlePaste\n const incoming = e.data ? e.data.length : 1;\n if (this._remainingChars() < incoming) {\n e.preventDefault();\n }\n });\n }\n\n // Markdown shortcuts (# heading, - bullet, 1. ordered, > quote) on space\n if (this.options.markdown !== false) {\n this.editor.addEventListener('keydown', (e) => {\n if (e.key === ' ' && !e.ctrlKey && !e.metaKey && !e.altKey) {\n this.handleMarkdownShortcut(e);\n }\n });\n }\n\n // Handle cut events\n this.editor.addEventListener('cut', () => {\n // Check content after cut operation\n setTimeout(() => {\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n }, 0);\n });\n\n // Focus editor on load\n setTimeout(() => {\n // Ensure editor has proper content structure on load\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n // Set the initial undo/redo dimmed state (nothing to undo yet).\n this.updateHistoryButtons();\n this.focus();\n }, 100);\n\n // Handle focus events to ensure content structure\n this.editor.addEventListener('focus', () => {\n // Ensure there's always a paragraph element for editing when focusing\n setTimeout(() => {\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n }, 0);\n });\n }\n\n /**\n * Handle content changes\n */\n onContentChange() {\n // Check if editor is empty and create a paragraph element if needed\n this.ensureEditorHasContent();\n this._emitChange();\n }\n\n /**\n * Notify listeners of a content change WITHOUT running the empty-content\n * reset (used when an intentionally-empty block — e.g. a fresh heading from\n * a markdown shortcut — must not be wiped by ensureEditorHasContent).\n */\n _emitChange() {\n this.modules.forEach(module => {\n if (typeof module.onContentChange === 'function') {\n module.onContentChange();\n }\n });\n\n // Keep the status bar (word/char count + breadcrumb) in sync after every\n // mutation, including programmatic ones (find/replace, undo/redo, toolbar).\n this.updateStatusbar();\n\n // Get current content\n const content = this.getContent();\n\n // Call onChange callback if provided\n if (this.options.onChange && typeof this.options.onChange === 'function') {\n this.options.onChange(content);\n }\n\n // Persist draft if autosave is enabled\n this._scheduleAutosave(content);\n\n // Emit change events ('change' is the documented name; 'text-change' kept\n // for backward compatibility).\n this.emit('change', content);\n this.emit('text-change', content);\n }\n\n /**\n * Ensure editor always has a paragraph element for editing\n * This prevents users from editing directly in the editor container\n */\n ensureEditorHasContent() {\n if (!this.isEditorEmpty()) return;\n\n // Only act when the caret/selection is actually inside this editor — avoids\n // clearing formats or stealing focus based on a selection elsewhere.\n const selInEditor = this.isSelectionInEditableArea(window.getSelection());\n\n // Rebuild to a clean paragraph when needed — this strips leftover empty\n // formatting tags (e.g. <b><i><u>) that survive a \"delete all\".\n if (this.editor.innerHTML !== '<p><br></p>') {\n const paragraph = document.createElement('p');\n paragraph.innerHTML = '<br>';\n this.editor.innerHTML = '';\n this.editor.appendChild(paragraph);\n this.setCursorToElement(paragraph);\n this.editor.focus();\n }\n\n // Clearing the DOM is not enough: browsers keep a \"pending\" inline-format\n // state for the next typed character. Toggle off any active inline format\n // so new text isn't unexpectedly bold/italic/underline/strikethrough.\n if (selInEditor || document.activeElement === this.editor) {\n this._clearStickyInlineFormats();\n }\n\n this.updateToolbarButtonStates();\n this.updateStatusbar();\n }\n\n /**\n * Turn off any active inline formatting command so the next typed character\n * starts unformatted. Only affects a collapsed caret (no DOM mutation).\n */\n _clearStickyInlineFormats() {\n ['bold', 'italic', 'underline', 'strikeThrough'].forEach((cmd) => {\n if (queryFormatState(cmd)) execFormat(cmd);\n });\n }\n\n /**\n * Ensure there's always a paragraph element available for editing\n * This prevents users from editing directly in the editor container\n */\n ensureParagraphForEditing() {\n const children = this.editor.children;\n \n // If editor has no children, create a paragraph\n if (children.length === 0) {\n const paragraph = document.createElement('p');\n paragraph.innerHTML = '<br>';\n this.editor.appendChild(paragraph);\n this.setCursorToElement(paragraph);\n return;\n }\n \n // Check if the last child is a block element that can contain text\n const lastChild = children[children.length - 1];\n const blockTags = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'PRE', 'ARTICLE', 'SECTION', 'MAIN', 'ASIDE'];\n \n // Only add paragraph if the last child is not a block element that can contain text\n if (!blockTags.includes(lastChild.tagName)) {\n // Add a paragraph element at the end for editing\n const paragraph = document.createElement('p');\n paragraph.innerHTML = '<br>';\n this.editor.appendChild(paragraph);\n }\n }\n\n /**\n * Check if editor is empty or contains only empty elements\n */\n isEditorEmpty() {\n // Real text content → not empty (ignore zero-width spaces).\n const text = this.editor.textContent;\n if (text && text.replace(/\\u200B/g, '').trim() !== '') return false;\n\n // Embedded/void media counts as content even with no text.\n if (this.editor.querySelector('img, table, hr, video, iframe, audio, figure')) {\n return false;\n }\n\n // Otherwise empty — including the case where only empty formatting tags\n // remain (e.g. <p><b><i><u><br></u></i></b></p> after deleting everything).\n return true;\n }\n\n /**\n * Set cursor position to a specific element\n */\n setCursorToElement(element) {\n const range = document.createRange();\n const selection = window.getSelection();\n \n // Try to set cursor at the beginning of the element\n if (element.firstChild && element.firstChild.nodeType === Node.TEXT_NODE) {\n range.setStart(element.firstChild, 0);\n } else {\n range.setStart(element, 0);\n }\n \n range.collapse(true);\n \n selection.removeAllRanges();\n selection.addRange(range);\n }\n\n /**\n * Coalesce selection-driven UI updates to one run per animation frame.\n */\n _scheduleSelectionUpdate() {\n if (this._selUpdateQueued) return;\n this._selUpdateQueued = true;\n requestAnimationFrame(() => {\n this._selUpdateQueued = false;\n this.onSelectionChange();\n });\n }\n\n /**\n * Handle selection changes\n */\n onSelectionChange() {\n const selection = window.getSelection();\n const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;\n \n // Check if selection is within rich-editor-area\n const isInEditableArea = this.isSelectionInEditableArea(selection);\n\n // If the selection is inside this editor, it is the active instance.\n if (isInEditableArea) {\n Editor.currentInstance = this;\n // Remember the last real (non-collapsed) selection so popups (colour,\n // etc.) can restore it even if a tap clears the live selection on mobile.\n if (range && !range.collapsed) {\n this._lastRange = range.cloneRange();\n }\n }\n\n // Update all modules with selection info\n this.modules.forEach(module => {\n if (typeof module.onSelectionChange === 'function') {\n module.onSelectionChange(range, isInEditableArea);\n }\n });\n \n // Update toolbar button states\n this.updateToolbarButtonStates();\n \n // Update toolbar buttons accessibility\n this.updateToolbarAccessibility(isInEditableArea);\n \n // Update statusbar when selection changes\n this.updateStatusbar();\n }\n\n /**\n * Check if current selection is within the rich-editor-area\n */\n isSelectionInEditableArea(selection) {\n if (!selection || selection.rangeCount === 0) {\n return false;\n }\n\n const range = selection.getRangeAt(0);\n const startContainer = range.startContainer;\n const endContainer = range.endContainer;\n \n // Check if both start and end containers are within rich-editor-area\n const startInEditor = this.isNodeInEditableArea(startContainer);\n const endInEditor = this.isNodeInEditableArea(endContainer);\n \n return startInEditor && endInEditor;\n }\n\n /**\n * Check if a node is within the rich-editor-area\n */\n isNodeInEditableArea(node) {\n if (!node) return false;\n \n // Traverse up the DOM tree to find rich-editor-area\n let currentNode = node.nodeType === Node.TEXT_NODE ? node.parentNode : node;\n \n while (currentNode && currentNode !== document.body) {\n if (currentNode === this.editor || \n (currentNode.classList && currentNode.classList.contains('rich-editor-area'))) {\n return true;\n }\n currentNode = currentNode.parentNode;\n }\n \n return false;\n }\n\n /**\n * Update toolbar accessibility based on selection location\n */\n updateToolbarAccessibility(isInEditableArea) {\n const toolbar = this.getModule('toolbar');\n if (!toolbar) return;\n \n // List of commands that should be disabled when outside editable area\n // Note: undo/redo are NOT in this list - they should always work\n const editingCommands = [\n 'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript',\n 'color', 'background', 'link', 'table', 'heading', \n 'font-family', 'line-height', 'capitalization', 'text-align', 'list',\n 'indent-increase', 'indent-decrease', 'text-size'\n ];\n \n editingCommands.forEach(command => {\n toolbar.setButtonDisabled(command, !isInEditableArea);\n });\n \n // These commands should always be enabled regardless of selection location\n const alwaysEnabledCommands = ['more', 'undo', 'redo', 'code-view', 'theme'];\n alwaysEnabledCommands.forEach(command => {\n toolbar.setButtonDisabled(command, false);\n });\n }\n\n /**\n * Update statusbar - extracted from EditorCore\n * TODO: Copy implementation from EditorCore.updateStatusbar()\n */\n updateStatusbar() {\n if (!this.statusbar) return;\n\n const sel = window.getSelection();\n if (!sel) return;\n\n // Update breadcrumb\n if (this.statusbarEls.breadcrumb && this.options.features.breadcrumb) {\n const currentNode = sel.anchorNode;\n // Only reflect a selection that lives inside THIS editor. On a page with\n // several editors the selection is global, so without this guard each\n // statusbar would walk up to <body> and show another editor's path.\n if (!currentNode || !this.editor.contains(currentNode)) {\n this.statusbarEls.breadcrumb.textContent = 'editor';\n } else {\n const path = [];\n let element = currentNode?.nodeType === 3 ? currentNode.parentElement : currentNode;\n\n while (element && element !== this.editor && element !== document.body) {\n if (element.tagName) {\n let tagInfo = element.tagName.toLowerCase();\n if (element.className && typeof element.className === 'string') {\n const classes = element.className.trim();\n if (classes) {\n tagInfo += '.' + classes.split(' ').join('.');\n }\n }\n if (element.id) {\n tagInfo += '#' + element.id;\n }\n path.unshift(tagInfo);\n }\n element = element.parentElement;\n }\n\n this.statusbarEls.breadcrumb.textContent = path.length > 0 ? path.join(' > ') : 'editor';\n }\n }\n\n // Update word count\n if (this.statusbarEls.wordcount && this.options.features.wordCount) {\n // While in HTML code-view, count the rendered text of the source being\n // edited (not the stale visual content that's hidden behind it).\n let text;\n const codeView = this.getModule('code-view');\n if (codeView && typeof codeView.isInCodeView === 'function' && codeView.isInCodeView()) {\n const tmp = document.createElement('div');\n tmp.innerHTML = codeView.getCurrentContent ? codeView.getCurrentContent() : '';\n text = tmp.textContent || '';\n } else {\n text = this.editor.textContent || '';\n }\n const words = text.trim() ? text.trim().split(/\\s+/).length : 0;\n const chars = text.length;\n const charsNoSpaces = text.replace(/\\s/g, '').length;\n \n let label = `${words} words, ${chars} chars (${charsNoSpaces} no spaces)`;\n if (this.options.maxLength) {\n label += ` • ${Math.max(0, this.options.maxLength - chars)} left`;\n }\n this.statusbarEls.wordcount.textContent = label;\n }\n }\n\n /**\n * Focus editor\n */\n focus() {\n if (this.editor) {\n this.editor.focus();\n }\n }\n\n /**\n * Get editor content\n */\n getContent() {\n return this.editor.innerHTML;\n }\n\n /**\n * Set editor content\n */\n setContent(html) {\n // Wrap plain text content in paragraph tag if needed\n const processedContent = this.wrapTextInParagraph(html);\n this.editor.innerHTML = processedContent;\n this.onContentChange();\n }\n\n /* ----- Export / import in common formats (HTML · JSON · Markdown) ----- */\n\n /** HTML string (alias of getContent). */\n getHTML() { return this.getContent(); }\n /** Set content from an HTML string (sanitised). */\n setHTML(html) { this.setContent(sanitizeHtml(html || '')); }\n\n /** Structured JSON document `{ type:'doc', content:[…] }`. */\n getJSON() { return domToJson(this.getContent()); }\n /** Set content from a JSON document (produced by getJSON). */\n setJSON(json) { this.setContent(sanitizeHtml(jsonToHtml(json))); }\n\n /** Markdown string. */\n getMarkdown() { return htmlToMarkdown(this.getContent()); }\n /** Set content from a Markdown string. */\n setMarkdown(md) { this.setContent(sanitizeHtml(markdownToHtml(md || ''))); }\n\n /**\n * Get the plain text content of the editor (no markup).\n * @returns {string}\n */\n getText() {\n return this.editor.textContent || '';\n }\n\n /**\n * Whether the editor has no meaningful content.\n * @returns {boolean}\n */\n isEmpty() {\n return this.isEditorEmpty();\n }\n\n /**\n * Clear all content, leaving an empty paragraph.\n */\n clear() {\n this.editor.innerHTML = '<p><br></p>';\n this.onContentChange();\n this.updatePlaceholderVisibility();\n }\n\n /**\n * Insert plain text at the current caret position.\n * @param {string} text\n */\n insertText(text) {\n if (typeof text !== 'string') return;\n this.focus();\n execFormat('insertText', text);\n this.onContentChange();\n }\n\n /**\n * Insert HTML at the current caret position (sanitized to prevent XSS).\n * @param {string} html\n */\n insertHTML(html) {\n if (typeof html !== 'string') return;\n this.focus();\n execFormat('insertHTML', sanitizeHtml(html));\n this.onContentChange();\n }\n\n /**\n * Handle a paste event: sanitize pasted HTML, or paste as plain text.\n * @param {ClipboardEvent} e\n */\n handlePaste(e) {\n const clipboard = e.clipboardData || window.clipboardData;\n if (!clipboard) return; // let the browser handle it\n\n // Pasted image file (screenshot, copied image) → insert as image.\n const items = clipboard.items ? Array.from(clipboard.items) : [];\n const imageItem = items.find(it => it.kind === 'file' && it.type && it.type.startsWith('image/'));\n if (imageItem) {\n const file = imageItem.getAsFile();\n if (file) {\n e.preventDefault();\n this.insertImageFile(file);\n return;\n }\n }\n\n let html = clipboard.getData('text/html');\n let text = clipboard.getData('text/plain');\n\n // Nothing useful to insert ourselves → fall back to default behavior\n if (!html && !text) return;\n\n e.preventDefault();\n\n // Enforce character limit on paste (force plain text trimmed to remaining)\n if (this.options.maxLength) {\n const remaining = this._remainingChars();\n if (remaining <= 0) return;\n const plain = (text || '').slice(0, remaining);\n execFormat('insertText', plain);\n } else if (!this.options.pasteAsPlainText && html) {\n execFormat('insertHTML', sanitizeHtml(html));\n } else if (text) {\n execFormat('insertText', text);\n }\n\n setTimeout(() => {\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n this.onContentChange();\n }, 0);\n }\n\n /**\n * Number of characters that can still be added before hitting maxLength\n * (accounts for the current selection being replaced). Infinity if no limit.\n */\n _remainingChars() {\n if (!this.options.maxLength) return Infinity;\n const sel = window.getSelection();\n const selLen = sel && !sel.isCollapsed ? sel.toString().length : 0;\n return this.options.maxLength - (this.getText().length - selLen);\n }\n\n /**\n * Read an image File and insert it (as a base64 data URL) at the caret.\n * @param {File} file\n */\n insertImageFile(file) {\n if (!file || !file.type || !file.type.startsWith('image/')) return;\n\n const cfg = this.options.image || {};\n // Validate against optional accept / maxSize before doing anything.\n if (cfg.accept && cfg.accept !== 'image/*') {\n const ok = cfg.accept.split(',').some(a => {\n a = a.trim();\n return a.endsWith('/*') ? file.type.startsWith(a.slice(0, -1)) : file.type === a;\n });\n if (!ok) { this.emit('image:error', { file, reason: 'type' }); return; }\n }\n if (cfg.maxSize && file.size > cfg.maxSize) {\n this.emit('image:error', { file, reason: 'size' });\n return;\n }\n\n // Build a real <img> from a src (validates via the Image format).\n const makeImg = (src, extra = '') => {\n const ImageClass = this.registry.get('formats/image');\n if (ImageClass && typeof ImageClass.create === 'function') {\n const img = ImageClass.create(src);\n if (!img) return null;\n if (extra) img.setAttribute('data-state', extra);\n return img;\n }\n return null;\n };\n\n // No upload hook → embed as base64 (backward-compatible default).\n if (typeof cfg.upload !== 'function') {\n const reader = new FileReader();\n reader.onload = (ev) => {\n const html = (makeImg(ev.target.result) || {}).outerHTML\n || `<img src=\"${ev.target.result}\" class=\"inserted-image\" style=\"max-width:100%\" contenteditable=\"false\">`;\n this.focus();\n execFormat('insertHTML', html);\n this.onContentChange();\n };\n reader.readAsDataURL(file);\n return;\n }\n\n // Upload hook: insert a placeholder, await the URL, then swap the src.\n const placeholderId = 'rte-up-' + Math.round(performance.now()) + '-' + (this._upCounter = (this._upCounter || 0) + 1);\n const ph = makeImg('data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%27120%27 height=%2790%27%3E%3Crect width=%27100%25%27 height=%27100%25%27 fill=%27%23efedff%27/%3E%3C/svg%3E', 'uploading');\n if (!ph) return;\n ph.id = placeholderId;\n ph.style.opacity = '0.6';\n this.focus();\n execFormat('insertHTML', ph.outerHTML);\n this.emit('image:upload', { file });\n\n Promise.resolve(cfg.upload(file)).then((url) => {\n const el = this.editor.querySelector('#' + placeholderId);\n if (!el) return;\n if (url) {\n el.src = url;\n el.style.opacity = '';\n el.removeAttribute('data-state');\n el.removeAttribute('id');\n this.emit('image:uploaded', { file, url });\n } else {\n el.remove();\n }\n this.onContentChange();\n }).catch((err) => {\n const el = this.editor.querySelector('#' + placeholderId);\n if (el) el.remove();\n this.emit('image:error', { file, reason: 'upload', error: err });\n this.onContentChange();\n });\n }\n\n /**\n * Place the caret at the given viewport coordinates (used for drag-drop).\n */\n placeCaretAtPoint(x, y) {\n let range = null;\n if (document.caretRangeFromPoint) {\n range = document.caretRangeFromPoint(x, y);\n } else if (document.caretPositionFromPoint) {\n const pos = document.caretPositionFromPoint(x, y);\n if (pos) {\n range = document.createRange();\n range.setStart(pos.offsetNode, pos.offset);\n }\n }\n if (range) {\n range.collapse(true);\n const sel = window.getSelection();\n sel.removeAllRanges();\n sel.addRange(range);\n }\n }\n\n /**\n * Transform a markdown marker at the start of the current block when space\n * is pressed: \"# \" → H1..H6, \"- \"/\"* \" → bullet list, \"1. \" → ordered list,\n * \"> \" → blockquote.\n * @param {KeyboardEvent} e\n */\n handleMarkdownShortcut(e) {\n const sel = window.getSelection();\n if (!sel || !sel.isCollapsed || !sel.rangeCount) return;\n const range = sel.getRangeAt(0);\n\n // Find the nearest plain block (P/DIV) containing the caret.\n let block = range.startContainer;\n block = block.nodeType === Node.TEXT_NODE ? block.parentElement : block;\n while (block && block !== this.editor && block.tagName !== 'P' && block.tagName !== 'DIV') {\n block = block.parentElement;\n }\n if (!block || block === this.editor) return;\n\n // Text from block start to the caret = the marker the user typed.\n const pre = document.createRange();\n pre.selectNodeContents(block);\n pre.setEnd(range.startContainer, range.startOffset);\n const marker = pre.toString();\n\n const blockMap = { '#': 'h1', '##': 'h2', '###': 'h3', '####': 'h4', '#####': 'h5', '######': 'h6', '>': 'blockquote' };\n const blockTag = blockMap[marker];\n const listType = (marker === '-' || marker === '*') ? 'ul'\n : /^\\d+\\.$/.test(marker) ? 'ol' : null;\n if (!blockTag && !listType) return;\n\n e.preventDefault();\n\n const history = this.getModule('history');\n if (history && typeof history.saveBeforeFormat === 'function') history.saveBeforeFormat();\n\n // Remove the marker text the user typed.\n pre.deleteContents();\n\n if (blockTag) {\n // Replace the block element directly (execCommand formatBlock is\n // unreliable on a now-empty block).\n const el = document.createElement(blockTag);\n while (block.firstChild) el.appendChild(block.firstChild);\n // Ensure a <br> placeholder so the empty block stays focusable/visible\n if (el.textContent === '' && !el.querySelector('*')) {\n el.innerHTML = '<br>';\n }\n block.replaceWith(el);\n const caret = document.createRange();\n caret.selectNodeContents(el);\n caret.collapse(true);\n sel.removeAllRanges();\n sel.addRange(caret);\n } else {\n const caret = document.createRange();\n caret.selectNodeContents(block);\n caret.collapse(true);\n sel.removeAllRanges();\n sel.addRange(caret);\n execFormat(listType === 'ul' ? 'insertUnorderedList' : 'insertOrderedList');\n }\n // Use _emitChange (not onContentChange) so a fresh empty heading/quote\n // isn't wiped by the empty-content reset.\n this._emitChange();\n this.updatePlaceholderVisibility();\n }\n\n /**\n * Set text direction ('ltr' | 'rtl').\n */\n setDirection(dir) {\n const d = dir === 'rtl' ? 'rtl' : 'ltr';\n this.editor.setAttribute('dir', d);\n const toolbar = this.getModule('toolbar');\n if (toolbar) toolbar.setButtonActive('text-direction', d === 'rtl');\n }\n\n /**\n * @returns {'ltr'|'rtl'} Current text direction.\n */\n getDirection() {\n return this.editor.getAttribute('dir') === 'rtl' ? 'rtl' : 'ltr';\n }\n\n /**\n * Toggle between LTR and RTL.\n */\n toggleDirection() {\n this.setDirection(this.getDirection() === 'rtl' ? 'ltr' : 'rtl');\n }\n\n /**\n * Normalized autosave config ({ key, debounce }) or null when disabled.\n */\n _autosaveCfg() {\n if (this._autosaveMemo !== undefined) return this._autosaveMemo;\n const a = this.options.autosave;\n if (!a) { this._autosaveMemo = null; return null; }\n this._autosaveMemo = {\n key: (typeof a === 'object' && a.key) ? a.key : 'yjd-autosave',\n debounce: (typeof a === 'object' && a.debounce) ? a.debounce : 1000\n };\n return this._autosaveMemo;\n }\n\n /** Read previously autosaved content (or null). */\n _getAutosaved() {\n const cfg = this._autosaveCfg();\n if (!cfg) return null;\n try { return localStorage.getItem(cfg.key); } catch (e) { return null; }\n }\n\n /** Debounced write of content to localStorage. */\n _scheduleAutosave(content) {\n const cfg = this._autosaveCfg();\n if (!cfg) return;\n clearTimeout(this._autosaveTimer);\n this._autosaveTimer = setTimeout(() => {\n try { localStorage.setItem(cfg.key, content); } catch (e) { /* storage unavailable */ }\n }, cfg.debounce);\n }\n\n /** Remove the autosaved draft from storage. */\n clearAutosave() {\n const cfg = this._autosaveCfg();\n if (!cfg) return;\n try { localStorage.removeItem(cfg.key); } catch (e) { /* ignore */ }\n }\n\n /**\n * Remove inline formatting (and links) from the current selection.\n */\n clearFormatting() {\n const historyModule = this.getModule('history');\n if (historyModule && typeof historyModule.saveBeforeFormat === 'function') {\n historyModule.saveBeforeFormat();\n }\n this.focus();\n execFormat('removeFormat');\n execFormat('unlink');\n this.onContentChange();\n this.updateToolbarButtonStates();\n }\n\n /**\n * Convert the current block to a given type.\n * @param {('p'|'h1'|'h2'|'h3'|'h4'|'h5'|'h6'|'blockquote'|'pre'|'ul'|'ol')} type\n */\n setBlockType(type) {\n const sel = window.getSelection();\n if (!sel || !sel.rangeCount) return;\n let block = sel.getRangeAt(0).startContainer;\n block = block.nodeType === Node.TEXT_NODE ? block.parentElement : block;\n const BLOCK = /^(P|DIV|H[1-6]|BLOCKQUOTE|PRE|LI)$/;\n while (block && block !== this.editor && !BLOCK.test(block.tagName)) {\n block = block.parentElement;\n }\n if (!block || block === this.editor) return;\n\n const history = this.getModule('history');\n if (history && typeof history.saveBeforeFormat === 'function') history.saveBeforeFormat();\n\n if (type === 'ul' || type === 'ol') {\n const r = document.createRange();\n r.selectNodeContents(block);\n r.collapse(true);\n sel.removeAllRanges();\n sel.addRange(r);\n execFormat(type === 'ul' ? 'insertUnorderedList' : 'insertOrderedList');\n } else {\n const el = document.createElement(type);\n while (block.firstChild) el.appendChild(block.firstChild);\n if (el.textContent === '' && !el.querySelector('*')) el.innerHTML = '<br>';\n block.replaceWith(el);\n const r = document.createRange();\n r.selectNodeContents(el);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n }\n this._emitChange();\n this.updatePlaceholderVisibility();\n }\n\n /**\n * Insert a horizontal rule at the current caret position.\n */\n insertHorizontalRule() {\n const historyModule = this.getModule('history');\n if (historyModule && typeof historyModule.saveBeforeFormat === 'function') {\n historyModule.saveBeforeFormat();\n }\n this.focus();\n execFormat('insertHorizontalRule');\n this.onContentChange();\n }\n\n /**\n * Whether a block element has no real (text/media) content.\n */\n _isBlockEmpty(el) {\n if (!el) return true;\n if (el.querySelector && el.querySelector('img, table, hr, video, iframe, audio, figure')) {\n return false;\n }\n return (el.textContent || '').replace(/\\u200B/g, '').trim() === '';\n }\n\n /**\n * Insert a block-level element at the editor's top level, next to the block\n * containing the caret — never nested inside a heading or inline formatting\n * tag (which would be invalid HTML). Removes the source block if it became\n * empty, and guarantees an editable paragraph after the inserted block.\n * @param {HTMLElement} blockEl\n */\n insertBlock(blockEl) {\n const sel = window.getSelection();\n let topBlock = null;\n if (sel && sel.rangeCount) {\n const range = sel.getRangeAt(0);\n if (!range.collapsed) range.deleteContents();\n let node = range.startContainer;\n node = node.nodeType === Node.TEXT_NODE ? node.parentNode : node;\n while (node && node !== this.editor && node.parentNode !== this.editor) {\n node = node.parentNode;\n }\n if (node && node.parentNode === this.editor) topBlock = node;\n }\n\n if (topBlock) {\n const wasEmpty = this._isBlockEmpty(topBlock);\n if (topBlock.nextSibling) {\n this.editor.insertBefore(blockEl, topBlock.nextSibling);\n } else {\n this.editor.appendChild(blockEl);\n }\n // Remove the originating block if it held only the caret / empty format tags\n if (wasEmpty) topBlock.remove();\n } else {\n this.editor.appendChild(blockEl);\n }\n\n // Guarantee an editable paragraph after the inserted block\n if (!blockEl.nextSibling) {\n const p = document.createElement('p');\n p.innerHTML = '<br>';\n this.editor.appendChild(p);\n }\n }\n\n /**\n * Enable/disable read-only mode.\n * @param {boolean} readOnly\n */\n setReadOnly(readOnly) {\n this._readOnly = !!readOnly;\n this.editor.contentEditable = this._readOnly ? 'false' : 'true';\n this.editor.setAttribute('aria-readonly', this._readOnly ? 'true' : 'false');\n this.wrapper.classList.toggle('read-only', this._readOnly);\n\n // Disable/enable toolbar interaction\n const toolbar = this.getModule('toolbar');\n if (toolbar && toolbar.buttons) {\n toolbar.buttons.forEach((_, command) => {\n toolbar.setButtonDisabled(command, this._readOnly);\n });\n }\n }\n\n /**\n * @returns {boolean} Whether the editor is in read-only mode.\n */\n isReadOnly() {\n return !!this._readOnly;\n }\n\n /**\n * Get module instance\n */\n getModule(name) {\n return this.modules.get(name);\n }\n\n /**\n * Get format class\n */\n getFormat(name) {\n return this.formats.get(name);\n }\n\n /**\n * Register new items\n */\n register(path, definition, suppressWarning = false) {\n this.registry.register(path, definition, suppressWarning);\n }\n\n /**\n * Handle toolbar button clicks\n */\n handleToolbarClick(data) {\n const { command, button, value } = data;\n \n // Set this editor as current instance for the duration of this command\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = this;\n \n // Emit toolbar-click event for modules to listen\n this.emit('toolbar-click', data);\n \n // Commands that should always work regardless of selection location\n const alwaysAllowedCommands = ['more', 'undo', 'redo', 'code-view', 'theme', 'text-direction', 'find'];\n\n if (alwaysAllowedCommands.includes(command)) {\n // These commands can execute regardless of selection location\n switch (command) {\n case 'more':\n // More command is handled by toolbar module itself\n return;\n case 'undo':\n this.undo();\n return;\n case 'redo':\n this.redo();\n return;\n case 'code-view':\n // Code view command is handled by CodeView module itself\n // The module listens to 'toolbar-click' events and handles it internally\n return;\n case 'text-direction':\n this.toggleDirection();\n return;\n case 'find':\n // Find/replace module listens to 'toolbar-click' and opens its panel\n return;\n }\n }\n \n // For all other commands, check if current selection is in editable area\n const selection = window.getSelection();\n const isInEditableArea = this.isSelectionInEditableArea(selection);\n \n if (!isInEditableArea) {\n console.warn(`Command '${command}' blocked: Selection outside editable area`);\n return;\n }\n \n // Handle formatting commands (only when selection is in editable area)\n switch (command) {\n case 'bold':\n case 'italic': \n case 'underline':\n case 'strike':\n case 'subscript':\n case 'superscript':\n case 'color':\n case 'background':\n case 'link':\n case 'table':\n case 'heading':\n case 'font-family':\n case 'line-height':\n case 'capitalization':\n case 'text-align':\n case 'text-size':\n case 'list':\n case 'indent-increase':\n case 'indent-decrease':\n case 'emoji':\n case 'image':\n case 'video':\n case 'tag':\n\n case 'import':\n this.toggleFormat(command);\n break;\n case 'clear-format':\n this.clearFormatting();\n break;\n case 'horizontal-rule':\n this.insertHorizontalRule();\n break;\n default:\n console.warn(`Unknown command: ${command}`);\n }\n }\n\n /**\n * Toggle format on current selection\n */\n toggleFormat(formatName) {\n // Save state before applying format\n const historyModule = this.getModule('history');\n if (historyModule && typeof historyModule.saveBeforeFormat === 'function') {\n historyModule.saveBeforeFormat();\n }\n\n // Map format names to registry keys\n const formatMap = {\n 'bold': 'bold',\n 'italic': 'italic', \n 'underline': 'underline',\n 'strike': 'strike',\n 'subscript': 'subscript',\n 'superscript': 'superscript',\n 'color': 'color',\n 'background': 'background',\n 'link': 'link',\n 'table': 'table',\n 'heading': 'heading',\n 'font-family': 'font-family',\n 'line-height': 'line-height',\n 'capitalization': 'capitalization',\n 'text-align': 'text-align',\n 'text-size': 'text-size',\n 'list': 'list',\n 'indent-increase': 'indent-increase',\n 'indent-decrease': 'indent-decrease',\n 'emoji': 'emoji',\n 'image': 'image',\n 'video': 'video',\n 'tag': 'tag',\n\n 'import': 'import'\n };\n \n const registryKey = formatMap[formatName];\n if (!registryKey) {\n console.warn(`Unknown format: ${formatName}`);\n return;\n }\n \n const FormatClass = this.registry.get(`formats/${registryKey}`);\n if (!FormatClass) {\n return;\n }\n \n // Create format instance and toggle\n const formatInstance = new FormatClass();\n formatInstance.toggle();\n \n // Update button state\n this.updateToolbarButtonStates();\n \n // Trigger content change for formats that modify content immediately\n // (like bold, italic, underline, etc. that use execCommand)\n const immediateFormats = ['bold', 'italic', 'underline', 'strike', 'subscript', 'superscript'];\n if (immediateFormats.includes(formatName)) {\n // Use setTimeout to ensure DOM changes are complete\n setTimeout(() => {\n this.onContentChange();\n }, 0);\n }\n }\n\n /**\n * Get a cached format instance for state checks (created once per editor).\n */\n _getFormatInstance(formatName) {\n if (!this._fmtCache) this._fmtCache = new Map();\n if (this._fmtCache.has(formatName)) return this._fmtCache.get(formatName);\n const FormatClass = this.registry.get(`formats/${formatName}`);\n if (!FormatClass) return null;\n let inst;\n if (FormatClass.createForEditor) {\n inst = FormatClass.createForEditor(this.instanceId);\n } else {\n const original = Editor.currentInstance;\n Editor.currentInstance = this;\n inst = new FormatClass();\n Editor.currentInstance = original;\n }\n this._fmtCache.set(formatName, inst);\n return inst;\n }\n\n /**\n * Update toolbar button states based on current selection\n */\n updateToolbarButtonStates() {\n const toolbar = this.getModule('toolbar');\n if (!toolbar) return;\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n \n // Check if selection is in editable area\n const isInEditableArea = this.isSelectionInEditableArea(selection);\n \n const formats = ['heading', 'font-family', 'line-height', 'capitalization', 'text-align', 'list', 'indent-increase', 'indent-decrease', 'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript', 'color', 'background', 'link', 'table', 'text-size'];\n \n formats.forEach(formatName => {\n // Only check format state if selection is in editable area\n if (isInEditableArea) {\n // Reuse a cached instance per format (was creating 19 instances on\n // every caret move — wasteful garbage). isActive() reads live\n // selection/DOM, so a cached instance is safe.\n const formatInstance = this._getFormatInstance(formatName);\n if (formatInstance) {\n toolbar.setButtonActive(formatName, formatInstance.isActive());\n if (formatName === 'line-height' && typeof formatInstance.updateButtonText === 'function') {\n formatInstance.updateButtonText();\n }\n }\n } else {\n // Clear active state for buttons when outside editable area\n toolbar.setButtonActive(formatName, false);\n }\n });\n\n // Special handling for text-size: always update button text to show current size\n if (isInEditableArea) {\n const TextSizeClass = this.registry.get('formats/text-size');\n if (TextSizeClass && typeof TextSizeClass.updateButtonTextStatic === 'function') {\n TextSizeClass.updateButtonTextStatic(this.instanceId);\n }\n }\n\n this.updateColorSwatches();\n this.updateHistoryButtons();\n }\n\n /**\n * Undo/redo are always visible (no show/hide flicker); they're just dimmed\n * and disabled when there's nothing to act on.\n */\n updateHistoryButtons() {\n const toolbar = this.getModule('toolbar');\n if (!toolbar || typeof toolbar.getButton !== 'function') return;\n const history = this.getModule('history');\n const canUndo = !!(history && typeof history.canUndo === 'function' && history.canUndo());\n const canRedo = !!(history && typeof history.canRedo === 'function' && history.canRedo());\n const setState = (btn, enabled) => {\n if (!btn) return;\n btn.classList.remove('rte-hidden'); // always shown now\n btn.disabled = !enabled;\n btn.classList.toggle('is-disabled', !enabled);\n };\n setState(toolbar.getButton('undo'), canUndo);\n setState(toolbar.getButton('redo'), canRedo);\n }\n\n /**\n * Reflect the colour at the caret on the toolbar's colour/background swatch\n * bars. Falls back to the CSS default (via clearing the inline style) when no\n * explicit colour is applied.\n */\n updateColorSwatches() {\n const toolbar = this.getModule('toolbar');\n if (!toolbar || typeof toolbar.getButton !== 'function') return;\n\n const apply = (name, color) => {\n const btn = toolbar.getButton(name);\n if (!btn) return;\n const swatch = btn.querySelector('.rte-swatch');\n if (!swatch) return;\n if (color) {\n swatch.style.background = color;\n btn.classList.add('has-color');\n } else {\n swatch.style.removeProperty('background');\n btn.classList.remove('has-color');\n }\n };\n\n const ColorClass = this.registry.get('formats/color');\n if (ColorClass && typeof ColorClass.getCurrentColor === 'function') {\n apply('color', ColorClass.getCurrentColor());\n }\n const BgClass = this.registry.get('formats/background');\n if (BgClass && typeof BgClass.getCurrentColor === 'function') {\n apply('background', BgClass.getCurrentColor());\n }\n }\n\n /**\n * Undo last action\n */\n undo() {\n const history = this.getModule('history');\n if (history && typeof history.undo === 'function') {\n history.undo();\n } else {\n execFormat('undo');\n }\n }\n\n /**\n * Redo last undone action\n */\n redo() {\n const history = this.getModule('history');\n if (history && typeof history.redo === 'function') {\n history.redo();\n } else {\n execFormat('redo');\n }\n }\n\n /**\n * Add event listener\n * @param {string} event - Event name\n * @param {function} handler - Event handler\n */\n on(event, handler) {\n if (!this.events.has(event)) {\n this.events.set(event, []);\n }\n this.events.get(event).push(handler);\n }\n\n /**\n * Remove event listener\n * @param {string} event - Event name\n * @param {function} handler - Event handler\n */\n off(event, handler) {\n if (this.events.has(event)) {\n const handlers = this.events.get(event);\n const index = handlers.indexOf(handler);\n if (index > -1) {\n handlers.splice(index, 1);\n }\n }\n }\n\n /**\n * Emit event\n * @param {string} event - Event name\n * @param {*} data - Event data\n */\n emit(event, data) {\n if (this.events.has(event)) {\n this.events.get(event).forEach(handler => {\n try {\n handler(data);\n } catch (error) {\n console.error(`Error in event handler for ${event}:`, error);\n }\n });\n }\n }\n\n /**\n * Prevent focus loss when clicking on UI elements\n * @param {HTMLElement} element - Element to attach listener to\n * @param {string} allowedSelector - CSS selector for elements that should allow normal click behavior\n */\n preventFocusLoss(element, allowedSelector = 'button, input, select, textarea, [contenteditable]') {\n if (!element) return;\n \n element.addEventListener('mousedown', (e) => {\n // Allow normal behavior for interactive elements\n if (e.target.closest(allowedSelector)) {\n return;\n }\n \n // Prevent default behavior for non-interactive areas\n e.preventDefault();\n \n // Restore focus to editor after event processing\n setTimeout(() => {\n this.focus();\n }, 0);\n });\n }\n\n /**\n * Get current editor instance\n * @returns {Editor|null} Current editor instance\n */\n static getCurrentInstance() {\n return Editor.currentInstance;\n }\n\n /**\n * Utility function to maintain editor focus after UI interactions\n * @param {Function} callback - Function to execute before maintaining focus\n * @param {Editor} editor - Editor instance to maintain focus on\n */\n static maintainFocus(callback, editor = null) {\n if (typeof callback === 'function') {\n callback();\n }\n const editorInstance = editor || Editor.getCurrentInstance();\n if (editorInstance) {\n setTimeout(() => editorInstance.focus(), 0);\n }\n }\n\n /**\n * Get popup container for this editor instance\n * @returns {HTMLElement} Popup container element\n */\n getPopupContainer() {\n return this.popupContainer;\n }\n\n /**\n * Get popup container from current editor instance\n * @returns {HTMLElement|null} Popup container element or null if no current instance\n */\n static getPopupContainer() {\n const currentInstance = Editor.getCurrentInstance();\n return currentInstance ? currentInstance.getPopupContainer() : null;\n }\n\n /**\n * Get popup instance for this editor\n * @param {string} popupType - Type of popup (e.g., 'link', 'image', 'table')\n * @returns {Object|null} Popup instance or null if not found\n */\n getPopupInstance(popupType) {\n return this.popupInstances.get(popupType);\n }\n\n /**\n * Set popup instance for this editor\n * @param {string} popupType - Type of popup\n * @param {Object} popupInstance - Popup instance\n */\n setPopupInstance(popupType, popupInstance) {\n this.popupInstances.set(popupType, popupInstance);\n }\n\n /**\n * Get popup instance by editor ID and popup type\n * @param {string} editorId - Editor instance ID\n * @param {string} popupType - Type of popup\n * @returns {Object|null} Popup instance or null if not found\n */\n static getPopupInstanceById(editorId, popupType) {\n const editor = Editor.instances.get(editorId);\n return editor ? editor.getPopupInstance(popupType) : null;\n }\n\n /**\n * Get editor instance by ID\n * @param {string} editorId - Editor instance ID\n * @returns {Editor|null} Editor instance or null if not found\n */\n static getInstanceById(editorId) {\n return Editor.instances.get(editorId);\n }\n\n /**\n * Get all editor instances\n * @returns {Map} Map of all editor instances\n */\n static getAllInstances() {\n return Editor.instances;\n }\n\n /**\n * Destroy popup instances for this editor\n */\n destroyPopupInstances() {\n this.popupInstances.forEach((popupInstance, popupType) => {\n if (popupInstance && typeof popupInstance.destroy === 'function') {\n popupInstance.destroy();\n }\n });\n this.popupInstances.clear();\n }\n\n /**\n * Update placeholder visibility based on editor content\n */\n updatePlaceholderVisibility() {\n // Use isEditorEmpty() (text AND media) rather than textContent alone, so an\n // image/table-only editor doesn't keep showing the placeholder.\n if (this.isEditorEmpty()) {\n this.editor.classList.add('placeholder-visible');\n } else {\n this.editor.classList.remove('placeholder-visible');\n }\n }\n\n /**\n * Destroy editor\n */\n destroy() {\n // Remove active-tracking listeners\n if (this._markActive) {\n this.wrapper.removeEventListener('pointerdown', this._markActive, true);\n this.wrapper.removeEventListener('focusin', this._markActive, true);\n this._markActive = null;\n }\n\n // Remove the document-level selection listener\n if (this._onDocSelectionChange) {\n document.removeEventListener('selectionchange', this._onDocSelectionChange);\n this._onDocSelectionChange = null;\n }\n if (this._fmtCache) this._fmtCache.clear();\n\n // Cancel any pending autosave write\n clearTimeout(this._autosaveTimer);\n\n // Destroy all modules\n this.modules.forEach(module => {\n if (typeof module.destroy === 'function') {\n module.destroy();\n }\n });\n\n // Destroy popup instances\n this.destroyPopupInstances();\n\n // Remove DOM elements\n if (this.wrapper && this.wrapper.parentNode) {\n this.wrapper.parentNode.removeChild(this.wrapper);\n }\n\n // Clear references\n this.modules.clear();\n this.formats.clear();\n this.events.clear(); // Clear events\n \n // Remove from instances map\n Editor.instances.delete(this.instanceId);\n \n // Clear current instance if this was the current one\n if (Editor.currentInstance === this) {\n Editor.currentInstance = null;\n }\n }\n} ","import { execFormat, queryFormatState } from '../utils/exec-command.js';\n\n/**\n * Base Format class - Inspired by Quill's architecture\n * All text formats should extend this class\n */\nexport class Format {\n static formatName = '';\n static tagName = '';\n static className = '';\n\n constructor(domNode) {\n this.domNode = domNode;\n }\n\n /**\n * Create a new format node\n * @param {*} value - Format value\n * @returns {HTMLElement}\n */\n static create(value) {\n const node = document.createElement(this.tagName);\n if (this.className) {\n node.className = this.className;\n }\n return node;\n }\n\n getOffsetWithin(container, range) {\n let offset = 0;\n const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);\n let currentNode;\n\n while ((currentNode = walker.nextNode())) {\n if (currentNode === range.startContainer) {\n return offset + range.startOffset;\n }\n offset += currentNode.textContent.length;\n }\n\n return offset;\n }\n\n /**\n * Check if format is active at current selection\n * @returns {boolean}\n */\n} \n\n/**\n * Inline Format - for formats like bold, italic, underline\n * Handles inline formatting that wraps text within the same line/block\n */\nexport class InlineFormat extends Format {\n /**\n * Create inline format element\n * @param {*} value - Format value\n * @returns {HTMLElement}\n */\n static create(value) {\n const node = super.create(value);\n return node;\n }\n\n /**\n * Apply inline format to selection\n * Wraps selected text or inserts format marker at cursor\n * @param {*} value - Format value\n */\n apply(value) {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0); \n if (range.collapsed) {\n // No selection - insert format marker at cursor\n const formatNode = this.constructor.create(value);\n formatNode.appendChild(document.createTextNode('\\u200B')); // Zero-width space\n range.insertNode(formatNode);\n \n // Position cursor inside the format node\n const newRange = document.createRange();\n newRange.setStart(formatNode.firstChild, 1);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n // Has selection - wrap selected content\n const contents = range.extractContents();\n const formatNode = this.constructor.create(value);\n formatNode.appendChild(contents);\n range.insertNode(formatNode);\n \n // Select the formatted content\n const newRange = document.createRange();\n newRange.selectNodeContents(formatNode);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n }\n\n /**\n * Remove inline format from selection\n * Unwraps formatted content or removes format at cursor\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n \n if (range.collapsed) {\n // Handle cursor position\n this.removeAtCursor(range, selection);\n } else {\n // Handle selection\n this.removeFromSelection(range, selection);\n }\n }\n\n /**\n * Remove format at cursor position\n * @param {Range} range - Current range\n * @param {Selection} selection - Current selection\n */\n removeAtCursor(range, selection) {\n const container = range.startContainer;\n const formatNode = this.findFormatNode(container);\n \n if (!formatNode || !formatNode.parentNode) return;\n\n const text = formatNode.textContent;\n const absoluteOffset = this.getOffsetWithin(formatNode, range);\n\n // Split the format node at cursor position\n const beforeText = text.slice(0, absoluteOffset);\n const afterText = text.slice(absoluteOffset);\n \n const fragment = document.createDocumentFragment();\n \n if (beforeText) {\n const beforeNode = formatNode.cloneNode(false);\n beforeNode.textContent = beforeText;\n fragment.appendChild(beforeNode);\n }\n \n // Insert zero-width space as marker\n const zwspNode = document.createTextNode('\\u200B');\n fragment.appendChild(zwspNode);\n \n if (afterText) {\n const afterNode = formatNode.cloneNode(false);\n afterNode.textContent = afterText;\n fragment.appendChild(afterNode);\n }\n\n formatNode.replaceWith(fragment);\n\n // Position cursor after the marker\n const newRange = document.createRange();\n newRange.setStartAfter(zwspNode);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n\n /**\n * Remove format from selection\n */\n removeFromSelection(range, selection) {\n const formatName = this.constructor.formatName;\n execFormat(formatName);\n if (formatName === 'strike') {\n execFormat('strikeThrough');\n }\n }\n\n\n\n /**\n * Find the format node containing the given node\n * @param {Node} node - DOM node\n * @returns {Element|null} Format node\n */\n findFormatNode(node) {\n while (node && node !== document.body) {\n if (node.nodeType === Node.ELEMENT_NODE && \n node.tagName === this.constructor.tagName) {\n return node;\n }\n node = node.parentNode;\n }\n return null;\n }\n\n /**\n * Find all format nodes within a range\n * @param {Range} range - Selection range\n * @returns {Element[]} Array of format nodes\n */\n findFormatNodesInRange(range) {\n const nodes = [];\n const walker = document.createTreeWalker(\n range.commonAncestorContainer,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node) => {\n if (node.tagName === this.constructor.tagName && \n range.intersectsNode(node)) {\n return NodeFilter.FILTER_ACCEPT;\n }\n return NodeFilter.FILTER_SKIP;\n }\n }\n );\n\n let node;\n while ((node = walker.nextNode())) {\n nodes.push(node);\n }\n\n return nodes;\n }\n\n /**\n * Check if inline format is active at current selection\n * @returns {boolean}\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n let node = range.startContainer;\n\n const tagName = this.constructor.tagName;\n const altTags = this.constructor.alternativeTagNames || [];\n const formatName = this.constructor.formatName;\n\n // Đặc biệt với một số lệnh hỗ trợ execCommand\n const commandSupported = ['bold', 'italic', 'underline'];\n if (commandSupported.includes(formatName?.toLowerCase())) {\n return queryFormatState(formatName);\n }\n\n // Kiểm tra DOM tag\n while (node && node !== document.body) {\n if (\n node.nodeType === Node.ELEMENT_NODE &&\n (node.tagName === tagName ||\n altTags.includes(node.tagName))\n ) {\n return true;\n }\n node = node.parentNode;\n }\n\n return false;\n }\n\n}\n\n/**\n * Block Format - for formats like headers, paragraphs, alignment\n * Handles block-level formatting that affects entire blocks/paragraphs\n */\nexport class BlockFormat extends Format {\n /**\n * Create block format element\n * @param {*} value - Format value\n * @returns {HTMLElement}\n */\n static create(value) {\n const node = super.create(value);\n return node;\n }\n\n /**\n * Apply block format to selection\n * Converts current block(s) or creates new block\n * @param {*} value - Format value\n */\n apply(value) {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const blocks = this.getBlockElements(range);\n\n if (blocks.length === 0) {\n // No block found - create new one\n this.createBlockAtCursor(range, value);\n } else {\n // Convert existing blocks\n blocks.forEach(block => {\n this.convertBlock(block, value);\n });\n }\n }\n\n /**\n * Remove block format from selection\n * Converts blocks back to default (paragraph) or removes formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const blocks = this.getBlockElements(range);\n\n blocks.forEach(block => {\n this.removeBlockFormat(block);\n });\n }\n\n /**\n * Create new block at cursor position\n * @param {Range} range - Current range\n * @param {*} value - Format value\n */\n createBlockAtCursor(range, value) {\n const blockNode = this.constructor.create(value);\n \n // Try to preserve style from existing block if cursor is inside one\n const existingBlock = this.getBlockElement(range.startContainer);\n if (existingBlock && existingBlock.style && existingBlock.style.cssText) {\n blockNode.style.cssText = existingBlock.style.cssText;\n }\n \n if (range.collapsed) {\n // No selection - create empty block\n blockNode.appendChild(document.createTextNode(''));\n range.insertNode(blockNode);\n \n // Position cursor inside the block\n const newRange = document.createRange();\n newRange.setStart(blockNode, 0);\n newRange.collapse(true);\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n // Has selection - wrap in block\n const contents = range.extractContents();\n blockNode.appendChild(contents);\n range.insertNode(blockNode);\n \n // Select the content in the block\n const newRange = document.createRange();\n newRange.selectNodeContents(blockNode);\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n }\n\n /**\n * Convert existing block to new format\n * @param {Element} block - Block element to convert\n * @param {*} value - Format value\n */\n convertBlock(block, value) {\n const newBlock = this.constructor.create(value);\n \n // Copy all child nodes\n while (block.firstChild) {\n newBlock.appendChild(block.firstChild);\n }\n \n // Copy relevant attributes\n if (block.className && this.shouldPreserveClass(block.className)) {\n newBlock.className = block.className;\n }\n \n // Copy style attributes to preserve formatting like text-align\n if (block.style && block.style.cssText) {\n newBlock.style.cssText = block.style.cssText;\n }\n \n // Replace the block\n block.parentNode.replaceChild(newBlock, block);\n }\n\n /**\n * Remove block format (convert to paragraph)\n * @param {Element} block - Block element\n */\n removeBlockFormat(block) {\n const paragraph = document.createElement('P');\n \n // Move all child nodes to paragraph\n while (block.firstChild) {\n paragraph.appendChild(block.firstChild);\n }\n \n // Copy style attributes to preserve formatting like text-align\n if (block.style && block.style.cssText) {\n paragraph.style.cssText = block.style.cssText;\n }\n \n // Replace the block\n block.parentNode.replaceChild(paragraph, block);\n }\n\n /**\n * Get block elements in range\n * @param {Range} range - Selection range\n * @returns {Element[]} Array of block elements\n */\n getBlockElements(range) {\n const blocks = [];\n let startBlock = this.getBlockElement(range.startContainer);\n let endBlock = this.getBlockElement(range.endContainer);\n\n // Nếu endBlock ngay sau startBlock và selection kết thúc ở vị trí 0 của endBlock\n if (startBlock && endBlock && startBlock.nextElementSibling === endBlock) {\n const endAtStartOfEndBlock =\n range.endContainer === endBlock &&\n range.endOffset === 0;\n if (endAtStartOfEndBlock) {\n endBlock = startBlock;\n }\n }\n\n if (startBlock === endBlock) {\n if (startBlock) blocks.push(startBlock);\n } else {\n // Multiple blocks\n let current = startBlock;\n while (current && current !== endBlock) {\n if (this.isBlockElement(current)) {\n blocks.push(current);\n }\n current = this.getNextBlockElement(current);\n }\n if (endBlock && this.isBlockElement(endBlock)) {\n blocks.push(endBlock);\n }\n }\n\n return blocks.filter((block, index, self) =>\n self.indexOf(block) === index\n );\n }\n\n /**\n * Get block element containing node\n * @param {Node} node - DOM node\n * @returns {Element|null} Block element\n */\n getBlockElement(node) {\n while (node && node.nodeType !== Node.ELEMENT_NODE) {\n node = node.parentNode;\n }\n\n while (node && node !== document.body) {\n if (this.isBlockElement(node)) {\n return node;\n }\n node = node.parentNode;\n }\n\n return null;\n }\n\n /**\n * Check if element is a block element\n * @param {Element} element - DOM element\n * @returns {boolean}\n */\n isBlockElement(element) {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;\n \n const blockTags = [\n 'P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', \n 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI', 'SECTION', 'ARTICLE'\n ];\n return blockTags.includes(element.tagName.toUpperCase());\n }\n\n /**\n * Get next block element\n * @param {Element} element - Current element\n * @returns {Element|null} Next block element\n */\n getNextBlockElement(element) {\n let next = element.nextElementSibling;\n while (next) {\n if (this.isBlockElement(next)) {\n return next;\n }\n next = next.nextElementSibling;\n }\n return null;\n }\n\n /**\n * Check if block format is active at current selection\n * @param {*} value - Optional specific value to check\n * @returns {boolean}\n */\n isActive(value = null) {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n const block = this.getBlockElement(range.startContainer);\n \n if (!block) return false;\n\n // Check if block matches our format\n if (block.tagName === this.constructor.tagName) {\n return value ? this.hasValue(block, value) : true;\n }\n\n return false;\n }\n\n /**\n * Check if block has specific value\n * Override in subclasses for specific value checking\n * @param {Element} block - Block element\n * @param {*} value - Value to check\n * @returns {boolean}\n */\n hasValue(block, value) {\n return true; // Default implementation\n }\n\n /**\n * Check if class should be preserved during conversion\n * Override in subclasses for specific class handling\n * @param {string} className - Class name\n * @returns {boolean}\n */\n shouldPreserveClass(className) {\n return false; // Default: don't preserve classes\n }\n} ","/**\n * renderStatic — paint stored HTML into a read-only view that looks exactly\n * like the editor's content area, without booting an editor.\n *\n * It sanitizes the HTML (same allowlist the editor uses on paste/setContent)\n * and tags the host element with `.yjd-content`, the class the stylesheet uses\n * to style typography, lists, tables, images, and mention tokens. Load the\n * editor stylesheet (or StylesLoader.loadStyles()) on the page for it to match.\n *\n * import { renderStatic } from '@oix1987/yjd/core';\n * renderStatic(post.body_html, document.querySelector('#post'));\n *\n * @param {string} html Stored HTML (untrusted — it is sanitized).\n * @param {Element} [target] Element to render into. If omitted, a fresh\n * <div.yjd-content> is created and returned.\n * @returns {Element} the element the content was rendered into.\n */\nimport { sanitizeHtml } from './utils/sanitize.js';\n\nexport function renderStatic(html, target) {\n const safe = sanitizeHtml(html || '');\n const el = target || document.createElement('div');\n el.classList.add('yjd-content');\n el.innerHTML = safe;\n return el;\n}\n\nexport default renderStatic;\n","import Editor from '../core/editor.js';\n\n/**\n * Helper function to save history state before applying format\n * This should be called by all format operations to ensure proper undo/redo functionality\n */\nexport function saveBeforeFormat() {\n const editor = Editor.getCurrentInstance();\n if (editor) {\n const historyModule = editor.getModule('history');\n if (historyModule && typeof historyModule.saveBeforeFormat === 'function') {\n historyModule.saveBeforeFormat();\n }\n }\n}\n\n/**\n * Helper function to trigger content change after format operations\n * This ensures onChange callback is called when formatting is applied\n */\nexport function triggerContentChange() {\n const editor = Editor.getCurrentInstance();\n if (editor && typeof editor.onContentChange === 'function') {\n // Use setTimeout to ensure the DOM changes are complete\n setTimeout(() => {\n editor.onContentChange();\n }, 0);\n }\n}\n\n/**\n * Helper function to save before format and trigger content change\n * This is a convenience function that combines both operations\n */\nexport function saveBeforeFormatAndTriggerChange() {\n saveBeforeFormat();\n triggerContentChange();\n}\n\n/**\n * Helper function to check if history module is available\n */\nexport function hasHistoryModule() {\n const editor = Editor.getCurrentInstance();\n if (editor) {\n const historyModule = editor.getModule('history');\n return historyModule && typeof historyModule.saveBeforeFormat === 'function';\n }\n return false;\n} ","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport { execFormat, queryFormatState } from '../utils/exec-command.js';\n\n/**\n * Bold Format - Handles bold text formatting\n */\nclass Bold extends InlineFormat {\n static formatName = 'bold';\n static tagName = 'B';\n static alternativeTagNames = ['STRONG'];\n\n /**\n * Apply bold formatting\n */\n apply() {\n // Save state before applying format\n saveBeforeFormat();\n execFormat('bold');\n }\n\n /**\n * Remove bold formatting\n */\n remove() {\n execFormat('bold');\n }\n\n /**\n * Toggle bold formatting\n */\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n execFormat('bold');\n }\n\n /**\n * Check if bold formatting is active\n */\n isActive() {\n return queryFormatState('bold');\n }\n\n\n}\n\n\nexport default Bold;\n","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\n\n/**\n * Italic Format - Handles italic text formatting\n */\nclass Italic extends InlineFormat {\n static formatName = 'italic';\n static tagName = 'I';\n static alternativeTagNames = ['EM'];\n\n /**\n * Toggle italic formatting\n */\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n\n if (this.isActive()) {\n this.remove();\n } else {\n this.apply();\n }\n }\n}\n\nexport default Italic;\n","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\n\n/**\n * Underline Format - Handles underline text formatting\n * Extracted from FormatManager.js logic\n */\nclass Underline extends InlineFormat {\n static formatName = 'underline';\n static tagName = 'U';\n static alternativeTagNames = ['SPAN'];\n\n /**\n * Toggle underline formatting\n */\n\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n\n if (this.isActive()) {\n this.remove();\n } else {\n this.apply();\n }\n }\n\n}\n\nexport default Underline; ","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\n\n/**\n * Strike Format - Handles strikethrough text formatting\n * Extracted from FormatManager.js logic\n */\nclass Strike extends InlineFormat {\n static formatName = 'strike';\n static tagName = 'S';\n static alternativeTagNames = ['STRIKE', 'DEL'];\n\n /**\n * Toggle strikethrough formatting\n */\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n\n if (this.isActive()) {\n this.remove();\n } else {\n this.apply();\n }\n }\n\n\n \n}\n\nexport default Strike; ","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport registry from '../core/registry.js';\n/**\n * Subscript Format - Handles subscript text formatting\n * Creates <sub> elements for subscript text\n */\nclass Subscript extends InlineFormat {\n static formatName = 'subscript';\n static tagName = 'SUB';\n\n removeSuperscriptBeforeApply() {\n // Resolved via registry (not a static import) to avoid a circular\n // dependency between subscript.js and superscript.js.\n const Superscript = registry.get('formats/superscript');\n if (!Superscript) return;\n const superscript = new Superscript();\n if (superscript.isActive()) {\n superscript.remove();\n }\n }\n /**\n * Toggle subscript formatting\n */\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n\n if (this.isActive()) {\n this.remove();\n } else {\n this.removeSuperscriptBeforeApply();\n this.apply();\n }\n }\n\n\n}\n\nexport default Subscript; ","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport registry from '../core/registry.js';\n/**\n * Superscript Format - Handles superscript text formatting\n * Creates <sup> elements for superscript text\n */\nclass Superscript extends InlineFormat {\n static formatName = 'superscript';\n static tagName = 'SUP';\n\n /**\n * Toggle superscript formatting\n */\n removeSubscriptBeforeApply() {\n // Resolved via registry (not a static import) to avoid a circular\n // dependency between superscript.js and subscript.js.\n const Subscript = registry.get('formats/subscript');\n if (!Subscript) return;\n const subscript = new Subscript();\n if (subscript.isActive()) {\n subscript.remove();\n }\n }\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n\n if (this.isActive()) {\n this.remove();\n } else {\n // Ensure mutual exclusivity: remove subscript before applying superscript\n this.removeSubscriptBeforeApply();\n this.apply();\n }\n }\n}\n\nexport default Superscript; ","/**\n * Inline Icons — a single, cohesive outline icon set (Lucide-style).\n * Every icon is a 24×24, stroke-based glyph using `currentColor`, so they all\n * share one visual weight and follow the button's text/accent colour.\n */\nconst S = (body) =>\n `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${body}</svg>`;\n\nexport const Icons = {\n // --- Text formatting ---\n bold: S('<path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/><path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/>'),\n italic: S('<line x1=\"19\" x2=\"10\" y1=\"4\" y2=\"4\"/><line x1=\"14\" x2=\"5\" y1=\"20\" y2=\"20\"/><line x1=\"15\" x2=\"9\" y1=\"4\" y2=\"20\"/>'),\n underline: S('<path d=\"M6 4v6a6 6 0 0 0 12 0V4\"/><line x1=\"4\" x2=\"20\" y1=\"20\" y2=\"20\"/>'),\n strike: S('<path d=\"M16 4H9a3 3 0 0 0-2.83 4\"/><path d=\"M14 12a4 4 0 0 1 0 8H6\"/><line x1=\"4\" x2=\"20\" y1=\"12\" y2=\"12\"/>'),\n subscript: S('<path d=\"m4 5 8 8\"/><path d=\"m12 5-8 8\"/><path d=\"M20 19h-4c0-1.5.44-2 1.5-2.5S20 15.33 20 14c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07\"/>'),\n superscript: S('<path d=\"m4 19 8-8\"/><path d=\"m12 19-8-8\"/><path d=\"M20 12h-4c0-1.5.44-2 1.5-2.5S20 8.33 20 7c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07\"/>'),\n\n // --- Alignment ---\n 'align-left': S('<line x1=\"21\" x2=\"3\" y1=\"6\" y2=\"6\"/><line x1=\"15\" x2=\"3\" y1=\"12\" y2=\"12\"/><line x1=\"17\" x2=\"3\" y1=\"18\" y2=\"18\"/>'),\n 'align-center': S('<line x1=\"21\" x2=\"3\" y1=\"6\" y2=\"6\"/><line x1=\"17\" x2=\"7\" y1=\"12\" y2=\"12\"/><line x1=\"19\" x2=\"5\" y1=\"18\" y2=\"18\"/>'),\n 'align-right': S('<line x1=\"21\" x2=\"3\" y1=\"6\" y2=\"6\"/><line x1=\"21\" x2=\"9\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"7\" y1=\"18\" y2=\"18\"/>'),\n 'align-justify': S('<line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"/>'),\n\n // --- Lists ---\n 'list-bullet': S('<line x1=\"8\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"8\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"21\" y1=\"18\" y2=\"18\"/><circle cx=\"3.5\" cy=\"6\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"3.5\" cy=\"12\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"3.5\" cy=\"18\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/>'),\n 'list-ordered': S('<line x1=\"10\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"10\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"10\" x2=\"21\" y1=\"18\" y2=\"18\"/><path d=\"M4 6h1v4\"/><path d=\"M4 10h2\"/><path d=\"M6 18H4c0-1 2-2 2-3s-1-1.5-2-1\"/>'),\n 'list-alpha': S('<line x1=\"10\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"10\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"10\" x2=\"21\" y1=\"18\" y2=\"18\"/><path d=\"M4 10V8a1 1 0 0 1 2 0v2\"/><path d=\"M4 9h2\"/><path d=\"M4 14h1.5a1 1 0 0 1 0 2H4l2-2\"/>'),\n 'list-roman': S('<line x1=\"10\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"10\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"10\" x2=\"21\" y1=\"18\" y2=\"18\"/><path d=\"M5 7v3\"/><path d=\"M4 14h2l-1 4\"/>'),\n list: S('<line x1=\"8\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"8\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"21\" y1=\"18\" y2=\"18\"/><circle cx=\"3.5\" cy=\"6\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"3.5\" cy=\"12\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"3.5\" cy=\"18\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/>'),\n\n // --- Indentation ---\n 'indent-increase': S('<polyline points=\"3 8 7 12 3 16\"/><line x1=\"21\" x2=\"11\" y1=\"6\" y2=\"6\"/><line x1=\"21\" x2=\"11\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"11\" y1=\"18\" y2=\"18\"/>'),\n 'indent-decrease': S('<polyline points=\"7 8 3 12 7 16\"/><line x1=\"21\" x2=\"11\" y1=\"6\" y2=\"6\"/><line x1=\"21\" x2=\"11\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"11\" y1=\"18\" y2=\"18\"/>'),\n\n // --- Media ---\n image: S('<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21\"/>'),\n video: S('<path d=\"m22 8-6 4 6 4V8Z\"/><rect width=\"14\" height=\"12\" x=\"2\" y=\"6\" rx=\"2\" ry=\"2\"/>'),\n\n // --- Table ---\n table: S('<path d=\"M12 3v18\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/>'),\n 'table-profile': S('<path d=\"M15 3v18\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M21 9H3\"/><path d=\"M21 15H3\"/>'),\n 'add-row-above': S('<rect x=\"3\" y=\"13\" width=\"18\" height=\"8\" rx=\"2\"/><line x1=\"12\" x2=\"12\" y1=\"3\" y2=\"9\"/><line x1=\"9\" x2=\"15\" y1=\"6\" y2=\"6\"/>'),\n 'add-row-below': S('<rect x=\"3\" y=\"3\" width=\"18\" height=\"8\" rx=\"2\"/><line x1=\"12\" x2=\"12\" y1=\"15\" y2=\"21\"/><line x1=\"9\" x2=\"15\" y1=\"18\" y2=\"18\"/>'),\n 'add-col-left': S('<rect x=\"13\" y=\"3\" width=\"8\" height=\"18\" rx=\"2\"/><line x1=\"3\" x2=\"9\" y1=\"12\" y2=\"12\"/><line x1=\"6\" x2=\"6\" y1=\"9\" y2=\"15\"/>'),\n 'add-col-right': S('<rect x=\"3\" y=\"3\" width=\"8\" height=\"18\" rx=\"2\"/><line x1=\"15\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"18\" x2=\"18\" y1=\"9\" y2=\"15\"/>'),\n 'delete-row': S('<rect x=\"3\" y=\"9\" width=\"18\" height=\"6\" rx=\"2\"/><line x1=\"9\" x2=\"15\" y1=\"12\" y2=\"12\"/>'),\n 'delete-col': S('<rect x=\"9\" y=\"3\" width=\"6\" height=\"18\" rx=\"2\"/><line x1=\"12\" x2=\"12\" y1=\"9\" y2=\"15\"/>'),\n 'delete-table': S('<path d=\"M3 6h18\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\"/><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\"/><line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\"/>'),\n\n // --- Colour ---\n color: S('<path d=\"M5.5 19 12 5l6.5 14\"/><path d=\"M8 14h8\"/>'),\n background: S('<path d=\"m9 11-6 6v3h9l3-3\"/><path d=\"m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4\"/>'),\n 'no-color': S('<circle cx=\"12\" cy=\"12\" r=\"9\"/><line x1=\"5.6\" x2=\"18.4\" y1=\"5.6\" y2=\"18.4\"/>'),\n 'custom-color': S('<path d=\"M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.93 0 1.65-.75 1.65-1.69 0-.43-.18-.83-.44-1.12-.29-.29-.44-.65-.44-1.13a1.64 1.64 0 0 1 1.67-1.67h2c3.05 0 5.55-2.5 5.55-5.55C22 6 17.5 2 12 2z\"/><circle cx=\"8.5\" cy=\"7.5\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"6.5\" cy=\"12.5\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"13.5\" cy=\"6.5\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"17.5\" cy=\"10.5\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/>'),\n\n // --- History ---\n undo: S('<path d=\"M9 14 4 9l5-5\"/><path d=\"M4 9h10.5a5.5 5.5 0 0 1 0 11H11\"/>'),\n redo: S('<path d=\"m15 14 5-5-5-5\"/><path d=\"M20 9H9.5a5.5 5.5 0 0 0 0 11H13\"/>'),\n\n // --- Insert ---\n link: S('<path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/>'),\n emoji: S('<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M8 14s1.5 2 4 2 4-2 4-2\"/><line x1=\"9\" x2=\"9.01\" y1=\"9\" y2=\"9\"/><line x1=\"15\" x2=\"15.01\" y1=\"9\" y2=\"9\"/>'),\n tag: S('<path d=\"M12.6 2.6A2 2 0 0 0 11.2 2H4a2 2 0 0 0-2 2v7.2a2 2 0 0 0 .6 1.4l8.7 8.7a2.4 2.4 0 0 0 3.4 0l6.6-6.6a2.4 2.4 0 0 0 0-3.4z\"/><circle cx=\"7.5\" cy=\"7.5\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/>'),\n import: S('<path d=\"M12 3v12\"/><path d=\"m8 11 4 4 4-4\"/><path d=\"M8 5H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-4\"/>'),\n code: S('<polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/>'),\n 'code-view': S('<path d=\"m18 16 4-4-4-4\"/><path d=\"m6 8-4 4 4 4\"/><path d=\"m14.5 4-5 16\"/>'),\n 'clear-format': S('<path d=\"M4 7V4h16v3\"/><path d=\"M5 20h6\"/><path d=\"M13 4 8 20\"/><path d=\"m15 15 5 5\"/><path d=\"m20 15-5 5\"/>'),\n 'horizontal-rule': S('<path d=\"M5 12h14\"/>'),\n find: S('<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>'),\n 'chevron-up': S('<path d=\"m18 15-6-6-6 6\"/>'),\n 'chevron-down': S('<path d=\"m6 9 6 6 6-6\"/>'),\n close: S('<path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/>'),\n 'text-direction': S('<path d=\"M8 3 4 7l4 4\"/><path d=\"M4 7h16\"/><path d=\"m16 21 4-4-4-4\"/><path d=\"M20 17H4\"/>'),\n\n // --- UI / utility ---\n check: S('<polyline points=\"20 6 9 17 4 12\"/>'),\n dropdown: S('<path d=\"m6 9 6 6 6-6\"/>'),\n more: S('<circle cx=\"12\" cy=\"12\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"19\" cy=\"12\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"5\" cy=\"12\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\"/>'),\n theme: S('<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/><path d=\"m4.9 4.9 1.4 1.4\"/><path d=\"m17.7 17.7 1.4 1.4\"/><path d=\"M2 12h2\"/><path d=\"M20 12h2\"/><path d=\"m6.3 17.7-1.4 1.4\"/><path d=\"m19.1 4.9-1.4 1.4\"/>'),\n\n // --- Typography (dropdown triggers; mostly shown as text) ---\n heading: S('<path d=\"M4 12h8\"/><path d=\"M4 18V6\"/><path d=\"M12 18V6\"/><path d=\"m17 12 3-2v8\"/>'),\n 'font-family': S('<polyline points=\"4 7 4 4 20 4 20 7\"/><line x1=\"9\" x2=\"15\" y1=\"20\" y2=\"20\"/><line x1=\"12\" x2=\"12\" y1=\"4\" y2=\"20\"/>'),\n 'line-height': S('<path d=\"M3 5h12\"/><path d=\"M3 12h12\"/><path d=\"M3 19h12\"/><path d=\"M19 5v14\"/><path d=\"m16.5 7.5 2.5-2.5 2.5 2.5\"/><path d=\"m16.5 16.5 2.5 2.5 2.5-2.5\"/>'),\n capitalization: S('<path d=\"M4 18 8 8l4 10\"/><path d=\"M5.5 14h5\"/><path d=\"M16 18a3 3 0 1 0 0-6 3 3 0 0 0-3 3v3\"/><path d=\"M19 12v6\"/>'),\n 'text-size': S('<path d=\"M21 14h-5\"/><path d=\"M16 16v-3.5a2.5 2.5 0 0 1 5 0V16\"/><path d=\"M4.5 13h6\"/><path d=\"m3 16 4.5-9 4.5 9\"/>')\n};\n\n/**\n * Icon utility functions\n */\nexport class IconUtils {\n /**\n * Get icon SVG content by name\n * @param {string} iconName - Name of the icon\n * @returns {string} SVG content or empty string if not found\n */\n static getIcon(iconName) {\n return Icons[iconName] || '';\n }\n\n /**\n * Create icon element with proper styling\n * @param {string} iconName - Name of the icon\n * @param {Object} options - Options for icon styling\n * @returns {HTMLElement} Icon element\n */\n static createIconElement(iconName, options = {}) {\n const iconElement = document.createElement('span');\n iconElement.className = `icon icon-${iconName}`;\n\n // Apply default styles\n iconElement.style.display = 'inline-flex';\n iconElement.style.alignItems = 'center';\n iconElement.style.justifyContent = 'center';\n iconElement.style.width = options.width || '16px';\n iconElement.style.height = options.height || '16px';\n iconElement.style.verticalAlign = 'middle';\n\n // Set SVG content\n iconElement.innerHTML = this.getIcon(iconName);\n\n return iconElement;\n }\n\n /**\n * Check if icon exists\n * @param {string} iconName - Name of the icon\n * @returns {boolean} True if icon exists\n */\n static hasIcon(iconName) {\n return iconName in Icons;\n }\n\n /**\n * Get all available icon names\n * @returns {string[]} Array of icon names\n */\n static getIconNames() {\n return Object.keys(Icons);\n }\n}\n\n// Export default for backward compatibility\nexport default IconUtils;\n","/**\n * Popup Helper Utility\n * Helps popups append to the yjd-rich-editor instead of document.body\n * Now supports multiple editor instances with separate popup containers\n */\nimport Editor from '../core/editor.js';\n\n/**\n * Get the appropriate container for popups\n * @param {string} editorId - Optional editor instance ID\n * @returns {HTMLElement} Container element for popups\n */\nexport function getPopupContainer(editorId = null) {\n let editor;\n \n if (editorId) {\n // Get specific editor instance\n editor = Editor.getInstanceById(editorId);\n } else {\n // Try to get current editor instance\n editor = Editor.getCurrentInstance();\n }\n \n if (editor) {\n return editor.getPopupContainer();\n }\n \n // Fallback to document.body if no editor instance\n return document.body;\n}\n\n/**\n * Append popup to the appropriate container\n * @param {HTMLElement} popup - Popup element to append\n * @param {string} editorId - Optional editor instance ID\n */\nexport function appendPopup(popup, editorId = null) {\n const container = getPopupContainer(editorId);\n \n // Remove from current parent if exists\n if (popup.parentNode) {\n popup.parentNode.removeChild(popup);\n }\n \n container.appendChild(popup);\n \n // Note: pointer-events are now controlled by CSS rules\n // Popup containers have pointer-events: none by default\n // Interactive elements inside popups have pointer-events: auto\n}\n\n/**\n * Get popup dimensions by temporarily showing it if needed\n * @param {HTMLElement} popup - Popup element\n * @returns {Object} Object with width and height\n */\nfunction getPopupDimensions(popup) {\n if (!popup) return { width: 300, height: 200 };\n \n // Try getBoundingClientRect first\n const rect = popup.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n return { width: rect.width, height: rect.height };\n }\n \n // Try offsetWidth/offsetHeight\n if (popup.offsetWidth > 0 && popup.offsetHeight > 0) {\n return { width: popup.offsetWidth, height: popup.offsetHeight };\n }\n \n // Check if popup is hidden\n const computedStyle = window.getComputedStyle(popup);\n const isHidden = computedStyle.display === 'none' || computedStyle.visibility === 'hidden';\n \n if (isHidden) {\n // Temporarily show popup to get dimensions\n const originalDisplay = popup.style.display;\n const originalVisibility = popup.style.visibility;\n const originalPosition = popup.style.position;\n const originalTop = popup.style.top;\n const originalLeft = popup.style.left;\n const originalZIndex = popup.style.zIndex;\n \n // Make popup visible but off-screen\n popup.style.display = 'block';\n popup.style.visibility = 'visible';\n popup.style.position = 'absolute';\n popup.style.top = '-9999px';\n popup.style.left = '-9999px';\n popup.style.zIndex = '-1';\n \n // Force reflow\n popup.offsetHeight;\n \n // Get dimensions\n const tempRect = popup.getBoundingClientRect();\n const width = tempRect.width > 0 ? tempRect.width : 300;\n const height = tempRect.height > 0 ? tempRect.height : 200;\n \n // Restore original styles\n popup.style.display = originalDisplay;\n popup.style.visibility = originalVisibility;\n popup.style.position = originalPosition;\n popup.style.top = originalTop;\n popup.style.left = originalLeft;\n popup.style.zIndex = originalZIndex;\n \n return { width, height };\n }\n \n // Last resort: try computed styles\n const computedWidth = parseInt(computedStyle.width);\n const computedHeight = parseInt(computedStyle.height);\n \n return {\n width: computedWidth > 0 ? computedWidth : 300,\n height: computedHeight > 0 ? computedHeight : 200\n };\n}\n\n/**\n * Calculate position for popup relative to anchor element\n * @param {HTMLElement} anchor - Anchor element\n * @param {HTMLElement} popup - Popup element\n * @param {Object} options - Positioning options\n * @returns {Object} Position object with top and left values\n */\nexport function calculatePopupPosition(anchor, popup, options = {}) {\n const {\n offsetX = 0,\n offsetY = 5,\n preferTop = false,\n preferLeft = false\n } = options;\n\n const anchorRect = anchor.getBoundingClientRect();\n const container = getPopupContainer();\n const isInWrapper = container.classList.contains('rich-editor-popup-container');\n \n let top, left;\n \n if (isInWrapper) {\n // Position relative to wrapper\n const wrapperRect = container.getBoundingClientRect();\n \n // Calculate position relative to wrapper\n top = anchorRect.top - wrapperRect.top + anchorRect.height + offsetY;\n left = anchorRect.left - wrapperRect.left + offsetX;\n \n // Get popup dimensions using the helper function\n const { width: popupWidth, height: popupHeight } = getPopupDimensions(popup);\n\n \n // Check if popup would overflow bottom of wrapper\n if (top + popupHeight > wrapperRect.height && !preferTop) {\n // Try to position above the anchor\n const topPosition = anchorRect.top - wrapperRect.top - popupHeight - offsetY;\n if (topPosition >= 0) {\n top = topPosition;\n } else {\n // If still doesn't fit, try to center it vertically within the wrapper\n top = Math.max(offsetY, (wrapperRect.height - popupHeight) / 2);\n }\n }\n \n // Check if popup would overflow right of wrapper\n if (left + popupWidth + 5 > wrapperRect.width && !preferLeft) {\n left = wrapperRect.width - popupWidth - offsetX -15;\n }\n \n // Ensure popup doesn't go off-screen\n if (left < 0) left = offsetX;\n if (top < 0) top = offsetY;\n \n } else {\n // Fallback to document.body positioning\n top = anchorRect.bottom + window.scrollY + offsetY;\n left = anchorRect.left + window.scrollX + offsetX;\n\n \n // Get popup dimensions using the helper function\n const { width: popupWidth, height: popupHeight } = getPopupDimensions(popup);\n \n // Check if popup would overflow right edge\n if (left + popupWidth > window.innerWidth && !preferLeft) {\n left = window.innerWidth - popupWidth - offsetX;\n }\n \n // Check if popup would overflow bottom edge\n if (top + popupHeight > window.innerHeight + window.scrollY && !preferTop) {\n // Try to position above the anchor\n const topPosition = anchorRect.top + window.scrollY - popupHeight - offsetY;\n if (topPosition >= window.scrollY) {\n top = topPosition;\n } else {\n // If still doesn't fit, try to center it vertically within the viewport\n top = Math.max(window.scrollY + offsetY, window.scrollY + (window.innerHeight - popupHeight) / 2);\n }\n }\n \n // Ensure popup doesn't go off-screen\n if (left < 0) left = offsetX;\n if (top < 0) top = offsetY;\n }\n \n return { top, left };\n}\n\n/**\n * Set popup position\n * @param {HTMLElement} popup - Popup element\n * @param {Object} position - Position object with top and left values\n */\nexport function setPopupPosition(popup, position) {\n popup.style.position = 'absolute';\n popup.style.top = `${position.top}px`;\n popup.style.left = `${position.left}px`;\n popup.style.zIndex = '1000';\n}\n","/**\n * Color Picker Component - Simple color picker with popup\n */\nimport IconUtils from './icons.js';\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass ColorPicker {\n constructor(options = {}) {\n this.options = {\n colors: [\n '#000000', '#333333', '#666666', '#999999', '#cccccc', '#eeeeee',\n '#ff0000', '#ff6600', '#ffcc00', '#ffff00', '#99ff00', '#00ff00',\n '#00ffcc', '#00ccff', '#0066ff', '#0000ff', '#6600ff', '#cc00ff',\n '#ff00cc', '#ff0066', '#800000', '#ff8000', '#808000', '#008000',\n '#008080', '#0080ff', '#004080', '#800080', '#804080', '#ff0080'\n ],\n customColorEnabled: true,\n onColorSelect: null,\n editor: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.currentColor = '#000000';\n this.clickOutsideHandler = null;\n \n this.createColorPicker();\n }\n\n /**\n * Create color picker popup\n */\n createColorPicker() {\n // Create popup\n this.popup = document.createElement('div');\n this.popup.className = 'color-picker-popup';\n \n // Create color grid\n this.createColorGrid();\n \n // Create custom color input if enabled\n if (this.options.customColorEnabled) {\n this.createCustomColorInput();\n }\n \n // Add popup to container\n appendPopup(this.popup);\n \n // Prevent focus loss when clicking on popup\n if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {\n this.options.editor.preventFocusLoss(this.popup);\n }\n }\n\n /**\n * Create color grid\n */\n createColorGrid() {\n const grid = document.createElement('div');\n grid.className = 'color-grid';\n \n this.options.colors.forEach(color => {\n const colorButton = document.createElement('button');\n colorButton.type = 'button';\n colorButton.className = 'color-button';\n colorButton.style.backgroundColor = color;\n colorButton.dataset.color = color;\n colorButton.title = color;\n \n colorButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectColor(color);\n // Maintain editor focus after color selection\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n grid.appendChild(colorButton);\n });\n \n this.popup.appendChild(grid);\n }\n\n /**\n * Create custom color input\n */\n createCustomColorInput() {\n const customContainer = document.createElement('div');\n customContainer.className = 'custom-color-container';\n \n // No color button\n const noColorButton = document.createElement('button');\n noColorButton.type = 'button';\n noColorButton.className = 'color-button no-color-button';\n noColorButton.title = 'No Color';\n noColorButton.style.backgroundColor = 'transparent';\n \n // Add icon to button\n const noColorIcon = IconUtils.createIconElement('no-color', {\n width: '24',\n height: '24'\n });\n noColorButton.appendChild(noColorIcon);\n \n noColorButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectColor('transparent');\n // Maintain editor focus after color selection\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n // White button\n const whiteButton = document.createElement('button');\n whiteButton.type = 'button';\n whiteButton.className = 'color-button white-button';\n whiteButton.style.backgroundColor = '#ffffff';\n whiteButton.style.border = '1px solid #ccc';\n whiteButton.title = 'White';\n \n whiteButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectColor('#ffffff');\n // Maintain editor focus after color selection\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n // Black button\n const blackButton = document.createElement('button');\n blackButton.type = 'button';\n blackButton.className = 'color-button black-button';\n blackButton.style.backgroundColor = '#000000';\n blackButton.title = 'Black';\n \n blackButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectColor('#000000');\n // Maintain editor focus after color selection\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n // Custom color button with hidden input\n const customColorButton = document.createElement('button');\n customColorButton.type = 'button';\n customColorButton.className = 'color-button custom-color-button';\n customColorButton.title = 'Custom Color';\n customColorButton.style.backgroundColor = 'transparent';\n customColorButton.style.border = '1px solid #ccc';\n customColorButton.style.font = 'none !important';\n // Add icon to button\n const iconElement = IconUtils.createIconElement('custom-color', {\n width: '16px',\n height: '16px'\n });\n customColorButton.appendChild(iconElement);\n \n const customInput = document.createElement('input');\n customInput.type = 'color';\n customInput.className = 'custom-color-input';\n customInput.value = this.currentColor;\n customInput.style.visibility = 'hidden';\n customInput.style.pointerEvents = 'none'; // ngăn không cho click\n customInput.style.opacity = '0'; // ẩn hẳn về mặt thị giác\n customColorButton.addEventListener('click', (e) => {\n customInput.style.visibility = 'visible';\n customInput.style.pointerEvents = 'auto';\n customInput.style.opacity = '1';\n e.preventDefault();\n e.stopPropagation();\n customInput.click();\n\n });\n \n customInput.addEventListener('change', (e) => {\n customInput.style.visibility = 'hidden';\n customInput.style.pointerEvents = 'none'; // ngăn không cho click\n customInput.style.opacity = '0'; // ẩn hẳn về mặt thị giác\n this.selectColor(e.target.value);\n // Maintain editor focus after color selection\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n customContainer.appendChild(noColorButton);\n customContainer.appendChild(whiteButton);\n customContainer.appendChild(blackButton);\n customContainer.appendChild(customColorButton);\n customContainer.appendChild(customInput);\n \n this.popup.appendChild(customContainer);\n }\n\n /**\n * Setup click outside handler\n */\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n // Add slight delay to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n /**\n * Remove click outside handler\n */\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n /**\n * Show color picker popup\n * @param {HTMLElement} anchor - Element to position popup relative to\n */\n show(anchor) {\n if (!anchor) return;\n \n // Ensure popup is in DOM\n if (!document.body.contains(this.popup)) {\n appendPopup(this.popup);\n }\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup by adding visible class\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Setup click outside handler\n this.setupClickOutside();\n }\n\n /**\n * Hide color picker popup\n */\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n /**\n * Select color and trigger callback\n * @param {string} color - Selected color\n */\n selectColor(color) {\n this.currentColor = color;\n \n if (this.options.onColorSelect) {\n this.options.onColorSelect(color);\n }\n \n this.hide();\n }\n\n /**\n * Destroy color picker\n */\n destroy() {\n this.removeClickOutside();\n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n }\n}\n\nexport default ColorPicker; ","import { InlineFormat } from '../core/format.js';\nimport ColorPicker from '../ui/color-picker.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\nimport { execFormat, setStyleWithCSS } from '../utils/exec-command.js';\n\n/**\n * Color Format - Handles text color formatting\n */\nclass Color extends InlineFormat {\n static formatName = 'color';\n static tagName = 'SPAN';\n static attribute = 'color';\n\n // Selection saved when the picker opens, restored before applying — so the\n // colour still lands on the right text after a tap clears the live selection\n // (mobile/touch) or focus moves to the picker.\n static savedRanges = new Map();\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Color format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a color picker instance\n let colorPicker = currentEditor.getPopupInstance('color');\n \n if (!colorPicker) {\n // Create new color picker instance for this editor\n const editorId = this.editorId;\n colorPicker = new ColorPicker({\n onColorSelect: (color) => {\n Color.applyColorToCurrentSelection(color, editorId);\n },\n editor: Editor.getCurrentInstance()\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('color', colorPicker);\n }\n \n this.colorPicker = colorPicker;\n }\n\n /**\n * Static method to apply color to current selection\n */\n static applyColorToCurrentSelection(color, editorId = null) {\n const selection = window.getSelection();\n // Restore the selection captured when the picker opened (a tap on the\n // picker may have collapsed the live selection, especially on mobile).\n const saved = editorId != null ? Color.savedRanges.get(editorId) : null;\n if (saved) {\n selection.removeAllRanges();\n selection.addRange(saved);\n }\n if (editorId != null) Color.savedRanges.delete(editorId);\n\n if (!selection || !selection.rangeCount || selection.isCollapsed) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n setStyleWithCSS(true);\n // 'transparent' is the picker's \"reset to default\" entry — clear the colour\n // back to the editor default rather than painting an explicit colour.\n execFormat('foreColor', color === 'transparent' ? 'inherit' : color);\n\n // Refresh toolbar state (active highlight + swatch) and notify listeners.\n setTimeout(() => {\n const currentEditor = Editor.getCurrentInstance();\n if (currentEditor) {\n if (typeof currentEditor.updateToolbarButtonStates === 'function') {\n currentEditor.updateToolbarButtonStates();\n }\n if (typeof currentEditor.onContentChange === 'function') {\n currentEditor.onContentChange();\n }\n }\n }, 0);\n }\n\n /**\n * Toggle color formatting - shows/hides color picker\n */\n toggle() {\n if (this.colorPicker.isVisible) {\n this.colorPicker.hide();\n } else {\n this.showColorPicker();\n }\n }\n\n /**\n * Show color picker positioned relative to color button on toolbar\n */\n showColorPicker() {\n // Find color button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n\n // Capture the current selection so we can apply the colour to it even if a\n // tap on the picker clears the live selection. Fall back to the editor's\n // last non-collapsed range (mobile clears the selection on touchstart).\n const sel = window.getSelection();\n if (sel && sel.rangeCount && !sel.isCollapsed) {\n Color.savedRanges.set(this.editorId, sel.getRangeAt(0).cloneRange());\n } else if (editor._lastRange) {\n Color.savedRanges.set(this.editorId, editor._lastRange.cloneRange());\n }\n\n const toolbar = editor.getModule('toolbar');\n let colorButton = null;\n \n if (toolbar) {\n colorButton = toolbar.getButton('color');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!colorButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n colorButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.color-btn');\n }\n }\n \n // Final fallback: find any color button in the current editor's wrapper\n if (!colorButton) {\n colorButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.color-btn');\n }\n \n if (!colorButton) {\n console.warn('Color button not found for editor:', this.editorId);\n return;\n }\n \n this.colorPicker.show(colorButton);\n }\n\n /**\n * Check if color formatting is active in current selection\n */\n isActive() {\n return !!Color.getCurrentColor();\n }\n\n /**\n * Return the explicit text colour applied at the current selection, or null\n * when the text uses the editor's default colour. We look for an EXPLICIT\n * inline colour (the `<span style=\"color:…\">` / `<font color>` that the\n * editor inserts) rather than comparing the computed colour to a hardcoded\n * default — the default depends on the active theme, so a hardcoded compare\n * made the button look permanently \"active\".\n */\n static getCurrentColor() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n let node = selection.getRangeAt(0).startContainer;\n if (node.nodeType === Node.TEXT_NODE) node = node.parentNode;\n\n while (node && node.nodeType === Node.ELEMENT_NODE) {\n if (node.classList && node.classList.contains('rich-editor-area')) break;\n if (node.style && node.style.color && node.style.color !== 'inherit') {\n return node.style.color;\n }\n if (node.tagName === 'FONT' && node.getAttribute('color')) {\n return node.getAttribute('color');\n }\n node = node.parentNode;\n }\n return null;\n }\n}\n\nexport default Color; ","import { InlineFormat } from '../core/format.js';\nimport ColorPicker from '../ui/color-picker.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\nimport { execFormat, setStyleWithCSS } from '../utils/exec-command.js';\n\n/**\n * Background Color Format - Handles text background color formatting\n * Now supports multiple editor instances with separate popup instances\n */\nclass Background extends InlineFormat {\n static formatName = 'background';\n static tagName = 'SPAN';\n static attribute = 'background-color';\n\n // Selection saved when the picker opens, restored before applying (see Color).\n static savedRanges = new Map();\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Background format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a background color picker instance\n let colorPicker = currentEditor.getPopupInstance('background');\n \n if (!colorPicker) {\n // Create new color picker instance for this editor\n colorPicker = new ColorPicker({\n onColorSelect: (color) => {\n Background.applyBackgroundToCurrentSelection(color, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('background', colorPicker);\n }\n \n this.colorPicker = colorPicker;\n }\n\n /**\n * Create a new Background format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Background} Background format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Background();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Static method to apply background color to current selection\n * @param {string} color - Background color value\n * @param {string} editorId - Editor instance ID\n */\n static applyBackgroundToCurrentSelection(color, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for background color application');\n return;\n }\n \n const selection = window.getSelection();\n // Restore the selection captured when the picker opened (a tap on the\n // picker may have collapsed the live selection, especially on mobile).\n const saved = editorId != null ? Background.savedRanges.get(editorId) : null;\n if (saved) {\n selection.removeAllRanges();\n selection.addRange(saved);\n }\n if (editorId != null) Background.savedRanges.delete(editorId);\n\n if (!selection || !selection.rangeCount || selection.isCollapsed) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n setStyleWithCSS(true);\n execFormat('backColor', color === 'transparent' ? 'inherit' : color);\n\n // Refresh toolbar state (active highlight + swatch) and notify listeners.\n setTimeout(() => {\n if (editor) {\n if (typeof editor.updateToolbarButtonStates === 'function') {\n editor.updateToolbarButtonStates();\n }\n if (typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }\n }, 0);\n }\n\n /**\n * Toggle background color formatting - shows/hides color picker\n */\n toggle() {\n if (this.colorPicker.isVisible) {\n this.colorPicker.hide();\n } else {\n this.showColorPicker();\n }\n }\n\n /**\n * Show color picker positioned relative to background button on toolbar\n */\n showColorPicker() {\n // Find background button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n\n // Capture the selection so the colour applies even if a tap clears it.\n // Fall back to the editor's last non-collapsed range (mobile touch clears it).\n const sel = window.getSelection();\n if (sel && sel.rangeCount && !sel.isCollapsed) {\n Background.savedRanges.set(this.editorId, sel.getRangeAt(0).cloneRange());\n } else if (editor._lastRange) {\n Background.savedRanges.set(this.editorId, editor._lastRange.cloneRange());\n }\n\n const toolbar = editor.getModule('toolbar');\n let backgroundButton = null;\n \n if (toolbar) {\n backgroundButton = toolbar.getButton('background');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!backgroundButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n backgroundButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.background-btn');\n }\n }\n \n // Final fallback: find any background button in the current editor's wrapper\n if (!backgroundButton) {\n backgroundButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.background-btn');\n }\n \n if (!backgroundButton) {\n console.warn('Background button not found for editor:', this.editorId);\n return;\n }\n \n this.colorPicker.show(backgroundButton);\n }\n\n /**\n * Check if background color formatting is active in current selection\n */\n isActive() {\n return !!Background.getCurrentColor();\n }\n\n /**\n * Return the explicit background colour applied at the current selection, or\n * null when none is set. Looks for an explicit inline background (the span\n * the editor inserts) instead of comparing the computed colour to hardcoded\n * white/transparent — which misfired against themed backgrounds.\n */\n static getCurrentColor() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n let node = selection.getRangeAt(0).startContainer;\n if (node.nodeType === Node.TEXT_NODE) node = node.parentNode;\n\n while (node && node.nodeType === Node.ELEMENT_NODE) {\n if (node.classList && node.classList.contains('rich-editor-area')) break;\n if (node.style && node.style.backgroundColor && node.style.backgroundColor !== 'inherit') {\n return node.style.backgroundColor;\n }\n node = node.parentNode;\n }\n return null;\n }\n}\n\nexport default Background; ","/**\n * Link Popup Component — a compact, inline link input that appears right at the\n * selected text (Notion/Medium style). Shows just a URL field + Apply; the\n * display-text field only appears when no text is selected.\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass LinkPopup {\n constructor(options = {}) {\n this.options = {\n onLinkSelect: null,\n editor: null,\n ...options\n };\n\n this.popup = null;\n this.isVisible = false;\n this.urlInput = null;\n this.textInput = null;\n\n this.createPopup();\n }\n\n createPopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'link-popup link-popup--inline';\n\n const content = document.createElement('div');\n content.className = 'link-popup-content';\n\n // Display-text field — only shown when there's no selected text to link.\n this.textGroup = document.createElement('div');\n this.textGroup.className = 'link-popup-row';\n this.textInput = document.createElement('input');\n this.textInput.type = 'text';\n this.textInput.className = 'yjd-input';\n this.textInput.placeholder = 'Text to display';\n this.textGroup.appendChild(this.textInput);\n\n // URL row: input + Apply.\n const row = document.createElement('div');\n row.className = 'link-popup-row';\n\n this.urlInput = document.createElement('input');\n this.urlInput.type = 'text';\n this.urlInput.className = 'yjd-input';\n this.urlInput.placeholder = 'Paste or type a link…';\n\n this.applyBtn = document.createElement('button');\n this.applyBtn.type = 'button';\n this.applyBtn.className = 'yjd-button-confirm link-popup-apply';\n this.applyBtn.textContent = 'Apply';\n this.applyBtn.onclick = () => { this.handleOk(); this._refocusEditor(); };\n\n row.appendChild(this.urlInput);\n row.appendChild(this.applyBtn);\n\n content.appendChild(this.textGroup);\n content.appendChild(row);\n this.popup.appendChild(content);\n\n const onKey = (e) => {\n if (e.key === 'Enter') { e.preventDefault(); this.handleOk(); this._refocusEditor(); }\n if (e.key === 'Escape') { this.hide(); this._refocusEditor(); }\n };\n this.urlInput.onkeydown = onKey;\n this.textInput.onkeydown = onKey;\n\n appendPopup(this.popup);\n\n // Prevent focus loss when clicking on popup\n if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {\n this.options.editor.preventFocusLoss(this.popup);\n }\n }\n\n _refocusEditor() {\n if (this.options.editor) setTimeout(() => this.options.editor.focus(), 0);\n }\n\n handleOk() {\n const raw = this.urlInput.value.trim();\n if (!raw) { this.urlInput.focus(); return; }\n\n // Friendly normalisation: bare domains get https://; keep anchors,\n // root-relative paths and explicit schemes (mailto:, tel:, …) as-is.\n let url = raw;\n const hasScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url);\n if (!hasScheme && !url.startsWith('/') && !url.startsWith('#')) {\n url = 'https://' + url;\n }\n\n const text = this.textInput.value.trim();\n if (this.options.onLinkSelect) this.options.onLinkSelect({ url, text });\n this.hide();\n }\n\n show(anchor, existingLink = null, selectedText = '') {\n if (!anchor) return;\n\n const hasSelection = !!selectedText;\n this.urlInput.value = existingLink ? existingLink.url : '';\n this.textInput.value = selectedText || (existingLink ? existingLink.text : '');\n // No need to ask for display text when text is already selected.\n this.textGroup.style.display = hasSelection ? 'none' : '';\n\n const position = calculatePopupPosition(anchor, this.popup, { offsetY: 8, offsetX: 0 });\n setPopupPosition(this.popup, position);\n\n this.popup.classList.add('visible');\n this.isVisible = true;\n\n setTimeout(() => this.urlInput.focus(), 60);\n setTimeout(() => {\n document.addEventListener('click', this.closeOnClickOutside);\n }, 100);\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n document.removeEventListener('click', this.closeOnClickOutside);\n }\n\n closeOnClickOutside = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n }\n\n destroy() {\n document.removeEventListener('click', this.closeOnClickOutside);\n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n }\n}\n\nexport default LinkPopup;\n","import { InlineFormat } from '../core/format.js';\nimport LinkPopup from '../ui/link-popup.js';\nimport Editor from '../core/editor.js';\nimport { isSafeUrl } from '../utils/sanitize.js';\n\n/**\n * Link Format - Simple link insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Link extends InlineFormat {\n static formatName = 'link';\n static tagName = 'A';\n \n // Map to store saved ranges for each editor instance\n static savedRanges = new Map();\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Link format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a link popup instance\n let linkPopup = currentEditor.getPopupInstance('link');\n \n if (!linkPopup) {\n // Create new popup instance for this editor\n linkPopup = new LinkPopup({\n onLinkSelect: (linkData) => {\n Link.insertLink(linkData, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('link', linkPopup);\n }\n \n this.linkPopup = linkPopup;\n }\n\n /**\n * Create a new Link format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Link} Link format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Link();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Insert link at saved cursor position\n * @param {Object} linkData - Link data with url and text\n * @param {string} editorId - Editor instance ID\n */\n static insertLink(linkData, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for link insertion');\n return;\n }\n\n // Block unsafe URL schemes (javascript:, data:text/html, vbscript:, ...)\n if (!isSafeUrl(linkData.url)) {\n console.warn('Blocked unsafe link URL:', linkData.url);\n return;\n }\n\n // Get saved range for this specific editor\n const savedRange = Link.savedRanges.get(editorId);\n if (!savedRange) {\n console.warn('No saved range found for editor:', editorId);\n return;\n }\n \n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(savedRange);\n \n const range = selection.getRangeAt(0);\n \n if (range.collapsed) {\n // No selection - insert link at cursor\n const linkElement = document.createElement('A');\n linkElement.href = linkData.url;\n linkElement.target = '_blank';\n linkElement.rel = 'noopener noreferrer';\n linkElement.textContent = linkData.text || linkData.url;\n range.insertNode(linkElement);\n } else {\n // Has selection - wrap existing content with link while preserving styles\n const fragment = range.extractContents();\n const linkElement = document.createElement('A');\n linkElement.href = linkData.url;\n linkElement.target = '_blank';\n linkElement.rel = 'noopener noreferrer';\n \n // Move all nodes from fragment to link element\n while (fragment.firstChild) {\n linkElement.appendChild(fragment.firstChild);\n }\n \n range.insertNode(linkElement);\n }\n \n // Position cursor after link\n const newRange = document.createRange();\n newRange.setStartAfter(range.endContainer);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n \n // Clear saved range for this editor\n Link.savedRanges.delete(editorId);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }\n\n /**\n * Toggle link popup\n */\n toggle() {\n if (this.linkPopup.isVisible) {\n this.linkPopup.hide();\n } else {\n this.showPopup();\n }\n }\n\n /**\n * Show link popup\n */\n showPopup() {\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) {\n console.warn('No editor found for ID:', this.editorId);\n return;\n }\n\n // Save the current range and anchor the popup right at the selected text.\n const selection = window.getSelection();\n let rect = null;\n if (selection && selection.rangeCount > 0) {\n const range = selection.getRangeAt(0).cloneRange();\n Link.savedRanges.set(this.editorId, range);\n rect = range.getBoundingClientRect();\n // A collapsed caret can report an empty rect — fall back to its element.\n if (!rect || (rect.width === 0 && rect.height === 0)) {\n const node = range.startContainer.nodeType === Node.TEXT_NODE\n ? range.startContainer.parentElement\n : range.startContainer;\n if (node && node.getBoundingClientRect) rect = node.getBoundingClientRect();\n }\n }\n\n const existingLink = this.getCurrentLink();\n let selectedText = '';\n if (selection && !selection.isCollapsed) {\n selectedText = selection.toString().trim();\n }\n\n // Anchor at the selection rect (virtual anchor); fall back to the toolbar\n // button when there's no usable rect.\n let anchor = null;\n if (rect && (rect.width || rect.height)) {\n anchor = { getBoundingClientRect: () => rect };\n } else {\n const toolbar = editor.getModule('toolbar');\n anchor = toolbar && (toolbar.getButton('link')\n || editor.wrapper.querySelector('.rich-editor-toolbar-btn[data-command=\"link\"]'));\n }\n if (!anchor) {\n console.warn('No anchor for link popup, editor:', this.editorId);\n return;\n }\n\n this.linkPopup.show(anchor, existingLink, selectedText);\n }\n\n /**\n * Get current link if cursor is in one\n */\n getCurrentLink() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n \n let node = selection.getRangeAt(0).startContainer;\n \n // Find parent link element\n while (node && node !== document.body) {\n if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'A') {\n return {\n url: node.href || '',\n text: node.textContent || ''\n };\n }\n node = node.parentNode;\n }\n \n return null;\n }\n\n /**\n * Check if cursor is in a link\n */\n isActive() {\n return this.getCurrentLink() !== null;\n }\n}\n\nexport default Link; ","/**\n * Table Popup Component - Interactive table size picker\n */\nimport { PopupPositioning } from '../utils/popup-positioning.js';\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass TablePopup {\n constructor(options = {}) {\n this.options = {\n maxRows: 8,\n maxCols: 8,\n onTableSelect: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.selectedRows = 1;\n this.selectedCols = 1;\n this.grid = null;\n this.sizeDisplay = null;\n \n this.createPopup();\n }\n\n createPopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'table-popup';\n \n const content = document.createElement('div');\n content.className = 'table-popup-content';\n \n // Create size display text\n this.createSizeDisplay();\n \n // Create grid selector\n this.createGridSelector();\n \n content.appendChild(this.grid);\n content.appendChild(this.sizeDisplay);\n this.popup.appendChild(content);\n appendPopup(this.popup);\n }\n\n createSizeDisplay() {\n this.sizeDisplay = document.createElement('div');\n this.sizeDisplay.className = 'table-size-display';\n \n }\n createGridSelector() {\n this.grid = document.createElement('div');\n this.grid.className = 'table-grid-selector';\n \n // Create grid of cells\n for (let row = 1; row <= this.options.maxRows; row++) {\n for (let col = 1; col <= this.options.maxCols; col++) {\n const cell = document.createElement('div');\n cell.className = 'table-grid-cell';\n cell.dataset.row = row;\n cell.dataset.col = col;\n \n // Mouse events\n cell.addEventListener('mouseenter', () => {\n this.highlightGrid(row, col);\n });\n \n cell.addEventListener('click', () => {\n this.selectSize(row, col);\n this.handleInsert();\n });\n \n this.grid.appendChild(cell);\n }\n }\n \n // Reset hover when leaving grid\n this.grid.addEventListener('mouseleave', () => {\n this.highlightGrid(1, 1);\n });\n }\n\n highlightGrid(rows, cols) {\n this.selectedRows = rows;\n this.selectedCols = cols;\n \n // Update size display text\n this.updateSizeDisplay(rows, cols);\n \n // Update grid visual\n const cells = this.grid.querySelectorAll('.table-grid-cell');\n cells.forEach(cell => {\n const cellRow = parseInt(cell.dataset.row);\n const cellCol = parseInt(cell.dataset.col);\n \n if (cellRow <= rows && cellCol <= cols) {\n cell.classList.add('highlighted');\n } else {\n cell.classList.remove('highlighted');\n }\n });\n }\n\n updateSizeDisplay(rows, cols) {\n if (this.sizeDisplay) {\n this.sizeDisplay.textContent = `${rows}x${cols}`;\n }\n }\n\n selectSize(rows, cols) {\n this.selectedRows = rows;\n this.selectedCols = cols;\n this.updateSizeDisplay(rows, cols);\n }\n\n handleInsert() {\n if (this.options.onTableSelect) {\n this.options.onTableSelect({\n rows: this.selectedRows,\n cols: this.selectedCols\n });\n }\n \n this.hide();\n }\n\n show(anchor) {\n if (!anchor) return;\n \n // Reset selection\n this.selectedRows = 1;\n this.selectedCols = 1;\n this.highlightGrid(1, 1);\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Click outside to close\n setTimeout(() => {\n document.addEventListener('click', this.closeOnClickOutside);\n }, 100);\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n document.removeEventListener('click', this.closeOnClickOutside);\n }\n\n closeOnClickOutside = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n }\n\n destroy() {\n document.removeEventListener('click', this.closeOnClickOutside);\n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n }\n}\n\nexport default TablePopup; ","import { BlockFormat } from '../core/format.js';\nimport TablePopup from '../ui/table-popup.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Table Format - HTML table insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Table extends BlockFormat {\n static formatName = 'table';\n static tagName = 'TABLE';\n static savedRanges = new Map(); // Map to store saved ranges for each editor\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Table format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a table popup instance\n let tablePopup = currentEditor.getPopupInstance('table');\n \n if (!tablePopup) {\n // Create new table popup instance for this editor\n tablePopup = new TablePopup({\n onTableSelect: (tableData) => {\n Table.insertTable(tableData, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('table', tablePopup);\n }\n \n this.tablePopup = tablePopup;\n }\n\n /**\n * Create a new Table format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Table} Table format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Table();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Insert table at saved cursor position\n * @param {Object} tableData - Table data with rows and cols\n * @param {string} editorId - Editor instance ID\n */\n static insertTable(tableData, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for table insertion');\n return;\n }\n \n // Get saved range for this editor\n const savedRange = Table.savedRanges.get(editorId);\n if (!savedRange) return;\n \n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(savedRange);\n \n const range = selection.getRangeAt(0);\n\n // Create table HTML\n const tableElement = Table.createTableElement(tableData.rows, tableData.cols);\n\n // Clear any selected content\n if (!range.collapsed) {\n range.deleteContents();\n }\n\n // Insert the table as a top-level block (not nested inside a heading or\n // inline formatting tags, which would produce invalid HTML).\n if (typeof editor.insertBlock === 'function') {\n editor.insertBlock(tableElement);\n } else {\n range.insertNode(tableElement);\n }\n\n // Position cursor in first cell\n const firstCell = tableElement.querySelector('td');\n if (firstCell) {\n const newRange = document.createRange();\n newRange.setStart(firstCell, 0);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n \n // Clear saved range for this editor\n Table.savedRanges.delete(editorId);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }\n\n /**\n * Create table element\n */\n static createTableElement(rows, cols) {\n const table = document.createElement('table');\n table.className = 'rich-editor-table';\n table.cellSpacing = '0';\n table.cellPadding = '0';\n table.border = '1';\n \n const tbody = document.createElement('tbody');\n \n for (let r = 0; r < rows; r++) {\n const row = document.createElement('tr');\n \n for (let c = 0; c < cols; c++) {\n const cell = document.createElement('td');\n cell.innerHTML = '<br>'; // empty placeholder (keeps height, not counted as content)\n cell.style.minWidth = '50px';\n cell.style.minHeight = '24px';\n cell.style.padding = '4px 8px';\n cell.style.border = '1px solid #ddd';\n cell.style.verticalAlign = 'top';\n \n // Make cells editable\n cell.contentEditable = 'true';\n \n row.appendChild(cell);\n }\n \n tbody.appendChild(row);\n }\n \n table.appendChild(tbody);\n \n // Add table styles\n table.style.borderCollapse = 'collapse';\n table.style.width = '100%';\n table.style.margin = '10px 0';\n \n return table;\n }\n\n /**\n * Toggle table popup\n */\n toggle() {\n if (this.tablePopup.isVisible) {\n this.tablePopup.hide();\n } else {\n this.showPopup();\n }\n }\n\n /**\n * Show table popup\n */\n showPopup() {\n // Lưu vị trí con trỏ hiện tại cho editor này\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n Table.savedRanges.set(this.editorId, selection.getRangeAt(0).cloneRange());\n }\n \n // Find table button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let tableButton = null;\n \n if (toolbar) {\n tableButton = toolbar.getButton('table');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!tableButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n tableButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.table-btn');\n }\n }\n \n // Final fallback: find any table button in the current editor's wrapper\n if (!tableButton) {\n tableButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.table-btn');\n }\n \n if (!tableButton) {\n console.warn('Table button not found for editor:', this.editorId);\n return;\n }\n \n this.tablePopup.show(tableButton);\n }\n\n /**\n * Check if cursor is in a table\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n \n let node = selection.getRangeAt(0).startContainer;\n \n // Find parent table element\n while (node && node !== document.body) {\n if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'TABLE') {\n return true;\n }\n node = node.parentNode;\n }\n \n return false;\n }\n\n /**\n * Get current table if cursor is in one\n */\n getCurrentTable() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n \n let node = selection.getRangeAt(0).startContainer;\n \n // Find parent table element\n while (node && node !== document.body) {\n if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'TABLE') {\n return node;\n }\n node = node.parentNode;\n }\n \n return null;\n }\n\n /**\n * Apply table formatting (not applicable for this format)\n */\n apply() {\n this.showPopup();\n }\n\n /**\n * Remove table formatting\n */\n remove() {\n const table = this.getCurrentTable();\n if (table) {\n // Hide resize handles before removing table\n if (window.richEditor && window.richEditor.resizeHandles) {\n window.richEditor.resizeHandles.hideHandles();\n }\n table.parentNode.removeChild(table);\n }\n }\n}\n\nexport default Table; ","import IconUtils from './icons.js';\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Custom Select Component - Reusable dropdown/popup select component\n */\nclass CustomSelect {\n constructor(options = {}) {\n this.options = {\n items: [], // Array of items to display\n onItemSelect: null, // Callback when item is selected\n displayProperty: 'label', // Property to display as text\n valueProperty: 'value', // Property to use as value\n className: 'custom-select', // CSS class for the popup\n title: '', // Optional header label shown at the top of the popup\n width: 200, // Popup width\n height: 280, // Popup height\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.currentValue = null;\n this.clickOutsideHandler = null;\n this.initialized = false;\n \n this.createSelect();\n }\n\n /**\n * Create select popup\n */\n createSelect() {\n // Create popup\n this.popup = document.createElement('div');\n this.popup.className = `${this.options.className}-popup`;\n\n // Optional header so it's clear what the dropdown controls.\n if (this.options.title) {\n const header = document.createElement('div');\n header.className = 'custom-select-header';\n header.textContent = this.options.title;\n this.popup.appendChild(header);\n }\n\n // Add popup to container\n appendPopup(this.popup);\n \n // Initialize async\n this.init();\n }\n\n /**\n * Initialize component with async operations\n */\n async init() {\n // Create item list\n await this.createItemList();\n this.initialized = true;\n }\n\n /**\n * Create item list\n */\n async createItemList() {\n const list = document.createElement('div');\n list.className = 'item-list';\n \n // Get check icon\n const checkIconSvg = IconUtils.getIcon('check');\n \n this.options.items.forEach(item => {\n const itemButton = document.createElement('button');\n itemButton.type = 'button';\n itemButton.className = 'custom-select-item-button';\n itemButton.dataset.value = this.getItemValue(item);\n \n // Create item content with text and checkmark\n const itemText = document.createElement('div');\n itemText.className = 'item-text';\n itemText.innerHTML = this.getItemDisplay(item);\n \n const checkmark = document.createElement('span');\n checkmark.className = 'item-checkmark';\n checkmark.innerHTML = checkIconSvg || '';\n \n itemButton.appendChild(itemText);\n itemButton.appendChild(checkmark);\n \n itemButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectItem(item);\n });\n \n list.appendChild(itemButton);\n });\n \n this.popup.appendChild(list);\n }\n\n /**\n * Get display text for item\n */\n getItemDisplay(item) {\n return item[this.options.displayProperty] || item.toString();\n }\n\n /**\n * Get value for item\n */\n getItemValue(item) {\n return item[this.options.valueProperty] || item[this.options.displayProperty] || item;\n }\n\n /**\n * Update items in the select\n */\n async updateItems(items) {\n this.options.items = items;\n \n // Remove existing list\n const existingList = this.popup.querySelector('.item-list');\n if (existingList) {\n existingList.remove();\n }\n \n // Create new list\n await this.createItemList();\n }\n\n /**\n * Setup click outside handler\n */\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n // Don't hide if clicking on block toolbar or its buttons\n if (e.target.closest('.block-toolbar')) {\n return;\n }\n \n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n // Add slight delay to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n /**\n * Remove click outside handler\n */\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n \n /**\n * Setup scroll handler to update popup position\n */\n setupScrollHandler() {\n if (this.scrollHandler) {\n window.removeEventListener('scroll', this.scrollHandler);\n }\n \n this.scrollHandler = () => {\n if (this.isVisible) {\n this.updatePosition();\n }\n };\n \n window.addEventListener('scroll', this.scrollHandler);\n }\n \n /**\n * Remove scroll handler\n */\n removeScrollHandler() {\n if (this.scrollHandler) {\n window.removeEventListener('scroll', this.scrollHandler);\n this.scrollHandler = null;\n }\n }\n\n /**\n * Show select popup\n */\n async show(anchor) {\n if (!anchor) return;\n\n // Capture the editor selection NOW so the format applies to the right place\n // even if a tap on the popup clears the live selection (mobile/touch).\n // Prefer the LIVE selection when it's inside the editor — including a\n // collapsed caret (needed for \"apply then keep typing\"). Only fall back to\n // the last real range when the selection is genuinely gone/outside.\n const sel = window.getSelection();\n const ed = Editor.getCurrentInstance && Editor.getCurrentInstance();\n const editorEl = ed && ed.editor;\n if (sel && sel.rangeCount && editorEl && editorEl.contains(sel.anchorNode)) {\n this._savedRange = sel.getRangeAt(0).cloneRange();\n } else {\n this._savedRange = ed && ed._lastRange ? ed._lastRange.cloneRange() : null;\n }\n\n // Wait for initialization if not ready\n if (!this.initialized) {\n await new Promise(resolve => {\n const checkInit = () => {\n if (this.initialized) {\n resolve();\n } else {\n setTimeout(checkInit, 10);\n }\n };\n checkInit();\n });\n }\n \n // Ensure popup is in DOM\n if (!document.body.contains(this.popup)) {\n appendPopup(this.popup);\n }\n \n // Update current selection highlight\n this.highlightCurrentItem(this.currentValue);\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup by adding visible class\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Setup click outside handler\n this.setupClickOutside();\n \n // Setup scroll handler to update position\n this.setupScrollHandler();\n \n // Store reference to anchor for potential repositioning\n this.currentAnchor = anchor;\n }\n\n /**\n * Hide select popup\n */\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n this.currentAnchor = null;\n }\n \n /**\n * Update popup position based on current anchor\n */\n updatePosition() {\n if (this.isVisible && this.currentAnchor) {\n // Calculate and set popup position\n const position = calculatePopupPosition(this.currentAnchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n }\n }\n\n /**\n * Set current value\n */\n setCurrentValue(value) {\n this.currentValue = value;\n this.highlightCurrentItem(value);\n }\n\n /**\n * Highlight current item in the list\n */\n highlightCurrentItem(value) {\n // Remove previous highlights\n this.popup.querySelectorAll('.custom-select-item-button.current').forEach(btn => {\n btn.classList.remove('current');\n });\n \n // Highlight current item - find by comparing dataset.value directly\n if (value != null) {\n const buttons = this.popup.querySelectorAll('.custom-select-item-button');\n for (const button of buttons) {\n if (button.dataset.value === value.toString()) {\n button.classList.add('current');\n break;\n }\n }\n }\n }\n\n /**\n * Select item and trigger callback\n */\n selectItem(item) {\n const value = this.getItemValue(item);\n this.currentValue = value;\n\n // Restore the selection captured when the popup opened, so the format\n // applies even if the tap cleared the live selection (mobile/touch).\n if (this._savedRange) {\n const s = window.getSelection();\n s.removeAllRanges();\n s.addRange(this._savedRange);\n }\n\n if (this.options.onItemSelect) {\n this.options.onItemSelect(value, item);\n }\n\n this.hide();\n }\n\n /**\n * Get current selected value\n */\n getCurrentValue() {\n return this.currentValue;\n }\n\n /**\n * Destroy select component\n */\n destroy() {\n this.removeClickOutside();\n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n }\n}\n\nexport default CustomSelect; ","import { BlockFormat } from '../core/format.js';\nimport CustomSelect from '../ui/customselect.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Heading Format - Handles heading and paragraph formatting\n * Now supports multiple editor instances with separate popup instances\n */\nclass Heading extends BlockFormat {\n static formatName = 'heading';\n static tagName = 'H1'; // Default tag, will be overridden\n \n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Heading format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a heading select instance\n let customSelect = currentEditor.getPopupInstance('heading');\n \n if (!customSelect) {\n // Create new custom select instance for this editor\n const tagMap = Heading.getTagMap();\n const items = Object.values(tagMap).map(tagData => ({\n value: tagData.tag,\n label: tagData.element,\n title: tagData.title\n }));\n\n customSelect = new CustomSelect({\n items: items,\n displayProperty: 'label',\n valueProperty: 'value',\n className: 'heading-select',\n onItemSelect: (value, item) => {\n Heading.applyTagToCurrentSelection(value, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('heading', customSelect);\n }\n \n this.customSelect = customSelect;\n }\n\n /**\n * Create a new Heading format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Heading} Heading format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Heading();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Get display name for tag\n * @param {string} tag - HTML tag name\n * @returns {string} Display name\n */\n static getTagMap() {\n return {\n 'H1': { tag: 'H1', element: '<h1 style=\"margin:0\">Heading 1</h1>', title: 'Heading 1' },\n 'H2': { tag: 'H2', element: '<h2 style=\"margin:0\">Heading 2</h2>', title: 'Heading 2' },\n 'H3': { tag: 'H3', element: '<h3 style=\"margin:0\">Heading 3</h3>', title: 'Heading 3' },\n 'H4': { tag: 'H4', element: '<h4 style=\"margin:0\">Heading 4</h4>', title: 'Heading 4' },\n 'H5': { tag: 'H5', element: '<h5 style=\"margin:0\">Heading 5</h5>', title: 'Heading 5' },\n 'H6': { tag: 'H6', element: '<h6 style=\"margin:0\">Heading 6</h6>', title: 'Heading 6' },\n 'P': { tag: 'P', element: '<p style=\"margin:0\">Paragraph</p>', title: 'Paragraph' },\n 'PRE': { tag: 'PRE', element: '<pre style=\"margin:0\">Code</pre>', title: 'Preformatted' },\n 'BLOCKQUOTE': { tag: 'BLOCKQUOTE', element: '<blockquote style=\"margin:0\">Quote</blockquote>', title: 'Quote' }\n };\n }\n\n static getTagDisplayName(tag) {\n const tagMap = this.getTagMap();\n return tagMap[tag]?.title || 'Paragraph';\n }\n\n /**\n * Update custom button text based on current tag\n */\n updateButtonText() {\n const currentTag = this.getCurrentTag();\n const displayName = Heading.getTagDisplayName(currentTag || 'P');\n \n // Find heading button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let headingButton = null;\n \n if (toolbar) {\n headingButton = toolbar.getButton('heading');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!headingButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n headingButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.heading-btn');\n }\n }\n \n // Final fallback: find any heading button in the current editor's wrapper\n if (!headingButton) {\n headingButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.heading-btn');\n }\n \n if (headingButton && headingButton.updateText) {\n headingButton.updateText(displayName);\n } else if (headingButton) {\n headingButton.textContent = displayName;\n }\n }\n\n /**\n * Create element with specific tag\n * @param {string} tag - HTML tag name (H1, H2, P, etc.)\n * @returns {HTMLElement}\n */\n static create(tag = 'P') {\n const node = document.createElement(tag.toUpperCase());\n return node;\n }\n\n /**\n * Static method to apply tag to current selection\n * @param {string} tag - HTML tag name\n * @param {string} editorId - Editor instance ID\n */\n static applyTagToCurrentSelection(tag, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for heading application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n const headingFormat = Heading.createForEditor(editorId);\n if (headingFormat) {\n headingFormat.apply(tag);\n \n // Update button text after applying\n headingFormat.updateButtonText();\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply heading format with specified tag\n * @param {string} tag - HTML tag name (H1, H2, P, etc.)\n */\n apply(tag = 'P') {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Lưu selection trước khi đổi\n const range = selection.getRangeAt(0);\n const isCollapsed = range.collapsed; // true = không bôi đen gì\n\n const blocks = this.getBlockElements(range);\n\n if (blocks.length === 0) {\n // Không có block - tạo mới\n const newBlock = this.createBlockAtCursor(range, tag);\n \n // Sau khi tạo block mới → đặt con trỏ vào block\n const newRange = document.createRange();\n newRange.setStart(newBlock, 0);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n // selection hiện tại\n const selection = window.getSelection();\n if (!selection.rangeCount) return;\n const range = selection.getRangeAt(0);\n const isCollapsed = range.collapsed;\n\n // đảm bảo blocks là mảng\n const blocksArray = Array.from(blocks);\n \n\n // tìm block chứa 1 node\n function findBlockIndex(node, blocks) {\n while (node && node.nodeType !== 9 /*document*/) {\n const idx = blocks.indexOf(node);\n if (idx !== -1) return idx;\n node = node.parentNode;\n }\n return -1;\n }\n\n // tính số ký tự từ đầu block tới vị trí (sử dụng Range.toString())\n function charOffsetFromBlockStart(block, container, offset) {\n const r = document.createRange();\n r.setStart(block, 0);\n r.setEnd(container, offset);\n return r.toString().length;\n }\n\n const startBlockIndex = findBlockIndex(range.startContainer, blocksArray);\n const endBlockIndex = findBlockIndex(range.endContainer, blocksArray);\n\n let startCharOffset = 0, endCharOffset = 0;\n if (startBlockIndex !== -1) {\n startCharOffset = charOffsetFromBlockStart(blocksArray[startBlockIndex], range.startContainer, range.startOffset);\n }\n if (!isCollapsed && endBlockIndex !== -1) {\n endCharOffset = charOffsetFromBlockStart(blocksArray[endBlockIndex], range.endContainer, range.endOffset);\n }\n\n // --- Thực hiện convert và lấy lại node mới trả về (nếu convertBlock trả về node mới)\n const newBlocks = blocksArray.map(b => {\n const newNode = this.convertBlock(b, tag);\n return newNode || b; // nếu convertBlock trả về undefined thì dùng lại b (convert in-place)\n });\n\n // helper: từ charOffset tìm text node + offset bên trong nó; nếu không tìm thì trả về block để set ở cuối\n function resolvePositionByCharOffset(block, charOffset) {\n const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT, null, false);\n let node;\n let remaining = charOffset;\n while ((node = walker.nextNode())) {\n const len = node.nodeValue.length;\n if (remaining <= len) return { node, offset: remaining };\n remaining -= len;\n }\n // không tìm text node phù hợp => đặt ở cuối block\n return { node: block, offset: block.childNodes.length };\n }\n\n // tái tạo range\n const newRange = document.createRange();\n\n if (isCollapsed) {\n const idx = (startBlockIndex !== -1 ? startBlockIndex : 0);\n const pos = resolvePositionByCharOffset(newBlocks[idx], startCharOffset);\n if (pos.node.nodeType === Node.TEXT_NODE) newRange.setStart(pos.node, pos.offset);\n else newRange.setStart(pos.node, Math.max(0, pos.offset));\n newRange.collapse(true);\n } else {\n if (startBlockIndex === -1 || endBlockIndex === -1) {\n // fallback: nếu không nằm trong blocks thì giữ range cũ (hoặc handle theo logic của bạn)\n selection.removeAllRanges();\n selection.addRange(range);\n return;\n }\n const s = resolvePositionByCharOffset(newBlocks[startBlockIndex], startCharOffset);\n const e = resolvePositionByCharOffset(newBlocks[endBlockIndex], endCharOffset);\n\n // setStart/setEnd chấp nhận text node + offset hoặc element + childIndex\n if (s.node.nodeType === Node.TEXT_NODE) newRange.setStart(s.node, s.offset);\n else newRange.setStart(s.node, Math.min(s.offset, s.node.childNodes.length));\n\n if (e.node.nodeType === Node.TEXT_NODE) newRange.setEnd(e.node, e.offset);\n else newRange.setEnd(e.node, Math.min(e.offset, e.node.childNodes.length));\n }\n\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n } \n\n\n /**\n * Create new block at cursor position\n * @param {Range} range - Current range\n * @param {string} tag - HTML tag name\n */\n createBlockAtCursor(range, tag) {\n const blockNode = this.constructor.create(tag);\n \n // Try to preserve style from existing block if cursor is inside one\n const existingBlock = this.getBlockElement(range.startContainer);\n if (existingBlock && existingBlock.style && existingBlock.style.cssText) {\n blockNode.style.cssText = existingBlock.style.cssText;\n }\n \n if (range.collapsed) {\n // No selection - create empty block\n blockNode.appendChild(document.createTextNode(''));\n range.insertNode(blockNode);\n \n // Position cursor inside the block\n const newRange = document.createRange();\n newRange.setStart(blockNode, 0);\n newRange.collapse(true);\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n // Has selection - wrap in block\n const contents = range.extractContents();\n blockNode.appendChild(contents);\n range.insertNode(blockNode);\n \n // Select the content in the block\n const newRange = document.createRange();\n newRange.selectNodeContents(blockNode);\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n }\n\n /**\n * Convert existing block to new format\n * @param {Element} block - Block element to convert\n * @param {string} tag - HTML tag name\n * @returns {Element} - The new block element\n */\n convertBlock(block, tag) {\n const newBlock = this.constructor.create(tag);\n \n // Copy all child nodes\n while (block.firstChild) {\n newBlock.appendChild(block.firstChild);\n }\n \n // Copy relevant attributes\n if (block.className && this.shouldPreserveClass(block.className)) {\n newBlock.className = block.className;\n }\n \n // Copy style attributes to preserve formatting like text-align\n if (block.style && block.style.cssText) {\n newBlock.style.cssText = block.style.cssText;\n }\n \n // Replace the block\n block.parentNode.replaceChild(newBlock, block);\n \n return newBlock;\n }\n\n /**\n * Set cursor at start of block (fallback method)\n * @param {Element} block - Block element\n */\n setCursorAtStartOfBlock(block) {\n const selection = window.getSelection();\n const range = document.createRange();\n \n // Find first text node or position at start of block\n const walker = document.createTreeWalker(\n block,\n NodeFilter.SHOW_TEXT,\n null,\n false\n );\n \n const firstTextNode = walker.nextNode();\n if (firstTextNode) {\n range.setStart(firstTextNode, 0);\n range.collapse(true);\n } else {\n range.setStart(block, 0);\n range.collapse(true);\n }\n\n selection.removeAllRanges();\n selection.addRange(range);\n }\n\n /**\n * Toggle heading format - shows/hides tag picker\n */\n async toggle() {\n if (this.customSelect.isVisible) {\n this.customSelect.hide();\n } else {\n await this.showTagPicker();\n }\n }\n\n /**\n * Show custom select positioned relative to heading button on toolbar\n */\n async showTagPicker() {\n // Find heading button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let headingButton = null;\n \n if (toolbar) {\n headingButton = toolbar.getButton('heading');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!headingButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n headingButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.heading-btn');\n }\n }\n \n // Final fallback: find any heading button in the current editor's wrapper\n if (!headingButton) {\n headingButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.heading-btn');\n }\n \n if (!headingButton) {\n console.warn('Heading button not found for editor:', this.editorId);\n return;\n }\n \n // Update current selection before showing\n const currentTag = this.getCurrentTag();\n if (currentTag) {\n this.customSelect.setCurrentValue(currentTag);\n }\n \n await this.customSelect.show(headingButton);\n }\n\n /**\n * Check if heading format is active - always return false (no active state)\n * Only update button text to show current tag\n * @param {string} tag - Optional specific tag to check\n * @returns {boolean}\n */\n isActive(tag = null) {\n // Always update button text to show current tag\n this.updateButtonText();\n \n // Never show active state for heading button\n return false;\n }\n\n /**\n * Get current tag of the selection\n * @returns {string|null} Current tag name or null\n */\n getCurrentTag() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n const block = this.getBlockElement(range.startContainer);\n \n if (!block) return null;\n\n const headingTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'PRE', 'BLOCKQUOTE'];\n if (headingTags.includes(block.tagName)) {\n return block.tagName;\n }\n\n return null;\n }\n}\n\nexport default Heading; ","import { InlineFormat } from '../core/format.js';\nimport CustomSelect from '../ui/customselect.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\nimport { execFormat } from '../utils/exec-command.js';\n\n/**\n * Font Family Format - Handles font family formatting\n * Now supports multiple editor instances with separate popup instances\n */\nclass FontFamily extends InlineFormat {\n static formatName = 'fontFamily';\n static tagName = 'SPAN';\n \n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for FontFamily format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a font family select instance\n let customSelect = currentEditor.getPopupInstance('font-family');\n \n if (!customSelect) {\n // Create new custom select instance for this editor\n const fontMap = FontFamily.getFontMap();\n const items = Object.values(fontMap).map(fontData => ({\n value: fontData.font,\n label: fontData.element,\n title: fontData.title\n }));\n\n customSelect = new CustomSelect({\n items: items,\n displayProperty: 'label',\n valueProperty: 'value',\n className: 'font-family-select',\n onItemSelect: (value, item) => {\n FontFamily.applyFontFamilyToCurrentSelection(value, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('font-family', customSelect);\n }\n \n this.customSelect = customSelect;\n \n // Set up event listener for selection changes\n this.setupSelectionListener();\n }\n\n /**\n * Create a new FontFamily format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {FontFamily} FontFamily format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new FontFamily();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Set up event listener for selection changes to update button text\n */\n setupSelectionListener() {\n // Use a debounced function to avoid too many updates\n let updateTimeout;\n const debouncedUpdate = () => {\n clearTimeout(updateTimeout);\n updateTimeout = setTimeout(() => {\n // Only update if selection is in this editor\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n const range = selection.getRangeAt(0);\n const editor = Editor.getInstanceById(this.editorId);\n if (editor && (editor.editor.contains(range.startContainer) || editor.editor.isSameNode(range.startContainer))) {\n this.updateButtonText();\n }\n }\n }, 50); // 50ms delay\n };\n\n // Listen for selection changes\n document.addEventListener('selectionchange', debouncedUpdate);\n \n // Also listen for mouseup and keyup events for immediate feedback\n document.addEventListener('mouseup', debouncedUpdate);\n document.addEventListener('keyup', debouncedUpdate);\n \n // Store the listener for cleanup\n this.selectionListener = debouncedUpdate;\n }\n\n /**\n * Get font map with different font families\n */\n static getFontMap() {\n return {\n 'Arial': { \n font: 'Arial, sans-serif', \n element: '<span style=\"font-family: Arial, sans-serif\">Arial</span>', \n title: 'Arial' \n },\n 'Helvetica': { \n font: 'Helvetica, Arial, sans-serif', \n element: '<span style=\"font-family: Helvetica, Arial, sans-serif\">Helvetica</span>', \n title: 'Helvetica' \n },\n 'Times New Roman': { \n font: '\"Times New Roman\", Times, serif', \n element: '<span style=\"font-family: \\'Times New Roman\\', Times, serif\">Times New Roman</span>', \n title: 'Times New Roman' \n },\n 'Georgia': { \n font: 'Georgia, serif', \n element: '<span style=\"font-family: Georgia, serif\">Georgia</span>', \n title: 'Georgia' \n },\n 'Verdana': { \n font: 'Verdana, Geneva, sans-serif', \n element: '<span style=\"font-family: Verdana, Geneva, sans-serif\">Verdana</span>', \n title: 'Verdana' \n },\n 'Courier New': { \n font: '\"Courier New\", Courier, monospace', \n element: '<span style=\"font-family: \\'Courier New\\', Courier, monospace\">Courier New</span>', \n title: 'Courier New' \n },\n 'Trebuchet MS': { \n font: '\"Trebuchet MS\", Helvetica, sans-serif', \n element: '<span style=\"font-family: \\'Trebuchet MS\\', Helvetica, sans-serif\">Trebuchet MS</span>', \n title: 'Trebuchet MS' \n },\n 'Comic Sans MS': { \n font: '\"Comic Sans MS\", cursive', \n element: '<span style=\"font-family: \\'Comic Sans MS\\', cursive\">Comic Sans MS</span>', \n title: 'Comic Sans MS' \n },\n 'Impact': { \n font: 'Impact, Charcoal, sans-serif', \n element: '<span style=\"font-family: Impact, Charcoal, sans-serif\">Impact</span>', \n title: 'Impact' \n },\n 'Lucida Console': { \n font: '\"Lucida Console\", Monaco, monospace', \n element: '<span style=\"font-family: \\'Lucida Console\\', Monaco, monospace\">Lucida Console</span>', \n title: 'Lucida Console' \n }\n };\n }\n\n\n /**\n * Get display name for font\n * @param {string} font - Font family value\n * @returns {string} Display name\n */\n static getFontDisplayName(font) {\n const fontMap = this.getFontMap();\n // Find by font value\n for (const [key, value] of Object.entries(fontMap)) {\n if (value.font === font || key === font) {\n return value.title;\n }\n }\n return 'Arial';\n }\n\n /**\n * Update custom button text based on current font\n */\n updateButtonText() {\n const currentFont = this.getCurrentFont();\n const displayName = FontFamily.getFontDisplayName(currentFont || 'Arial, sans-serif');\n \n // Find font-family button in the specific editor's toolbar using editorId\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let fontFamilyButton = null;\n \n if (toolbar) {\n fontFamilyButton = toolbar.getButton('font-family');\n }\n \n // Fallback: find button by class in the specific editor's toolbar\n if (!fontFamilyButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n fontFamilyButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.font-family-btn');\n }\n }\n \n // Final fallback: find any font-family button in the specific editor's wrapper\n if (!fontFamilyButton) {\n fontFamilyButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.font-family-btn');\n }\n \n if (fontFamilyButton && fontFamilyButton.updateText) {\n fontFamilyButton.updateText(displayName);\n } else if (fontFamilyButton) {\n fontFamilyButton.textContent = displayName;\n }\n }\n\n /**\n * Create element with specific font family\n * @param {string} font - Font family value\n * @returns {HTMLElement}\n */\n static create(font = 'Arial, sans-serif') {\n const node = document.createElement('span');\n node.style.fontFamily = font;\n return node;\n }\n\n /**\n * Static method to apply font family to current selection\n * @param {string} font - Font family value\n * @param {string} editorId - Editor instance ID\n */\n static applyFontFamilyToCurrentSelection(font, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for font family application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n const fontFamilyFormat = FontFamily.createForEditor(editorId);\n if (fontFamilyFormat) {\n fontFamilyFormat.apply(font);\n \n // Update button text after applying\n fontFamilyFormat.updateButtonText();\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply font family format with specified font\n * @param {string} font - Font family value\n */\n apply(font = 'Arial, sans-serif') {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n const range = selection.getRangeAt(0);\n\n function isCaretInsideFontSpan(selection, font) {\n if (!selection.rangeCount) return false;\n const range = selection.getRangeAt(0);\n let node = range.startContainer;\n\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n\n const fontNormalized = font.split(',')[0].trim().toLowerCase();\n\n while (node && node.nodeType === Node.ELEMENT_NODE) {\n if (node.tagName === 'SPAN') {\n const styleFont = node.style.fontFamily;\n if (styleFont) {\n const styleFontNormalized = styleFont.split(',')[0].trim().toLowerCase();\n if (styleFontNormalized === fontNormalized) {\n if (\n node.childNodes.length === 1 &&\n node.firstChild.nodeType === Node.TEXT_NODE &&\n node.firstChild.textContent === '\\u200B'\n ) {\n return true; // Đang trong span marker rồi\n }\n return true; // Đang trong span font-family đó\n }\n }\n }\n node = node.parentNode;\n }\n return false;\n }\n\n // Hàm đặt caret vào bên trong span mới\n function moveCaretInside(el) {\n const sel = window.getSelection();\n const range = document.createRange();\n const textNode = el.firstChild;\n range.setStart(textNode, textNode.length);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n\n if (range.collapsed) {\n if (isCaretInsideFontSpan(selection, font)) {\n // Đã ở trong span font rồi, không cần làm gì thêm\n return;\n }\n\n let node = range.startContainer;\n let offset = range.startOffset;\n\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n\n const currentSpan = node.closest && node.closest('span');\n\n // Trường hợp 1: caret trong span rỗng chứa \\u200B\n if (currentSpan && currentSpan.textContent === \"\\u200B\") {\n currentSpan.style.fontFamily = font;\n return;\n }\n\n // Trường hợp 2: caret trong span có text thật\n if (currentSpan && currentSpan.firstChild && currentSpan.firstChild.nodeType === Node.TEXT_NODE) {\n const textNode = currentSpan.firstChild;\n const caretPos = range.startOffset;\n\n const textBefore = textNode.data.slice(0, caretPos);\n const textAfter = textNode.data.slice(caretPos);\n\n const parent = currentSpan.parentNode;\n\n if (caretPos === 0) {\n // Chèn span mới trước currentSpan\n const newSpan = document.createElement('span');\n newSpan.style.fontFamily = font;\n newSpan.appendChild(document.createTextNode('\\u200B'));\n parent.insertBefore(newSpan, currentSpan);\n moveCaretInside(newSpan);\n } else if (caretPos === textNode.data.length) {\n // Chèn span mới sau currentSpan\n const newSpan = document.createElement('span');\n newSpan.style.fontFamily = font;\n newSpan.appendChild(document.createTextNode('\\u200B'));\n parent.insertBefore(newSpan, currentSpan.nextSibling);\n moveCaretInside(newSpan);\n } else {\n // Tách thành 3 span\n const span1 = document.createElement('span');\n span1.style.fontFamily = currentSpan.style.fontFamily;\n span1.appendChild(document.createTextNode(textBefore));\n\n const span2 = document.createElement('span');\n span2.style.fontFamily = font;\n span2.appendChild(document.createTextNode('\\u200B'));\n\n const span3 = document.createElement('span');\n span3.style.fontFamily = currentSpan.style.fontFamily;\n span3.appendChild(document.createTextNode(textAfter));\n\n parent.insertBefore(span1, currentSpan);\n parent.insertBefore(span2, currentSpan);\n parent.insertBefore(span3, currentSpan);\n parent.removeChild(currentSpan);\n\n moveCaretInside(span2);\n }\n return;\n }\n\n // Trường hợp 3: không ở trong span nào → tạo mới\n const newSpan = document.createElement('span');\n newSpan.style.fontFamily = font;\n newSpan.appendChild(document.createTextNode('\\u200B'));\n range.insertNode(newSpan);\n moveCaretInside(newSpan);\n\n } else {\n // Có selection → áp dụng fontName\n execFormat('fontName', font);\n }\n}\n\n \n /**\n * Toggle font family format - shows/hides font picker\n */\n async toggle(anchorButton = null) {\n if (this.customSelect.isVisible) {\n this.customSelect.hide();\n } else {\n await this.showFontPicker(anchorButton);\n }\n }\n\n /**\n * Show custom select positioned relative to font family button on toolbar\n */\n async showFontPicker(anchorButton = null) {\n // Use provided anchor button or find the default toolbar button\n let fontFamilyButton = anchorButton;\n \n if (!fontFamilyButton) {\n // Find font-family button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n \n if (toolbar) {\n fontFamilyButton = toolbar.getButton('font-family');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!fontFamilyButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n fontFamilyButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.font-family-btn');\n }\n }\n \n // Final fallback: find any font-family button in the current editor's wrapper\n if (!fontFamilyButton) {\n fontFamilyButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.font-family-btn');\n }\n }\n \n if (!fontFamilyButton) return;\n \n // Update current selection before showing\n const currentFont = this.getCurrentFont();\n if (currentFont) {\n this.customSelect.setCurrentValue(currentFont);\n }\n \n await this.customSelect.show(fontFamilyButton);\n }\n\n /**\n * Check if font family format is active - always return false (no active state)\n * Only update button text to show current font\n * @param {string} font - Optional specific font to check\n * @returns {boolean}\n */\n isActive(font = null) {\n // Always update button text to show current font\n this.updateButtonText();\n \n // Never show active state for font family button\n return false;\n }\n\n /**\n * Get current font family of the selection\n * @returns {string|null} Current font family or null\n */\n getCurrentFont() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n let currentNode = range.startContainer;\n \n // If text node, get parent element\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentElement;\n }\n \n // Get the specific editor instance\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return 'Arial, sans-serif';\n \n // Check if the selection is within this editor\n if (!editor.editor.contains(currentNode) && !editor.editor.isSameNode(currentNode)) {\n // Selection is not in this editor, return default\n return 'Arial, sans-serif';\n }\n \n // Find element with font-family style\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const element = currentNode;\n \n // Priority 1: Check if this element has explicit inline font-family\n if (element.style.fontFamily) {\n return element.style.fontFamily;\n }\n \n // Priority 2: Check computed font-family\n const computedStyle = window.getComputedStyle(element);\n const fontFamily = computedStyle.fontFamily;\n if (fontFamily && fontFamily !== 'initial' && fontFamily !== 'inherit') {\n return fontFamily;\n }\n }\n currentNode = currentNode.parentElement;\n }\n\n // Default fallback\n return 'Arial, sans-serif';\n }\n\n /**\n * Set current font for future typing\n * @param {string} font - Font family value\n */\n setCurrentFont(font) {\n // Store for future typing operations\n this.currentFont = font;\n }\n}\n\nexport default FontFamily; ","import { InlineFormat } from '../core/format.js';\nimport CustomSelect from '../ui/customselect.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Line Height Format - Handles line height formatting\n * Now supports multiple editor instances with separate popup instances\n */\nclass LineHeight extends InlineFormat {\n static formatName = 'lineHeight';\n static tagName = 'SPAN';\n \n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for LineHeight format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a line height select instance\n let customSelect = currentEditor.getPopupInstance('line-height');\n \n if (!customSelect) {\n // Create new custom select instance for this editor\n const heightMap = LineHeight.getHeightMap();\n const items = Object.values(heightMap).map(heightData => ({\n value: heightData.height,\n label: heightData.element,\n title: heightData.title\n }));\n\n customSelect = new CustomSelect({\n items: items,\n displayProperty: 'label',\n valueProperty: 'value',\n className: 'line-height-select',\n onItemSelect: (value, item) => {\n LineHeight.applyLineHeightToCurrentSelection(value, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('line-height', customSelect);\n }\n \n this.customSelect = customSelect;\n \n // Set up event listener for selection changes\n this.setupSelectionListener();\n }\n\n /**\n * Create a new LineHeight format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {LineHeight} LineHeight format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new LineHeight();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Set up event listener for selection changes to update button text\n */\n setupSelectionListener() {\n // Use a debounced function to avoid too many updates\n let updateTimeout;\n const debouncedUpdate = () => {\n clearTimeout(updateTimeout);\n updateTimeout = setTimeout(() => {\n // Only update if selection is in this editor\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n const range = selection.getRangeAt(0);\n const editor = Editor.getInstanceById(this.editorId);\n if (editor && (editor.editor.contains(range.startContainer) || editor.editor.isSameNode(range.startContainer))) {\n this.updateButtonText();\n }\n }\n }, 50); // 50ms delay\n };\n\n // Listen for selection changes\n document.addEventListener('selectionchange', debouncedUpdate);\n \n // Also listen for mouseup and keyup events for immediate feedback\n document.addEventListener('mouseup', debouncedUpdate);\n document.addEventListener('keyup', debouncedUpdate);\n \n // Store the listener for cleanup\n this.selectionListener = debouncedUpdate;\n }\n\n /**\n * Get height map with different line heights\n */\n static getHeightMap() {\n return {\n '1.0': { \n height: '1', \n element: '<span>1.0</span>', \n title: '1.0' \n },\n '1.2': { \n height: '1.2', \n element: '<span>1.2</span>', \n title: '1.2' \n },\n '1.5': { \n height: '1.5', \n element: '<span>1.5</span>', \n title: '1.5' \n },\n '1.8': { \n height: '1.8', \n element: '<span>1.8</span>', \n title: '1.8' \n },\n '2.0': { \n height: '2', \n element: '<span>2.0</span>', \n title: '2.0' \n },\n '2.5': { \n height: '2.5', \n element: '<span>2.5</span>', \n title: '2.5' \n },\n '3.0': { \n height: '3', \n element: '<span>3.0</span>', \n title: '3.0' \n }\n };\n }\n\n /**\n * Get display name for line height\n * @param {string} height - Line height value\n * @returns {string} Display name\n */\n static getHeightDisplayName(height) {\n const heightMap = this.getHeightMap();\n if (heightMap[height]?.title) return heightMap[height].title;\n // Always show the applied value rather than a placeholder\n const num = parseFloat(height);\n if (!isNaN(num)) return String(num);\n return 'Normal';\n }\n\n /**\n * Update custom button text based on current line height\n */\n updateButtonText() {\n const currentHeight = this.getCurrentHeight();\n const displayName = LineHeight.getHeightDisplayName(currentHeight || '1.15');\n \n // Find line-height button in the specific editor's toolbar using editorId\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let lineHeightButton = null;\n \n if (toolbar) {\n lineHeightButton = toolbar.getButton('line-height');\n }\n \n // Fallback: find button by class in the specific editor's toolbar\n if (!lineHeightButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n lineHeightButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.line-height-btn');\n }\n }\n \n // Final fallback: find any line-height button in the specific editor's wrapper\n if (!lineHeightButton) {\n lineHeightButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.line-height-btn');\n }\n \n if (lineHeightButton && lineHeightButton.updateText) {\n lineHeightButton.updateText(displayName);\n } else if (lineHeightButton) {\n lineHeightButton.textContent = displayName;\n }\n }\n\n /**\n * Create element with specific line height\n * @param {string} height - Line height value\n * @returns {HTMLElement}\n */\n static create(height = '1.15') {\n const node = document.createElement('span');\n node.style.lineHeight = height;\n return node;\n }\n\n /**\n * Static method to apply line height to current selection\n * @param {string} height - Line height value\n * @param {string} editorId - Editor instance ID\n */\n static applyLineHeightToCurrentSelection(height, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for line height application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n const lineHeightFormat = LineHeight.createForEditor(editorId);\n if (lineHeightFormat) {\n lineHeightFormat.apply(height);\n \n // Update button text after applying\n lineHeightFormat.updateButtonText();\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply line height format with specified height\n * @param {string} height - Line height value\n */\n apply(height = '1.15') {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n // Hàm đặt caret vào bên trong span mới\n function moveCaretInside(el) {\n const sel = window.getSelection();\n const range = document.createRange();\n const textNode = el.firstChild;\n range.setStart(textNode, textNode.length);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n // Save state before applying format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n \n if (range.collapsed) {\n // No selection - set style for future typing\n let node = range.startContainer;\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n\n // Tìm phần tử block cha gần nhất (div, p, li, ...)\n const blockParent = node.closest('div, p, li, section, article') || node;\n blockParent.style.lineHeight = height;\n moveCaretInside(blockParent);\n \n return;\n }\n\n // Apply to block elements if possible for better line height effect\n const blockElements = this.getBlockElementsInRange(range);\n \n if (blockElements.length > 0) {\n // Apply to block elements\n blockElements.forEach(block => {\n block.style.lineHeight = height;\n });\n } else {\n // Fallback: wrap in span with line-height\n const heightSpan = this.constructor.create(height);\n \n try {\n const contents = range.extractContents();\n heightSpan.appendChild(contents);\n range.insertNode(heightSpan);\n \n // Select the content in the span\n const newRange = document.createRange();\n newRange.selectNodeContents(heightSpan);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } catch (error) {\n console.warn('Failed to apply line height manually:', error);\n }\n }\n }\n\n /**\n * Get block elements within the range\n * @param {Range} range - Selection range\n * @returns {Array} Array of block elements\n */\n getBlockElementsInRange(range) {\n const blockElements = [];\n const blockTags = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI'];\n \n // Create a fragment of the selection\n const fragment = range.cloneContents();\n \n // Get all potential block elements in the fragment\n const walker = document.createTreeWalker(\n fragment,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node) => {\n return blockTags.includes(node.tagName) ? \n NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;\n }\n }\n );\n\n // Get corresponding elements from the actual document\n let node = walker.nextNode();\n while (node) {\n // Find the actual element in the document that corresponds to this fragment node\n const actualNode = range.commonAncestorContainer.querySelector(\n `${node.tagName.toLowerCase()}:not([data-processed])`\n );\n if (actualNode && range.intersectsNode(actualNode)) {\n blockElements.push(actualNode);\n // Mark as processed to avoid duplicates\n actualNode.setAttribute('data-processed', 'true');\n }\n node = walker.nextNode();\n }\n\n // Clean up the temporary attribute\n blockElements.forEach(el => el.removeAttribute('data-processed'));\n\n // If no block elements found in selection, get the closest parent block element\n if (blockElements.length === 0) {\n let currentNode = range.startContainer;\n \n // If text node, get parent element\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentElement;\n }\n \n // Find parent block element\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE && \n blockTags.includes(currentNode.tagName)) {\n blockElements.push(currentNode);\n break;\n }\n currentNode = currentNode.parentElement;\n }\n }\n \n return blockElements;\n }\n\n /**\n * Toggle line height format - shows/hides height picker\n */\n async toggle() {\n if (this.customSelect.isVisible) {\n this.customSelect.hide();\n } else {\n await this.showHeightPicker();\n }\n }\n\n /**\n * Show custom select positioned relative to line height button on toolbar\n */\n async showHeightPicker() {\n // Find line-height button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let lineHeightButton = null;\n \n if (toolbar) {\n lineHeightButton = toolbar.getButton('line-height');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!lineHeightButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n lineHeightButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.line-height-btn');\n }\n }\n \n // Final fallback: find any line-height button in the current editor's wrapper\n if (!lineHeightButton) {\n lineHeightButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.line-height-btn');\n }\n \n if (!lineHeightButton) {\n console.warn('Line-height button not found for editor:', this.editorId);\n return;\n }\n \n // Update current selection before showing\n const currentHeight = this.getCurrentHeight();\n if (currentHeight) {\n this.customSelect.setCurrentValue(currentHeight);\n }\n \n await this.customSelect.show(lineHeightButton);\n }\n\n /**\n * Check if line height format is active - always return false (no active state)\n * Only update button text to show current height\n * @param {string} height - Optional specific height to check\n * @returns {boolean}\n */\n isActive(height = null) {\n // Always update button text to show current height\n this.updateButtonText();\n \n // Never show active state for line height button\n return false;\n }\n\n /**\n * Get current line height of the selection\n * @returns {string|null} Current line height or null\n */\n getCurrentHeight() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n let currentNode = range.startContainer;\n \n // If text node, get parent element\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentElement;\n }\n \n // Get the specific editor instance\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return '1.15';\n \n // Check if the selection is within this editor\n if (!editor.editor.contains(currentNode) && !editor.editor.isSameNode(currentNode)) {\n // Selection is not in this editor, return default\n return '1.15';\n }\n \n // Find element with line-height style\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const element = currentNode;\n \n // Priority 1: Check if this element has explicit inline line-height\n if (element.style.lineHeight) {\n const height = element.style.lineHeight;\n return this.normalizeHeightValue(height);\n }\n \n // Priority 2: Check computed line-height\n const computedStyle = window.getComputedStyle(element);\n const lineHeight = computedStyle.lineHeight;\n \n if (lineHeight && lineHeight !== 'normal' && lineHeight !== 'initial' && lineHeight !== 'inherit') {\n // Convert pixel values to relative values if possible\n if (lineHeight.endsWith('px')) {\n const fontSize = parseFloat(computedStyle.fontSize);\n const lineHeightPx = parseFloat(lineHeight);\n if (fontSize > 0) {\n const relative = (lineHeightPx / fontSize).toFixed(2);\n return this.normalizeHeightValue(relative);\n }\n }\n return this.normalizeHeightValue(lineHeight);\n }\n }\n currentNode = currentNode.parentElement;\n }\n\n // Default fallback\n return '1.15';\n }\n\n /**\n * Normalize height value to match heightMap keys\n * @param {string} height - Raw height value\n * @returns {string} Normalized height value\n */\n normalizeHeightValue(height) {\n if (!height) return '1.15';\n \n // Convert to number and back to string to normalize\n const numValue = parseFloat(height);\n if (isNaN(numValue)) return '1.15';\n \n // Round to 1 decimal place and convert back to string\n const normalized = numValue.toFixed(1);\n \n // Check if this normalized value exists in our heightMap\n const heightMap = this.constructor.getHeightMap();\n if (heightMap[normalized]) {\n return normalized;\n }\n \n // If not in map, return the original value\n return height;\n }\n\n\n /**\n * Clean up event listeners to prevent memory leaks\n */\n destroy() {\n if (this.selectionListener) {\n document.removeEventListener('selectionchange', this.selectionListener);\n document.removeEventListener('mouseup', this.selectionListener);\n document.removeEventListener('keyup', this.selectionListener);\n this.selectionListener = null;\n }\n }\n}\n\nexport default LineHeight; ","import { InlineFormat } from '../core/format.js';\nimport CustomSelect from '../ui/customselect.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Capitalization Format - Handles text capitalization\n * Now supports multiple editor instances with separate popup instances\n */\nclass Capitalization extends InlineFormat {\n static formatName = 'capitalization';\n static tagName = 'SPAN';\n \n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Capitalization format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a capitalization select instance\n let customSelect = currentEditor.getPopupInstance('capitalization');\n \n if (!customSelect) {\n // Create new custom select instance for this editor\n const capMap = Capitalization.getCapitalizationMap();\n const items = Object.values(capMap).map(capData => ({\n value: capData.style,\n label: capData.element,\n title: capData.title\n }));\n\n customSelect = new CustomSelect({\n items: items,\n displayProperty: 'label',\n valueProperty: 'value',\n className: 'capitalization-select',\n onItemSelect: (value, item) => {\n Capitalization.applyCapitalizationToCurrentSelection(value, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('capitalization', customSelect);\n }\n \n this.customSelect = customSelect;\n }\n\n /**\n * Create a new Capitalization format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Capitalization} Capitalization format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Capitalization();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Get capitalization map with different text transformations\n */\n static getCapitalizationMap() {\n return {\n 'capitalize': { \n style: 'capitalize', \n element: '<span>Capitalize</span>', \n title: 'Capitalize' \n },\n 'uppercase': { \n style: 'uppercase', \n element: '<span>UPPERCASE</span>', \n title: 'UPPERCASE' \n },\n 'lowercase': { \n style: 'lowercase', \n element: '<span>lowercase</span>', \n title: 'lowercase' \n },\n 'small-caps': { \n style: 'small-caps', \n element: '<span>Small Caps</span>', \n title: 'Small Caps' \n }\n };\n }\n\n /**\n * Get display name for capitalization\n * @param {string} style - Text transform value\n * @returns {string} Display name\n */\n static getCapitalizationDisplayName(style) {\n const capMap = this.getCapitalizationMap();\n return capMap[style]?.title || 'Capitalization';\n }\n\n /**\n * Update custom button text based on current capitalization\n */\n updateButtonText() {\n const currentCap = this.getCurrentCapitalization();\n const displayName = Capitalization.getCapitalizationDisplayName(currentCap || 'none');\n \n // Find capitalization button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let capitalizationButton = null;\n \n if (toolbar) {\n capitalizationButton = toolbar.getButton('capitalization');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!capitalizationButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n capitalizationButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.capitalization-btn');\n }\n }\n \n // Final fallback: find any capitalization button in the current editor's wrapper\n if (!capitalizationButton) {\n capitalizationButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.capitalization-btn');\n }\n \n if (capitalizationButton && capitalizationButton.updateText) {\n capitalizationButton.updateText(displayName);\n } else if (capitalizationButton) {\n capitalizationButton.textContent = displayName;\n }\n }\n\n /**\n * Create element with specific text transformation\n * @param {string} style - Text transform value\n * @returns {HTMLElement}\n */\n static create(style = 'none') {\n const node = document.createElement('span');\n if (style === 'small-caps') {\n node.style.fontVariant = 'small-caps';\n } else {\n node.style.textTransform = style;\n }\n return node;\n }\n\n /**\n * Static method to apply capitalization to current selection\n * @param {string} style - Text transform value\n * @param {string} editorId - Editor instance ID\n */\n static applyCapitalizationToCurrentSelection(style, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for capitalization application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n const capFormat = Capitalization.createForEditor(editorId);\n if (capFormat) {\n capFormat.apply(style);\n \n // Update button text after applying\n capFormat.updateButtonText();\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Check if an element has capitalization-related inline or computed styles\n * @param {Element} element\n * @returns {boolean}\n */\n hasCapitalizationStyling(element) {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;\n if (element.style.fontVariant === 'small-caps') return true;\n if (element.style.textTransform && element.style.textTransform !== 'none') return true;\n const computed = window.getComputedStyle(element);\n if (computed.fontVariant === 'small-caps') return true;\n if (computed.textTransform && computed.textTransform !== 'none') return true;\n return false;\n }\n\n /**\n * Determine whether an element is our capitalization wrapper (inline styles only)\n * @param {Element} element\n * @returns {boolean}\n */\n isCapitalizationElement(element) {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;\n if (element.style.fontVariant === 'small-caps') return true;\n if (element.style.textTransform && element.style.textTransform !== 'none') return true;\n return false;\n }\n\n /**\n * Find nearest ancestor element that is a capitalization wrapper\n * @param {Node} node\n * @returns {Element|null}\n */\n findAncestorCapitalizationElement(node) {\n let current = node;\n if (!current) return null;\n if (current.nodeType === Node.TEXT_NODE) current = current.parentElement;\n while (current && current !== document.body) {\n if (this.isCapitalizationElement(current)) return current;\n current = current.parentElement;\n }\n return null;\n }\n\n /**\n * Apply capitalization style directly to an element\n * @param {Element} element\n * @param {string} style\n */\n setElementCapitalizationStyle(element, style) {\n if (!element) return;\n if (style === 'small-caps') {\n element.style.fontVariant = 'small-caps';\n element.style.textTransform = '';\n } else if (style === 'none') {\n element.style.fontVariant = '';\n element.style.textTransform = '';\n } else {\n element.style.fontVariant = '';\n element.style.textTransform = style;\n }\n }\n\n /**\n * Apply capitalization format with specified style\n * @param {string} style - Text transform value\n */\n apply(style = 'none') {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Lưu trạng thái trước khi format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n if (range.collapsed) return;\n\n // Small Caps is a visual style (not a one-way text transform): wrap the\n // selected text in a span so it can render — and be toggled off — properly.\n if (style === 'small-caps') {\n const frag = range.extractContents();\n const span = document.createElement('span');\n span.style.fontVariant = 'small-caps';\n span.appendChild(frag);\n range.insertNode(span);\n const r = document.createRange();\n r.selectNodeContents(span);\n selection.removeAllRanges();\n selection.addRange(r);\n return;\n }\n\n // Hàm đổi chữ theo style\n function transformText(text, style) {\n switch (style) {\n case 'uppercase':\n return text.toUpperCase();\n case 'lowercase':\n return text.toLowerCase();\n case 'capitalize':\n text =text.toLowerCase();\n return text.replace(/\\b\\w/g, char => char.toUpperCase());\n default:\n return text; // 'none' hoặc không đổi\n }\n }\n function removeEmptyElements(node) {\n if (!node) return;\n\n // Duyệt cây DOM từ node này xuống\n const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT, null);\n const toRemove = [];\n\n while (walker.nextNode()) {\n const el = walker.currentNode;\n // Nếu không có text hoặc chỉ toàn khoảng trắng & không có element con\n if (!el.textContent.trim() && el.childElementCount === 0) {\n toRemove.push(el);\n }\n }\n\n toRemove.forEach(el => el.remove());\n }\n\n // Nếu có selection: đổi text bên trong\n const contents = range.extractContents();\n const walker = document.createTreeWalker(contents, NodeFilter.SHOW_TEXT, null);\n\n while (walker.nextNode()) {\n const textNode = walker.currentNode;\n textNode.textContent = transformText(textNode.textContent, style);\n }\n\n range.deleteContents();\n range.insertNode(contents);\n removeEmptyElements(range.commonAncestorContainer);\n\n // Giữ nguyên selection\n selection.removeAllRanges();\n selection.addRange(range);\n }\n\n /**\n * Remove existing capitalization formatting from range\n * @param {Range} range - Selection range\n */\n removeExistingCapitalization(range) {\n const root = range.commonAncestorContainer;\n const elementsToProcess = new Set();\n\n // Helper to maybe add element\n const maybeAdd = (el) => {\n if (el && el.nodeType === Node.ELEMENT_NODE && this.hasCapitalizationStyling(el) && range.intersectsNode(el)) {\n elementsToProcess.add(el);\n }\n };\n\n // Include the root if applicable\n if (root && root.nodeType === Node.ELEMENT_NODE) {\n maybeAdd(root);\n }\n\n // Walk descendants\n const walker = document.createTreeWalker(\n root,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node) => (range.intersectsNode(node) && this.hasCapitalizationStyling(node)) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP\n }\n );\n let node;\n while ((node = walker.nextNode())) {\n elementsToProcess.add(node);\n }\n\n // Include ancestors from start and end containers\n const addAncestors = (startNode) => {\n let current = startNode.nodeType === Node.TEXT_NODE ? startNode.parentElement : startNode;\n while (current && current !== document.body) {\n maybeAdd(current);\n current = current.parentElement;\n }\n };\n addAncestors(range.startContainer);\n addAncestors(range.endContainer);\n\n Array.from(elementsToProcess).forEach(element => {\n // Clear text transform and font variant styles\n element.style.textTransform = '';\n element.style.fontVariant = '';\n \n // If element has no other styles, unwrap it\n if (!element.style.cssText.trim() && !element.className) {\n this.unwrapElement(element);\n }\n });\n }\n\n /**\n * Unwrap an element, moving its children to its parent\n * @param {Element} element - Element to unwrap\n */\n unwrapElement(element) {\n const parent = element.parentNode;\n if (!parent) return;\n\n while (element.firstChild) {\n parent.insertBefore(element.firstChild, element);\n }\n parent.removeChild(element);\n }\n\n /**\n * Toggle capitalization format - shows/hides capitalization picker\n */\n async toggle() {\n if (this.customSelect.isVisible) {\n this.customSelect.hide();\n } else {\n await this.showCapitalizationPicker();\n }\n }\n\n /**\n * Show custom select positioned relative to capitalization button on toolbar\n */\n async showCapitalizationPicker() {\n // Find capitalization button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let capitalizationButton = null;\n \n if (toolbar) {\n capitalizationButton = toolbar.getButton('capitalization');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!capitalizationButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n capitalizationButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.capitalization-btn');\n }\n }\n \n // Final fallback: find any capitalization button in the current editor's wrapper\n if (!capitalizationButton) {\n capitalizationButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.capitalization-btn');\n }\n \n if (!capitalizationButton) {\n console.warn('Capitalization button not found for editor:', this.editorId);\n return;\n }\n \n // Update current selection before showing\n const currentCap = this.getCurrentCapitalization();\n if (currentCap) {\n this.customSelect.setCurrentValue(currentCap);\n }\n \n await this.customSelect.show(capitalizationButton);\n }\n\n /**\n * Check if capitalization format is active - always return false (no active state)\n * Only update button text to show current capitalization\n * @param {string} style - Optional specific style to check\n * @returns {boolean}\n */\n isActive(style = null) {\n // Always update button text to show current capitalization\n this.updateButtonText();\n \n // Never show active state for capitalization button\n return false;\n }\n\n /**\n * Get current capitalization of the selection\n * @returns {string|null} Current text transform or null\n */\n getCurrentCapitalization() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n let currentNode = range.startContainer;\n \n // If text node, get parent element\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentElement;\n }\n \n // Find element with text-transform or font-variant style\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const element = currentNode;\n \n // Priority 1: Check if this element has explicit inline styles\n if (element.style.fontVariant === 'small-caps') {\n return 'small-caps';\n }\n if (element.style.textTransform && element.style.textTransform !== 'none') {\n return element.style.textTransform;\n }\n \n // Priority 2: Check computed styles\n const computedStyle = window.getComputedStyle(element);\n if (computedStyle.fontVariant === 'small-caps') {\n return 'small-caps';\n }\n if (computedStyle.textTransform && computedStyle.textTransform !== 'none') {\n return computedStyle.textTransform;\n }\n }\n currentNode = currentNode.parentElement;\n }\n\n // Default fallback\n return 'none';\n }\n\n /**\n * Set current capitalization for future typing\n * @param {string} style - Text transform value\n */\n setCurrentCapitalization(style) {\n // Store for future typing operations\n this.currentCapitalization = style;\n }\n\n /**\n * Quick toggle methods for common capitalizations\n */\n static toggleUppercase() {\n const cap = new Capitalization();\n const current = cap.getCurrentCapitalization();\n const newStyle = current === 'uppercase' ? 'none' : 'uppercase';\n Capitalization.applyCapitalizationToCurrentSelection(newStyle);\n }\n\n static toggleLowercase() {\n const cap = new Capitalization();\n const current = cap.getCurrentCapitalization();\n const newStyle = current === 'lowercase' ? 'none' : 'lowercase';\n Capitalization.applyCapitalizationToCurrentSelection(newStyle);\n }\n\n static toggleCapitalize() {\n const cap = new Capitalization();\n const current = cap.getCurrentCapitalization();\n const newStyle = current === 'capitalize' ? 'none' : 'capitalize';\n Capitalization.applyCapitalizationToCurrentSelection(newStyle);\n }\n\n static toggleSmallCaps() {\n const cap = new Capitalization();\n const current = cap.getCurrentCapitalization();\n const newStyle = current === 'small-caps' ? 'none' : 'small-caps';\n Capitalization.applyCapitalizationToCurrentSelection(newStyle);\n }\n}\n\nexport default Capitalization; ","import IconUtils from './icons.js';\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\n/**\n * Text Align Picker Component - Popup for selecting text alignment\n */\nclass TextAlignPicker {\n constructor(options = {}) {\n this.options = {\n alignments: [\n { value: 'left', label: 'Align Left', icon: 'align-left' },\n { value: 'center', label: 'Align Center', icon: 'align-center' },\n { value: 'right', label: 'Align Right', icon: 'align-right' },\n { value: 'justify', label: 'Justify', icon: 'align-justify' }\n ],\n onAlignSelect: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.currentAlignment = 'left';\n this.clickOutsideHandler = null;\n \n this.createAlignPicker();\n }\n\n /**\n * Create text align picker popup\n */\n createAlignPicker() {\n // Create popup\n this.popup = document.createElement('div');\n this.popup.className = 'text-align-picker-popup';\n \n // Create alignment buttons\n this.createAlignmentButtons();\n \n // Add popup to container\n appendPopup(this.popup);\n }\n\n /**\n * Create alignment buttons\n */\n async createAlignmentButtons() {\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'align-button-container';\n \n // Icons are now inline, no need to preload\n \n // Create buttons\n for (const alignment of this.options.alignments) {\n const alignButton = document.createElement('button');\n alignButton.type = 'button';\n alignButton.className = 'align-button';\n alignButton.dataset.alignment = alignment.value;\n alignButton.title = alignment.label;\n \n // Icon only — the label lives in the tooltip (title) above.\n const iconSvg = IconUtils.getIcon(alignment.icon);\n if (iconSvg) {\n alignButton.innerHTML = `<span class=\"icon-wrapper\">${iconSvg}</span>`;\n } else {\n alignButton.textContent = alignment.label.charAt(0);\n }\n \n alignButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectAlignment(alignment.value);\n });\n \n buttonContainer.appendChild(alignButton);\n }\n \n this.popup.appendChild(buttonContainer);\n }\n\n /**\n * Setup click outside handler\n */\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n // Add slight delay to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n /**\n * Remove click outside handler\n */\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n /**\n * Show text align picker popup\n * @param {HTMLElement} anchor - Element to position popup relative to\n */\n show(anchor) {\n if (!anchor) return;\n \n // Ensure popup is in DOM\n if (!document.body.contains(this.popup)) {\n appendPopup(this.popup);\n }\n \n // Update current alignment state\n this.updateCurrentAlignment();\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup by adding visible class\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Setup click outside handler\n this.setupClickOutside();\n }\n\n /**\n * Hide text align picker popup\n */\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n /**\n * Select alignment and trigger callback\n * @param {string} alignment - Selected alignment\n */\n selectAlignment(alignment) {\n this.currentAlignment = alignment;\n \n if (this.options.onAlignSelect) {\n this.options.onAlignSelect(alignment);\n }\n \n this.hide();\n }\n\n /**\n * Update current alignment state based on selection\n */\n updateCurrentAlignment() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n try {\n const range = selection.getRangeAt(0);\n\n // Lấy tất cả block trong vùng chọn\n const blocks = this.getSelectedBlockElements(range);\n\n // Nếu có block → lấy block đầu tiên, nếu không thì fallback về block bao quanh\n const firstBlock = blocks.length > 0\n ? blocks[0]\n : this.getBlockElement(range.commonAncestorContainer);\n\n if (firstBlock) {\n const textAlign = window.getComputedStyle(firstBlock).textAlign;\n this.currentAlignment =\n textAlign === 'left' || textAlign === 'start' || !textAlign\n ? 'left'\n : textAlign;\n } else {\n this.currentAlignment = 'left';\n }\n\n // Cập nhật trạng thái nút trong popup\n const buttons = this.popup.querySelectorAll('.align-button');\n buttons.forEach(button => {\n if (button.dataset.alignment === this.currentAlignment) {\n button.classList.add('active');\n } else {\n button.classList.remove('active');\n }\n });\n\n // Cập nhật icon trên toolbar\n this.updateToolbarButtonIcon(this.currentAlignment);\n } catch (error) {\n console.error('Error updating current alignment:', error);\n }\n }\n\n /**\n * Lấy tất cả block element trong vùng chọn\n */\n getSelectedBlockElements(range) {\n const blocks = [];\n const startBlock = this.getBlockElement(range.startContainer);\n const endBlock = this.getBlockElement(range.endContainer);\n\n if (startBlock) blocks.push(startBlock);\n\n if (startBlock && endBlock && startBlock !== endBlock) {\n let current = startBlock;\n while (current && current !== endBlock) {\n current = current.nextElementSibling;\n if (current && this.getBlockElement(current) && !blocks.includes(current)) {\n blocks.push(current);\n }\n }\n if (endBlock && !blocks.includes(endBlock)) {\n blocks.push(endBlock);\n }\n }\n\n return blocks;\n }\n\n /**\n * Update toolbar button icon based on alignment\n * @param {string} alignment - Current alignment\n */\n updateToolbarButtonIcon(alignment) {\n // Import TextAlign class to use its static method\n import('../formats/text-align.js').then(module => {\n const TextAlign = module.default;\n TextAlign.updateToolbarButtonIcon(alignment);\n }).catch(error => {\n console.warn('Could not import TextAlign class:', error);\n });\n }\n\n /**\n * Get the block element containing the given node\n */\n getBlockElement(node) {\n if (!node) return null;\n \n let currentNode = node;\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.parentNode;\n }\n return null;\n }\n\n /**\n * Destroy text align picker\n */\n destroy() {\n this.removeClickOutside();\n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n }\n}\n\nexport default TextAlignPicker; ","import { BlockFormat } from '../core/format.js';\nimport TextAlignPicker from '../ui/text-align-picker.js';\nimport IconUtils from '../ui/icons.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\nimport { execFormat } from '../utils/exec-command.js';\n\n/**\n * Text Align Format - Handles text alignment formatting\n * Now supports multiple editor instances with separate popup instances\n */\nclass TextAlign extends BlockFormat {\n static formatName = 'text-align';\n static tagName = 'P';\n static attribute = 'style';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for TextAlign format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a text align picker instance\n let alignPicker = currentEditor.getPopupInstance('text-align');\n \n if (!alignPicker) {\n // Create new text align picker instance for this editor\n alignPicker = new TextAlignPicker({\n onAlignSelect: (alignment) => {\n TextAlign.applyAlignToCurrentSelection(alignment, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('text-align', alignPicker);\n }\n \n this.alignPicker = alignPicker;\n }\n\n /**\n * Create a new TextAlign format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {TextAlign} TextAlign format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new TextAlign();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create block element with text alignment\n * @param {string} value - Alignment value (left, center, right, justify)\n * @returns {HTMLElement}\n */\n static create(value) {\n const node = document.createElement(this.tagName);\n if (value && value !== 'left') {\n node.style.textAlign = value;\n }\n return node;\n }\n\n /**\n * Static method to apply alignment to current selection or cursor position\n * @param {string} alignment - Alignment value\n * @param {string} editorId - Editor instance ID\n */\n static applyAlignToCurrentSelection(alignment, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for text alignment application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n // Lưu vị trí caret trước khi thay đổi\n const originalRange = selection.getRangeAt(0);\n const caretContainer = originalRange.endContainer;\n const caretOffset = originalRange.endOffset;\n try {\n const range = selection.getRangeAt(0);\n const blockElements = TextAlign.getSelectedBlockElements(range);\n \n if (blockElements.length === 0) {\n // If no block elements found, create one\n execFormat('formatBlock', 'p');\n const newRange = selection.getRangeAt(0);\n const newBlocks = TextAlign.getSelectedBlockElements(newRange);\n newBlocks.forEach(block => {\n TextAlign.applyAlignmentToBlock(block, alignment);\n });\n } else {\n // Apply alignment to existing blocks\n blockElements.forEach(block => {\n TextAlign.applyAlignmentToBlock(block, alignment);\n });\n }\n \n // Update toolbar button icon after applying alignment\n TextAlign.updateToolbarButtonIcon(alignment, editorId);\n // Khôi phục caret\n selection.removeAllRanges();\n const newCaretRange = document.createRange();\n newCaretRange.setStart(caretContainer, caretOffset);\n newCaretRange.collapse(true);\n selection.addRange(newCaretRange);\n\n } catch (error) {\n console.error('Error applying text alignment:', error);\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply alignment to a specific block element\n */\n static applyAlignmentToBlock(block, alignment) {\n if (alignment === 'left') {\n block.style.textAlign = '';\n } else {\n block.style.textAlign = alignment;\n }\n }\n\n /**\n * Get icon name for alignment value\n * @param {string} alignment - Alignment value\n * @returns {string} Icon name\n */\n static getIconNameForAlignment(alignment) {\n const iconMap = {\n 'left': 'align-left',\n 'center': 'align-center',\n 'right': 'align-right',\n 'justify': 'align-justify'\n };\n return iconMap[alignment] || 'align-center';\n }\n\n /**\n * Update toolbar button icon based on alignment\n * @param {string} alignment - Current alignment\n * @param {string} editorId - Editor instance ID\n */\n static updateToolbarButtonIcon(alignment, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let button = null;\n \n if (toolbar) {\n button = toolbar.getButton('text-align');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!button) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n button = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-align-btn');\n }\n }\n \n // Final fallback: find any text-align button in the current editor's wrapper\n if (!button) {\n button = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-align-btn');\n }\n \n if (!button) return;\n\n const iconName = TextAlign.getIconNameForAlignment(alignment);\n const titleMap = {\n 'left': 'Align Left',\n 'center': 'Align Center', \n 'right': 'Align Right',\n 'justify': 'Justify'\n };\n \n // Update button title\n button.title = titleMap[alignment] || 'Text Alignment';\n \n // Update icon\n const svgContent = IconUtils.getIcon(iconName);\n if (svgContent) {\n const iconSpan = button.querySelector('.icon');\n if (iconSpan) {\n iconSpan.innerHTML = svgContent;\n } else {\n button.innerHTML = `<span class=\"icon\">${svgContent}</span>`;\n }\n }\n }\n\n /**\n * Get all selected block elements\n */\n static getSelectedBlockElements(range) {\n const blocks = [];\n\n // Xác định block chứa điểm bắt đầu và kết thúc\n const startBlock = TextAlign.getBlockElement(range.startContainer);\n const endBlock = TextAlign.getBlockElement(range.endContainer);\n\n if (!startBlock || !endBlock) return blocks;\n\n // Nếu chỉ trong 1 block\n if (startBlock === endBlock) {\n blocks.push(startBlock);\n return blocks;\n }\n\n // Duyệt từ startBlock tới endBlock\n let currentBlock = startBlock;\n while (currentBlock) {\n // Chỉ thêm block nếu nó giao với range\n const blockRange = document.createRange();\n blockRange.selectNodeContents(currentBlock);\n\n if (range.compareBoundaryPoints(Range.END_TO_START, blockRange) < 0 &&\n range.compareBoundaryPoints(Range.START_TO_END, blockRange) > 0) {\n blocks.push(currentBlock);\n }\n\n if (currentBlock === endBlock) break;\n currentBlock = TextAlign.getNextBlockElement(currentBlock);\n }\n\n return blocks;\n}\n\n /**\n * Get the block element containing the given node\n */\n static getBlockElement(node) {\n if (!node) return null;\n \n let currentNode = node;\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.parentNode;\n }\n return null;\n }\n\n /**\n * Get next block element in document order\n */\n static getNextBlockElement(element) {\n let currentNode = element.nextSibling;\n \n while (currentNode) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.nextSibling;\n }\n \n return null;\n }\n\n /**\n * Apply alignment formatting with specified value\n * @param {string} value - Alignment value (left, center, right, justify)\n */\n apply(value = 'left') {\n TextAlign.applyAlignToCurrentSelection(value, this.editorId);\n }\n\n /**\n * Remove alignment formatting (reset to left)\n */\n remove() {\n TextAlign.applyAlignToCurrentSelection('left', this.editorId);\n }\n\n /**\n * Toggle alignment formatting - shows/hides alignment picker\n */\n toggle() {\n if (this.alignPicker.isVisible) {\n this.alignPicker.hide();\n } else {\n this.showAlignPicker();\n }\n }\n\n /**\n * Show alignment picker positioned relative to align button on toolbar\n */\n showAlignPicker() {\n // Find text-align button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let alignButton = null;\n \n if (toolbar) {\n alignButton = toolbar.getButton('text-align');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!alignButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n alignButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-align-btn');\n }\n }\n \n // Final fallback: find any text-align button in the current editor's wrapper\n if (!alignButton) {\n alignButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-align-btn');\n }\n \n if (!alignButton) {\n console.warn('Text-align button not found for editor:', this.editorId);\n return;\n }\n \n this.alignPicker.show(alignButton);\n }\n\n /**\n * Check if specific alignment is active in current selection\n * Always returns false because text-align button should not have active state\n * Instead, the button icon changes to reflect current alignment\n */\n isActive(alignment = null) {\n // Update button icon based on current alignment\n const currentAlignment = TextAlign.getCurrentAlignment();\n TextAlign.updateToolbarButtonIcon(currentAlignment, this.editorId);\n\n // Highlight when a non-default alignment (center / right / justify) is set.\n return !!currentAlignment && currentAlignment !== 'left' && currentAlignment !== 'start';\n }\n\n /**\n * Get current alignment of selection\n */\n static getCurrentAlignment() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return 'left';\n\n try {\n const range = selection.getRangeAt(0);\n // Lấy tất cả block trong vùng chọn\n const blocks = TextAlign.getSelectedBlockElements(range);\n\n // Nếu có nhiều block -> lấy block đầu tiên\n const firstBlock = blocks.length > 0 \n ? blocks[0] \n : TextAlign.getBlockElement(range.commonAncestorContainer);\n\n if (!firstBlock) return 'left';\n\n const textAlign = window.getComputedStyle(firstBlock).textAlign;\n return textAlign === 'left' || textAlign === 'start' || !textAlign ? 'left' : textAlign;\n } catch (error) {\n console.error('Error getting current alignment:', error);\n return 'left';\n }\n}\n\n}\n\nexport default TextAlign; ","import IconUtils from './icons.js';\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\n/**\n * List Picker Component - Popup for selecting list types\n */\nclass ListPicker {\n constructor(options = {}) {\n this.options = {\n listTypes: [\n { value: 'bullet', label: 'Bullet List', icon: 'list-bullet' },\n { value: 'ordered', label: 'Numbered List', icon: 'list-ordered' },\n { value: 'roman', label: 'Roman Numerals List', icon: 'list-roman' },\n { value: 'alpha', label: 'Alphabetical List', icon: 'list-alpha' }\n ],\n onListSelect: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.currentListType = null;\n this.clickOutsideHandler = null;\n \n this.createListPicker();\n }\n\n /**\n * Create list picker popup\n */\n createListPicker() {\n // Create popup\n this.popup = document.createElement('div');\n this.popup.className = 'list-picker-popup';\n \n // Create list type buttons\n this.createListTypeButtons();\n \n // Add popup to container\n appendPopup(this.popup);\n }\n\n /**\n * Create list type buttons\n */\n async createListTypeButtons() {\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'list-button-container';\n \n // Icons are now inline, no need to preload\n \n // Create buttons\n for (const listType of this.options.listTypes) {\n const listButton = document.createElement('button');\n listButton.type = 'button';\n listButton.className = 'list-button';\n listButton.dataset.listType = listType.value;\n listButton.title = listType.label;\n \n // Add icon\n const iconSvg = IconUtils.getIcon(listType.icon);\n if (iconSvg) {\n listButton.innerHTML = iconSvg;\n } else {\n listButton.textContent = listType.label.charAt(0);\n }\n \n listButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectListType(listType.value);\n });\n \n buttonContainer.appendChild(listButton);\n }\n \n this.popup.appendChild(buttonContainer);\n }\n\n /**\n * Setup click outside handler\n */\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n // Add slight delay to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n /**\n * Remove click outside handler\n */\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n /**\n * Show list picker popup\n * @param {HTMLElement} anchor - Element to position popup relative to\n */\n show(anchor) {\n if (!anchor) return;\n \n // Ensure popup is in DOM\n if (!document.body.contains(this.popup)) {\n appendPopup(this.popup);\n }\n \n // Update current list type state\n this.updateCurrentListType();\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup by adding visible class\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Setup click outside handler\n this.setupClickOutside();\n }\n\n /**\n * Hide list picker popup\n */\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n /**\n * Select list type and trigger callback\n * @param {string} listType - Selected list type\n */\n selectListType(listType) {\n this.currentListType = listType;\n \n if (this.options.onListSelect) {\n this.options.onListSelect(listType);\n }\n \n this.hide();\n }\n\n /**\n * Update current list type state based on selection\n */\n updateCurrentListType() {\n try {\n const listType = this.getCurrentListType();\n this.currentListType = listType;\n \n // Update button states\n this.updateButtonStates(listType);\n } catch (error) {\n console.warn('Error updating current list type:', error);\n }\n }\n\n /**\n * Update button states based on current list type\n * @param {string|null} currentListType - Current active list type\n */\n updateButtonStates(currentListType) {\n const buttons = this.popup.querySelectorAll('.list-button');\n buttons.forEach(button => {\n button.classList.remove('active');\n if (currentListType && button.dataset.listType === currentListType) {\n button.classList.add('active');\n }\n });\n }\n\n /**\n * Update toolbar button icon based on selection\n * @param {string} listType - Current list type\n */\n updateToolbarButtonIcon(listType) {\n const button = document.querySelector('.rich-editor-toolbar-btn.list-btn');\n if (!button) return;\n\n const iconMap = {\n 'bullet': 'list-bullet',\n 'ordered': 'list-ordered',\n 'roman': 'list-roman',\n 'alpha': 'list-alpha'\n };\n\n const titleMap = {\n 'bullet': 'Bullet List',\n 'ordered': 'Numbered List',\n 'roman': 'Roman Numerals List',\n 'alpha': 'Alphabetical List'\n };\n \n const iconName = iconMap[listType] || 'list-bullet';\n \n // Update button title\n button.title = titleMap[listType] || 'List';\n \n // Update icon\n const svgContent = IconUtils.getIcon(iconName);\n if (svgContent) {\n const iconSpan = button.querySelector('.icon');\n if (iconSpan) {\n iconSpan.innerHTML = svgContent;\n } else {\n button.innerHTML = `<span class=\"icon\">${svgContent}</span>`;\n }\n }\n }\n\n /**\n * Get parent list of an element\n */\n getParentList(element) {\n let current = element;\n while (current && current !== document.body) {\n if (current.tagName === 'UL' || current.tagName === 'OL') {\n return current;\n }\n current = current.parentElement;\n }\n return null;\n }\n\n /**\n * Get list type from list element\n */\n getListType(listElement) {\n if (listElement.tagName === 'OL') {\n const type = listElement.style.listStyleType;\n if (type === 'upper-roman') return 'roman';\n if (type === 'lower-alpha') return 'alpha';\n return 'ordered';\n }\n return 'bullet';\n }\n\n /**\n * Get block element containing the given node\n */\n getBlockElement(node) {\n if (!node) return null;\n \n let currentNode = node;\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI', 'UL', 'OL'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.parentNode;\n }\n return null;\n }\n\n /**\n * Get current list type\n * @returns {string|null}\n */\n getCurrentListType() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n const listElement = this.getParentList(range.commonAncestorContainer);\n \n return listElement ? this.getListType(listElement) : null;\n }\n\n /**\n * Destroy the list picker\n */\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n this.currentListType = null;\n }\n}\n\nexport default ListPicker; ","import { BlockFormat } from '../core/format.js';\nimport ListPicker from '../ui/list-picker.js';\nimport IconUtils from '../ui/icons.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * List Format - Handles list formatting (bullet, ordered, checklist)\n * Now supports multiple editor instances with separate popup instances\n */\nclass List extends BlockFormat {\n static formatName = 'list';\n static tagName = 'UL';\n static attribute = 'class';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for List format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a list picker instance\n let listPicker = currentEditor.getPopupInstance('list');\n \n if (!listPicker) {\n // Create new list picker instance for this editor\n listPicker = new ListPicker({\n onListSelect: (listType) => {\n List.applyListToCurrentSelection(listType, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('list', listPicker);\n }\n \n this.listPicker = listPicker;\n }\n\n /**\n * Create a new List format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {List} List format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new List();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create list element with specified type\n * @param {string} value - List type (bullet, ordered, checklist)\n * @returns {HTMLElement}\n */\n static create(value) {\n let node;\n \n switch(value) {\n case 'ordered':\n node = document.createElement('OL');\n node.style.listStyleType = 'decimal'; // 1, 2, 3...\n break;\n case 'roman':\n node = document.createElement('OL');\n node.style.listStyleType = 'upper-roman'; // I, II, III...\n break;\n case 'alpha':\n node = document.createElement('OL');\n node.style.listStyleType = 'lower-alpha'; // a, b, c...\n break;\n case 'bullet':\n default:\n node = document.createElement('UL');\n node.style.listStyleType = 'disc'; // bullet points\n break;\n }\n \n return node;\n }\n\n /**\n * Static method to apply list to current selection or cursor position\n * @param {string} listType - List type\n * @param {string} editorId - Editor instance ID\n */\n static applyListToCurrentSelection(listType, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for list application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n try {\n const range = selection.getRangeAt(0);\n const blockElements = List.getSelectedBlockElements(range);\n \n if (blockElements.length === 0) {\n // If no block elements found, create one\n List.createListFromSelection(listType);\n } else {\n // Apply list to existing blocks\n List.convertBlocksToList(blockElements, listType);\n\n }\n \n // Update toolbar button icon after applying list\n List.updateToolbarButtonIcon(listType, editorId);\n\n } catch (error) {\n console.error('Error applying list:', error);\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Create list from current selection\n */\n static createListFromSelection(listType) {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const selectedText = range.toString() || 'List item';\n \n // Create list element\n const listElement = List.create(listType);\n const listItem = document.createElement('LI');\n \n // Preserve HTML content if selection contains formatted text\n if (range.toString() === range.cloneContents().textContent) {\n // Plain text selection\n listItem.textContent = selectedText;\n } else {\n // HTML selection - preserve formatting\n const fragment = range.cloneContents();\n listItem.appendChild(fragment);\n }\n \n // Try to preserve style from existing block if cursor is inside one\n const existingBlock = List.getBlockElement(range.startContainer);\n if (existingBlock && existingBlock.style && existingBlock.style.cssText) {\n listItem.style.cssText = existingBlock.style.cssText;\n }\n \n listElement.appendChild(listItem);\n \n // Replace selection with list\n range.deleteContents();\n range.insertNode(listElement);\n \n // Position cursor in the list item\n const newRange = document.createRange();\n newRange.selectNodeContents(listItem);\n newRange.collapse(false);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n\n /**\n * Convert existing block elements to list\n */\n static convertBlocksToList(blocks, listType) {\n if (blocks.length === 0) return;\n\n // Check if blocks are already in a list\n const existingList = List.getParentList(blocks[0]);\n if (existingList) {\n // If already in a list, toggle or change list type\n List.toggleOrChangeListType(existingList, listType);\n return;\n }\n\n // Create new list\n const listElement = List.create(listType);\n const firstBlock = blocks[0];\n \n // Insert list before first block\n firstBlock.parentNode.insertBefore(listElement, firstBlock);\n \n let firstListItem = null;\n \n // Convert each block to list item\n blocks.forEach((block, index) => {\n const listItem = document.createElement('LI');\n \n // Preserve all HTML content including formatting (bold, italic, etc.)\n listItem.innerHTML = block.innerHTML || block.textContent || '';\n \n // Copy style attributes to preserve formatting like text-align\n if (block.style && block.style.cssText) {\n listItem.style.cssText = block.style.cssText;\n }\n \n listElement.appendChild(listItem);\n block.remove();\n \n // Lưu lại list item đầu tiên để đặt con trỏ\n if (index === 0) {\n firstListItem = listItem;\n }\n });\n \n // Đặt con trỏ vào list item đầu tiên\n if (firstListItem) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(firstListItem);\n range.collapse(false); // false = cuối nội dung để dễ tiếp tục gõ\n sel.removeAllRanges();\n sel.addRange(range);\n }\n }\n\n /**\n * Toggle or change list type\n */\n static toggleOrChangeListType(existingList, newListType) {\n const currentType = List.getListType(existingList);\n \n if (currentType === newListType) {\n // Same type - remove list formatting\n List.removeListFormatting(existingList);\n } else {\n // Different type - change list type\n List.changeListType(existingList, newListType);\n }\n }\n\n /**\n * Get list type from list element\n */\n static getListType(listElement) {\n if (listElement.tagName === 'OL') {\n const type = listElement.style.listStyleType;\n if (type === 'upper-roman') return 'roman';\n if (type === 'lower-alpha') return 'alpha';\n return 'ordered';\n }\n return 'bullet';\n }\n\n /**\n * Change list type\n */\n static changeListType(existingList, newListType) {\n const newList = List.create(newListType);\n \n // Copy all list items\n Array.from(existingList.children).forEach(item => {\n const newItem = document.createElement('LI');\n \n // Preserve all HTML content including formatting\n newItem.innerHTML = item.innerHTML || item.textContent || '';\n \n // Copy style attributes to preserve formatting like text-align\n if (item.style && item.style.cssText) {\n newItem.style.cssText = item.style.cssText;\n }\n \n newList.appendChild(newItem);\n });\n \n // Replace existing list\n existingList.parentNode.replaceChild(newList, existingList);\n \n // Đặt con trỏ vào list item đầu tiên\n if (newList.firstElementChild) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(newList.firstElementChild);\n range.collapse(false); // false = cuối nội dung để dễ tiếp tục gõ\n sel.removeAllRanges();\n sel.addRange(range);\n }\n }\n\n /**\n * Remove list formatting\n */\n static removeListFormatting(listElement) {\n const parent = listElement.parentNode;\n let firstParagraph = null;\n \n // Convert list items back to paragraphs\n Array.from(listElement.children).forEach((item, index) => {\n const p = document.createElement('P');\n \n // Preserve all HTML content including formatting\n p.innerHTML = item.innerHTML || item.textContent || '';\n \n // Copy style attributes to preserve formatting like text-align\n if (item.style && item.style.cssText) {\n p.style.cssText = item.style.cssText;\n }\n \n parent.insertBefore(p, listElement);\n \n // Lưu lại paragraph đầu tiên để đặt con trỏ\n if (index === 0) {\n firstParagraph = p;\n }\n });\n \n // Remove the list element\n listElement.remove();\n \n // Đặt con trỏ vào paragraph đầu tiên\n if (firstParagraph) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(firstParagraph);\n range.collapse(false); // false = cuối nội dung để dễ tiếp tục gõ\n sel.removeAllRanges();\n sel.addRange(range);\n }\n }\n\n /**\n * Get parent list of an element\n */\n static getParentList(element) {\n let current = element;\n while (current && current !== document.body) {\n if (current.tagName === 'UL' || current.tagName === 'OL') {\n return current;\n }\n current = current.parentElement;\n }\n return null;\n }\n\n /**\n * Get icon name for list type\n * @param {string} listType - List type\n * @returns {string} Icon name\n */\n static getIconNameForListType(listType) {\n const iconMap = {\n 'bullet': 'list-bullet',\n 'ordered': 'list-ordered',\n 'roman': 'list-roman',\n 'alpha': 'list-alpha'\n };\n return iconMap[listType] || 'list-bullet';\n }\n\n /**\n * Update toolbar button icon based on list type\n * @param {string} listType - Current list type\n * @param {string} editorId - Editor instance ID\n */\n static updateToolbarButtonIcon(listType, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let button = null;\n \n if (toolbar) {\n button = toolbar.getButton('list');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!button) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n button = toolbarContainer.querySelector('.rich-editor-toolbar-btn.list-btn');\n }\n }\n \n // Final fallback: find any list button in the current editor's wrapper\n if (!button) {\n button = editor.wrapper.querySelector('.rich-editor-toolbar-btn.list-btn');\n }\n \n if (!button) return;\n\n const iconName = List.getIconNameForListType(listType);\n const titleMap = {\n 'bullet': 'Bullet List',\n 'ordered': 'Numbered List',\n 'roman': 'Roman Numerals List',\n 'alpha': 'Alphabetical List'\n };\n \n // Update button title\n button.title = titleMap[listType] || 'List';\n \n // Update icon\n const svgContent = IconUtils.getIcon(iconName);\n if (svgContent) {\n const iconSpan = button.querySelector('.icon');\n if (iconSpan) {\n iconSpan.innerHTML = svgContent;\n } else {\n button.innerHTML = `<span class=\"icon\">${svgContent}</span>`;\n }\n }\n }\n\n /**\n * Get all selected block elements\n */\n static getSelectedBlockElements(range) {\n const blocks = [];\n const startContainer = range.startContainer;\n const endContainer = range.endContainer;\n \n // Get start block\n const startBlock = List.getBlockElement(startContainer);\n if (startBlock) blocks.push(startBlock);\n \n // If selection spans multiple blocks, get all blocks in between\n if (startContainer !== endContainer) {\n let currentNode = startBlock;\n while (currentNode && currentNode !== endContainer) {\n const nextBlock = List.getNextBlockElement(currentNode);\n if (nextBlock && !blocks.includes(nextBlock)) {\n blocks.push(nextBlock);\n currentNode = nextBlock;\n } else {\n break;\n }\n }\n \n // Get end block\n const endBlock = List.getBlockElement(endContainer);\n if (endBlock && !blocks.includes(endBlock)) {\n blocks.push(endBlock);\n }\n }\n \n return blocks;\n }\n\n /**\n * Get the block element containing the given node\n */\n static getBlockElement(node) {\n if (!node) return null;\n \n let currentNode = node;\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.parentNode;\n }\n return null;\n }\n\n /**\n * Get next block element in document order\n */\n static getNextBlockElement(element) {\n let currentNode = element.nextSibling;\n \n while (currentNode) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.nextSibling;\n }\n \n return null;\n }\n\n /**\n * Apply list formatting\n * @param {string} value - List type\n */\n apply(value = 'bullet') {\n List.applyListToCurrentSelection(value, this.editorId);\n }\n\n /**\n * Remove list formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const listElement = List.getParentList(range.commonAncestorContainer);\n \n if (listElement) {\n List.removeListFormatting(listElement);\n }\n }\n\n /**\n * Toggle list formatting - shows/hides list picker\n */\n toggle() {\n if (this.listPicker.isVisible) {\n this.listPicker.hide();\n } else {\n this.showListPicker();\n }\n }\n\n /**\n * Show list picker popup positioned relative to list button on toolbar\n */\n showListPicker() {\n // Find list button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let listButton = null;\n \n if (toolbar) {\n listButton = toolbar.getButton('list');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!listButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n listButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.list-btn');\n }\n }\n \n // Final fallback: find any list button in the current editor's wrapper\n if (!listButton) {\n listButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.list-btn');\n }\n \n if (!listButton) {\n console.warn('List button not found for editor:', this.editorId);\n return;\n }\n \n this.listPicker.show(listButton);\n }\n\n /**\n * Check if list formatting is active\n * Always returns false because list button should not have active state\n * Instead, the button icon changes to reflect current list type\n */\n isActive(listType = null) {\n // Update button icon based on current list type\n const currentListType = List.getCurrentListType();\n if (currentListType) {\n List.updateToolbarButtonIcon(currentListType, this.editorId);\n } else {\n // Reset to default bullet list icon\n List.updateToolbarButtonIcon('bullet', this.editorId);\n }\n \n // Highlight the button when the caret is inside a list.\n return !!currentListType;\n }\n\n /**\n * Get current list type\n * @returns {string|null}\n */\n static getCurrentListType() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n const listElement = List.getParentList(range.commonAncestorContainer);\n \n return listElement ? List.getListType(listElement) : null;\n }\n\n /**\n * Get current list type (instance method)\n * @returns {string|null}\n */\n getCurrentListType() {\n return List.getCurrentListType();\n }\n}\n\nexport default List; ","import { BlockFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Indent Format - Handles text indentation (increase/decrease)\n */\nclass Indent extends BlockFormat {\n static formatName = 'indent';\n static tagName = 'DIV';\n static attribute = 'style';\n\n constructor() {\n super();\n }\n\n /**\n * Create element with indentation\n * @param {string} value - Indent level (e.g., '20px', '40px')\n * @returns {HTMLElement}\n */\n static create(value) {\n const node = document.createElement('DIV');\n if (value) {\n node.style.paddingLeft = value;\n }\n return node;\n }\n\n /**\n * Apply indent to current selection\n * @param {string} direction - 'increase' or 'decrease'\n */\n static applyIndentToCurrentSelection(direction) {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n try {\n const range = selection.getRangeAt(0);\n const blockElements = Indent.getSelectedBlockElements(range);\n \n if (blockElements.length === 0) {\n // If no block elements found, create one and apply indent\n const newBlock = document.createElement('DIV');\n newBlock.style.paddingLeft = direction === 'increase' ? '20px' : '0px';\n \n // Get selected text or use default\n const selectedText = range.toString() || '';\n if (selectedText) {\n newBlock.textContent = selectedText;\n range.deleteContents();\n range.insertNode(newBlock);\n } else {\n // Insert at cursor position\n range.insertNode(newBlock);\n newBlock.innerHTML = '<br>'; // Make it editable\n }\n \n // Position cursor in the new block\n const newRange = document.createRange();\n newRange.selectNodeContents(newBlock);\n newRange.collapse(false);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n // Apply indent to existing blocks\n blockElements.forEach(block => {\n Indent.applyIndentToBlock(block, direction);\n });\n }\n } catch (error) {\n console.error('Error applying indent:', error);\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n const currentEditor = Editor.getCurrentInstance();\n if (currentEditor && typeof currentEditor.onContentChange === 'function') {\n currentEditor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply indent to a specific block element\n * @param {HTMLElement} block - Block element to indent\n * @param {string} direction - 'increase' or 'decrease'\n */\n static applyIndentToBlock(block, direction) {\n if (!block || !block.style) return;\n\n const currentIndent = parseInt(block.style.paddingLeft) || 0;\n let newIndent;\n\n if (direction === 'increase') {\n newIndent = currentIndent + 20; // Increase by 20px\n } else {\n newIndent = Math.max(0, currentIndent - 20); // Decrease by 20px, minimum 0\n }\n\n if (newIndent === 0) {\n block.style.paddingLeft = '';\n } else {\n block.style.paddingLeft = newIndent + 'px';\n }\n }\n\n /**\n * Get all selected block elements\n */\n static getSelectedBlockElements(range) {\n const blocks = [];\n const startContainer = range.startContainer;\n const endContainer = range.endContainer;\n \n // Get start block\n const startBlock = Indent.getBlockElement(startContainer);\n if (startBlock) blocks.push(startBlock);\n \n // If selection spans multiple blocks, get all blocks in between\n if (startContainer !== endContainer) {\n let currentNode = startBlock;\n while (currentNode && currentNode !== endContainer) {\n const nextBlock = Indent.getNextBlockElement(currentNode);\n if (nextBlock && !blocks.includes(nextBlock)) {\n blocks.push(nextBlock);\n currentNode = nextBlock;\n } else {\n break;\n }\n }\n \n // Get end block\n const endBlock = Indent.getBlockElement(endContainer);\n if (endBlock && !blocks.includes(endBlock)) {\n blocks.push(endBlock);\n }\n }\n \n return blocks;\n }\n\n /**\n * Get the block element containing the given node\n */\n static getBlockElement(node) {\n if (!node) return null;\n \n let currentNode = node;\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.parentNode;\n }\n return null;\n }\n\n /**\n * Get next block element in document order\n */\n static getNextBlockElement(element) {\n let currentNode = element.nextSibling;\n \n while (currentNode) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.nextSibling;\n }\n \n return null;\n }\n\n /**\n * Apply indent formatting\n * @param {string} value - Direction ('increase' or 'decrease')\n */\n apply(value = 'increase') {\n Indent.applyIndentToCurrentSelection(value);\n }\n\n /**\n * Remove indent formatting (reset to 0)\n */\n remove() {\n Indent.applyIndentToCurrentSelection('remove');\n }\n\n /**\n * Check if indent formatting is active\n * @returns {boolean}\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n const blockElement = Indent.getBlockElement(range.commonAncestorContainer);\n \n if (!blockElement) return false;\n \n const paddingLeft = parseInt(blockElement.style.paddingLeft) || 0;\n return paddingLeft > 0;\n }\n\n /**\n * Get current indent level\n * @returns {number}\n */\n static getCurrentIndentLevel() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return 0;\n\n const range = selection.getRangeAt(0);\n const blockElement = Indent.getBlockElement(range.commonAncestorContainer);\n \n if (!blockElement) return 0;\n \n return parseInt(blockElement.style.paddingLeft) || 0;\n }\n\n /**\n * Get current indent level (instance method)\n * @returns {number}\n */\n getCurrentIndentLevel() {\n return Indent.getCurrentIndentLevel();\n }\n}\n\n/**\n * Indent Increase Format - Handles increasing indentation\n */\nclass IndentIncrease extends Indent {\n static formatName = 'indent-increase';\n\n /**\n * Apply increase indent formatting\n */\n apply() {\n Indent.applyIndentToCurrentSelection('increase');\n }\n\n /**\n * Toggle increase indent - always increases\n */\n toggle() {\n this.apply();\n }\n\n /**\n * Never active - this is an action button\n */\n isActive() {\n return false;\n }\n}\n\n/**\n * Indent Decrease Format - Handles decreasing indentation\n */\nclass IndentDecrease extends Indent {\n static formatName = 'indent-decrease';\n\n /**\n * Apply decrease indent formatting\n */\n apply() {\n Indent.applyIndentToCurrentSelection('decrease');\n }\n\n /**\n * Toggle decrease indent - always decreases\n */\n toggle() {\n this.apply();\n }\n\n /**\n * Never active - this is an action button\n */\n isActive() {\n return false;\n }\n}\n\nexport { Indent as default, IndentIncrease, IndentDecrease }; ","/**\n * Emoji Picker Component - Popup for selecting emojis\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass EmojiPicker {\n constructor(options = {}) {\n this.options = {\n emojis: [\n // Smileys & People\n '😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆', '😉', '😊',\n '😋', '😎', '😍', '🥰', '😘', '😗', '😙', '😚', '🙂', '🤗',\n '😳', '🥺', '😦', '😧', '😨', '😰', '😥', '😢', '😭', '😱',\n '🤬', '😈', '👿', '💀', '☠️', '💩', '🤡', '👹', '👺', '👻',\n ],\n onEmojiSelect: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.clickOutsideHandler = null;\n \n this.createEmojiPicker();\n }\n\n /**\n * Detect operating system\n * @returns {string} 'mac' or 'windows'\n */\n detectOS() {\n const platform = navigator.platform.toLowerCase();\n if (platform.includes('mac')) {\n return 'mac';\n } else if (platform.includes('win')) {\n return 'windows';\n }\n // Default to windows for other platforms\n return 'windows';\n }\n\n /**\n * Get emoji shortcut message based on OS\n * @returns {string} HTML string for the shortcut message\n */\n getEmojiShortcutMessage() {\n const os = this.detectOS();\n \n if (os === 'mac') {\n return `<div style=\"color: rgb(113, 120, 124); font-style: normal; font-weight: 400; line-height: normal; text-align: center;\">Get more emojis with <span style=\"border-radius: 2.2px; background: #EEE; padding: 2px 4px;\">⌘</span> <span style=\"color: #000;\">+</span> <span style=\"border-radius: 2.2px; background: #EEE; padding: 2px 4px;\">CTRL</span> <span style=\"color: #000;\">+</span> <span style=\"border-radius: 2.2px; background: #EEE; padding: 2px 4px;\">SPACE</span></div>`;\n } else {\n return `<div style=\"color: rgb(113, 120, 124); font-style: normal; font-weight: 400; line-height: normal; text-align: center;\">Get more emojis with <span style=\"border-radius: 2.2px; background: #EEE; padding: 2px 4px;\">WIN</span> <span style=\"color: #000;\">+</span> <span style=\"border-radius: 2.2px; background: #EEE; padding: 2px 4px;\">.</span></div>`;\n }\n }\n\n /**\n * Create emoji picker popup\n */\n createEmojiPicker() {\n // Create popup\n this.popup = document.createElement('div');\n this.popup.className = 'emoji-picker-popup';\n \n // Create emoji grid\n this.createEmojiGrid();\n const emojiTextMessage = document.createElement('div');\n emojiTextMessage.className = 'emoji-text-message';\n \n emojiTextMessage.innerHTML = this.getEmojiShortcutMessage();\n this.popup.appendChild(emojiTextMessage);\n\n // Add popup to container\n appendPopup(this.popup);\n }\n\n /**\n * Create emoji grid\n */\n createEmojiGrid() {\n const emojiGrid = document.createElement('div');\n emojiGrid.className = 'emoji-grid';\n \n // Create emoji buttons\n this.options.emojis.forEach(emoji => {\n const emojiButton = document.createElement('button');\n emojiButton.type = 'button';\n emojiButton.className = 'emoji-button';\n emojiButton.textContent = emoji;\n emojiButton.title = emoji;\n \n emojiButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectEmoji(emoji);\n });\n \n emojiGrid.appendChild(emojiButton);\n });\n \n this.popup.appendChild(emojiGrid);\n }\n\n /**\n * Setup click outside handler\n */\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n // Add slight delay to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n /**\n * Remove click outside handler\n */\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n /**\n * Show emoji picker popup\n * @param {HTMLElement} anchor - Element to position popup relative to\n */\n show(anchor) {\n if (!anchor) return;\n \n // Ensure popup is in DOM\n if (!document.body.contains(this.popup)) {\n appendPopup(this.popup);\n }\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup by adding visible class\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Setup click outside handler\n this.setupClickOutside();\n }\n\n /**\n * Hide emoji picker popup\n */\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n /**\n * Select emoji and trigger callback\n * @param {string} emoji - Selected emoji\n */\n selectEmoji(emoji) {\n if (this.options.onEmojiSelect) {\n this.options.onEmojiSelect(emoji);\n }\n \n this.hide();\n }\n\n /**\n * Destroy the emoji picker\n */\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n }\n}\n\nexport default EmojiPicker; ","import { InlineFormat } from '../core/format.js';\nimport EmojiPicker from '../ui/emoji-picker.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Emoji Format - Handles emoji insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Emoji extends InlineFormat {\n static formatName = 'emoji';\n static tagName = 'SPAN';\n static className = 'emoji';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Emoji format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has an emoji picker instance\n let emojiPicker = currentEditor.getPopupInstance('emoji');\n \n if (!emojiPicker) {\n // Create new emoji picker instance for this editor\n emojiPicker = new EmojiPicker({\n onEmojiSelect: (emoji) => {\n Emoji.insertEmojiAtCurrentPosition(emoji, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('emoji', emojiPicker);\n }\n \n this.emojiPicker = emojiPicker;\n }\n\n /**\n * Create a new Emoji format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Emoji} Emoji format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Emoji();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create emoji element\n * @param {string} value - Emoji character\n * @returns {HTMLElement}\n */\n static create(value) {\n const span = document.createElement('SPAN');\n span.className = 'emoji';\n span.textContent = value;\n span.setAttribute('data-emoji', value);\n return span;\n }\n\n /**\n * Insert emoji at current cursor position\n * @param {string} emoji - Emoji character to insert\n * @param {string} editorId - Editor instance ID\n */\n static insertEmojiAtCurrentPosition(emoji, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for emoji insertion');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n try {\n const range = selection.getRangeAt(0);\n \n // Check if cursor is inside an existing emoji span\n let currentNode = range.startContainer;\n let emojiParent = null;\n \n // If cursor is in a text node, check its parent\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentNode;\n }\n \n // Find if we're inside an emoji span\n while (currentNode && currentNode !== editor.element) {\n if (currentNode.classList && currentNode.classList.contains('emoji')) {\n emojiParent = currentNode;\n break;\n }\n currentNode = currentNode.parentNode;\n }\n \n // If cursor is inside an emoji span, move it outside\n if (emojiParent) {\n // Move cursor after the emoji span\n range.setStartAfter(emojiParent);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n \n // Create emoji element\n const emojiElement = Emoji.create(emoji);\n \n // Insert emoji at cursor position\n range.deleteContents();\n range.insertNode(emojiElement);\n \n // Create a zero-width space character after the emoji\n const zeroWidthSpace = document.createTextNode('\\u200B'); // Zero-width space\n \n // Insert zero-width space after emoji\n range.setStartAfter(emojiElement);\n range.insertNode(zeroWidthSpace);\n \n // Position cursor after the zero-width space\n range.setStartAfter(zeroWidthSpace);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n \n } catch (error) {\n console.error('Error inserting emoji:', error);\n }\n }\n\n /**\n * Apply emoji formatting - shows emoji picker\n */\n apply(value) {\n if (value) {\n Emoji.insertEmojiAtCurrentPosition(value, this.editorId);\n } else {\n this.showEmojiPicker();\n }\n }\n\n /**\n * Remove emoji formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const emojiElement = this.getEmojiElement(range);\n \n if (emojiElement) {\n // Replace emoji element with its text content\n const textNode = document.createTextNode(emojiElement.textContent);\n emojiElement.parentNode.replaceChild(textNode, emojiElement);\n }\n }\n\n /**\n * Toggle emoji formatting - shows emoji picker\n */\n toggle() {\n if (this.emojiPicker.isVisible) {\n this.emojiPicker.hide();\n } else {\n this.showEmojiPicker();\n }\n }\n\n /**\n * Show emoji picker popup\n */\n showEmojiPicker() {\n // Find emoji button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let emojiButton = null;\n \n if (toolbar) {\n emojiButton = toolbar.getButton('emoji');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!emojiButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n emojiButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.emoji-btn');\n }\n }\n \n // Final fallback: find any emoji button in the current editor's wrapper\n if (!emojiButton) {\n emojiButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.emoji-btn');\n }\n \n if (!emojiButton) {\n console.warn('Emoji button not found for editor:', this.editorId);\n return;\n }\n \n this.emojiPicker.show(emojiButton);\n }\n\n /**\n * Check if emoji formatting is active\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n const emojiElement = this.getEmojiElement(range);\n \n return emojiElement !== null;\n }\n\n /**\n * Get emoji element from selection\n * @param {Range} range - Selection range\n * @returns {HTMLElement|null}\n */\n getEmojiElement(range) {\n let node = range.commonAncestorContainer;\n \n // If it's a text node, get its parent\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n \n // Check if current node is an emoji\n if (node.classList && node.classList.contains('emoji')) {\n return node;\n }\n \n // Check if selection contains an emoji\n const emojiInSelection = range.cloneContents().querySelector('.emoji');\n if (emojiInSelection) {\n return emojiInSelection;\n }\n \n return null;\n }\n}\n\nexport default Emoji; ","import { InlineFormat } from '../core/format.js';\nimport Editor from '../core/editor.js';\nimport { isSafeUrl } from '../utils/sanitize.js';\n\n/**\n * Image Format - Handles image insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Image extends InlineFormat {\n static formatName = 'image';\n static tagName = 'IMG';\n static className = 'inserted-image';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Image format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n }\n\n /**\n * Create a new Image format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Image} Image format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Image();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create image element\n * @param {string} src - Image source URL\n * @param {string} alt - Alt text\n * @returns {HTMLElement}\n */\n static create(src, alt = '') {\n // Allow http(s)/relative URLs and raster data: image URIs; reject the rest.\n if (!isSafeUrl(src, { allowDataImage: true })) {\n console.warn('Blocked unsafe image URL:', src);\n return null;\n }\n const img = document.createElement('IMG');\n img.src = src;\n img.alt = alt || 'Inserted image';\n img.className = 'inserted-image';\n img.style.maxWidth = '100%';\n img.style.height = 'auto';\n img.setAttribute('contenteditable', 'false');\n return img;\n }\n\n /**\n * Insert image at current cursor position\n * @param {string} src - Image source URL\n * @param {string} alt - Alt text\n * @param {string} editorId - Editor instance ID\n */\n static insertImageAtCurrentPosition(src, alt = '', editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for image insertion');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection) return;\n // No caret (or caret outside this editor) → append at the end of the editor.\n if (!selection.rangeCount || !editor.editor.contains(selection.anchorNode)) {\n const r = document.createRange();\n r.selectNodeContents(editor.editor);\n r.collapse(false);\n selection.removeAllRanges();\n selection.addRange(r);\n }\n\n try {\n const range = selection.getRangeAt(0);\n // Create image element\n const imageElement = Image.create(src, alt);\n // Abort if the URL was rejected as unsafe\n if (!imageElement) return;\n // Insert image at cursor position\n range.deleteContents();\n range.insertNode(imageElement);\n // Add a space after the image for easier editing\n const spaceNode = document.createTextNode(' ');\n range.setStartAfter(imageElement);\n range.insertNode(spaceNode);\n // Position cursor after the space\n range.setStartAfter(spaceNode);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n } catch (error) {\n console.error('Error inserting image:', error);\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n const currentEditor = Editor.getCurrentInstance();\n if (currentEditor && typeof currentEditor.onContentChange === 'function') {\n currentEditor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply image formatting — insert a known src, or open the file picker.\n */\n apply(src, alt) {\n if (src) {\n Image.insertImageAtCurrentPosition(src, alt, this.editorId);\n } else {\n this.openFilePicker();\n }\n }\n\n /**\n * Open the native file browser, then insert the chosen image straight into\n * the editor (visible immediately). The selection is captured before the\n * dialog steals focus and restored before insertion.\n */\n openFilePicker() {\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n\n const selection = window.getSelection();\n const savedRange = selection && selection.rangeCount\n ? selection.getRangeAt(0).cloneRange()\n : null;\n\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = (editor.options.image && editor.options.image.accept) || 'image/*';\n input.style.display = 'none';\n input.addEventListener('change', () => {\n const file = input.files && input.files[0];\n if (file) {\n // Restore the caret captured before the file dialog stole focus.\n editor.focus();\n const sel = window.getSelection();\n if (savedRange) {\n sel.removeAllRanges();\n sel.addRange(savedRange);\n } else if (!sel.rangeCount || !editor.editor.contains(sel.anchorNode)) {\n const r = document.createRange();\n r.selectNodeContents(editor.editor);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n }\n // Single insertion path → honours the image.upload hook + validation.\n editor.insertImageFile(file);\n }\n input.remove();\n });\n document.body.appendChild(input);\n input.click();\n }\n\n /**\n * Remove image formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const imageElement = this.getImageElement(range);\n \n if (imageElement) {\n imageElement.remove();\n }\n }\n\n /**\n * Toggle image — opens the native file picker.\n */\n toggle() {\n this.openFilePicker();\n }\n\n /**\n * Check if image formatting is active\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n const imageElement = this.getImageElement(range);\n \n return imageElement !== null;\n }\n\n /**\n * Get image element from selection\n * @param {Range} range - Selection range\n * @returns {HTMLElement|null}\n */\n getImageElement(range) {\n let node = range.commonAncestorContainer;\n \n // If it's a text node, get its parent\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n \n // Check if current node is an image\n if (node.tagName === 'IMG' && node.classList && node.classList.contains('inserted-image')) {\n return node;\n }\n \n // Check if selection contains an image\n const imageInSelection = range.cloneContents().querySelector('.inserted-image');\n if (imageInSelection) {\n return imageInSelection;\n }\n \n return null;\n }\n\n /**\n * Handle file upload\n * @param {File} file - Image file\n * @returns {Promise<string>} - Promise that resolves to image URL\n */\n static async handleFileUpload(file) {\n return new Promise((resolve, reject) => {\n if (!file || !file.type.startsWith('image/')) {\n reject(new Error('Please select a valid image file'));\n return;\n }\n\n const reader = new FileReader();\n reader.onload = (e) => {\n resolve(e.target.result);\n };\n reader.onerror = () => {\n reject(new Error('Failed to read file'));\n };\n reader.readAsDataURL(file);\n });\n }\n\n /**\n * Validate image URL\n * @param {string} url - Image URL\n * @returns {Promise<boolean>} - Promise that resolves to validation result\n */\n static validateImageUrl(url) {\n return new Promise((resolve) => {\n // Check if it's a valid image URL format\n const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];\n const hasValidExtension = imageExtensions.some(ext => \n url.toLowerCase().includes(`.${ext}`)\n );\n \n // Check if it's a data URL\n if (url.startsWith('data:image/')) {\n resolve(true);\n return;\n }\n \n // Check if it's a valid HTTP(S) URL\n if (!/^https?:\\/\\//.test(url)) {\n resolve(false);\n return;\n }\n \n // If it has a valid extension, assume it's valid\n if (hasValidExtension) {\n resolve(true);\n return;\n }\n \n // Try to load the image (fallback)\n const img = new Image();\n img.onload = () => {\n resolve(true);\n };\n img.onerror = () => {\n // If loading fails, but URL looks like an image, still allow it\n // This handles cases where CORS blocks loading but the URL is valid\n if (url.includes('imgur.com') || url.includes('drive.google.com') || hasValidExtension) {\n resolve(true);\n } else {\n resolve(false);\n }\n };\n \n // Set timeout to avoid hanging\n setTimeout(() => {\n // If no response after 5 seconds, still allow if URL looks valid\n if (hasValidExtension || url.includes('imgur.com') || url.includes('drive.google.com')) {\n resolve(true);\n } else {\n resolve(false);\n }\n }, 5000);\n \n img.src = url;\n });\n }\n}\n\nexport default Image; ","/**\n * Video Popup Component - Popup for inserting videos\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass VideoPopup {\n constructor(options = {}) {\n this.options = {\n onVideoInsert: null,\n editor: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.clickOutsideHandler = null;\n this.selectedVideoSrc = null;\n this.savedSelection = null; // Save editor selection\n \n this.createVideoPopup();\n }\n\n /**\n * Create video popup\n */\n createVideoPopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'video-popup';\n \n const content = document.createElement('div');\n content.className = 'video-popup-content';\n \n // Title\n const title = document.createElement('h3');\n title.textContent = 'Insert video';\n title.className = 'yjd-input-title';\n content.appendChild(title);\n \n // Container\n const uploadContainer = document.createElement('div');\n uploadContainer.className = 'video-input-container';\n\n const textLabel = document.createElement('p');\n textLabel.textContent = 'Your video url';\n textLabel.className = 'yjd-input-label';\n\n const inputgroup1 = document.createElement('div');\n inputgroup1.className = 'yjd-input-upload-group';\n this.inputGroup = inputgroup1; // Store reference\n\n // input url\n this.urlInput = document.createElement('input');\n this.urlInput.type = 'url';\n this.urlInput.className = 'yjd-input';\n this.urlInput.placeholder = 'Please enter your video URL';\n this.urlInput.addEventListener('input', () => {\n this.updateInsertButton();\n // Show preview if URL is valid\n const url = this.urlInput.value.trim();\n if (url && this.isValidVideoUrl(url)) {\n this.showPreview(url);\n } else {\n this.removePreview();\n }\n if(this.urlInput.value.trim()){\n this.customButton.style.display = 'none';\n }else{\n this.customButton.style.display = 'block';\n }\n });\n\n // Hidden file input\n this.fileInput = document.createElement('input');\n this.fileInput.type = 'file';\n this.fileInput.accept = 'video/*';\n this.fileInput.className = 'image-input-hidden'; // ẩn bằng CSS\n this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));\n\n // Custom button\n const customButton = document.createElement('button');\n customButton.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" x2=\"12\" y1=\"3\" y2=\"15\"/></svg>`;\n customButton.className = 'yjd-custom-upload-button';\n this.customButton = customButton;\n customButton.addEventListener('click', () => this.fileInput.click());\n\n // Create preview container\n this.createPreviewContainer();\n\n // Append elements\n inputgroup1.appendChild(this.urlInput);\n inputgroup1.appendChild(this.fileInput);\n inputgroup1.appendChild(customButton);\n uploadContainer.appendChild(textLabel);\n uploadContainer.appendChild(inputgroup1);\n uploadContainer.appendChild(this.previewContainer);\n content.appendChild(uploadContainer);\n \n // Buttons\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'yjd-button-container';\n \n const cancelButton = document.createElement('button');\n cancelButton.type = 'button';\n cancelButton.className = 'image-button yjd-button-cancel';\n cancelButton.textContent = 'Cancel';\n cancelButton.addEventListener('click', () => {\n this.hide();\n // Maintain editor focus after popup close\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n this.insertButton = document.createElement('button');\n this.insertButton.type = 'button';\n this.insertButton.className = 'image-button yjd-button-confirm button-disable';\n this.insertButton.textContent = 'Add video';\n this.insertButton.disabled = true;\n this.insertButton.addEventListener('click', () => {\n this.insertVideo();\n // Maintain editor focus after insert\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n buttonContainer.appendChild(cancelButton);\n buttonContainer.appendChild(this.insertButton);\n content.appendChild(buttonContainer);\n \n this.popup.appendChild(content);\n appendPopup(this.popup);\n \n // Prevent focus loss when clicking on popup\n if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {\n this.options.editor.preventFocusLoss(this.popup);\n }\n }\n\n async handleFileSelect(e) {\n const file = e.target.files[0];\n if (!file) return;\n \n try {\n const { default: Video } = await import('../formats/video.js');\n this.selectedVideoSrc = await Video.handleFileUpload(file);\n this.urlInput.value = '';\n this.showPreview(this.selectedVideoSrc);\n this.updateInsertButton();\n } catch (error) {\n alert(error.message);\n }\n }\n\n updateInsertButton() {\n const hasVideo = this.selectedVideoSrc || this.urlInput.value.trim();\n this.insertButton.disabled = !hasVideo;\n this.insertButton.classList.toggle('button-disable', !hasVideo);\n }\n\n /**\n * Show video preview\n */\n showPreview(videoSrc) {\n if (!videoSrc) return;\n \n this.videoPreview.src = videoSrc;\n this.previewContainer.style.display = 'block';\n this.selectedVideoSrc = videoSrc;\n \n // Hide input group\n this.toggleInputGroup(false);\n \n // Recalculate position after preview is shown to ensure buttons remain visible\n this.recalculatePosition();\n }\n\n /**\n * Remove video preview and show input again\n */\n removePreview() {\n this.selectedVideoSrc = null;\n this.previewContainer.style.display = 'none';\n this.videoPreview.src = '';\n \n // Show input group and reset file input\n this.toggleInputGroup(true);\n if (this.fileInput) {\n this.fileInput.value = '';\n }\n \n this.updateInsertButton();\n \n // Recalculate position after preview is removed\n this.recalculatePosition();\n }\n\n /**\n * Toggle input group visibility\n */\n toggleInputGroup(show) {\n if (!this.inputGroup) return;\n \n if (show) {\n this.inputGroup.style.display = 'flex';\n this.inputGroup.style.visibility = 'visible';\n if (this.customButton) {\n this.customButton.style.pointerEvents = 'auto';\n }\n } else {\n this.inputGroup.style.display = 'none';\n this.inputGroup.style.visibility = 'hidden';\n }\n }\n\n /**\n * Create preview container with video and remove button\n */\n createPreviewContainer() {\n this.previewContainer = document.createElement('div');\n this.previewContainer.className = 'video-preview-container';\n this.previewContainer.style.cssText = 'display: none; position: relative;';\n \n // Video preview\n this.videoPreview = document.createElement('video');\n this.videoPreview.className = 'video-preview';\n this.videoPreview.style.cssText = 'max-width: 100%; max-height: 200px; border-radius: 8px; object-fit: contain;';\n this.videoPreview.controls = true;\n this.videoPreview.muted = true;\n \n // Remove button\n this.removeButton = document.createElement('button');\n this.removeButton.className = 'video-remove-button';\n this.removeButton.innerHTML = '×';\n this.removeButton.style.cssText = `\n position: absolute; top: 5px; right: 5px; background: rgba(0,0,0,0.7);\n color: white; border: none; border-radius: 50%; width: 24px; height: 24px;\n cursor: pointer; font-size: 16px; font-weight: bold;\n `;\n this.removeButton.addEventListener('click', () => this.removePreview());\n \n this.previewContainer.appendChild(this.videoPreview);\n this.previewContainer.appendChild(this.removeButton);\n }\n\n /**\n * Check if URL is a valid video URL\n */\n isValidVideoUrl(url) {\n try {\n const urlObj = new URL(url);\n const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv'];\n const videoHosts = ['youtube.com', 'youtu.be', 'vimeo.com', 'dailymotion.com'];\n \n const pathname = urlObj.pathname.toLowerCase();\n const hasVideoExtension = videoExtensions.some(ext => pathname.endsWith(ext));\n const isFromVideoHost = videoHosts.some(host => urlObj.hostname.includes(host));\n \n return hasVideoExtension || isFromVideoHost;\n } catch {\n return false;\n }\n }\n\n async insertVideo() {\n let src = this.selectedVideoSrc || this.urlInput.value.trim();\n \n if (!src) return;\n \n // Always validate URL (both file upload and URL input)\n try {\n const { default: Video } = await import('../formats/video.js');\n const isValid = await Video.validateVideoUrl(src);\n if (!isValid) {\n alert('Invalid video URL. Please check the URL and try again.');\n return;\n }\n } catch (error) {\n alert('Error validating video URL.');\n return;\n }\n \n // Restore editor selection before inserting\n this.restoreSelection();\n \n if (this.options.onVideoInsert) {\n this.options.onVideoInsert(src);\n }\n \n this.hide();\n this.reset();\n }\n\n reset() {\n this.fileInput.value = '';\n this.urlInput.value = '';\n this.selectedVideoSrc = null;\n \n // Hide preview and show input\n this.previewContainer.style.display = 'none';\n this.videoPreview.src = '';\n this.toggleInputGroup(true);\n \n this.updateInsertButton();\n this.customButton.style.display = 'block';\n }\n\n /**\n * Save current editor selection\n */\n saveSelection() {\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n this.savedSelection = selection.getRangeAt(0).cloneRange();\n }\n }\n\n /**\n * Restore editor selection\n */\n restoreSelection() {\n if (this.savedSelection) {\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(this.savedSelection);\n }\n }\n\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n show(anchor) {\n if (!anchor) return;\n \n // Save current editor selection before showing popup\n this.saveSelection();\n \n // Reset state when showing popup\n this.reset();\n \n // Store anchor for recalculation\n this.currentAnchor = anchor;\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n this.popup.classList.add('visible');\n this.isVisible = true;\n \n this.setupClickOutside();\n }\n\n /**\n * Recalculate popup position to ensure it stays within viewport\n */\n recalculatePosition() {\n if (!this.currentAnchor || !this.isVisible) return;\n \n // Small delay to ensure DOM updates are complete\n setTimeout(() => {\n const position = calculatePopupPosition(this.currentAnchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n }, 10);\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n // Clear saved selection to avoid memory leaks\n this.savedSelection = null;\n }\n\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n }\n}\n\nexport default VideoPopup; ","import { InlineFormat } from '../core/format.js';\nimport VideoPopup from '../ui/video-popup.js';\nimport Editor from '../core/editor.js';\nimport { isSafeUrl } from '../utils/sanitize.js';\n\n/**\n * Video Format - Handles video insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Video extends InlineFormat {\n static formatName = 'video';\n static tagName = 'VIDEO';\n static className = 'inserted-video';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Video format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a video popup instance\n let videoPopup = currentEditor.getPopupInstance('video');\n \n if (!videoPopup) {\n // Create new video popup instance for this editor\n videoPopup = new VideoPopup({\n onVideoInsert: (src) => {\n Video.insertVideoAtCurrentPosition(src, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('video', videoPopup);\n }\n \n this.videoPopup = videoPopup;\n }\n\n /**\n * Create a new Video format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Video} Video format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Video();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create video element\n * @param {string} src - Video source URL\n * @returns {HTMLElement}\n */\n static create(src) {\n // Check if it's a YouTube URL\n if (Video.isYouTubeUrl(src)) {\n return Video.createYouTubeEmbed(src);\n }\n \n // Reject unsafe URL schemes for direct (non-YouTube) video sources.\n if (!isSafeUrl(src)) {\n console.warn('Blocked unsafe video URL:', src);\n return null;\n }\n\n // Create regular video element for direct video URLs\n const video = document.createElement('VIDEO');\n video.src = src;\n video.className = 'inserted-video';\n video.controls = true;\n video.style.maxWidth = '100%';\n video.style.height = 'auto';\n video.setAttribute('contenteditable', 'false');\n return video;\n }\n\n /**\n * Create YouTube embedded iframe\n * @param {string} url - YouTube URL\n * @returns {HTMLElement}\n */\n static createYouTubeEmbed(url) {\n const videoId = Video.getYouTubeVideoId(url);\n if (!videoId) {\n throw new Error('Invalid YouTube URL');\n }\n \n const iframe = document.createElement('IFRAME');\n iframe.src = `https://www.youtube.com/embed/${videoId}`;\n iframe.className = 'inserted-video youtube-video';\n iframe.width = '560';\n iframe.height = '315';\n iframe.style.maxWidth = '100%';\n iframe.style.width = '560px'; // Set explicit width\n iframe.style.height = '315px'; // Set explicit height\n iframe.style.position = 'relative'; // Add position relative\n iframe.style.display = 'block'; // Make it block level\n iframe.setAttribute('frameborder', '0');\n iframe.setAttribute('allowfullscreen', '');\n iframe.setAttribute('contenteditable', 'false');\n \n return iframe;\n }\n\n /**\n * Insert video at current cursor position\n * @param {string} src - Video source URL\n * @param {string} editorId - Editor instance ID\n */\n static insertVideoAtCurrentPosition(src, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for video insertion');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) {\n return;\n }\n\n try {\n const range = selection.getRangeAt(0);\n // Create video element\n const videoElement = Video.create(src);\n // Abort if the URL was rejected as unsafe\n if (!videoElement) return;\n // Insert video at cursor position\n range.deleteContents();\n range.insertNode(videoElement);\n // Add a space after the video for easier editing\n const spaceNode = document.createTextNode(' ');\n range.setStartAfter(videoElement);\n range.insertNode(spaceNode);\n // Position cursor after the space\n range.setStartAfter(spaceNode);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n } catch (error) {\n console.error('Error inserting video:', error);\n }\n }\n\n /**\n * Apply video formatting - shows video popup\n */\n apply(src) {\n if (src) {\n Video.insertVideoAtCurrentPosition(src, this.editorId);\n } else {\n this.showVideoPopup();\n }\n }\n\n /**\n * Remove video formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const videoElement = this.getVideoElement(range);\n \n if (videoElement) {\n videoElement.remove();\n }\n }\n\n /**\n * Toggle video formatting - shows video popup\n */\n toggle() {\n if (this.videoPopup.isVisible) {\n this.videoPopup.hide();\n } else {\n this.showVideoPopup();\n }\n }\n\n /**\n * Show video popup\n */\n showVideoPopup() {\n // Find video button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let videoButton = null;\n \n if (toolbar) {\n videoButton = toolbar.getButton('video');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!videoButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n videoButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.video-btn');\n }\n }\n \n // Final fallback: find any video button in the current editor's wrapper\n if (!videoButton) {\n videoButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.video-btn');\n }\n \n if (!videoButton) {\n console.warn('Video button not found for editor:', this.editorId);\n return;\n }\n \n this.videoPopup.show(videoButton);\n }\n\n /**\n * Check if video formatting is active\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n const videoElement = this.getVideoElement(range);\n \n return videoElement !== null;\n }\n\n /**\n * Get video element from selection\n * @param {Range} range - Selection range\n * @returns {HTMLElement|null}\n */\n getVideoElement(range) {\n let node = range.commonAncestorContainer;\n \n // If it's a text node, get its parent\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n \n // Check if current node is a video or iframe\n if ((node.tagName === 'VIDEO' || node.tagName === 'IFRAME') && \n node.classList && node.classList.contains('inserted-video')) {\n return node;\n }\n \n // Check if selection contains a video or iframe\n const videoInSelection = range.cloneContents().querySelector('.inserted-video');\n if (videoInSelection) {\n return videoInSelection;\n }\n \n return null;\n }\n\n /**\n * Handle file upload\n * @param {File} file - Video file\n * @returns {Promise<string>} - Promise that resolves to video URL\n */\n static async handleFileUpload(file) {\n return new Promise((resolve, reject) => {\n if (!file || !file.type.startsWith('video/')) {\n reject(new Error('Please select a valid video file'));\n return;\n }\n\n const reader = new FileReader();\n reader.onload = (e) => {\n resolve(e.target.result);\n };\n reader.onerror = () => {\n reject(new Error('Failed to read file'));\n };\n reader.readAsDataURL(file);\n });\n }\n\n /**\n * Validate video URL\n * @param {string} url - Video URL\n * @returns {Promise<boolean>} - Promise that resolves to validation result\n */\n static validateVideoUrl(url) {\n return new Promise((resolve) => {\n // Check if it's a YouTube URL\n if (Video.isYouTubeUrl(url)) {\n resolve(true);\n return;\n }\n \n // Check if it's a valid video URL format\n const videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];\n const hasValidExtension = videoExtensions.some(ext => \n url.toLowerCase().includes(`.${ext}`)\n );\n \n if (hasValidExtension) {\n resolve(true);\n return;\n }\n \n // Try to load as video element (for direct video URLs)\n const video = document.createElement('video');\n video.onloadedmetadata = () => {\n resolve(true);\n };\n video.onerror = () => {\n resolve(false);\n };\n \n // Set timeout to avoid hanging\n setTimeout(() => {\n resolve(false);\n }, 5000);\n \n video.src = url;\n });\n }\n\n /**\n * Check if URL is a YouTube URL\n * @param {string} url - URL to check\n * @returns {boolean} - Whether it's a YouTube URL\n */\n static isYouTubeUrl(url) {\n const youtubeRegex = /(?:https?:\\/\\/)?(?:www\\.)?(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/|youtube\\.com\\/embed\\/)([a-zA-Z0-9_-]{11})/;\n return youtubeRegex.test(url);\n }\n\n /**\n * Extract YouTube video ID from URL\n * @param {string} url - YouTube URL\n * @returns {string|null} - Video ID or null if not found\n */\n static getYouTubeVideoId(url) {\n const youtubeRegex = /(?:https?:\\/\\/)?(?:www\\.)?(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/|youtube\\.com\\/embed\\/)([a-zA-Z0-9_-]{11})/;\n const match = url.match(youtubeRegex);\n return match ? match[1] : null;\n }\n}\n\nexport default Video; ","/**\n * Tag Popup Component - Popup for inserting custom tags\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass TagPopup {\n constructor(options = {}) {\n this.options = {\n onTagInsert: null,\n editor: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.clickOutsideHandler = null;\n this.selectedTagType = 'mention';\n \n this.createTagPopup();\n }\n\n createTagPopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'tag-popup';\n \n const content = document.createElement('div');\n content.className = 'tag-popup-content';\n \n // Title\n const title = document.createElement('h3');\n title.textContent = 'Insert tags';\n title.className = 'yjd-input-title';\n content.appendChild(title);\n \n // Tag type selector\n const group1 = document.createElement('div');\n group1.className = 'yjd-input-group';\n \n const typeLabel = document.createElement('label');\n typeLabel.textContent = 'Type';\n typeLabel.className = 'yjd-input-label';\n \n this.typeSelect = document.createElement('select');\n this.typeSelect.className = 'yjd-select-input';\n this.typeSelect.innerHTML = `\n <option value=\"mention\">Mention</option>\n <option value=\"hashtag\">Hashtag</option>\n <option value=\"custom\">Custom</option>\n `;\n this.typeSelect.addEventListener('change', () => this.updateSuggestions());\n \n group1.appendChild(typeLabel);\n group1.appendChild(this.typeSelect);\n content.appendChild(group1);\n \n // Content input\n const group2 = document.createElement('div');\n group2.className = 'yjd-input-group';\n \n const contentLabel = document.createElement('label');\n contentLabel.textContent = 'Content';\n contentLabel.className = 'yjd-input-label';\n \n this.contentInput = document.createElement('input');\n this.contentInput.type = 'text';\n this.contentInput.className = 'yjd-input';\n this.contentInput.placeholder = 'Please enter tag content';\n this.contentInput.addEventListener('input', () => this.updateInsertButton());\n this.contentInput.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n this.insertTag();\n }\n });\n \n group2.appendChild(contentLabel);\n group2.appendChild(this.contentInput);\n content.appendChild(group2);\n \n // Suggestions\n const group3 = document.createElement('div');\n group3.className = 'yjd-input-group';\n this.suggestionsContainer = document.createElement('div');\n this.suggestionsContainer.className = 'tag-suggestions-container';\n \n const suggestionsLabel = document.createElement('label');\n suggestionsLabel.textContent = 'Suggestions';\n suggestionsLabel.className = 'yjd-input-label';\n \n this.suggestionsList = document.createElement('div');\n this.suggestionsList.className = 'yjd-suggestions-list';\n \n this.suggestionsContainer.appendChild(this.suggestionsList);\n group3.appendChild(suggestionsLabel);\n group3.appendChild(this.suggestionsContainer);\n content.appendChild(group3);\n \n // Buttons\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'yjd-button-container';\n \n const cancelButton = document.createElement('button');\n cancelButton.type = 'button';\n cancelButton.className = 'yjd-button-cancel';\n cancelButton.textContent = 'Cancel';\n cancelButton.addEventListener('click', () => this.hide());\n \n this.insertButton = document.createElement('button');\n this.insertButton.type = 'button';\n this.insertButton.className = 'yjd-button-confirm';\n this.insertButton.textContent = 'Insert Tag';\n this.insertButton.disabled = true;\n this.insertButton.addEventListener('click', () => this.insertTag());\n \n buttonContainer.appendChild(cancelButton);\n buttonContainer.appendChild(this.insertButton);\n content.appendChild(buttonContainer);\n \n this.popup.appendChild(content);\n appendPopup(this.popup);\n \n // Prevent focus loss when clicking on popup\n if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {\n this.options.editor.preventFocusLoss(this.popup);\n }\n \n this.updateSuggestions();\n }\n\n updateSuggestions() {\n this.selectedTagType = this.typeSelect.value;\n this.suggestionsList.innerHTML = '';\n \n const suggestions = this.getSuggestions(this.selectedTagType);\n \n suggestions.forEach(suggestion => {\n const suggestionButton = document.createElement('button');\n suggestionButton.type = 'button';\n suggestionButton.className = 'yjd-suggestion-button';\n suggestionButton.textContent = suggestion;\n \n suggestionButton.addEventListener('click', () => {\n this.contentInput.value = suggestion;\n this.updateInsertButton();\n this.contentInput.focus();\n });\n \n this.suggestionsList.appendChild(suggestionButton);\n });\n }\n\n getSuggestions(tagType) {\n const suggestions = {\n mention: ['john', 'admin', 'team', 'support'],\n hashtag: ['urgent', 'done', 'important'],\n custom: ['warning', 'info', 'success']\n };\n \n return suggestions[tagType] || [];\n }\n\n updateInsertButton() {\n const hasContent = this.contentInput.value.trim();\n this.insertButton.disabled = !hasContent;\n }\n\n insertTag() {\n const content = this.contentInput.value.trim();\n \n if (!content) return;\n \n if (this.options.onTagInsert) {\n this.options.onTagInsert(this.selectedTagType, content);\n }\n \n this.hide();\n this.reset();\n }\n\n reset() {\n this.contentInput.value = '';\n this.typeSelect.value = 'mention';\n this.selectedTagType = 'mention';\n this.updateInsertButton();\n this.updateSuggestions();\n }\n\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n show(anchor) {\n if (!anchor) return;\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n this.popup.classList.add('visible');\n this.isVisible = true;\n \n this.setupClickOutside();\n \n setTimeout(() => {\n this.contentInput.focus();\n }, 100);\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n }\n}\n\nexport default TagPopup; ","import { InlineFormat } from '../core/format.js';\nimport TagPopup from '../ui/tag-popup.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Tag Format - Handles custom tag insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Tag extends InlineFormat {\n static formatName = 'tag';\n static tagName = 'SPAN';\n static className = 'custom-tag';\n static savedRanges = new Map(); // Map to store saved ranges for each editor\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Tag format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a tag popup instance\n let tagPopup = currentEditor.getPopupInstance('tag');\n \n if (!tagPopup) {\n // Create new tag popup instance for this editor\n tagPopup = new TagPopup({\n onTagInsert: (tagType, tagContent) => {\n Tag.insertTagAtCurrentPosition(tagType, tagContent, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('tag', tagPopup);\n }\n \n this.tagPopup = tagPopup;\n }\n\n /**\n * Create a new Tag format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Tag} Tag format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Tag();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create tag element\n * @param {string} tagType - Type of tag (@, #, custom)\n * @param {string} content - Tag content\n * @returns {HTMLElement}\n */\n static create(tagType, content) {\n const span = document.createElement('SPAN');\n span.className = `custom-tag tag-${tagType}`;\n \n let displayText = content;\n if (tagType === 'mention') {\n displayText = `@${content}`;\n } else if (tagType === 'hashtag') {\n displayText = `#${content}`;\n } else if (tagType === 'custom') {\n displayText = `<${content}>`;\n }\n \n span.textContent = displayText;\n span.setAttribute('data-tag-type', tagType);\n span.setAttribute('data-tag-content', content);\n span.setAttribute('contenteditable', 'false');\n \n return span;\n }\n\n /**\n * Insert tag at current cursor position\n * @param {string} tagType - Type of tag\n * @param {string} content - Tag content\n * @param {string} editorId - Editor instance ID\n */\n static insertTagAtCurrentPosition(tagType, content, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for tag insertion');\n return;\n }\n \n // Use saved range if available, otherwise get current selection\n const selection = window.getSelection();\n if (!selection) return;\n\n try {\n // Restore saved range if exists for this editor\n const savedRange = Tag.savedRanges.get(editorId);\n if (savedRange) {\n selection.removeAllRanges();\n selection.addRange(savedRange);\n Tag.savedRanges.delete(editorId);\n } else if (!selection.rangeCount) {\n return;\n }\n\n const range = selection.getRangeAt(0);\n \n // Create tag element\n const tagElement = Tag.create(tagType, content);\n \n // Insert tag at cursor position\n range.deleteContents();\n range.insertNode(tagElement);\n \n // Add a space after the tag for easier editing\n const spaceNode = document.createTextNode(' ');\n range.setStartAfter(tagElement);\n range.insertNode(spaceNode);\n \n // Position cursor after the space\n range.setStartAfter(spaceNode);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Focus back on editor\n if (editor && editor.element) {\n editor.element.focus();\n }\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n \n } catch (error) {\n console.error('Error inserting tag:', error);\n }\n }\n\n /**\n * Apply tag formatting - shows tag popup\n */\n apply(tagType, content) {\n if (tagType && content) {\n Tag.insertTagAtCurrentPosition(tagType, content, this.editorId);\n } else {\n // Save current selection before showing popup\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n Tag.savedRanges.set(this.editorId, selection.getRangeAt(0).cloneRange());\n }\n this.showTagPopup();\n }\n }\n\n /**\n * Remove tag formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const tagElement = this.getTagElement(range);\n \n if (tagElement) {\n // Replace tag element with its text content\n const textNode = document.createTextNode(tagElement.textContent);\n tagElement.parentNode.replaceChild(textNode, tagElement);\n }\n }\n\n /**\n * Toggle tag formatting - shows tag popup\n */\n toggle() {\n if (this.tagPopup.isVisible) {\n this.tagPopup.hide();\n } else {\n // Save current selection before showing popup\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n Tag.savedRanges.set(this.editorId, selection.getRangeAt(0).cloneRange());\n }\n this.showTagPopup();\n }\n }\n\n /**\n * Show tag popup\n */\n showTagPopup() {\n // Find tag button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let tagButton = null;\n \n if (toolbar) {\n tagButton = toolbar.getButton('tag');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!tagButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n tagButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.tag-btn');\n }\n }\n \n // Final fallback: find any tag button in the current editor's wrapper\n if (!tagButton) {\n tagButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.tag-btn');\n }\n \n if (!tagButton) {\n console.warn('Tag button not found for editor:', this.editorId);\n return;\n }\n \n this.tagPopup.show(tagButton);\n }\n\n /**\n * Check if tag formatting is active\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n return this.getTagElement(range) !== null;\n }\n\n /**\n * Get tag element from selection\n * @param {Range} range - Selection range\n * @returns {HTMLElement|null}\n */\n getTagElement(range) {\n let node = range.commonAncestorContainer;\n \n // If it's a text node, get its parent\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n \n // Check if current node is a tag\n if (node.classList && node.classList.contains('custom-tag')) {\n return node;\n }\n \n // Check if selection contains a tag\n const tagInSelection = range.cloneContents().querySelector('.custom-tag');\n return tagInSelection || null;\n }\n\n /**\n * Get predefined tag suggestions\n * @param {string} tagType - Type of tag\n * @returns {Array} - Array of suggestions\n */\n static getSuggestions(tagType) {\n const suggestions = {\n mention: ['john', 'sarah', 'admin', 'team', 'support'],\n hashtag: ['urgent', 'todo', 'done', 'review', 'important'],\n custom: ['note', 'warning', 'tip', 'info', 'success']\n };\n \n return suggestions[tagType] || [];\n }\n}\n\nexport default Tag; ","import { InlineFormat } from '../core/format.js';\nimport CustomSelect from '../ui/customselect.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\nimport { execFormat, queryFormatValue } from '../utils/exec-command.js';\n\n/**\n * Text Size Format - Handles font size formatting with 7 levels via execCommand\n * Now supports multiple editor instances with separate popup instances\n */\nclass TextSize extends InlineFormat {\n static formatName = 'textSize';\n static tagName = 'SPAN';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for TextSize format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a text size select instance\n let customSelect = currentEditor.getPopupInstance('text-size');\n \n if (!customSelect) {\n // Create new custom select instance for this editor\n const sizeMap = TextSize.getSizeMap();\n const items = Object.values(sizeMap).map(sizeData => ({\n value: sizeData.size,\n label: sizeData.element,\n title: sizeData.title\n }));\n\n customSelect = new CustomSelect({\n items: items,\n displayProperty: 'label',\n valueProperty: 'value',\n className: 'text-size-select',\n onItemSelect: (value) => {\n TextSize.applyTextSizeToCurrentSelection(value, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('text-size', customSelect);\n }\n \n this.customSelect = customSelect;\n }\n\n /**\n * Create a new TextSize format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {TextSize} TextSize format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new TextSize();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * 7-level text size map aligned with execCommand('fontSize', 1..7)\n */\n static getSizeMap() {\n return {\n '1': { size: '1', element: '<span >XX-Small</span>', title: 'XX-Small' },\n '2': { size: '2', element: '<span >X-Small</span>', title: 'X-Small' },\n '3': { size: '3', element: '<span >Small</span>', title: 'Small' },\n '4': { size: '4', element: '<span >Medium</span>', title: 'Medium' },\n '5': { size: '5', element: '<span >Large</span>', title: 'Large' },\n '6': { size: '6', element: '<span >X-Large</span>', title: 'X-Large' },\n '7': { size: '7', element: '<span >XX-Large</span>', title: 'XX-Large' },\n };\n }\n\n static getSizeDisplayName(size) {\n const sizeMap = this.getSizeMap();\n return sizeMap[size]?.title || 'Medium';\n }\n\n /**\n * Update button text based on current text size\n */\n updateButtonText() {\n const currentSize = this.getCurrentSize();\n const displayName = TextSize.getSizeDisplayName(currentSize || '4');\n\n // Find text-size button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let textSizeButton = null;\n \n if (toolbar) {\n textSizeButton = toolbar.getButton('text-size');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!textSizeButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n textSizeButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n }\n \n // Final fallback: find any text-size button in the current editor's wrapper\n if (!textSizeButton) {\n textSizeButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n \n if (textSizeButton && textSizeButton.updateText) {\n textSizeButton.updateText(displayName);\n } else if (textSizeButton) {\n textSizeButton.textContent = displayName;\n }\n }\n\n /**\n * Static method to update button text for any editor\n * @param {string} editorId - Editor instance ID\n */\n static updateButtonTextStatic(editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) return;\n \n const currentSize = TextSize.getCurrentSizeStatic();\n const displayName = TextSize.getSizeDisplayName(currentSize || '4');\n\n const toolbar = editor.getModule('toolbar');\n let textSizeButton = null;\n \n if (toolbar) {\n textSizeButton = toolbar.getButton('text-size');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!textSizeButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n textSizeButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n }\n \n // Final fallback: find any text-size button in the current editor's wrapper\n if (!textSizeButton) {\n textSizeButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n \n if (textSizeButton && textSizeButton.updateText) {\n textSizeButton.updateText(displayName);\n } else if (textSizeButton) {\n textSizeButton.textContent = displayName;\n }\n }\n\n static create(size = '4') {\n const node = document.createElement('span');\n // Fallback creation with an approximate CSS size\n node.style.fontSize = TextSize.sizeToCss(size);\n return node;\n }\n\n /**\n * Apply text size to current selection\n * @param {string} size - Text size value\n * @param {string} editorId - Editor instance ID\n */\n static applyTextSizeToCurrentSelection(size, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for text size application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n const sizeFormat = TextSize.createForEditor(editorId);\n if (sizeFormat) {\n sizeFormat.apply(size);\n sizeFormat.updateButtonText();\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Map execCommand size (1..7) to CSS font-size for fallback/labels\n */\n static sizeToCss(size) {\n const map = {\n '1': '10px',\n '2': '12px',\n '3': '14px',\n '4': '16px',\n '5': '20px',\n '6': '28px',\n '7': '36px',\n };\n return map[String(size)] || '16px';\n }\n\n /**\n * Apply text size using execCommand; works with selection and collapsed caret\n */\n apply(size = '4') {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n\n if (!range.collapsed) {\n // Bạn chưa nói đến xử lý khi bôi đen, nên mình bỏ qua\n execFormat('fontSize', String(size));\n\n // Lấy node bao quanh selection hiện tại\n const sel = window.getSelection();\n if (sel.rangeCount > 0) {\n const container = sel.getRangeAt(0).commonAncestorContainer;\n // Nếu container là text node → normalize ở parent\n if (container.nodeType === Node.TEXT_NODE) {\n container.parentNode.normalize();\n } else {\n container.normalize();\n }\n } \n \n return;\n }\n\n let node = range.startContainer;\n let offset = range.startOffset;\n\n // Nếu caret đang trong text node → lấy cha\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n\n // Kiểm tra nếu đang ở trong một <font>\n const currentFont = node.closest && node.closest('font');\n\n // ========================\n // Trường hợp 1: caret trong <font> rỗng (chỉ có \\u200B)\n // ========================\n if (currentFont && currentFont.textContent === \"\\u200B\") {\n currentFont.setAttribute('size', String(size));\n return;\n }\n\n // ========================\n // Trường hợp 2: caret trong <font> có ký tự thực\n // ========================\n if (currentFont && currentFont.firstChild && currentFont.firstChild.nodeType === Node.TEXT_NODE) {\n const textNode = currentFont.firstChild;\n const caretPos = range.startOffset; // vị trí caret trong text node\n\n // Loại bỏ ký tự ẩn trong tính toán\n \n const textBefore = textNode.data.slice(0, caretPos);\n const textAfter = textNode.data.slice(caretPos);\n\n const parent = currentFont.parentNode;\n\n if (caretPos === 0) {\n // Đang ở ĐẦU thẻ font → chèn font mới trước\n const newFont = document.createElement('font');\n newFont.setAttribute('size', String(size));\n newFont.appendChild(document.createTextNode(\"\\u200B\"));\n parent.insertBefore(newFont, currentFont);\n\n moveCaretInside(newFont);\n\n } else if (caretPos === textNode.data.length) {\n // Đang ở CUỐI thẻ font → chèn font mới sau\n const newFont = document.createElement('font');\n newFont.setAttribute('size', String(size));\n newFont.appendChild(document.createTextNode(\"\\u200B\"));\n parent.insertBefore(newFont, currentFont.nextSibling);\n\n moveCaretInside(newFont);\n\n } else {\n\n \n const font1 = document.createElement('font');\n font1.setAttribute('size', currentFont.getAttribute('size'));\n font1.appendChild(document.createTextNode(textBefore));\n\n const font2 = document.createElement('font');\n font2.setAttribute('size', String(size));\n font2.appendChild(document.createTextNode(\"\\u200B\"));\n\n const font3 = document.createElement('font');\n font3.setAttribute('size', currentFont.getAttribute('size'));\n font3.appendChild(document.createTextNode(textAfter));\n\n parent.insertBefore(font1, currentFont);\n parent.insertBefore(font2, currentFont);\n parent.insertBefore(font3, currentFont);\n\n parent.removeChild(currentFont);\n\n moveCaretInside(font2);\n }\n return;\n }\n\n // ========================\n // Trường hợp 3: không ở trong <font> nào → tạo mới\n // ========================\n const newFont = document.createElement('font');\n newFont.setAttribute('size', String(size));\n const zwsp = document.createTextNode(\"\\u200B\");\n newFont.appendChild(zwsp);\n\n range.insertNode(newFont);\n moveCaretInside(newFont);\n\n // Hàm phụ để đưa caret vào sau ký tự ẩn\n function moveCaretInside(fontEl) {\n const sel = window.getSelection();\n const range = document.createRange();\n const textNode = fontEl.firstChild;\n range.setStart(textNode, textNode.length);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n\n \n }\n\n async toggle() {\n if (this.customSelect.isVisible) {\n this.customSelect.hide();\n } else {\n await this.showSizePicker();\n }\n }\n\n async showSizePicker() {\n // Find text-size button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let textSizeButton = null;\n \n if (toolbar) {\n textSizeButton = toolbar.getButton('text-size');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!textSizeButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n textSizeButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n }\n \n // Final fallback: find any text-size button in the current editor's wrapper\n if (!textSizeButton) {\n textSizeButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n \n if (!textSizeButton) {\n console.warn('Text-size button not found for editor:', this.editorId);\n return;\n }\n\n const currentSize = this.getCurrentSize();\n if (currentSize) {\n this.customSelect.setCurrentValue(currentSize);\n }\n\n await this.customSelect.show(textSizeButton);\n }\n\n isActive(size = null) {\n this.updateButtonText();\n return false;\n }\n\n /**\n * Get current text size near caret/selection, return one of '1'..'7'\n */\n getCurrentSize() {\n return TextSize.getCurrentSizeStatic();\n }\n\n /**\n * Static method to get current text size\n * @returns {string} Current text size\n */\n static getCurrentSizeStatic() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return '4';\n\n try {\n // Try to use queryCommandValue when available (returns 1..7 in many browsers)\n const val = queryFormatValue('fontSize');\n const num = parseInt(val, 10);\n if (!isNaN(num) && num >= 1 && num <= 7) {\n return String(num);\n }\n } catch (_) {}\n\n const range = selection.getRangeAt(0);\n let currentNode = range.startContainer;\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentElement;\n }\n\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const element = currentNode;\n const inline = element.style?.fontSize;\n if (inline) return this.normalizeCssSizeToExecSize(inline);\n\n const computed = window.getComputedStyle(element).fontSize;\n if (computed) return this.normalizeCssSizeToExecSize(computed);\n }\n currentNode = currentNode.parentElement;\n }\n\n return '4';\n }\n\n /**\n * Normalize CSS px value to closest execCommand size 1..7\n */\n normalizeCssSizeToExecSize(cssSize) {\n const px = parseFloat(cssSize);\n if (isNaN(px)) return '4';\n const steps = [10, 12, 14, 16, 20, 28, 36];\n let closestIndex = 3; // default to '4' (16px)\n let minDiff = Infinity;\n for (let i = 0; i < steps.length; i++) {\n const diff = Math.abs(px - steps[i]);\n if (diff < minDiff) {\n minDiff = diff;\n closestIndex = i;\n }\n }\n return String(closestIndex + 1);\n }\n}\n\nexport default TextSize;\n\n\n","/**\n * Import Popup Component - Popup for importing various file types\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass ImportPopup {\n constructor(options = {}) {\n this.options = {\n onImport: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.clickOutsideHandler = null;\n this.selectedFile = null;\n this.fileType = null;\n \n this.createImportPopup();\n }\n\n createImportPopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'import-popup';\n \n const content = document.createElement('div');\n content.className = 'import-popup-content';\n \n // Title\n const title = document.createElement('h3');\n title.textContent = 'Import File';\n title.className = 'import-popup-title';\n content.appendChild(title);\n \n // File type selector\n const typeContainer = document.createElement('div');\n typeContainer.className = 'import-type-container';\n \n const typeLabel = document.createElement('label');\n typeLabel.textContent = 'File Type:';\n typeLabel.className = 'import-input-label';\n \n this.typeSelect = document.createElement('select');\n this.typeSelect.className = 'import-type-select';\n this.typeSelect.innerHTML = `\n <option value=\"\">Select file type...</option>\n <option value=\"html\">HTML (.html, .htm)</option>\n <option value=\"excel\">Excel/CSV (.csv, .xlsx, .xls)</option>\n <option value=\"pdf\">PDF (.pdf)</option>\n <option value=\"word\">Word (.doc, .docx)</option>\n `;\n this.typeSelect.addEventListener('change', () => this.updateFileInput());\n \n typeContainer.appendChild(typeLabel);\n typeContainer.appendChild(this.typeSelect);\n content.appendChild(typeContainer);\n \n // File input\n this.fileInput = document.createElement('input');\n this.fileInput.type = 'file';\n this.fileInput.className = 'import-file-input';\n this.fileInput.disabled = true;\n this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));\n \n content.appendChild(this.fileInput);\n \n // File info\n this.fileInfo = document.createElement('div');\n this.fileInfo.className = 'import-file-info';\n this.fileInfo.style.display = 'none';\n content.appendChild(this.fileInfo);\n \n // Buttons\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'import-button-container';\n \n const cancelButton = document.createElement('button');\n cancelButton.type = 'button';\n cancelButton.className = 'import-button cancel-button';\n cancelButton.textContent = 'Cancel';\n cancelButton.addEventListener('click', () => this.hide());\n \n this.importButton = document.createElement('button');\n this.importButton.type = 'button';\n this.importButton.className = 'import-button import-button-main';\n this.importButton.textContent = 'Import';\n this.importButton.disabled = true;\n this.importButton.addEventListener('click', () => this.processImport());\n \n buttonContainer.appendChild(cancelButton);\n buttonContainer.appendChild(this.importButton);\n content.appendChild(buttonContainer);\n \n this.popup.appendChild(content);\n appendPopup(this.popup);\n }\n\n updateFileInput() {\n const selectedType = this.typeSelect.value;\n \n if (selectedType) {\n this.fileType = selectedType;\n this.fileInput.disabled = false;\n \n const acceptTypes = this.getAcceptTypes(selectedType);\n this.fileInput.accept = acceptTypes;\n } else {\n this.fileType = null;\n this.fileInput.disabled = true;\n this.fileInput.accept = '';\n }\n \n this.updateImportButton();\n }\n\n getAcceptTypes(fileType) {\n const types = {\n html: '.html,.htm,text/html',\n excel: '.csv,.xlsx,.xls,text/csv',\n pdf: '.pdf,application/pdf',\n word: '.doc,.docx'\n };\n \n return types[fileType] || '';\n }\n\n handleFileSelect(e) {\n const file = e.target.files[0];\n if (file) {\n this.setSelectedFile(file);\n }\n }\n\n setSelectedFile(file) {\n this.selectedFile = file;\n \n this.fileInfo.style.display = 'block';\n this.fileInfo.innerHTML = `\n <div><strong>Name:</strong> ${file.name}</div>\n <div><strong>Size:</strong> ${this.formatFileSize(file.size)}</div>\n <div><strong>Type:</strong> ${file.type || 'Unknown'}</div>\n `;\n \n this.updateImportButton();\n }\n\n formatFileSize(bytes) {\n if (bytes === 0) return '0 Bytes';\n \n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n }\n\n updateImportButton() {\n this.importButton.disabled = !this.selectedFile || !this.fileType;\n }\n\n async processImport() {\n if (!this.selectedFile || !this.fileType) return;\n \n try {\n let content;\n \n if (this.fileType === 'html') {\n content = await this.readAsText(this.selectedFile);\n } else if (this.fileType === 'excel') {\n if (this.selectedFile.name.toLowerCase().endsWith('.csv')) {\n const csvContent = await this.readAsText(this.selectedFile);\n content = this.parseCSV(csvContent);\n } else {\n alert('Excel files (.xlsx/.xls) require additional libraries. Please use CSV format.');\n return;\n }\n } else if (this.fileType === 'pdf') {\n alert('PDF import requires additional libraries. Feature coming soon.');\n return;\n } else if (this.fileType === 'word') {\n alert('Word document import requires additional libraries. Feature coming soon.');\n return;\n }\n \n if (this.options.onImport) {\n this.options.onImport(content, this.fileType);\n }\n \n this.hide();\n this.reset();\n \n } catch (error) {\n console.error('Import error:', error);\n alert('Error importing file: ' + error.message);\n }\n }\n\n parseCSV(csvContent) {\n const lines = csvContent.split('\\n');\n const result = [];\n \n lines.forEach(line => {\n if (line.trim()) {\n const cells = line.split(',').map(cell => cell.trim().replace(/^[\"']|[\"']$/g, ''));\n result.push(cells);\n }\n });\n \n return result;\n }\n\n readAsText(file) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = (e) => resolve(e.target.result);\n reader.onerror = () => reject(new Error('Failed to read file'));\n reader.readAsText(file);\n });\n }\n\n reset() {\n this.selectedFile = null;\n this.fileType = null;\n this.typeSelect.value = '';\n this.fileInput.value = '';\n this.fileInput.disabled = true;\n this.fileInfo.style.display = 'none';\n this.updateImportButton();\n }\n\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n show(anchor) {\n if (!anchor) return;\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n this.popup.classList.add('visible');\n this.isVisible = true;\n \n this.setupClickOutside();\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n }\n}\n\nexport default ImportPopup; ","import { InlineFormat } from '../core/format.js';\nimport ImportPopup from '../ui/import-popup.js';\nimport Editor from '../core/editor.js';\nimport { sanitizeHtml } from '../utils/sanitize.js';\n\n/**\n * Import Format - Handles importing various file types\n * Now supports multiple editor instances with separate popup instances\n */\nclass Import extends InlineFormat {\n static formatName = 'import';\n static tagName = 'DIV';\n static className = 'imported-content';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Import format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has an import popup instance\n let importPopup = currentEditor.getPopupInstance('import');\n \n if (!importPopup) {\n // Create new import popup instance for this editor\n importPopup = new ImportPopup({\n onImport: (content, fileType) => {\n Import.insertImportedContent(content, fileType, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('import', importPopup);\n }\n \n this.importPopup = importPopup;\n }\n\n /**\n * Create a new Import format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Import} Import format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Import();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Insert imported content at current cursor position\n * @param {string} content - Imported content\n * @param {string} fileType - Type of imported file\n * @param {string} editorId - Editor instance ID\n */\n static insertImportedContent(content, fileType, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for content insertion');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n try {\n const range = selection.getRangeAt(0);\n \n // Create content element based on file type\n let contentElement;\n \n if (fileType === 'html') {\n contentElement = Import.processHtmlContent(content);\n } else if (fileType === 'excel') {\n contentElement = Import.processExcelContent(content);\n } else if (fileType === 'pdf' || fileType === 'word') {\n contentElement = Import.processTextContent(content);\n } else {\n contentElement = Import.processTextContent(content);\n }\n \n // Insert content at cursor position\n range.deleteContents();\n range.insertNode(contentElement);\n \n // Position cursor after the content\n range.setStartAfter(contentElement);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n \n } catch (error) {\n console.error('Error inserting imported content:', error);\n }\n }\n\n /**\n * Process HTML content\n * @param {string} htmlContent - HTML content to process\n * @returns {HTMLElement}\n */\n static processHtmlContent(htmlContent) {\n const container = document.createElement('div');\n container.className = 'imported-content html-content';\n\n // Sanitize untrusted HTML (DOMParser-based, inert) before inserting it.\n // sanitizeHtml strips scripts, event handlers and unsafe URLs.\n container.innerHTML = sanitizeHtml(htmlContent);\n\n // Additionally enforce the import tag/attribute whitelist.\n Import.cleanHtmlContent(container);\n\n return container;\n }\n\n /**\n * Process Excel content (CSV-like data)\n * @param {Array} data - Excel data as array of arrays\n * @returns {HTMLElement}\n */\n static processExcelContent(data) {\n const container = document.createElement('div');\n container.className = 'imported-content excel-content';\n \n if (!Array.isArray(data) || data.length === 0) {\n container.textContent = 'No data to import';\n return container;\n }\n \n // Create table\n const table = document.createElement('table');\n table.className = 'imported-table';\n \n // Add header row if available\n if (data.length > 0) {\n const thead = document.createElement('thead');\n const headerRow = document.createElement('tr');\n \n data[0].forEach(cellData => {\n const th = document.createElement('th');\n th.textContent = cellData || '';\n headerRow.appendChild(th);\n });\n \n thead.appendChild(headerRow);\n table.appendChild(thead);\n }\n \n // Add data rows\n if (data.length > 1) {\n const tbody = document.createElement('tbody');\n \n for (let i = 1; i < data.length; i++) {\n const row = document.createElement('tr');\n \n data[i].forEach(cellData => {\n const td = document.createElement('td');\n td.textContent = cellData || '';\n row.appendChild(td);\n });\n \n tbody.appendChild(row);\n }\n \n table.appendChild(tbody);\n }\n \n container.appendChild(table);\n return container;\n }\n\n /**\n * Process plain text content (PDF, Word)\n * @param {string} textContent - Text content to process\n * @returns {HTMLElement}\n */\n static processTextContent(textContent) {\n const container = document.createElement('div');\n container.className = 'imported-content text-content';\n \n // Split into paragraphs and process\n const paragraphs = textContent.split(/\\n\\s*\\n/);\n \n paragraphs.forEach(paragraph => {\n if (paragraph.trim()) {\n const p = document.createElement('p');\n p.textContent = paragraph.trim();\n container.appendChild(p);\n }\n });\n \n return container;\n }\n\n /**\n * Clean HTML content by removing dangerous elements and attributes\n * @param {HTMLElement} element - Element to clean\n */\n static cleanHtmlContent(element) {\n const allowedTags = ['p', 'div', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', \n 'strong', 'b', 'em', 'i', 'u', 'ul', 'ol', 'li', 'br', \n 'table', 'thead', 'tbody', 'tr', 'th', 'td'];\n \n const allowedAttrs = ['class', 'style'];\n \n const walker = document.createTreeWalker(\n element,\n NodeFilter.SHOW_ELEMENT,\n null,\n false\n );\n \n const elementsToRemove = [];\n \n while (walker.nextNode()) {\n const node = walker.currentNode;\n \n // Remove dangerous tags\n if (!allowedTags.includes(node.tagName.toLowerCase())) {\n elementsToRemove.push(node);\n continue;\n }\n \n // Clean attributes\n const attrs = Array.from(node.attributes);\n attrs.forEach(attr => {\n if (!allowedAttrs.includes(attr.name.toLowerCase())) {\n node.removeAttribute(attr.name);\n }\n });\n }\n \n // Remove dangerous elements\n elementsToRemove.forEach(el => {\n if (el.parentNode) {\n el.parentNode.removeChild(el);\n }\n });\n }\n\n /**\n * Parse CSV content\n * @param {string} csvContent - CSV content\n * @returns {Array} - Array of arrays representing CSV data\n */\n static parseCSV(csvContent) {\n const lines = csvContent.split('\\n');\n const result = [];\n \n lines.forEach(line => {\n if (line.trim()) {\n // Simple CSV parsing (doesn't handle quoted values with commas)\n const cells = line.split(',').map(cell => cell.trim().replace(/^[\"']|[\"']$/g, ''));\n result.push(cells);\n }\n });\n \n return result;\n }\n\n /**\n * Apply import formatting - shows import popup\n */\n apply() {\n this.showImportPopup();\n }\n\n /**\n * Toggle import formatting - shows import popup\n */\n toggle() {\n if (this.importPopup.isVisible) {\n this.importPopup.hide();\n } else {\n this.showImportPopup();\n }\n }\n\n /**\n * Show import popup\n */\n showImportPopup() {\n // Find import button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let importButton = null;\n \n if (toolbar) {\n importButton = toolbar.getButton('import');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!importButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n importButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.import-btn');\n }\n }\n \n // Final fallback: find any import button in the current editor's wrapper\n if (!importButton) {\n importButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.import-btn');\n }\n \n if (!importButton) {\n console.warn('Import button not found for editor:', this.editorId);\n return;\n }\n \n this.importPopup.show(importButton);\n }\n\n /**\n * Check if import formatting is active\n */\n isActive() {\n return false; // Import doesn't have an \"active\" state\n }\n\n /**\n * Get supported file types\n */\n static getSupportedTypes() {\n return {\n html: {\n extensions: ['.html', '.htm'],\n mimeTypes: ['text/html'],\n name: 'HTML Files'\n },\n excel: {\n extensions: ['.csv', '.xlsx', '.xls'],\n mimeTypes: ['text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],\n name: 'Excel Files'\n },\n pdf: {\n extensions: ['.pdf'],\n mimeTypes: ['application/pdf'],\n name: 'PDF Files'\n },\n word: {\n extensions: ['.doc', '.docx'],\n mimeTypes: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],\n name: 'Word Documents'\n }\n };\n }\n}\n\nexport default Import; ","import IconUtils from './icons.js';\n\n/**\n * Create Custom Button - Simple utility to create styled button\n * @param {string} text - Button text content\n * @param {Object} options - Button options\n * @param {string} options.width - Button width (e.g., '120px', 'auto')\n * @returns {HTMLElement} Button element\n */\nfunction createCustomButton(text = 'Button', options = {}) {\n const { width = 'auto', icon = null } = options;\n\n // Create button\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'custom-select-button';\n\n // Optional leading icon so the control is recognisable at a glance.\n if (icon) {\n const leadIcon = IconUtils.createIconElement(icon);\n leadIcon.className = 'select-lead-icon';\n button.appendChild(leadIcon);\n }\n\n // Create text span\n const textSpan = document.createElement('span');\n textSpan.textContent = text;\n textSpan.className = 'button-text';\n\n // Create dropdown icon\n const dropdownIcon = IconUtils.createIconElement('dropdown');\n dropdownIcon.className = 'dropdown-icon';\n\n // Add text and icon to button\n button.appendChild(textSpan);\n button.appendChild(dropdownIcon);\n \n // Apply styles\n button.style.width = width;\n button.style.padding = '0px 5px 0px 8px';\n button.style.setProperty('height', '32px', 'important');\n button.style.setProperty('borderRadius', '6px', 'important');\n button.style.setProperty('alignItems', 'center', 'important');\n button.style.fontSize = '14px';\n button.style.fontWeight = '400';\n button.style.color = '#374151';\n button.style.background = '#FFFFFF';\n button.style.cursor = 'pointer';\n button.style.border = '1px solid #d1d5db';\n button.style.display = 'flex';\n button.style.justifyContent = 'space-between';\n button.style.alignItems = 'center';\n \n // Style the text span to take available space\n textSpan.style.flex = '1';\n textSpan.style.textAlign = 'left';\n \n // Style the dropdown icon\n \n // Add method to update button text\n button.updateText = function(newText) {\n textSpan.textContent = newText;\n };\n \n return button;\n}\n\nexport default createCustomButton; ","import Module from '../core/module.js';\nimport ColorPicker from '../ui/color-picker.js';\nimport IconUtils from '../ui/icons.js';\nimport createCustomButton from '../ui/select-button.js';\n\n/**\n * Toolbar Module - Pure UI component with dual toolbar support\n * Only handles toolbar creation and event emission\n * No business logic or state management\n */\nclass Toolbar extends Module {\n static DEFAULTS = {\n container: null,\n toolbar1: [\n // Most-used inline formatting leads; the block-style (Paragraph) picker\n // sits after it rather than first.\n { group: 'text-format', items: ['bold', 'italic', 'underline', 'strike'] },\n { group: 'paragraph', items: ['heading'] },\n { group: 'colors', items: ['color', 'background'] },\n { group: 'link', items: ['link'] },\n { group: 'paragraph-ops', items: ['list', 'indent-increase', 'indent-decrease', 'text-align'] },\n { group: 'insert', items: ['image', 'table'] },\n // Undo/redo live on the right and stay hidden until there's history.\n { group: 'history', items: ['undo', 'redo'] },\n { group: 'more', items: ['more'] }\n ],\n toolbar2: [\n { group: 'font', items: ['font-family', 'text-size', 'line-height'] },\n { group: 'script', items: ['subscript', 'superscript', 'capitalization'] },\n { group: 'media', items: ['emoji', 'video', 'tag', 'horizontal-rule'] },\n { group: 'tools', items: ['clear-format', 'text-direction', 'find', 'code-view'] }\n ]\n };\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.buttons = new Map();\n this.toolbar2Visible = false;\n this.events = new Map(); // Add event system\n \n \n // Handle toolbar configuration\n if (Array.isArray(options.toolbar)) {\n // If toolbar array is provided, use only those items - COMPLETELY OVERRIDE DEFAULTS\n this.options = {\n container: null,\n toolbar1: [\n { group: 'text-format', items: options.toolbar }\n ],\n toolbar2: []\n };\n } else if (options.toolbar1 || options.toolbar2) {\n // If specific toolbar1/toolbar2 config is provided, use it - COMPLETELY OVERRIDE DEFAULTS\n this.options = {\n container: null,\n toolbar1: options.toolbar1 || [],\n toolbar2: options.toolbar2 || []\n };\n } else {\n // Use full default configuration\n this.options = { ...Toolbar.DEFAULTS, ...options };\n }\n \n \n this.init();\n this.preloadIcons();\n }\n\n init() {\n this.container = this.createToolbarContainer();\n }\n\n /**\n * Preload icons for better performance\n */\n async preloadIcons() {\n // Icons are now inline, no need to preload\n // This method is kept for backward compatibility\n }\n\n /**\n * Create main toolbar container with both toolbars\n */\n createToolbarContainer() {\n const container = document.createElement('div');\n container.className = 'rich-editor-toolbar-container';\n container.setAttribute('role', 'toolbar');\n container.setAttribute('aria-label', 'Text formatting');\n\n // Prevent toolbar from taking focus away from editor\n this.editor.preventFocusLoss(container);\n\n // Keep the editor's text selection when a toolbar button is pressed (mouse\n // OR touch). Without this, tapping e.g. Bold on mobile can clear the\n // selection before the click handler runs, so the format applies to nothing.\n // preventing pointerdown's default stops the focus/selection change while\n // the click still fires normally.\n container.addEventListener('pointerdown', (e) => {\n if (e.target.closest('button')) e.preventDefault();\n });\n\n // Primary (always-visible) row and overflow (\"more\") row\n this.toolbar1 = document.createElement('div');\n this.toolbar1.className = 'rich-editor-toolbar-1';\n this.toolbar2 = document.createElement('div');\n this.toolbar2.className = 'rich-editor-toolbar-2';\n this.toolbar2.style.display = 'none';\n\n // Build every group (toolbar1 first = higher priority, then toolbar2) into\n // the primary row. reflow() later moves whatever doesn't fit into the\n // overflow row, so the toolbar adapts to any width instead of wrapping.\n this.flowGroups = [];\n const merged = [...(this.options.toolbar1 || []), ...(this.options.toolbar2 || [])];\n merged.forEach(group => {\n if (!group || !group.group || !Array.isArray(group.items)) return;\n // The \"more\" toggle is managed separately (added at the end).\n if (group.items.length === 1 && group.items[0] === 'more') return;\n const groupContainer = document.createElement('div');\n groupContainer.className = `toolbar-group toolbar-group-${group.group}`;\n group.items.forEach(item => {\n if (typeof item === 'string') this.addButton(groupContainer, item);\n });\n this.toolbar1.appendChild(groupContainer);\n this.flowGroups.push(groupContainer);\n });\n\n // The \"more\" button lives at the end of the primary row; shown only when\n // there is overflow.\n this.addMoreButton(this.toolbar1);\n this.moreBtn = this.buttons.get('more');\n if (this.moreBtn) this.moreBtn.classList.add('more-btn');\n\n container.appendChild(this.toolbar1);\n container.appendChild(this.toolbar2);\n\n // Responsive reflow: re-distribute groups whenever the toolbar resizes.\n if (typeof ResizeObserver !== 'undefined') {\n this._ro = new ResizeObserver(() => this._scheduleReflow());\n this._ro.observe(container);\n }\n requestAnimationFrame(() => this.reflow());\n\n // Keyboard navigation (arrow keys move between buttons; roving tabindex).\n this._setupKeyboardNav(container);\n\n return container;\n }\n\n /**\n * All currently focusable (visible, enabled) toolbar buttons in order.\n */\n _focusableButtons() {\n return Array.from(\n this.container.querySelectorAll('.rich-editor-toolbar-btn, .custom-select-button')\n ).filter(b => !b.disabled && b.offsetParent !== null);\n }\n\n /**\n * Roving tabindex: only one button is in the tab order at a time.\n */\n _updateRoving() {\n const btns = this._focusableButtons();\n btns.forEach((b, i) => { b.tabIndex = i === 0 ? 0 : -1; });\n }\n\n /**\n * Arrow-key navigation across the toolbar (ARIA toolbar pattern).\n */\n _setupKeyboardNav(container) {\n container.addEventListener('keydown', (e) => {\n if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) return;\n const btns = this._focusableButtons();\n if (!btns.length) return;\n const cur = btns.indexOf(document.activeElement);\n let next;\n if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = btns.length - 1;\n else if (cur === -1) next = 0;\n else next = e.key === 'ArrowRight'\n ? (cur + 1) % btns.length\n : (cur - 1 + btns.length) % btns.length;\n e.preventDefault();\n btns.forEach(b => { b.tabIndex = -1; });\n btns[next].tabIndex = 0;\n btns[next].focus();\n });\n }\n\n /**\n * Debounce reflow to one run per animation frame.\n */\n _scheduleReflow() {\n if (this._reflowQueued) return;\n this._reflowQueued = true;\n requestAnimationFrame(() => {\n this._reflowQueued = false;\n this.reflow();\n });\n }\n\n /**\n * Distribute groups between the primary row and the overflow (\"more\") row so\n * the primary row always fits on a single line at the current width.\n */\n reflow() {\n if (!this.toolbar1 || !this.flowGroups || !this.moreBtn) return;\n const GAP = 12; // matches .toolbar-group spacing\n\n // Pull every group back into the primary row (in priority order) to measure.\n this.flowGroups.forEach(g => this.toolbar1.insertBefore(g, this.moreBtn));\n\n // On small screens, skip the \"More\" split entirely — keep every tool in one\n // horizontally-scrollable row (how Google Docs / Notion handle mobile)\n // instead of wrapping into a cramped multi-row panel.\n if (typeof window !== 'undefined' && window.matchMedia &&\n window.matchMedia('(max-width: 640px)').matches) {\n this.moreBtn.style.display = 'none';\n this.toolbar2.style.display = 'none';\n this.toolbar2Visible = false;\n this._syncMoreButton();\n this._updateRoving();\n return;\n }\n\n const cs = getComputedStyle(this.toolbar1);\n const avail = this.toolbar1.clientWidth -\n (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0);\n if (avail <= 0) return; // not laid out yet; will reflow on resize\n\n let total = 0;\n this.flowGroups.forEach((g, i) => { total += g.offsetWidth + (i > 0 ? GAP : 0); });\n\n if (total <= avail) {\n // Everything fits — no overflow needed.\n this.moreBtn.style.display = 'none';\n this.toolbar2.style.display = 'none';\n this.toolbar2Visible = false;\n this._syncMoreButton();\n this._updateRoving();\n return;\n }\n\n // Overflow needed — keep groups that fit (reserving room for \"more\").\n const budget = avail - ((this.moreBtn.offsetWidth || 32) + GAP);\n let used = 0;\n let cut = this.flowGroups.length;\n for (let i = 0; i < this.flowGroups.length; i++) {\n const w = this.flowGroups[i].offsetWidth + (i > 0 ? GAP : 0);\n if (used + w > budget) { cut = i; break; }\n used += w;\n }\n if (cut < 1) cut = 1; // always keep at least one group visible\n\n for (let i = cut; i < this.flowGroups.length; i++) {\n this.toolbar2.appendChild(this.flowGroups[i]);\n }\n\n this.moreBtn.style.display = '';\n this.toolbar2.style.display = this.toolbar2Visible ? 'flex' : 'none';\n this._syncMoreButton();\n this._updateRoving();\n }\n\n /**\n * Sync the \"more\" button visual state with toolbar2 visibility.\n */\n _syncMoreButton() {\n const m = this.moreBtn;\n if (!m) return;\n m.setAttribute('aria-expanded', this.toolbar2Visible ? 'true' : 'false');\n if (this.toolbar2Visible) {\n m.classList.add('active');\n m.title = 'Hide more options';\n } else {\n m.classList.remove('active');\n m.title = 'More options';\n }\n }\n\n /**\n * Create toolbar element\n */\n createToolbar(className, toolbarItems) {\n const toolbar = document.createElement('div');\n toolbar.className = className;\n\n // Create button groups based on toolbar config\n if (Array.isArray(toolbarItems)) {\n toolbarItems.forEach(group => {\n if (group && group.group && Array.isArray(group.items)) {\n // Create group container\n const groupContainer = document.createElement('div');\n groupContainer.className = `toolbar-group toolbar-group-${group.group}`;\n \n // Add buttons to group\n group.items.forEach(item => {\n if (typeof item === 'string') {\n this.addButton(groupContainer, item);\n }\n });\n \n toolbar.appendChild(groupContainer);\n }\n });\n }\n\n return toolbar;\n }\n\n /**\n * Add button to toolbar\n */\n addButton(container, format) {\n // Special handling for more button\n if (format === 'more') {\n return this.addMoreButton(container);\n }\n\n // Custom buttons with dropdowns\n const customButtons = {\n 'heading': { text: 'Paragraph', width: '124px', title: 'Paragraph style', icon: 'heading' },\n 'font-family': { text: 'Font Family', width: '156px', title: 'Font', icon: 'font-family' },\n 'line-height': { text: 'Line Height', width: '116px', title: 'Line spacing', icon: 'line-height' },\n 'capitalization': { text: 'Capitalization', width: '146px', title: 'Letter case', icon: 'capitalization' },\n 'text-size': { text: 'Text Size', width: '116px', title: 'Font size', icon: 'text-size' }\n };\n\n if (customButtons[format]) {\n const config = customButtons[format];\n const customButton = createCustomButton(config.text, { width: config.width, icon: config.icon });\n customButton.dataset.command = format;\n customButton.classList.add('rich-editor-toolbar-btn', `${format}-btn`);\n customButton.title = config.title;\n customButton.setAttribute('aria-label', config.title);\n customButton.setAttribute('aria-haspopup', 'true');\n \n customButton.addEventListener('click', (e) => {\n e.preventDefault();\n this.emit('toolbar-click', { command: format, button: customButton });\n // Maintain editor focus after button click\n setTimeout(() => {\n this.editor.focus();\n }, 0);\n });\n\n this.buttons.set(format, customButton);\n container.appendChild(customButton);\n return customButton;\n }\n\n // Icon buttons with popups\n const iconButtons = {\n 'text-align': { icon: 'align-left', title: 'Align Left' },\n 'list': { icon: 'list', title: 'List' }\n };\n\n if (iconButtons[format]) {\n const config = iconButtons[format];\n const button = document.createElement('button');\n button.type = 'button';\n button.className = `rich-editor-toolbar-btn ${format}-btn`;\n button.dataset.command = format;\n button.title = config.title;\n button.setAttribute('aria-label', config.title);\n\n const svgContent = IconUtils.getIcon(config.icon);\n if (svgContent) {\n button.innerHTML = `<span class=\"icon\">${svgContent}</span>`;\n } else {\n button.textContent = format === 'text-align' ? '≡' : '•';\n }\n \n button.addEventListener('click', (e) => {\n e.preventDefault();\n this.emit('toolbar-click', { command: format, button: button });\n // Maintain editor focus after button click\n setTimeout(() => {\n this.editor.focus();\n }, 0);\n });\n\n this.buttons.set(format, button);\n container.appendChild(button);\n return button;\n }\n\n // Regular icon buttons\n const button = document.createElement('button');\n button.type = 'button';\n button.className = `rich-editor-toolbar-btn ${format}-btn`;\n button.dataset.command = format;\n \n // Add icon\n const iconElement = IconUtils.createIconElement(format, {\n width: '16px',\n height: '16px'\n });\n button.appendChild(iconElement);\n \n // Set title based on format\n const titles = {\n 'bold': 'Bold (Ctrl+B)',\n 'italic': 'Italic (Ctrl+I)',\n 'underline': 'Underline (Ctrl+U)',\n 'strike': 'Strikethrough',\n 'subscript': 'Subscript',\n 'superscript': 'Superscript',\n 'color': 'Text Color',\n 'background': 'Background Color',\n 'link': 'Insert/Edit Link',\n 'table': 'Insert Table',\n 'undo': 'Undo (Ctrl+Z)',\n 'redo': 'Redo (Ctrl+Y)',\n 'indent-increase': 'Increase Indent',\n 'indent-decrease': 'Decrease Indent',\n 'emoji': 'Insert Emoji',\n 'image': 'Insert Image',\n 'video': 'Insert Video',\n 'tag': 'Insert Tag',\n 'horizontal-rule': 'Insert Horizontal Rule',\n 'clear-format': 'Clear Formatting',\n 'text-direction': 'Toggle Text Direction (LTR/RTL)',\n 'find': 'Find & Replace (Ctrl+F)',\n\n 'import': 'Import Files',\n 'code-view': 'Switch to HTML Editor',\n\n };\n \n button.title = titles[format] || format;\n button.setAttribute('aria-label', button.title);\n\n // Colour buttons get a swatch bar that reflects the colour at the caret.\n if (format === 'color' || format === 'background') {\n const swatch = document.createElement('span');\n swatch.className = 'rte-swatch';\n button.appendChild(swatch);\n }\n\n // Add fallback for code-view\n if (format === 'code-view') {\n setTimeout(() => {\n if (!iconElement.innerHTML.trim()) {\n iconElement.innerHTML = '&lt;/&gt;';\n iconElement.style.fontSize = '12px';\n iconElement.style.fontWeight = 'bold';\n }\n }, 1000);\n }\n \n button.addEventListener('click', (e) => {\n e.preventDefault();\n this.emit('toolbar-click', { command: format, button });\n // Maintain editor focus after button click\n setTimeout(() => {\n this.editor.focus();\n }, 0);\n });\n\n this.buttons.set(format, button);\n container.appendChild(button);\n return button;\n }\n\n /**\n * Add more button to toggle toolbar 2\n */\n addMoreButton(container) {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'rich-editor-toolbar-btn more-btn';\n button.dataset.command = 'more';\n \n const iconElement = IconUtils.createIconElement('more', {\n width: '16px',\n height: '16px'\n });\n button.appendChild(iconElement);\n button.title = 'More Options';\n button.setAttribute('aria-label', 'More Options');\n button.setAttribute('aria-expanded', 'false');\n\n button.addEventListener('click', (e) => {\n e.preventDefault();\n this.toggleToolbar2();\n // Maintain editor focus after button click\n setTimeout(() => {\n this.editor.focus();\n }, 0);\n });\n\n this.buttons.set('more', button);\n container.appendChild(button);\n return button;\n }\n\n /**\n * Toggle toolbar 2 visibility\n */\n toggleToolbar2() {\n // Nothing to toggle when there's no overflow.\n if (this.moreBtn && this.moreBtn.style.display === 'none') return;\n\n this.toolbar2Visible = !this.toolbar2Visible;\n this.toolbar2.style.display = this.toolbar2Visible ? 'flex' : 'none';\n this._syncMoreButton();\n this._updateRoving();\n }\n\n /**\n * Get toolbar container element\n */\n getContainer() {\n return this.container;\n }\n\n /**\n * Get button by command\n */\n getButton(command) {\n return this.buttons.get(command);\n }\n\n /**\n * Set button active state\n */\n setButtonActive(command, isActive) {\n const button = this.buttons.get(command);\n if (button && button.classList) {\n if (isActive) {\n button.classList.add('active');\n } else {\n button.classList.remove('active');\n }\n button.setAttribute('aria-pressed', isActive ? 'true' : 'false');\n }\n }\n\n /**\n * Set button disabled state\n */\n setButtonDisabled(command, isDisabled) {\n const button = this.buttons.get(command);\n if (button) {\n button.disabled = isDisabled;\n button.style.opacity = isDisabled ? '0.5' : '1';\n button.style.cursor = isDisabled ? 'not-allowed' : 'pointer';\n }\n }\n\n /**\n * Set button title\n */\n setButtonTitle(command, title) {\n const button = this.buttons.get(command);\n if (button) {\n button.title = title;\n }\n }\n\n /**\n * Check if toolbar 2 is visible\n */\n isToolbar2Visible() {\n return this.toolbar2Visible;\n }\n\n /**\n * Event system methods\n */\n on(event, callback) {\n if (!this.events.has(event)) {\n this.events.set(event, []);\n }\n this.events.get(event).push(callback);\n }\n\n emit(event, data) {\n const callbacks = this.events.get(event);\n if (callbacks) {\n callbacks.forEach(callback => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in toolbar event ${event}:`, error);\n }\n });\n }\n }\n\n /**\n * Destroy toolbar\n */\n destroy() {\n if (this._ro) {\n this._ro.disconnect();\n this._ro = null;\n }\n if (this.container && this.container.parentNode) {\n this.container.parentNode.removeChild(this.container);\n }\n this.buttons.clear();\n this.events.clear();\n }\n}\n\nexport default Toolbar; ","import Module from '../core/module.js';\n\n/**\n * History Module - Handles undo/redo functionality\n * Extracted from FormatManager.js and ToolbarManager.js logic\n */\nclass History extends Module {\n static DEFAULTS = {\n delay: 1000, // Delay between history saves\n maxStack: 100, // Maximum number of undo states\n userOnly: false // Only save user-initiated changes\n };\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.stack = [];\n this.index = -1;\n this.lastSave = 0;\n this.savedSelection = null;\n \n this.init();\n }\n\n init() {\n this.setupEventListeners();\n this.saveState(); // Save initial state\n }\n\n /**\n * Setup event listeners for automatic history saving\n */\n setupEventListeners() {\n // Keep references so the listeners can be removed in destroy().\n this._onInput = () => {\n this.handleInput();\n };\n\n this._onKeydownSave = (e) => {\n // Save state before destructive operations\n if (e.key === 'Enter' || e.key === 'Backspace' || e.key === 'Delete') {\n this.saveState();\n }\n };\n\n this._onToolbarClick = (e) => {\n if (e.target.closest('.rich-editor-toolbar-btn')) {\n // Save state before applying format\n setTimeout(() => {\n this.saveState();\n }, 0);\n }\n };\n\n this._onUndoRedo = (e) => {\n if ((e.ctrlKey || e.metaKey) && !e.shiftKey && e.key === 'z') {\n e.preventDefault();\n this.undo();\n } else if (((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z') ||\n ((e.ctrlKey || e.metaKey) && e.key === 'y')) {\n e.preventDefault();\n this.redo();\n }\n };\n\n // Save state on input with debouncing\n this.editor.editor.addEventListener('input', this._onInput);\n // Save state on specific commands\n this.editor.editor.addEventListener('keydown', this._onKeydownSave);\n // Listen for toolbar clicks to save state before formatting\n this.editor.wrapper.addEventListener('click', this._onToolbarClick);\n // Handle undo/redo shortcuts - only when editor is focused\n this.editor.editor.addEventListener('keydown', this._onUndoRedo);\n\n // Listen for DOM changes to catch all formatting operations\n this.setupMutationObserver();\n }\n\n /**\n * Setup mutation observer to watch for DOM changes\n */\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n let shouldSave = false;\n \n for (const mutation of mutations) {\n // Check if the mutation is relevant (not just attribute changes on non-content elements)\n if (mutation.type === 'childList' || \n (mutation.type === 'attributes' && \n (mutation.target.nodeType === Node.TEXT_NODE || \n ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI', 'SPAN', 'STRONG', 'EM', 'U', 'S', 'SUB', 'SUP', 'A', 'IMG', 'VIDEO', 'TABLE', 'TR', 'TD', 'TH'].includes(mutation.target.tagName)))) {\n shouldSave = true;\n break;\n }\n }\n \n if (shouldSave) {\n // Debounce the save to avoid too many saves\n clearTimeout(this.mutationTimeout);\n this.mutationTimeout = setTimeout(() => {\n this.saveState();\n }, 100);\n }\n });\n\n // Start observing\n this.mutationObserver.observe(this.editor.editor, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class', 'href', 'src', 'alt', 'title']\n });\n }\n\n /**\n * Handle input event with debouncing\n */\n handleInput() {\n const now = Date.now();\n if (now - this.lastSave > this.options.delay) {\n this.saveState();\n this.lastSave = now;\n }\n }\n\n /**\n * Save current editor state\n */\n saveState() {\n const content = this.editor.getContent();\n const selection = this.saveSelection();\n \n // Don't save if content hasn't changed\n if (this.stack.length > 0 && this.stack[this.index]?.content === content) {\n return;\n }\n\n // Don't save if it's too soon after last save (debouncing)\n const now = Date.now();\n if (this.lastSave && now - this.lastSave < 50) {\n return;\n }\n\n // Remove any redo states if we're not at the end\n if (this.index < this.stack.length - 1) {\n this.stack.splice(this.index + 1);\n }\n\n // Add new state\n this.stack.push({\n content,\n selection,\n timestamp: now\n });\n\n // Limit stack size\n if (this.stack.length > this.options.maxStack) {\n this.stack.shift();\n } else {\n this.index++;\n }\n\n this.lastSave = now;\n }\n\n /**\n * Undo last change\n */\n undo() {\n if (!this.canUndo()) return false;\n\n this.index--;\n const state = this.stack[this.index];\n \n this.restoreState(state);\n this.onHistoryChange('undo');\n \n return true;\n }\n\n /**\n * Redo last undone change\n */\n redo() {\n if (!this.canRedo()) return false;\n\n this.index++;\n const state = this.stack[this.index];\n \n this.restoreState(state);\n this.onHistoryChange('redo');\n \n return true;\n }\n\n /**\n * Check if undo is possible\n */\n canUndo() {\n return this.index > 0;\n }\n\n /**\n * Check if redo is possible\n */\n canRedo() {\n return this.index < this.stack.length - 1;\n }\n\n /**\n * Restore editor state\n * @param {object} state - State to restore\n */\n restoreState(state) {\n if (!state) return;\n\n // Restore content\n this.editor.setContent(state.content);\n \n // Restore selection\n if (state.selection) {\n setTimeout(() => {\n this.restoreSelection(state.selection);\n }, 10);\n }\n }\n\n /**\n * Save current selection\n */\n saveSelection() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n const editorEl = this.editor.editor;\n \n // Calculate offset relative to editor\n const startOffset = this.getOffsetInEditor(range.startContainer, range.startOffset, editorEl);\n const endOffset = this.getOffsetInEditor(range.endContainer, range.endOffset, editorEl);\n \n return {\n startOffset,\n endOffset,\n collapsed: range.collapsed\n };\n }\n\n /**\n * Restore selection\n * @param {object} selectionState - Selection state to restore\n */\n restoreSelection(selectionState) {\n if (!selectionState) return;\n\n const editorEl = this.editor.editor;\n const range = document.createRange();\n const selection = window.getSelection();\n\n try {\n const startNode = this.getNodeAtOffset(editorEl, selectionState.startOffset);\n const endNode = this.getNodeAtOffset(editorEl, selectionState.endOffset);\n\n if (startNode && endNode) {\n range.setStart(startNode.node, startNode.offset);\n range.setEnd(endNode.node, endNode.offset);\n \n selection.removeAllRanges();\n selection.addRange(range);\n }\n } catch (error) {\n console.warn('Could not restore selection:', error);\n // Fallback: focus editor\n this.editor.focus();\n }\n }\n\n /**\n * Get offset of a position within editor\n * @param {Node} node - DOM node\n * @param {number} offset - Offset within node\n * @param {Element} root - Root element (editor)\n */\n getOffsetInEditor(node, offset, root) {\n let totalOffset = 0;\n const walker = document.createTreeWalker(\n root,\n NodeFilter.SHOW_TEXT,\n null,\n false\n );\n\n let currentNode;\n while (currentNode = walker.nextNode()) {\n if (currentNode === node) {\n return totalOffset + offset;\n }\n totalOffset += currentNode.textContent.length;\n }\n\n return totalOffset;\n }\n\n /**\n * Get node at specific offset within editor\n * @param {Element} root - Root element (editor)\n * @param {number} targetOffset - Target offset\n */\n getNodeAtOffset(root, targetOffset) {\n let currentOffset = 0;\n const walker = document.createTreeWalker(\n root,\n NodeFilter.SHOW_TEXT,\n null,\n false\n );\n\n let currentNode;\n while (currentNode = walker.nextNode()) {\n const nodeLength = currentNode.textContent.length;\n if (currentOffset + nodeLength >= targetOffset) {\n return {\n node: currentNode,\n offset: targetOffset - currentOffset\n };\n }\n currentOffset += nodeLength;\n }\n\n // Fallback: return last node\n return {\n node: root.lastChild || root,\n offset: 0\n };\n }\n\n /**\n * Clear history\n */\n clear() {\n this.stack = [];\n this.index = -1;\n this.saveState(); // Save current state as first entry\n }\n\n /**\n * Get current history state info\n */\n getState() {\n return {\n canUndo: this.canUndo(),\n canRedo: this.canRedo(),\n stackLength: this.stack.length,\n currentIndex: this.index\n };\n }\n\n /**\n * Called when history changes (undo/redo)\n * @param {string} action - 'undo' or 'redo'\n */\n onHistoryChange(action) {\n // Notify other modules about history change\n this.editor.modules.forEach(module => {\n if (module !== this && typeof module.onHistoryChange === 'function') {\n module.onHistoryChange(action, this.getState());\n }\n });\n\n // Trigger custom event\n const event = new CustomEvent('historychange', {\n detail: { action, state: this.getState() }\n });\n this.editor.editor.dispatchEvent(event);\n }\n\n /**\n * Force save current state (useful before major operations)\n */\n forceSave() {\n // Temporarily disable debouncing for force save\n const originalLastSave = this.lastSave;\n this.lastSave = 0;\n this.saveState();\n this.lastSave = originalLastSave;\n }\n\n /**\n * Save state before applying format (called by editor)\n */\n saveBeforeFormat() {\n this.forceSave();\n }\n\n /**\n * Destroy module\n */\n destroy() {\n // Remove event listeners\n if (this._onInput) {\n this.editor.editor.removeEventListener('input', this._onInput);\n this.editor.editor.removeEventListener('keydown', this._onKeydownSave);\n this.editor.editor.removeEventListener('keydown', this._onUndoRedo);\n this.editor.wrapper.removeEventListener('click', this._onToolbarClick);\n this._onInput = this._onKeydownSave = this._onUndoRedo = this._onToolbarClick = null;\n }\n\n // Disconnect mutation observer\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n this.mutationObserver = null;\n }\n\n // Clear timeout\n if (this.mutationTimeout) {\n clearTimeout(this.mutationTimeout);\n this.mutationTimeout = null;\n }\n\n this.stack = [];\n this.index = -1;\n this.savedSelection = null;\n }\n}\n\nexport default History; ","import Module from '../core/module.js';\nimport IconUtils from '../ui/icons.js';\nimport { execFormat, queryFormatState } from '../utils/exec-command.js';\n\n/**\n * Block Toolbar Module - Floating toolbar hiện lên khi select text hoặc ấn Enter\n */\nclass BlockToolbar extends Module {\n static DEFAULTS = {\n showOnSelection: true,\n showOnEnter: true,\n buttons: ['bold', 'italic', 'underline', 'strike', 'code']\n };\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.blockToolbar = null;\n this.isVisible = false;\n this.currentSelection = null; // Store current selection for scroll updates\n this.currentCursorPosition = null; // Store current cursor position for scroll updates\n this.originalTags = new Map(); // Store original tags before converting to code\n this.init();\n }\n\n init() {\n this.preloadIcons();\n this.createBlockToolbar();\n this.setupEventListeners();\n }\n\n async preloadIcons() {\n // Icons are now inline, no need to preload\n // This method is kept for backward compatibility\n }\n\n createBlockToolbar() {\n this.blockToolbar = document.createElement('div');\n this.blockToolbar.className = 'block-toolbar';\n \n // Create toolbar container\n const toolbarContainer = document.createElement('div');\n toolbarContainer.className = 'block-toolbar-container';\n \n // Button set is configurable via options.buttons (array of command names);\n // defaults to the inline formatting set.\n const meta = {\n bold: { icon: 'bold', title: 'Bold (Ctrl+B)' },\n italic: { icon: 'italic', title: 'Italic (Ctrl+I)' },\n underline: { icon: 'underline', title: 'Underline (Ctrl+U)' },\n strike: { icon: 'strike', title: 'Strikethrough' },\n code: { icon: 'code', title: 'Code' },\n 'font-family': { icon: 'font-family', title: 'Font Family' },\n link: { icon: 'link', title: 'Insert link' },\n color: { icon: 'color', title: 'Text color' },\n background: { icon: 'background', title: 'Background color' }\n };\n const names = Array.isArray(this.options.buttons) && this.options.buttons.length\n ? this.options.buttons\n : ['bold', 'italic', 'underline', 'strike', 'code', 'font-family'];\n const buttons = names.map(cmd => ({ cmd, icon: (meta[cmd] && meta[cmd].icon) || cmd, title: (meta[cmd] && meta[cmd].title) || cmd }));\n buttons.forEach(({ cmd, icon, title }) => {\n const button = document.createElement('button');\n button.className = 'block-toolbar-btn';\n button.title = title;\n button.dataset.command = cmd;\n const iconElement = IconUtils.createIconElement(icon, { width: '16px', height: '16px' });\n button.appendChild(iconElement);\n button.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.handleCommand(cmd, button);\n });\n toolbarContainer.appendChild(button);\n });\n \n // Create arrow element\n const arrow = document.createElement('div');\n arrow.className = 'block-toolbar-arrow';\n \n // Add container and arrow to toolbar\n this.blockToolbar.appendChild(toolbarContainer);\n this.blockToolbar.appendChild(arrow);\n \n this.editor.wrapper.appendChild(this.blockToolbar);\n }\n\n setupEventListeners() {\n // Keep references so listeners can be removed in destroy() (prevents leaks\n // on document/window when multiple editors are created/destroyed).\n this._onEditorMouseup = () => {\n setTimeout(() => this.handleSelectionChange(), 0);\n };\n\n this._onEditorKeydown = (e) => {\n // Only react to Enter; do NOT hide on every keystroke (that broke typing UX).\n if (e.key === 'Enter' && !e.shiftKey) {\n requestAnimationFrame(() => {\n setTimeout(() => this.showAtCursorAfterEnter(), 10);\n });\n }\n };\n\n this._onDocMousedown = (e) => {\n // Don't hide if clicking on font-family popup or its items\n if (e.target.closest('.font-family-select-popup') || e.target.closest('.custom-select-popup')) {\n return;\n }\n if (!e.target.closest('.block-toolbar') && !e.target.closest('.rich-editor-area')) {\n this.hide();\n }\n };\n\n this._onWindowScroll = () => {\n if (this.isVisible) this.updateToolbarPosition();\n };\n\n this._onEditorScroll = () => {\n if (this.isVisible) this.updateToolbarPosition();\n };\n\n this._onEditorKeyup = (e) => {\n // Shift + Enter hides the toolbar\n if (e.key === 'Enter' && e.shiftKey) {\n this.hide();\n return;\n }\n if (this.isVisible) {\n this.updateButtonStates();\n } else {\n this.handleSelectionChange();\n }\n };\n\n if (this.options.showOnSelection) {\n this.editor.editor.addEventListener('mouseup', this._onEditorMouseup);\n }\n if (this.options.showOnEnter) {\n this.editor.editor.addEventListener('keydown', this._onEditorKeydown);\n }\n document.addEventListener('mousedown', this._onDocMousedown);\n window.addEventListener('scroll', this._onWindowScroll);\n this.editor.editor.addEventListener('scroll', this._onEditorScroll);\n this.editor.editor.addEventListener('keyup', this._onEditorKeyup);\n }\n\n handleSelectionChange() {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return this.hide();\n const range = selection.getRangeAt(0);\n const isInEditableArea = this.editor.isSelectionInEditableArea ?\n this.editor.isSelectionInEditableArea(selection) :\n this.editor.editor.contains(range.commonAncestorContainer);\n if (!isInEditableArea) return this.hide();\n if (!range.collapsed && selection.toString().trim().length > 0) {\n this.showAtSelection(selection);\n } else {\n this.hide();\n }\n }\n\n showAtSelection(selection) {\n if (!selection || selection.rangeCount === 0) return;\n \n // Store current selection for scroll updates\n this.currentSelection = selection;\n this.currentCursorPosition = null;\n \n const range = selection.getRangeAt(0);\n const rect = range.getBoundingClientRect();\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n this.showAt(\n rect.left + rect.width / 2 - editorRect.left + scrollLeft,\n rect.top - editorRect.top + scrollTop - 10\n );\n }\n\n showAtCursorAfterEnter() {\n this.editor.focus();\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return;\n const range = selection.getRangeAt(0);\n const isInEditableArea = this.editor.isSelectionInEditableArea ?\n this.editor.isSelectionInEditableArea(selection) :\n this.editor.editor.contains(range.commonAncestorContainer);\n if (!isInEditableArea) return;\n \n this.ensureCursorAtEndOfLine(range);\n \n // Store current cursor position for scroll updates\n this.currentSelection = selection;\n this.currentCursorPosition = this.getCursorPositionAfterEnter();\n \n const rect = this.currentCursorPosition;\n if (!rect) return;\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n this.showAt(\n rect.left - editorRect.left + scrollLeft,\n rect.top - editorRect.top + scrollTop - 10\n );\n }\n\n ensureCursorAtEndOfLine(range) {\n if (!range.collapsed) return;\n const selection = window.getSelection();\n const currentNode = range.startContainer;\n if (currentNode.nodeType === Node.TEXT_NODE) {\n const textLength = currentNode.textContent.length;\n if (range.startOffset < textLength) {\n range.setStart(currentNode, textLength);\n range.setEnd(currentNode, textLength);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n } else if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const walker = document.createTreeWalker(currentNode, NodeFilter.SHOW_TEXT, null, false);\n let lastTextNode = null, node;\n while (node = walker.nextNode()) lastTextNode = node;\n if (lastTextNode) {\n const textLength = lastTextNode.textContent.length;\n range.setStart(lastTextNode, textLength);\n range.setEnd(lastTextNode, textLength);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n }\n }\n\n getCursorPositionAfterEnter() {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return null;\n const range = selection.getRangeAt(0);\n const marker = document.createElement('span');\n marker.innerHTML = '&#8203;';\n marker.style.position = 'absolute';\n marker.style.visibility = 'hidden';\n marker.style.pointerEvents = 'none';\n range.insertNode(marker);\n const rect = marker.getBoundingClientRect();\n if (marker.parentNode) marker.parentNode.removeChild(marker);\n const newRange = document.createRange();\n newRange.setStart(range.startContainer, range.startOffset);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n return rect;\n }\n\n showAtCursor() {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return;\n const range = selection.getRangeAt(0);\n const isInEditableArea = this.editor.isSelectionInEditableArea ?\n this.editor.isSelectionInEditableArea(selection) :\n this.editor.editor.contains(range.commonAncestorContainer);\n if (!isInEditableArea) return;\n let rect;\n if (range.collapsed) {\n const span = document.createElement('span');\n span.innerHTML = '&#8203;';\n span.style.position = 'absolute';\n span.style.visibility = 'hidden';\n span.style.pointerEvents = 'none';\n range.insertNode(span);\n rect = span.getBoundingClientRect();\n if (span.parentNode) span.parentNode.removeChild(span);\n const newRange = document.createRange();\n newRange.setStart(range.startContainer, range.startOffset);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n rect = range.getBoundingClientRect();\n }\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n this.showAt(\n rect.left - editorRect.left + scrollLeft,\n rect.top - editorRect.top + scrollTop - 10\n );\n }\n\n showAt(x, y) {\n if (!this.blockToolbar) return;\n this.blockToolbar.classList.add('visible');\n this.isVisible = true;\n this.ensureToolbarInViewport(x,y);\n this.updateButtonStates();\n }\n\n ensureToolbarInViewport(x,y) {\n if (!this.blockToolbar) return;\n \n // Lấy thông tin về editor-area\n const editorArea = this.editor.editor;\n const editorRect = editorArea.getBoundingClientRect();\n const toolbarRect = this.blockToolbar.getBoundingClientRect();\n const toolbarContainer = this.editor.wrapper.querySelector('.rich-editor-toolbar-container');\n const toolbarRect2 = toolbarContainer ? toolbarContainer.getBoundingClientRect() : null;\n \n \n let left = x - this.blockToolbar.offsetWidth/2;\n let top = editorRect.y + y -(toolbarRect2.height) - editorArea.scrollTop - (editorRect.y + window.scrollY) +toolbarContainer.offsetHeight-49;\n\n let arrowLeft = '50%';\n let arrowDirection = 'down'; // mũi tên hướng xuống\n \n // Trường hợp 1: Vượt quá lề trái của editor\n if (left < 0) {\n left =(x - (this.blockToolbar.offsetWidth * (10/100)));\n if(left < 0) left = 0;\n arrowLeft = '10%'; // Mũi tên ở 10%\n }\n // Trường hợp 2: Vượt quá lề phải của editor\n if (left + this.blockToolbar.offsetWidth > (this.editor.wrapper.offsetWidth - 2)) {\n left = x - this.blockToolbar.offsetWidth*0.9;\n arrowLeft = '90%'; // Mũi tên ở 90%\n }\n \n // Trường hợp 3: Vượt quá lề trên của editor\n\n if (top < toolbarRect2.height) {\n top = editorRect.y + y -(toolbarRect2.height) - editorArea.scrollTop +100 - (editorRect.y + window.scrollY)+toolbarContainer.offsetHeight-49;\n arrowDirection = 'up'; // Mũi tên hướng lên\n if(top < toolbarRect2.height ){\n this.hide();\n return;\n }\n }\n if(top > editorRect.height){\n this.hide();\n return;\n }\n // Cập nhật vị trí mũi tên\n const arrow = this.blockToolbar.querySelector('.block-toolbar-arrow');\n if (arrow) {\n arrow.style.left = arrowLeft;\n \n if (arrowDirection === 'up') {\n // Mũi tên hướng lên\n arrow.style.bottom = 'auto';\n arrow.style.top = '-8px';\n arrow.style.borderTop = 'none';\n arrow.style.borderBottom = '8px solid #fff';\n arrow.style.borderLeft = '6px solid transparent';\n arrow.style.borderRight = '6px solid transparent';\n } else {\n // Mũi tên hướng xuống (mặc định)\n arrow.style.top = 'auto';\n arrow.style.bottom = '-8px';\n arrow.style.borderBottom = 'none';\n arrow.style.borderTop = '8px solid #fff';\n arrow.style.borderLeft = '6px solid transparent';\n arrow.style.borderRight = '6px solid transparent';\n }\n }\n // Áp dụng vị trí cuối cùng\n this.blockToolbar.style.left = left + 'px';\n this.blockToolbar.style.top = top + 'px';\n }\n\n /**\n * Update toolbar position based on current selection or cursor position\n */\n updateToolbarPosition() {\n if (!this.isVisible) return;\n\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) {\n this.hide();\n return;\n }\n\n const range = selection.getRangeAt(0);\n const isInEditableArea = this.editor.isSelectionInEditableArea ?\n this.editor.isSelectionInEditableArea(selection) :\n this.editor.editor.contains(range.commonAncestorContainer);\n \n if (!isInEditableArea) {\n this.hide();\n return;\n }\n\n let rect;\n \n if (range.collapsed) {\n // For cursor position, get current cursor rect\n const span = document.createElement('span');\n span.innerHTML = '&#8203;';\n span.style.position = 'absolute';\n span.style.visibility = 'hidden';\n span.style.pointerEvents = 'none';\n \n try {\n range.insertNode(span);\n rect = span.getBoundingClientRect();\n if (span.parentNode) span.parentNode.removeChild(span);\n \n // Restore range\n const newRange = document.createRange();\n newRange.setStart(range.startContainer, range.startOffset);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } catch (e) {\n // If insertion fails, hide toolbar\n this.hide();\n return;\n }\n } else {\n // For selection, use selection rect\n rect = range.getBoundingClientRect();\n }\n\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n \n let x, y;\n if (range.collapsed) {\n x = rect.left - editorRect.left + scrollLeft;\n y = rect.top - editorRect.top + scrollTop - 10;\n } else {\n x = rect.left + rect.width / 2 - editorRect.left + scrollLeft;\n y = rect.top - editorRect.top + scrollTop - 10;\n }\n \n this.updateToolbarAt(x, y);\n \n // Update font-family popup position if it's visible\n const fontFamilyFormat = this.editor.registry.get('formats/font-family');\n if (fontFamilyFormat && fontFamilyFormat.selectInstance && fontFamilyFormat.selectInstance.isVisible) {\n fontFamilyFormat.selectInstance.updatePosition();\n }\n }\n\n /**\n * Update toolbar position at specific coordinates\n */\n updateToolbarAt(x, y) {\n if (!this.blockToolbar) return;\n \n this.ensureToolbarInViewport(x, y);\n this.updateButtonStates();\n }\n\n hide() {\n if (!this.blockToolbar || !this.isVisible) return;\n this.blockToolbar.classList.remove('visible');\n this.isVisible = false;\n // Clear stored positions\n this.currentSelection = null;\n this.currentCursorPosition = null;\n \n // Hide any open font-family popup when block toolbar is hidden\n const fontFamilyFormat = this.editor.registry.get('formats/font-family');\n if (fontFamilyFormat && fontFamilyFormat.selectInstance) {\n fontFamilyFormat.selectInstance.hide();\n }\n }\n\n handleCommand(command, button) {\n const selection = window.getSelection();\n const isInEditableArea = this.editor.isSelectionInEditableArea ?\n this.editor.isSelectionInEditableArea(selection) : true;\n if (!isInEditableArea) {\n this.hide();\n return;\n }\n \n // Special handling for font-family command\n if (command === 'font-family') {\n const fontFamilyFormat = this.editor.registry.get('formats/font-family');\n if (fontFamilyFormat) {\n const format = new fontFamilyFormat();\n format.toggle(button); // Pass the button as anchor\n this.updateButtonState(command, button);\n this.editor.focus();\n return;\n }\n }\n \n // Special handling for code command to use PRE tag from heading format\n if (command === 'code') {\n const headingFormat = this.editor.registry.get('formats/heading');\n if (headingFormat) {\n const heading = new headingFormat();\n const currentTag = heading.getCurrentTag();\n \n // If current tag is PRE, convert back to original tag or P\n // If current tag is not PRE, convert to PRE (code format)\n if (currentTag === 'PRE') {\n // Get the selection to find the block element\n const selection = window.getSelection();\n if (selection && selection.rangeCount) {\n const range = selection.getRangeAt(0);\n const block = this.getBlockElement(range.startContainer);\n \n if (block) {\n // Get original tag for this block, default to P\n const originalTag = this.originalTags.get(block) || 'P';\n heading.apply(originalTag); \n this.originalTags.delete(block); // Clean up\n } else {\n heading.apply('P');\n }\n } else {\n heading.apply('P');\n }\n } else {\n // Store original tag before converting to PRE\n const selection = window.getSelection();\n if (selection && selection.rangeCount) {\n const range = selection.getRangeAt(0);\n const block = this.getBlockElement(range.startContainer);\n \n if (block) {\n this.originalTags.set(block, currentTag || 'P');\n }\n }\n \n heading.apply('PRE');\n }\n \n this.updateButtonState(command, button);\n this.editor.focus();\n return;\n }\n }\n\n const formatClass = this.editor.registry.get(`formats/${command}`);\n if (formatClass) {\n const format = new formatClass();\n if (typeof format.toggle === 'function') format.toggle();\n else if (typeof format.apply === 'function') format.apply();\n } else {\n execFormat(command);\n }\n this.updateButtonState(command, button);\n this.editor.focus();\n }\n\n updateButtonStates() {\n if (!this.blockToolbar) return;\n const buttons = this.blockToolbar.querySelectorAll('.block-toolbar-btn');\n buttons.forEach(button => {\n const command = button.dataset.command;\n this.updateButtonState(command, button);\n });\n }\n\n updateButtonState(command, button) {\n if (!button) return;\n let isActive = false;\n if (command === 'font-family') {\n const fontFamilyFormat = this.editor.registry.get('formats/font-family');\n if (fontFamilyFormat) {\n const format = new fontFamilyFormat();\n isActive = format.isActive();\n }\n } else if (command === 'code') {\n // Check if current block is PRE tag\n const headingFormat = this.editor.registry.get('formats/heading');\n if (headingFormat) {\n const heading = new headingFormat();\n const currentTag = heading.getCurrentTag();\n isActive = currentTag === 'PRE';\n }\n } else if (command === 'strike') {\n const formatClass = this.editor.registry.get(`formats/${command}`);\n if (formatClass) {\n const format = new formatClass();\n isActive = format.isActive();\n }\n } else {\n isActive = queryFormatState(command);\n }\n if (isActive) button.classList.add('active');\n else button.classList.remove('active');\n }\n\n /**\n * Get block element from a node\n * @param {Node} node - Node to find block element for\n * @returns {Element|null} Block element or null\n */\n getBlockElement(node) {\n if (!node) return null;\n \n // If node is an element and is a block, return it\n if (node.nodeType === Node.ELEMENT_NODE) {\n const blockTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'PRE', 'BLOCKQUOTE', 'DIV'];\n if (blockTags.includes(node.tagName)) {\n return node;\n }\n }\n \n // Walk up the DOM tree to find block element\n let current = node;\n while (current && current !== this.editor.editor) {\n if (current.nodeType === Node.ELEMENT_NODE) {\n const blockTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'PRE', 'BLOCKQUOTE', 'DIV'];\n if (blockTags.includes(current.tagName)) {\n return current;\n }\n }\n current = current.parentNode;\n }\n \n return null;\n }\n\n destroy() {\n // Remove event listeners (document/window ones would otherwise leak)\n if (this._onDocMousedown) {\n document.removeEventListener('mousedown', this._onDocMousedown);\n window.removeEventListener('scroll', this._onWindowScroll);\n this.editor.editor.removeEventListener('mouseup', this._onEditorMouseup);\n this.editor.editor.removeEventListener('keydown', this._onEditorKeydown);\n this.editor.editor.removeEventListener('scroll', this._onEditorScroll);\n this.editor.editor.removeEventListener('keyup', this._onEditorKeyup);\n this._onDocMousedown = this._onWindowScroll = this._onEditorMouseup = null;\n this._onEditorKeydown = this._onEditorScroll = this._onEditorKeyup = null;\n }\n\n if (this.blockToolbar && this.blockToolbar.parentNode) {\n this.blockToolbar.parentNode.removeChild(this.blockToolbar);\n }\n this.blockToolbar = null;\n this.isVisible = false;\n this.originalTags.clear(); // Clean up stored tags\n }\n}\n\nexport default BlockToolbar; ","import Module from '../core/module.js';\nimport IconUtils from '../ui/icons.js';\n\n/**\n * Table Toolbar Module - Floating toolbar hiện lên khi click vào table\n */\nclass TableToolbar extends Module {\n static DEFAULTS = {\n fadeDelay: 3000, // Auto hide after 3 seconds of inactivity\n buttons: ['tableProfile', 'deleteTable', 'insertRowAbove', 'insertRowBelow', 'deleteRow', 'insertColRight', 'insertColLeft', 'deleteCol']\n };\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.tableToolbar = null;\n this.currentTable = null;\n this.currentCell = null;\n this.hideTimeout = null;\n this.isVisible = false;\n \n this.init();\n }\n\n async init() {\n await this.createTableToolbar();\n this.setupEventListeners();\n }\n\n\n\n /**\n * Tạo table toolbar element\n */\n async createTableToolbar() {\n this.tableToolbar = document.createElement('div');\n this.tableToolbar.className = 'table-toolbar';\n \n // Create toolbar container\n const toolbarContainer = document.createElement('div');\n toolbarContainer.className = 'table-toolbar-container';\n \n // Define button groups\n const buttonGroups = [\n {\n name: 'table-actions',\n buttons: [\n { cmd: 'tableProfile', icon: 'icon-table-profile', title: 'Table Profile' },\n { cmd: 'deleteTable', icon: 'icon-delete-table', title: 'Delete Table' }\n ]\n },\n {\n name: 'row-actions',\n buttons: [\n { cmd: 'insertRowAbove', icon: 'icon-add-row-above', title: 'Add Row Above' },\n { cmd: 'insertRowBelow', icon: 'icon-add-row-below', title: 'Add Row Below' },\n { cmd: 'deleteRow', icon: 'icon-delete-row', title: 'Delete Selected Row' }\n ]\n },\n {\n name: 'col-actions',\n buttons: [\n { cmd: 'insertColRight', icon: 'icon-add-col-right', title: 'Add Column Right' },\n { cmd: 'insertColLeft', icon: 'icon-add-col-left', title: 'Add Column Left' },\n { cmd: 'deleteCol', icon: 'icon-delete-col', title: 'Delete Selected Column' }\n ]\n }\n ];\n\n // Create groups\n for (const group of buttonGroups) {\n const groupDiv = document.createElement('div');\n groupDiv.className = `table-toolbar-group ${group.name}`;\n\n // Create buttons in this group\n for (const { cmd, icon, title } of group.buttons) {\n const button = document.createElement('button');\n button.className = 'table-toolbar-btn';\n button.title = title;\n button.dataset.command = cmd;\n\n // Load and set SVG icon using IconUtils\n const svgContent = IconUtils.getIcon(icon.replace('icon-', ''));\n if (svgContent) {\n button.innerHTML = svgContent;\n }\n\n button.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.handleCommand(cmd, button);\n });\n\n groupDiv.appendChild(button);\n }\n\n toolbarContainer.appendChild(groupDiv);\n }\n\n // Create arrow element\n const arrow = document.createElement('div');\n arrow.className = 'table-toolbar-arrow';\n \n // Add container and arrow to toolbar\n this.tableToolbar.appendChild(toolbarContainer);\n this.tableToolbar.appendChild(arrow);\n\n // Add to editor wrapper\n this.editor.wrapper.appendChild(this.tableToolbar);\n }\n\n /**\n * Setup event listeners\n */\n setupEventListeners() {\n // Keep references so listeners can be removed in destroy().\n this._onEditorClick = (e) => {\n const clickedCell = e.target.closest('td, th');\n const clickedTable = e.target.closest('table');\n\n if (clickedTable && clickedCell) {\n // Check if the clicked table is within the editable area\n const isInEditableArea = this.editor.isNodeInEditableArea ?\n this.editor.isNodeInEditableArea(clickedTable) : true;\n\n if (!isInEditableArea) {\n this.hide();\n return;\n }\n\n this.currentTable = clickedTable;\n this.currentCell = clickedCell;\n this.showAtTable(clickedTable);\n } else {\n this.hide();\n }\n };\n\n this._onDocMousedown = (e) => {\n if (!e.target.closest('.table-toolbar') && !e.target.closest('table')) {\n this.hide();\n }\n };\n\n this._onWindowScroll = () => {\n if (this.isVisible && this.currentTable) {\n this.updateToolbarPosition();\n }\n };\n\n this._onEditorScroll = () => {\n if (this.isVisible && this.currentTable) {\n this.updateToolbarPosition();\n }\n };\n\n // Listen for clicks on table cells\n this.editor.editor.addEventListener('click', this._onEditorClick);\n // Hide on outside click\n document.addEventListener('mousedown', this._onDocMousedown);\n // Track position on scroll instead of hiding\n window.addEventListener('scroll', this._onWindowScroll);\n this.editor.editor.addEventListener('scroll', this._onEditorScroll);\n\n // Update when selection changes within table\n this._onEditorKeyup = (e) => {\n if (this.isVisible && this.currentTable) {\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n const currentCell = selection.getRangeAt(0).startContainer;\n const tableCell = currentCell.nodeType === Node.TEXT_NODE \n ? currentCell.parentElement.closest('td, th')\n : currentCell.closest('td, th');\n \n if (tableCell && tableCell !== this.currentCell) {\n // Verify the table cell is still in editable area\n const isInEditableArea = this.editor.isNodeInEditableArea ? \n this.editor.isNodeInEditableArea(tableCell) : true;\n \n if (!isInEditableArea) {\n this.hide();\n return;\n }\n \n this.currentCell = tableCell;\n }\n }\n }\n };\n this.editor.editor.addEventListener('keyup', this._onEditorKeyup);\n }\n\n /**\n * Update toolbar position based on current table\n */\n updateToolbarPosition() {\n if (!this.isVisible || !this.currentTable) return;\n\n // Check if table is still in DOM and in editable area\n if (!document.body.contains(this.currentTable)) {\n this.hide();\n return;\n }\n\n const isInEditableArea = this.editor.isNodeInEditableArea ? \n this.editor.isNodeInEditableArea(this.currentTable) : true;\n \n if (!isInEditableArea) {\n this.hide();\n return;\n }\n\n // Update position based on current table position\n const rect = this.currentTable.getBoundingClientRect();\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n \n this.updateToolbarAt(\n rect.left - editorRect.left + scrollLeft,\n rect.top - editorRect.top + scrollTop,\n rect.width,\n rect.height\n );\n }\n\n /**\n * Update toolbar position at specific coordinates\n */\n updateToolbarAt(x, y, width, height) {\n if (!this.tableToolbar) return;\n \n this.ensureToolbarInViewport(x, y, width, height);\n }\n\n /**\n * Show toolbar at table position\n */\n showAtTable(table) {\n if (!table || !this.tableToolbar) return;\n\n const rect = table.getBoundingClientRect();\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n this.showAt(\n rect.left - editorRect.left + scrollLeft,\n rect.top - editorRect.top + scrollTop,\n rect.width,\n rect.height\n );\n }\n\n /**\n * Show toolbar at specific position\n */\n showAt(x, y, width, height) {\n if (!this.tableToolbar) return;\n\n this.tableToolbar.classList.add('visible');\n this.isVisible = true;\n\n this.ensureToolbarInViewport(x, y, width, height);\n this.clearHideTimeout();\n }\n\n /**\n * Ensure toolbar stays within viewport and editor bounds\n */\n ensureToolbarInViewport(x, y, width, height) {\n if (!this.tableToolbar) return;\n \n // Lấy thông tin về editor-area\n const editorArea = this.editor.editor;\n const editorRect = editorArea.getBoundingClientRect();\n const toolbarRect = this.tableToolbar.getBoundingClientRect();\n const toolbarContainer = this.editor.wrapper.querySelector('.rich-editor-toolbar-container');\n const toolbarRect2 = toolbarContainer ? toolbarContainer.getBoundingClientRect() : null;\n let left = x+width/2 - this.tableToolbar.offsetWidth/2;\n let top = y - 60- document.documentElement.scrollTop;\n let arrowLeft = '50%';\n let arrowDirection = 'down'; // mũi tên hướng xuống\n \n // Trường hợp 1: Vượt quá lề trái của editor\n if (left < 0) {\n left = (x - (this.tableToolbar.offsetWidth * (10/100)));\n if(left < 0) left = 0;\n arrowLeft = '10%'; // Mũi tên ở 10%\n }\n \n // Trường hợp 2: Vượt quá lề phải của editor\n if (left + this.tableToolbar.offsetWidth > (this.editor.wrapper.offsetWidth - 2)) {\n left = x - this.tableToolbar.offsetWidth*0.9;\n arrowLeft = '90%'; // Mũi tên ở 90%\n }\n \n // Trường hợp 3: Vượt quá lề trên của editor\n if (top < (toolbarRect2 ? toolbarRect2.height : 48)) {\n top = y + height +10 - document.documentElement.scrollTop;\n arrowDirection = 'up'; // Mũi tên hướng lên\n if(top < (toolbarRect2 ? toolbarRect2.height : 48)){\n this.hide();\n return;\n }\n }\n if(top > editorRect.height){\n this.hide();\n return;\n }\n // Cập nhật vị trí mũi tên (nếu có)\n const arrow = this.tableToolbar.querySelector('.table-toolbar-arrow');\n if (arrow) {\n arrow.style.left = arrowLeft;\n \n if (arrowDirection === 'up') {\n // Mũi tên hướng lên\n arrow.style.bottom = 'auto';\n arrow.style.top = '-8px';\n arrow.style.borderTop = 'none';\n arrow.style.borderBottom = '8px solid #fff';\n arrow.style.borderLeft = '6px solid transparent';\n arrow.style.borderRight = '6px solid transparent';\n } else {\n // Mũi tên hướng xuống (mặc định)\n arrow.style.top = 'auto';\n arrow.style.bottom = '-8px';\n arrow.style.borderBottom = 'none';\n arrow.style.borderTop = '8px solid #fff';\n arrow.style.borderLeft = '6px solid transparent';\n arrow.style.borderRight = '6px solid transparent';\n }\n }\n // Áp dụng vị trí cuối cùng\n this.tableToolbar.style.left = left + 'px';\n this.tableToolbar.style.top = top + 'px';\n }\n\n /**\n * Hide toolbar\n */\n hide() {\n if (!this.tableToolbar || !this.isVisible) return;\n\n this.tableToolbar.classList.remove('visible');\n this.isVisible = false;\n this.currentTable = null;\n this.currentCell = null;\n this.clearHideTimeout();\n \n }\n\n /**\n * Clear hide timeout\n */\n clearHideTimeout() {\n if (this.hideTimeout) {\n clearTimeout(this.hideTimeout);\n this.hideTimeout = null;\n }\n }\n\n /**\n * Handle command execution - only if table is in editable area\n */\n handleCommand(command, button) {\n\n if (!this.currentTable || !this.currentCell) {\n return;\n }\n\n // Double check that the table is still in editable area before executing command\n const isInEditableArea = this.editor.isNodeInEditableArea ? \n this.editor.isNodeInEditableArea(this.currentTable) : true;\n \n if (!isInEditableArea) {\n this.hide();\n return;\n }\n\n switch (command) {\n case 'tableProfile':\n this.showTableProfile();\n break;\n case 'deleteTable':\n this.deleteTable();\n break;\n case 'insertRowAbove':\n this.insertRowAbove();\n break;\n case 'insertRowBelow':\n this.insertRowBelow();\n break;\n case 'deleteRow':\n this.deleteRow();\n break;\n case 'insertColRight':\n this.insertColumnRight();\n break;\n case 'insertColLeft':\n this.insertColumnLeft();\n break;\n case 'deleteCol':\n this.deleteColumn();\n break;\n }\n\n this.editor.focus();\n }\n\n /**\n * Insert row above current cell\n */\n insertRowAbove() {\n const currentRow = this.currentCell.parentElement;\n const newRow = this.createNewRow(currentRow.cells.length);\n currentRow.parentElement.insertBefore(newRow, currentRow);\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n }\n\n /**\n * Insert row below current cell\n */\n insertRowBelow() {\n const currentRow = this.currentCell.parentElement;\n const newRow = this.createNewRow(currentRow.cells.length);\n \n if (currentRow.nextElementSibling) {\n currentRow.parentElement.insertBefore(newRow, currentRow.nextElementSibling);\n } else {\n currentRow.parentElement.appendChild(newRow);\n }\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n }\n\n /**\n * Insert column left of current cell\n */\n insertColumnLeft() {\n const cellIndex = Array.from(this.currentCell.parentElement.children).indexOf(this.currentCell);\n const tbody = this.currentTable.querySelector('tbody') || this.currentTable;\n const rows = tbody.querySelectorAll('tr');\n \n rows.forEach(row => {\n const newCell = this.createNewCell();\n const targetCell = row.children[cellIndex];\n if (targetCell) {\n row.insertBefore(newCell, targetCell);\n } else {\n row.appendChild(newCell);\n }\n });\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n }\n\n /**\n * Insert column right of current cell\n */\n insertColumnRight() {\n const cellIndex = Array.from(this.currentCell.parentElement.children).indexOf(this.currentCell);\n const tbody = this.currentTable.querySelector('tbody') || this.currentTable;\n const rows = tbody.querySelectorAll('tr');\n \n rows.forEach(row => {\n const newCell = this.createNewCell();\n const targetCell = row.children[cellIndex + 1];\n if (targetCell) {\n row.insertBefore(newCell, targetCell);\n } else {\n row.appendChild(newCell);\n }\n });\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n }\n\n /**\n * Delete current row\n */\n deleteRow() {\n const currentRow = this.currentCell.parentElement;\n const tbody = currentRow.parentElement;\n \n // Don't delete if it's the only row\n if (tbody.children.length <= 1) {\n return;\n }\n \n currentRow.remove();\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n \n this.hide();\n }\n\n /**\n * Delete current column\n */\n deleteColumn() {\n const cellIndex = Array.from(this.currentCell.parentElement.children).indexOf(this.currentCell);\n const tbody = this.currentTable.querySelector('tbody') || this.currentTable;\n const rows = tbody.querySelectorAll('tr');\n \n // Don't delete if it's the only column\n if (this.currentCell.parentElement.children.length <= 1) {\n console.warn('Cannot delete the only column in table');\n return;\n }\n \n rows.forEach(row => {\n if (row.children[cellIndex]) {\n row.children[cellIndex].remove();\n }\n });\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n \n this.hide();\n }\n\n /**\n * Show table profile/properties\n */\n showTableProfile() {\n if (!this.currentTable) return;\n \n const rows = this.currentTable.querySelectorAll('tr').length;\n const cols = this.currentTable.querySelector('tr') ? \n this.currentTable.querySelector('tr').querySelectorAll('td, th').length : 0;\n \n const tableInfo = {\n rows: rows,\n columns: cols,\n totalCells: rows * cols,\n tableWidth: this.currentTable.offsetWidth,\n tableHeight: this.currentTable.offsetHeight\n };\n \n // You can customize this to show a modal or popup with table information\n alert(`Table Profile:\\n` +\n `Rows: ${tableInfo.rows}\\n` +\n `Columns: ${tableInfo.columns}\\n` +\n `Total Cells: ${tableInfo.totalCells}\\n` +\n `Width: ${tableInfo.tableWidth}px\\n` +\n `Height: ${tableInfo.tableHeight}px`);\n \n }\n\n /**\n * Delete entire table\n */\n deleteTable() {\n // Hide resize handles before removing table\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.hideHandles();\n }\n \n this.currentTable.remove();\n this.hide();\n }\n\n /**\n * Create new row with specified number of cells\n */\n createNewRow(cellCount) {\n const row = document.createElement('tr');\n for (let i = 0; i < cellCount; i++) {\n row.appendChild(this.createNewCell());\n }\n return row;\n }\n\n /**\n * Create new cell\n */\n createNewCell() {\n const cell = document.createElement('td');\n cell.innerHTML = '&nbsp;';\n cell.style.minWidth = '50px';\n cell.style.minHeight = '24px';\n cell.style.padding = '4px 8px';\n cell.style.border = '1px solid #ddd';\n cell.style.verticalAlign = 'top';\n cell.contentEditable = 'true';\n return cell;\n }\n\n /**\n * Destroy module\n */\n destroy() {\n // Remove event listeners (document/window ones would otherwise leak)\n if (this._onDocMousedown) {\n document.removeEventListener('mousedown', this._onDocMousedown);\n window.removeEventListener('scroll', this._onWindowScroll);\n this.editor.editor.removeEventListener('click', this._onEditorClick);\n this.editor.editor.removeEventListener('scroll', this._onEditorScroll);\n this.editor.editor.removeEventListener('keyup', this._onEditorKeyup);\n this._onDocMousedown = this._onWindowScroll = this._onEditorClick = null;\n this._onEditorScroll = this._onEditorKeyup = null;\n }\n\n if (this.tableToolbar && this.tableToolbar.parentNode) {\n this.tableToolbar.parentNode.removeChild(this.tableToolbar);\n }\n\n this.clearHideTimeout();\n this.tableToolbar = null;\n this.currentTable = null;\n this.currentCell = null;\n this.isVisible = false;\n \n }\n}\n\nexport default TableToolbar; ","import Module from '../core/module.js';\nimport { sanitizeHtml } from '../utils/sanitize.js';\n\n/**\n * Code View Module - Toggles between normal editor view and HTML source code view\n */\nclass CodeView extends Module {\n constructor(editor, options = {}) {\n super(editor, options);\n \n this.isCodeView = false;\n this.originalContent = '';\n this.codeTextarea = null;\n this.disabledModules = new Set(); // Track disabled modules\n \n this.init();\n }\n\n init() {\n \n // Listen for code view toggle events\n this.editor.on('toolbar-click', (data) => {\n if (data.command === 'code-view') {\n this.toggleCodeView();\n }\n });\n }\n\n /**\n * Toggle between normal editor view and code view\n */\n toggleCodeView() {\n \n if (this.isCodeView) {\n this.showNormalView();\n } else {\n this.showCodeView();\n }\n \n this.updateToolbarButton();\n }\n\n /**\n * Show code view - display HTML source\n */\n showCodeView() {\n const editorArea = this.editor.editor;\n if (!editorArea) return;\n\n // Store original content\n this.originalContent = editorArea.innerHTML;\n \n // Create textarea for code editing\n this.codeTextarea = document.createElement('textarea');\n this.codeTextarea.className = 'code-view-textarea';\n this.codeTextarea.value = this.formatHTML(this.originalContent);\n \n // Replace editor content with textarea\n editorArea.style.display = 'none';\n editorArea.parentNode.insertBefore(this.codeTextarea, editorArea);\n \n // Add CSS class to wrapper for styling\n const wrapper = this.editor.wrapper;\n if (wrapper) {\n wrapper.classList.add('code-view-active');\n }\n \n // Focus on textarea\n this.codeTextarea.focus();\n \n // Set flag\n this.isCodeView = true;\n\n // Reflect the source content in the word/char counter\n if (typeof this.editor.updateStatusbar === 'function') {\n this.editor.updateStatusbar();\n }\n\n // Disable other features\n this.disableOtherFeatures();\n \n // Add event listener for real-time updates\n this.codeTextarea.addEventListener('input', () => {\n this.updateOriginalContent();\n });\n \n }\n\n /**\n * Show normal editor view - restore visual editor\n */\n showNormalView() {\n const editorArea = this.editor.editor;\n if (!editorArea || !this.codeTextarea) return;\n\n // Get updated content from textarea\n const updatedHTML = this.codeTextarea.value;\n \n // Remove textarea\n this.codeTextarea.parentNode.removeChild(this.codeTextarea);\n this.codeTextarea = null;\n \n // Remove CSS class from wrapper\n const wrapper = this.editor.wrapper;\n if (wrapper) {\n wrapper.classList.remove('code-view-active');\n }\n \n // Show editor area\n editorArea.style.display = '';\n\n // Update editor content (sanitize HTML typed in the source view to prevent XSS)\n editorArea.innerHTML = sanitizeHtml(updatedHTML);\n \n // Focus on editor\n editorArea.focus();\n \n // Set flag\n this.isCodeView = false;\n \n // Enable other features\n this.enableOtherFeatures();\n \n // Trigger content change event\n this.editor.onContentChange();\n \n }\n\n /**\n * Disable other features when in code view\n */\n disableOtherFeatures() {\n // Disable toolbar buttons (except code-view and theme)\n const toolbar = this.editor.getModule('toolbar');\n if (toolbar) {\n const allCommands = [\n 'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript',\n 'color', 'background', 'link', 'table', 'heading',\n 'font-family', 'line-height', 'capitalization', 'text-align', 'list',\n 'indent-increase', 'indent-decrease', 'text-size', 'emoji', 'image',\n 'video', 'tag', 'import', 'undo', 'redo',\n 'clear-format', 'horizontal-rule', 'text-direction', 'find', 'more'\n ];\n \n allCommands.forEach(command => {\n toolbar.setButtonDisabled(command, true);\n });\n \n // Keep code-view and theme enabled\n toolbar.setButtonDisabled('code-view', false);\n toolbar.setButtonDisabled('theme', false);\n }\n \n // Disable editor events\n this.disableEditorEvents();\n \n // Disable other modules\n this.disableOtherModules();\n \n // Hide any open popups\n this.hideAllPopups();\n }\n\n /**\n * Enable other features when returning to normal view\n */\n enableOtherFeatures() {\n // Enable toolbar buttons\n const toolbar = this.editor.getModule('toolbar');\n if (toolbar) {\n const allCommands = [\n 'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript',\n 'color', 'background', 'link', 'table', 'heading',\n 'font-family', 'line-height', 'capitalization', 'text-align', 'list',\n 'indent-increase', 'indent-decrease', 'text-size', 'emoji', 'image',\n 'video', 'tag', 'import', 'undo', 'redo',\n 'clear-format', 'horizontal-rule', 'text-direction', 'find', 'more'\n ];\n \n allCommands.forEach(command => {\n toolbar.setButtonDisabled(command, false);\n });\n }\n \n // Enable editor events\n this.enableEditorEvents();\n \n // Enable other modules\n this.enableOtherModules();\n }\n\n /**\n * Disable editor events when in code view\n */\n disableEditorEvents() {\n const editorArea = this.editor.editor;\n if (!editorArea) return;\n \n // Make editor non-editable to disable all editing functionality\n editorArea.contentEditable = false;\n \n // Add a visual indicator that editor is disabled\n editorArea.style.opacity = '0.5';\n editorArea.style.pointerEvents = 'none';\n editorArea.style.cursor = 'not-allowed';\n \n // Add a title to indicate the editor is disabled\n editorArea.title = 'Editor is disabled in code view mode. Click \"Switch to Visual Editor\" to return to normal editing.';\n }\n\n /**\n * Enable editor events when returning to normal view\n */\n enableEditorEvents() {\n const editorArea = this.editor.editor;\n if (!editorArea) return;\n \n // Restore editor functionality\n editorArea.contentEditable = true;\n editorArea.style.opacity = '';\n editorArea.style.pointerEvents = '';\n editorArea.style.cursor = '';\n editorArea.title = '';\n }\n\n /**\n * Disable other modules when in code view\n */\n disableOtherModules() {\n const modulesToDisable = ['history', 'block-toolbar', 'table-toolbar', 'resize-handles'];\n \n modulesToDisable.forEach(moduleName => {\n const module = this.editor.getModule(moduleName);\n if (module) {\n // Try to disable module if it has disable method\n if (typeof module.disable === 'function') {\n module.disable();\n this.disabledModules.add(moduleName);\n }\n // For modules without disable method, we can hide their UI elements\n else if (module.getContainer && typeof module.getContainer === 'function') {\n const container = module.getContainer();\n if (container) {\n container.style.display = 'none';\n this.disabledModules.add(moduleName);\n }\n }\n }\n });\n }\n\n /**\n * Enable other modules when returning to normal view\n */\n enableOtherModules() {\n this.disabledModules.forEach(moduleName => {\n const module = this.editor.getModule(moduleName);\n if (module) {\n // Try to enable module if it has enable method\n if (typeof module.enable === 'function') {\n module.enable();\n }\n // For modules without enable method, show their UI elements\n else if (module.getContainer && typeof module.getContainer === 'function') {\n const container = module.getContainer();\n if (container) {\n container.style.display = '';\n }\n }\n }\n });\n \n this.disabledModules.clear();\n }\n\n /**\n * Update original content when user types in textarea\n */\n updateOriginalContent() {\n if (this.codeTextarea) {\n this.originalContent = this.codeTextarea.value;\n \n // Trigger content change event to call onChange callback\n // Get the HTML content from textarea\n const content = this.codeTextarea.value;\n \n // Call onChange callback if provided\n if (this.editor.options.onChange && typeof this.editor.options.onChange === 'function') {\n this.editor.options.onChange(content);\n }\n \n // Emit text-change event\n this.editor.emit('text-change', content);\n\n // Keep the word/char counter in sync with the edited source\n if (typeof this.editor.updateStatusbar === 'function') {\n this.editor.updateStatusbar();\n }\n }\n }\n\n /**\n * Format HTML for better readability\n */\n formatHTML(html) {\n let formatted = html;\n \n // Tách thẻ mở và đóng thành dòng riêng biệt\n formatted = formatted.replace(/></g, '>\\n<');\n \n // Tách nội dung giữa thẻ mở và đóng thành dòng riêng\n formatted = formatted.replace(/>([^<>\\s][^<]*)</g, '>\\n$1\\n<');\n \n const lines = formatted.split('\\n');\n const indentSize = 4;\n const formattedLines = [];\n const tagStack = []; // Stack để theo dõi thẻ mở\n\n for (let line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n // Check for closing tag - xử lý trước khi in\n if (/^<\\/(\\w+)/.test(trimmed)) {\n const closeTagMatch = trimmed.match(/^<\\/(\\w+)/);\n if (closeTagMatch) {\n const tagName = closeTagMatch[1];\n // Tìm và loại bỏ thẻ mở tương ứng từ stack\n for (let i = tagStack.length - 1; i >= 0; i--) {\n if (tagStack[i] === tagName) {\n tagStack.splice(i, 1);\n break;\n }\n }\n }\n }\n\n // Apply indentation based on current stack level\n let currentLevel = tagStack.length;\n \n // Nếu là nội dung text (không phải thẻ), nó nằm bên trong thẻ cha nên cần thụt lề thêm\n if (!trimmed.startsWith('<')) {\n currentLevel = tagStack.length;\n }\n \n formattedLines.push(' '.repeat(currentLevel * indentSize) + trimmed);\n\n // Check for opening tag (not self-closing) - xử lý sau khi in\n const openTagMatch = trimmed.match(/^<(\\w+)/);\n if (\n openTagMatch &&\n !trimmed.startsWith('</') &&\n !trimmed.endsWith('/>') &&\n !['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr'].includes(openTagMatch[1].toLowerCase())\n ) {\n const tagName = openTagMatch[1];\n tagStack.push(tagName);\n }\n }\n\n return formattedLines.join('\\n');\n }\n\n /**\n * Update toolbar button state\n */\n updateToolbarButton() {\n \n const toolbar = this.editor.getModule('toolbar');\n if (toolbar) {\n toolbar.setButtonActive('code-view', this.isCodeView);\n \n // Update button title\n const buttonTitle = this.isCodeView ? 'Switch to Visual Editor' : 'Switch to HTML Editor';\n toolbar.setButtonTitle('code-view', buttonTitle);\n \n } else {\n }\n }\n\n /**\n * Check if currently in code view\n */\n isInCodeView() {\n return this.isCodeView;\n }\n\n /**\n * Get current content (from textarea if in code view, otherwise from editor)\n */\n getCurrentContent() {\n if (this.isCodeView && this.codeTextarea) {\n return this.codeTextarea.value;\n }\n return this.editor.editor.innerHTML;\n }\n\n /**\n * Set content programmatically\n */\n setContent(html) {\n if (this.isCodeView && this.codeTextarea) {\n this.codeTextarea.value = this.formatHTML(html);\n this.updateOriginalContent();\n } else {\n this.editor.editor.innerHTML = html;\n }\n }\n\n /**\n * Hide all popups when entering code view\n */\n hideAllPopups() {\n // Remove all popup elements from the DOM\n const popups = document.querySelectorAll('.rich-editor-popup, .color-picker-popup, .emoji-picker-popup, .link-popup, .image-popup, .video-popup, .table-popup, .tag-popup, .import-popup');\n popups.forEach(popup => {\n if (popup.parentNode) {\n popup.parentNode.removeChild(popup);\n }\n });\n \n // Clear any popup instances from the editor\n if (this.editor.popupInstances) {\n this.editor.popupInstances.clear();\n }\n }\n\n /**\n * Clean up when module is destroyed\n */\n destroy() {\n if (this.isCodeView) {\n this.showNormalView();\n }\n \n if (this.codeTextarea && this.codeTextarea.parentNode) {\n this.codeTextarea.parentNode.removeChild(this.codeTextarea);\n }\n \n this.codeTextarea = null;\n this.originalContent = '';\n this.isCodeView = false;\n this.disabledModules.clear();\n }\n}\n\nexport default CodeView; ","import Module from '../core/module.js';\nimport IconUtils from '../ui/icons.js';\n\n/**\n * Find & Replace module.\n *\n * Opens with Ctrl/Cmd+F or the toolbar \"find\" button. Highlights matches\n * (wrapping them in <mark> within single text nodes), supports next/prev\n * navigation, replace-current and replace-all. Matches that span across\n * formatting boundaries (e.g. half inside a <b>) are not highlighted — a\n * documented limitation of the per-text-node approach.\n */\nexport default class FindReplace extends Module {\n constructor(editor, options = {}) {\n super(editor, options);\n this.hits = [];\n this.activeIndex = -1;\n this.caseSensitive = false;\n this.isOpen = false;\n this.buildPanel();\n this.bindEvents();\n }\n\n buildPanel() {\n const panel = document.createElement('div');\n panel.className = 'yjd-find-replace';\n\n const mkInput = (ph, cls) => {\n const i = document.createElement('input');\n i.type = 'text';\n i.placeholder = ph;\n i.className = `yjd-find-input ${cls}`;\n i.setAttribute('aria-label', ph);\n return i;\n };\n const mkBtn = (label, title, cls = '', icon = null) => {\n const b = document.createElement('button');\n b.type = 'button';\n if (icon) {\n const svg = IconUtils.getIcon(icon);\n b.innerHTML = svg ? `<span class=\"icon\">${svg}</span>` : label;\n } else {\n b.textContent = label;\n }\n b.title = title;\n b.setAttribute('aria-label', title);\n b.className = `yjd-find-btn ${cls}`.trim();\n return b;\n };\n\n // Two rows: find controls, then replace controls\n const findRow = document.createElement('div');\n findRow.className = 'yjd-find-row';\n const replaceRow = document.createElement('div');\n replaceRow.className = 'yjd-find-row';\n\n this.findInput = mkInput('Find', 'yjd-find-field');\n this.replaceInput = mkInput('Replace with', 'yjd-find-field');\n this.countEl = document.createElement('span');\n this.countEl.className = 'yjd-find-count';\n this.countEl.textContent = '0/0';\n\n this.prevBtn = mkBtn('', 'Previous match', 'yjd-find-icon', 'chevron-up');\n this.nextBtn = mkBtn('', 'Next match', 'yjd-find-icon', 'chevron-down');\n this.caseBtn = mkBtn('Aa', 'Match case', 'yjd-find-icon yjd-find-toggle');\n this.closeBtn = mkBtn('', 'Close (Esc)', 'yjd-find-icon yjd-find-close', 'close');\n this.replaceBtn = mkBtn('Replace', 'Replace current');\n this.replaceAllBtn = mkBtn('Replace all', 'Replace all matches');\n\n findRow.append(this.findInput, this.countEl, this.prevBtn, this.nextBtn, this.caseBtn, this.closeBtn);\n replaceRow.append(this.replaceInput, this.replaceBtn, this.replaceAllBtn);\n panel.append(findRow, replaceRow);\n\n this.panel = panel;\n this.editor.wrapper.appendChild(panel);\n }\n\n bindEvents() {\n // Open with Ctrl/Cmd+F from within the editor\n this._onKeydown = (e) => {\n if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'f') {\n e.preventDefault();\n this.open();\n }\n };\n this.editor.editor.addEventListener('keydown', this._onKeydown);\n\n // Open from the toolbar \"find\" button\n this._onToolbarClick = (data) => {\n if (data && data.command === 'find') this.open();\n };\n this.editor.on('toolbar-click', this._onToolbarClick);\n\n this.findInput.addEventListener('input', () => this.runSearch());\n this.findInput.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') { e.preventDefault(); this.navigate(e.shiftKey ? -1 : 1); }\n else if (e.key === 'Escape') { e.preventDefault(); this.close(); }\n });\n this.replaceInput.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') { e.preventDefault(); this.replaceCurrent(); }\n else if (e.key === 'Escape') { e.preventDefault(); this.close(); }\n });\n this.prevBtn.addEventListener('click', () => this.navigate(-1));\n this.nextBtn.addEventListener('click', () => this.navigate(1));\n this.replaceBtn.addEventListener('click', () => this.replaceCurrent());\n this.replaceAllBtn.addEventListener('click', () => this.replaceAll());\n this.caseBtn.addEventListener('click', () => {\n this.caseSensitive = !this.caseSensitive;\n this.caseBtn.classList.toggle('active', this.caseSensitive);\n this.caseBtn.setAttribute('aria-pressed', this.caseSensitive ? 'true' : 'false');\n this.runSearch();\n });\n this.closeBtn.addEventListener('click', () => this.close());\n }\n\n open() {\n this.isOpen = true;\n this.panel.classList.add('open');\n // Sit just below the toolbar so the panel never covers its buttons\n // (the toolbar height changes when \"More\" is expanded).\n const toolbar = this.editor.wrapper.querySelector('.rich-editor-toolbar-container');\n if (toolbar) this.panel.style.top = (toolbar.offsetHeight + 6) + 'px';\n // Prefill with the current selection (if any, single-line)\n const sel = window.getSelection();\n const selText = sel && !sel.isCollapsed ? sel.toString() : '';\n if (selText && !selText.includes('\\n')) this.findInput.value = selText;\n this.findInput.focus();\n this.findInput.select();\n this.runSearch();\n }\n\n close() {\n this.isOpen = false;\n this.panel.classList.remove('open');\n this.clearHighlights();\n this.hits = [];\n this.activeIndex = -1;\n this.editor.focus();\n }\n\n escapeRegex(s) {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n\n clearHighlights() {\n const marks = this.editor.editor.querySelectorAll('mark.yjd-find-hit');\n marks.forEach((m) => {\n const parent = m.parentNode;\n if (!parent) return;\n while (m.firstChild) parent.insertBefore(m.firstChild, m);\n parent.removeChild(m);\n parent.normalize();\n });\n }\n\n runSearch() {\n this.clearHighlights();\n this.hits = [];\n this.activeIndex = -1;\n\n const term = this.findInput.value;\n if (!term) { this.updateCount(); return; }\n\n let regex;\n try {\n regex = new RegExp(this.escapeRegex(term), this.caseSensitive ? 'g' : 'gi');\n } catch (e) {\n this.updateCount();\n return;\n }\n\n const root = this.editor.editor;\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);\n const textNodes = [];\n let node;\n while ((node = walker.nextNode())) {\n if (node.nodeValue && node.nodeValue.length) textNodes.push(node);\n }\n\n textNodes.forEach((textNode) => {\n const text = textNode.nodeValue;\n const ranges = [];\n regex.lastIndex = 0;\n let m;\n while ((m = regex.exec(text)) !== null) {\n if (m[0].length === 0) { regex.lastIndex++; continue; }\n ranges.push([m.index, m.index + m[0].length]);\n }\n // Wrap from last match to first so earlier offsets stay valid\n for (let i = ranges.length - 1; i >= 0; i--) {\n const r = document.createRange();\n r.setStart(textNode, ranges[i][0]);\n r.setEnd(textNode, ranges[i][1]);\n const mark = document.createElement('mark');\n mark.className = 'yjd-find-hit';\n try { r.surroundContents(mark); } catch (e) { /* skip un-wrappable */ }\n }\n });\n\n // Collect in document order\n this.hits = Array.from(root.querySelectorAll('mark.yjd-find-hit'));\n if (this.hits.length) {\n this.activeIndex = 0;\n this.highlightActive(true);\n }\n this.updateCount();\n }\n\n highlightActive(scroll) {\n this.hits.forEach((m, i) => {\n m.classList.toggle('active', i === this.activeIndex);\n });\n if (scroll && this.activeIndex >= 0 && this.hits[this.activeIndex]) {\n this.hits[this.activeIndex].scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n }\n\n navigate(dir) {\n if (!this.hits.length) return;\n this.activeIndex = (this.activeIndex + dir + this.hits.length) % this.hits.length;\n this.highlightActive(true);\n this.updateCount();\n }\n\n updateCount() {\n const total = this.hits.length;\n const cur = total ? this.activeIndex + 1 : 0;\n this.countEl.textContent = `${cur}/${total}`;\n }\n\n replaceCurrent() {\n if (this.activeIndex < 0 || !this.hits[this.activeIndex]) return;\n const history = this.editor.getModule('history');\n if (history && typeof history.saveBeforeFormat === 'function') history.saveBeforeFormat();\n\n const mark = this.hits[this.activeIndex];\n const at = this.activeIndex;\n const parent = mark.parentNode;\n parent.replaceChild(document.createTextNode(this.replaceInput.value), mark);\n parent.normalize();\n this.editor.onContentChange();\n\n this.runSearch();\n if (this.hits.length) {\n this.activeIndex = Math.min(at, this.hits.length - 1);\n this.highlightActive(true);\n this.updateCount();\n }\n }\n\n replaceAll() {\n if (!this.hits.length) return;\n const history = this.editor.getModule('history');\n if (history && typeof history.saveBeforeFormat === 'function') history.saveBeforeFormat();\n\n const repl = this.replaceInput.value;\n this.hits.forEach((mark) => {\n const parent = mark.parentNode;\n if (parent) parent.replaceChild(document.createTextNode(repl), mark);\n });\n this.editor.editor.normalize();\n this.editor.onContentChange();\n this.runSearch();\n }\n\n destroy() {\n this.editor.editor.removeEventListener('keydown', this._onKeydown);\n this.editor.off('toolbar-click', this._onToolbarClick);\n this.clearHighlights();\n if (this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);\n super.destroy();\n }\n}\n","import Module from '../core/module.js';\nimport IconUtils from '../ui/icons.js';\n\n/**\n * Slash command menu.\n *\n * Type \"/\" at the start of a block (or after whitespace) to open a quick menu\n * of block commands. Filter by typing, navigate with ↑/↓, choose with Enter,\n * dismiss with Esc. Selecting a command removes the typed \"/query\" and applies\n * the block transform.\n */\nexport default class SlashMenu extends Module {\n constructor(editor, options = {}) {\n super(editor, options);\n this.isOpen = false;\n this.activeIndex = 0;\n this.query = '';\n this.filtered = [];\n this.commands = this.buildCommands();\n this.buildMenu();\n this.bindEvents();\n }\n\n buildCommands() {\n const ed = this.editor;\n return [\n { id: 'h1', label: 'Heading 1', hint: 'Big section heading', icon: 'heading', run: () => ed.setBlockType('h1') },\n { id: 'h2', label: 'Heading 2', hint: 'Medium heading', icon: 'heading', run: () => ed.setBlockType('h2') },\n { id: 'h3', label: 'Heading 3', hint: 'Small heading', icon: 'heading', run: () => ed.setBlockType('h3') },\n { id: 'ul', label: 'Bullet list', hint: 'Unordered list', icon: 'list-bullet', run: () => ed.setBlockType('ul') },\n { id: 'ol', label: 'Numbered list', hint: 'Ordered list', icon: 'list-ordered', run: () => ed.setBlockType('ol') },\n { id: 'quote', label: 'Quote', hint: 'Blockquote', icon: 'code', run: () => ed.setBlockType('blockquote') },\n { id: 'code', label: 'Code block', hint: 'Preformatted code', icon: 'code-view', run: () => ed.setBlockType('pre') },\n { id: 'hr', label: 'Divider', hint: 'Horizontal rule', icon: 'horizontal-rule', run: () => ed.insertHorizontalRule() },\n { id: 'table', label: 'Table', hint: '3×3 table', icon: 'table', run: () => this.insertTable() },\n { id: 'p', label: 'Text', hint: 'Plain paragraph', icon: 'font-family', run: () => ed.setBlockType('p') }\n ];\n }\n\n insertTable() {\n const Table = this.editor.registry.get('formats/table');\n if (Table && typeof Table.createTableElement === 'function' && typeof this.editor.insertBlock === 'function') {\n this.editor.insertBlock(Table.createTableElement(3, 3));\n }\n }\n\n buildMenu() {\n const menu = document.createElement('div');\n menu.className = 'yjd-slash-menu';\n menu.setAttribute('role', 'listbox');\n menu.style.display = 'none';\n this.menu = menu;\n document.body.appendChild(menu);\n }\n\n bindEvents() {\n this._onInput = () => this.handleInput();\n this.editor.editor.addEventListener('input', this._onInput);\n\n // Keyboard interaction while open (capture so we beat other handlers).\n this._onKeydown = (e) => {\n if (!this.isOpen) return;\n if (e.key === 'ArrowDown') { e.preventDefault(); this.move(1); }\n else if (e.key === 'ArrowUp') { e.preventDefault(); this.move(-1); }\n else if (e.key === 'Enter') { e.preventDefault(); this.choose(this.activeIndex); }\n else if (e.key === 'Escape') { e.preventDefault(); this.close(); }\n };\n this.editor.editor.addEventListener('keydown', this._onKeydown, true);\n\n this._onDocPointer = (e) => {\n if (this.isOpen && !this.menu.contains(e.target)) this.close();\n };\n document.addEventListener('pointerdown', this._onDocPointer, true);\n }\n\n handleInput() {\n const sel = window.getSelection();\n if (!sel || !sel.isCollapsed || !sel.rangeCount) return this.close();\n const range = sel.getRangeAt(0);\n const node = range.startContainer;\n if (node.nodeType !== Node.TEXT_NODE) return this.close();\n\n const before = node.textContent.slice(0, range.startOffset);\n const m = before.match(/(?:^|\\s)\\/([^\\s/]*)$/);\n if (!m) return this.close();\n\n this.query = m[1];\n this.slashNode = node;\n this.slashStart = range.startOffset - this.query.length - 1; // index of \"/\"\n const q = this.query.toLowerCase();\n this.filtered = this.commands.filter(c =>\n c.label.toLowerCase().includes(q) ||\n c.id.toLowerCase().includes(q) ||\n (c.hint || '').toLowerCase().includes(q));\n if (!this.filtered.length) return this.close();\n this.activeIndex = 0;\n this.render();\n this.open(range);\n }\n\n open(range) {\n this.isOpen = true;\n this.menu.style.display = 'block';\n // Position below the caret.\n const rect = range.getBoundingClientRect();\n const x = rect.left || (range.startContainer.parentElement || this.editor.editor).getBoundingClientRect().left;\n const y = rect.bottom || rect.top;\n this.menu.style.left = `${Math.round(x + window.scrollX)}px`;\n this.menu.style.top = `${Math.round(y + window.scrollY + 6)}px`;\n // Flip up if off the bottom.\n const mh = this.menu.offsetHeight;\n if (rect.bottom + mh + 8 > window.innerHeight) {\n this.menu.style.top = `${Math.round(rect.top + window.scrollY - mh - 6)}px`;\n }\n }\n\n close() {\n if (!this.isOpen) return;\n this.isOpen = false;\n this.menu.style.display = 'none';\n }\n\n move(delta) {\n this.activeIndex = (this.activeIndex + delta + this.filtered.length) % this.filtered.length;\n this.render();\n }\n\n render() {\n this.menu.innerHTML = '';\n this.filtered.forEach((cmd, i) => {\n const item = document.createElement('button');\n item.type = 'button';\n item.className = 'yjd-slash-item' + (i === this.activeIndex ? ' active' : '');\n item.setAttribute('role', 'option');\n item.setAttribute('aria-selected', i === this.activeIndex ? 'true' : 'false');\n\n const icon = document.createElement('span');\n icon.className = 'yjd-slash-icon';\n icon.innerHTML = IconUtils.getIcon(cmd.icon) || '';\n\n const text = document.createElement('span');\n text.className = 'yjd-slash-text';\n text.innerHTML = `<span class=\"yjd-slash-label\">${cmd.label}</span><span class=\"yjd-slash-hint\">${cmd.hint}</span>`;\n\n item.append(icon, text);\n // pointerdown (not click) so the editor selection isn't lost first.\n item.addEventListener('pointerdown', (e) => { e.preventDefault(); this.choose(i); });\n this.menu.appendChild(item);\n });\n }\n\n choose(index) {\n const cmd = this.filtered[index];\n if (!cmd) return this.close();\n\n // Remove the typed \"/query\" then run the command.\n try {\n const node = this.slashNode;\n const sel = window.getSelection();\n const del = document.createRange();\n del.setStart(node, this.slashStart);\n del.setEnd(node, this.slashStart + this.query.length + 1);\n del.deleteContents();\n const caret = document.createRange();\n caret.setStart(node, this.slashStart);\n caret.collapse(true);\n sel.removeAllRanges();\n sel.addRange(caret);\n } catch (e) { /* node moved; run anyway */ }\n\n this.close();\n this.editor.focus();\n cmd.run(this.editor);\n }\n\n destroy() {\n this.editor.editor.removeEventListener('input', this._onInput);\n this.editor.editor.removeEventListener('keydown', this._onKeydown, true);\n document.removeEventListener('pointerdown', this._onDocPointer, true);\n if (this.menu && this.menu.parentNode) this.menu.parentNode.removeChild(this.menu);\n super.destroy();\n }\n}\n","import Module from '../core/module.js';\n\n/**\n * @mention module — trigger-based autocomplete that inserts a token carrying an\n * id, so the serialized HTML/Markdown can tell the server who was tagged.\n *\n * new Editor(el, {\n * mention: {\n * trigger: '@',\n * source: async (query) => [{ id, name, avatar_url }],\n * renderItem: (item) => `<img src=\"${item.avatar_url}\"> ${item.name}`,\n * // optional extra triggers, e.g. '#' for task refs:\n * triggers: [{ char: '#', source: async (q) => [...] }]\n * }\n * })\n *\n * Token HTML: <span class=\"mention\" data-id=\"ID\" data-trigger=\"@\"\n * contenteditable=\"false\">@Name</span>\n * → getMarkdown() emits `@[Name](id)`. Fires editor.on('mention:select', item).\n */\nexport default class Mention extends Module {\n constructor(editor, options = {}) {\n super(editor, options);\n this.isOpen = false;\n this.activeIndex = 0;\n this.items = [];\n this.sources = this._buildSources();\n this.buildMenu();\n this.bindEvents();\n }\n\n _buildSources() {\n const cfg = this.editor.options.mention || this.options || {};\n const map = {};\n const renderItem = cfg.renderItem;\n if (typeof cfg.source === 'function') {\n map[cfg.trigger || '@'] = { source: cfg.source, renderItem: cfg.renderItem || renderItem };\n }\n (cfg.triggers || []).forEach((t) => {\n if (t && t.char && typeof t.source === 'function') {\n map[t.char] = { source: t.source, renderItem: t.renderItem || renderItem };\n }\n });\n return map;\n }\n\n get enabled() { return Object.keys(this.sources).length > 0; }\n\n buildMenu() {\n const menu = document.createElement('div');\n menu.className = 'yjd-mention-menu';\n menu.setAttribute('role', 'listbox');\n menu.style.display = 'none';\n this.menu = menu;\n document.body.appendChild(menu);\n }\n\n bindEvents() {\n if (!this.enabled) return;\n this._onInput = () => this.handleInput();\n this.editor.editor.addEventListener('input', this._onInput);\n\n this._onKeydown = (e) => {\n if (!this.isOpen) return;\n if (e.key === 'ArrowDown') { e.preventDefault(); this.move(1); }\n else if (e.key === 'ArrowUp') { e.preventDefault(); this.move(-1); }\n else if (e.key === 'Enter' || e.key === 'Tab') { e.preventDefault(); this.choose(this.activeIndex); }\n else if (e.key === 'Escape') { e.preventDefault(); this.close(); }\n };\n this.editor.editor.addEventListener('keydown', this._onKeydown, true);\n\n this._onDocPointer = (e) => { if (this.isOpen && !this.menu.contains(e.target)) this.close(); };\n document.addEventListener('pointerdown', this._onDocPointer, true);\n }\n\n handleInput() {\n const sel = window.getSelection();\n if (!sel || !sel.isCollapsed || !sel.rangeCount) return this.close();\n const range = sel.getRangeAt(0);\n const node = range.startContainer;\n if (node.nodeType !== Node.TEXT_NODE) return this.close();\n\n const triggers = Object.keys(this.sources).map((c) => c.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')).join('');\n const before = node.textContent.slice(0, range.startOffset);\n const m = before.match(new RegExp(`(?:^|\\\\s)([${triggers}])([^\\\\s${triggers}]*)$`));\n if (!m) return this.close();\n\n this.char = m[1];\n this.query = m[2];\n this.node = node;\n this.start = range.startOffset - this.query.length - 1; // index of trigger char\n this._loadFor(range);\n }\n\n _loadFor(range) {\n const src = this.sources[this.char];\n if (!src) return this.close();\n clearTimeout(this._t);\n const q = this.query, char = this.char;\n this._t = setTimeout(() => {\n Promise.resolve(src.source(q)).then((items) => {\n // Ignore stale responses (user kept typing / switched trigger).\n if (this.char !== char || this.query !== q) return;\n this.items = Array.isArray(items) ? items : [];\n if (!this.items.length) return this.close();\n this.activeIndex = 0;\n this.render(src.renderItem);\n this.open(range);\n }).catch(() => this.close());\n }, 120);\n }\n\n open(range) {\n this.isOpen = true;\n this.menu.style.display = 'block';\n const rect = range.getBoundingClientRect();\n const x = rect.left || (range.startContainer.parentElement || this.editor.editor).getBoundingClientRect().left;\n const y = rect.bottom || rect.top;\n this.menu.style.left = `${Math.round(x + window.scrollX)}px`;\n this.menu.style.top = `${Math.round(y + window.scrollY + 6)}px`;\n const mh = this.menu.offsetHeight;\n if (rect.bottom + mh + 8 > window.innerHeight) {\n this.menu.style.top = `${Math.round(rect.top + window.scrollY - mh - 6)}px`;\n }\n }\n\n close() {\n if (!this.isOpen) return;\n this.isOpen = false;\n this.menu.style.display = 'none';\n }\n\n move(d) {\n this.activeIndex = (this.activeIndex + d + this.items.length) % this.items.length;\n [...this.menu.children].forEach((el, i) => {\n el.classList.toggle('active', i === this.activeIndex);\n el.setAttribute('aria-selected', i === this.activeIndex ? 'true' : 'false');\n });\n }\n\n render(renderItem) {\n this.menu.innerHTML = '';\n this.items.forEach((item, i) => {\n const el = document.createElement('button');\n el.type = 'button';\n el.className = 'yjd-mention-item' + (i === this.activeIndex ? ' active' : '');\n el.setAttribute('role', 'option');\n el.setAttribute('aria-selected', i === this.activeIndex ? 'true' : 'false');\n const label = item.name || item.label || item.id || '';\n el.innerHTML = typeof renderItem === 'function'\n ? renderItem(item)\n : `${item.avatar_url ? `<img class=\"yjd-mention-avatar\" src=\"${item.avatar_url}\" alt=\"\">` : ''}<span class=\"yjd-mention-name\">${this.char}${label}</span>`;\n el.addEventListener('pointerdown', (e) => { e.preventDefault(); this.choose(i); });\n this.menu.appendChild(el);\n });\n }\n\n choose(index) {\n const item = this.items[index];\n if (!item) return this.close();\n const name = item.name || item.label || item.id || '';\n try {\n const node = this.node;\n const sel = window.getSelection();\n const del = document.createRange();\n del.setStart(node, this.start);\n del.setEnd(node, this.start + this.query.length + 1);\n del.deleteContents();\n\n const span = document.createElement('span');\n span.className = 'mention';\n span.setAttribute('data-id', String(item.id != null ? item.id : ''));\n span.setAttribute('data-trigger', this.char);\n span.setAttribute('contenteditable', 'false');\n span.textContent = this.char + name;\n del.insertNode(span);\n\n const space = document.createTextNode(' ');\n span.after(space);\n const caret = document.createRange();\n caret.setStart(space, 1);\n caret.collapse(true);\n sel.removeAllRanges();\n sel.addRange(caret);\n } catch (e) { /* node moved */ }\n\n this.close();\n this.editor.focus();\n if (typeof this.editor.onContentChange === 'function') this.editor.onContentChange();\n this.editor.emit('mention:select', item);\n }\n\n destroy() {\n if (this._onInput) this.editor.editor.removeEventListener('input', this._onInput);\n if (this._onKeydown) this.editor.editor.removeEventListener('keydown', this._onKeydown, true);\n if (this._onDocPointer) document.removeEventListener('pointerdown', this._onDocPointer, true);\n if (this.menu && this.menu.parentNode) this.menu.parentNode.removeChild(this.menu);\n super.destroy();\n }\n}\n","import Module from '../core/module.js';\n\n/**\n * Resize Handles Module - Adds resize functionality to images, videos, and tables\n * Creates 4 corner handles for dragging to resize elements\n */\nclass ResizeHandles extends Module {\n static DEFAULTS = {\n minWidth: 50,\n minHeight: 50,\n maxWidth: 800,\n maxHeight: 600,\n maintainAspectRatio: true, // For images and videos\n snapToGrid: false,\n gridSize: 10\n };\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.activeElement = null;\n this.handles = [];\n this.isResizing = false;\n this.startX = 0;\n this.startY = 0;\n this.startWidth = 0;\n this.startHeight = 0;\n this.currentHandle = null;\n this.aspectRatio = 1;\n \n this.init();\n }\n\n init() {\n this.createHandles();\n this.setupEventListeners();\n }\n\n /**\n * Create resize handles container\n */\n createHandles() {\n this.handlesContainer = document.createElement('div');\n this.handlesContainer.className = 'resize-handles-container';\n this.handlesContainer.style.position = 'absolute';\n this.handlesContainer.style.pointerEvents = 'none';\n this.handlesContainer.style.zIndex = '997'; // Lower than all toolbar elements\n this.handlesContainer.style.display = 'none';\n\n // Create 4 corner handles\n const handlePositions = [\n { name: 'nw', cursor: 'nw-resize', position: { top: -4, left: -4 } },\n { name: 'ne', cursor: 'ne-resize', position: { top: -4, right: -4 } },\n { name: 'sw', cursor: 'sw-resize', position: { bottom: -4, left: -4 } },\n { name: 'se', cursor: 'se-resize', position: { bottom: -4, right: -4 } }\n ];\n\n handlePositions.forEach(config => {\n const handle = this.createHandle(config);\n this.handles.push(handle);\n this.handlesContainer.appendChild(handle);\n });\n\n // Add to editor wrapper but ensure it's behind toolbars\n this.editor.wrapper.appendChild(this.handlesContainer);\n }\n\n /**\n * Create individual resize handle\n */\n createHandle(config) {\n const handle = document.createElement('div');\n handle.className = `resize-handle resize-handle-${config.name}`;\n handle.style.position = 'absolute';\n handle.style.width = '8px';\n handle.style.height = '8px';\n handle.style.backgroundColor = '#3b82f6';\n handle.style.border = '1px solid #fff';\n handle.style.borderRadius = '50%';\n handle.style.cursor = config.cursor;\n handle.style.pointerEvents = 'auto';\n handle.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';\n handle.style.zIndex = '999'; // Lower than toolbars\n handle.dataset.handle = config.name;\n\n // Position handle\n Object.entries(config.position).forEach(([key, value]) => {\n handle.style[key] = value + 'px';\n });\n\n // Add event listeners\n handle.addEventListener('mousedown', (e) => this.handleMouseDown(e, config.name));\n \n return handle;\n }\n\n /**\n * Setup event listeners\n */\n setupEventListeners() {\n // Keep references so listeners can be removed in destroy() (the document/\n // window handlers would otherwise leak across editor create/destroy cycles).\n this._onEditorClick = (e) => this.handleElementClick(e);\n this._onDocClick = (e) => {\n if (!this.isClickOnResizableElement(e) && !this.isClickOnHandle(e)) {\n this.hideHandles();\n }\n };\n this._onDocMousemove = (e) => this.handleMouseMove(e);\n this._onDocMouseup = (e) => this.handleMouseUp(e);\n this._onWindowScroll = () => {\n if (this.activeElement) this.updateHandlePosition();\n };\n this._onEditorScroll = () => {\n if (this.activeElement) this.updateHandlePosition();\n };\n\n this.editor.editor.addEventListener('click', this._onEditorClick);\n document.addEventListener('click', this._onDocClick);\n document.addEventListener('mousemove', this._onDocMousemove);\n document.addEventListener('mouseup', this._onDocMouseup);\n window.addEventListener('scroll', this._onWindowScroll);\n this.editor.editor.addEventListener('scroll', this._onEditorScroll);\n\n // Listen for DOM changes to update handles\n this.setupMutationObserver();\n }\n\n /**\n * Handle click on resizable elements\n */\n handleElementClick(e) {\n const target = e.target;\n \n // Debug logging\n \n \n // Find the actual resizable element\n let resizableElement = this.findResizableElement(target);\n \n if (resizableElement) {\n e.preventDefault();\n e.stopPropagation();\n this.showHandles(resizableElement);\n }\n }\n\n /**\n * Find the actual resizable element from a clicked target\n */\n findResizableElement(target) {\n // If target is already resizable, return it\n if (this.isResizableElement(target)) {\n return target;\n }\n \n // If target is inside a table (td, th, tr, tbody), find the table\n if (target.tagName === 'TD' || target.tagName === 'TH' || \n target.tagName === 'TR' || target.tagName === 'TBODY') {\n let parent = target.parentElement;\n while (parent && parent.tagName !== 'TABLE') {\n parent = parent.parentElement;\n }\n if (parent && this.isResizableElement(parent)) {\n return parent;\n }\n }\n \n // Check if any parent is resizable\n let parent = target.parentElement;\n while (parent && parent !== this.editor.wrapper) {\n if (this.isResizableElement(parent)) {\n return parent;\n }\n parent = parent.parentElement;\n }\n \n return null;\n }\n\n /**\n * Check if element is resizable\n */\n isResizableElement(element) {\n // Debug logging\n\n \n const isImage = element.classList.contains('inserted-image');\n const isVideo = element.classList.contains('inserted-video');\n const isTable = element.classList.contains('rich-editor-table');\n \n\n \n return isImage || isVideo || isTable;\n }\n\n /**\n * Check if click is on resizable element\n */\n isClickOnResizableElement(e) {\n return this.isResizableElement(e.target);\n }\n\n /**\n * Check if click is on resize handle\n */\n isClickOnHandle(e) {\n return e.target.classList.contains('resize-handle');\n }\n\n /**\n * Show resize handles for element\n */\n showHandles(element) {\n \n \n this.activeElement = element;\n this.updateHandlePosition();\n this.handlesContainer.style.display = 'block';\n \n // Store aspect ratio for images and videos\n if (element.classList.contains('inserted-image') || element.classList.contains('inserted-video')) {\n this.aspectRatio = element.offsetWidth / element.offsetHeight;\n }\n \n // For tables, ensure they have proper positioning and setup size monitoring\n if (element.classList.contains('rich-editor-table')) {\n element.style.position = 'relative';\n element.style.display = 'table';\n \n // Store initial dimensions for comparison\n this.lastTableWidth = element.offsetWidth;\n this.lastTableHeight = element.offsetHeight;\n \n // Setup periodic size checking for tables\n this.setupTableSizeMonitoring(element);\n }\n \n\n }\n\n /**\n * Hide resize handles\n */\n hideHandles() {\n // Clear table size monitoring\n if (this.tableSizeInterval) {\n clearInterval(this.tableSizeInterval);\n this.tableSizeInterval = null;\n }\n \n this.activeElement = null;\n this.handlesContainer.style.display = 'none';\n }\n\n /**\n * Update handle position based on active element\n */\n updateHandlePosition() {\n if (!this.activeElement) return;\n\n // Check if element still exists in DOM\n if (!document.body.contains(this.activeElement)) {\n this.hideHandles();\n return;\n }\n\n const elementRect = this.activeElement.getBoundingClientRect();\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = this.editor.wrapper.scrollTop || 0;\n const scrollLeft = this.editor.wrapper.scrollLeft || 0;\n\n // Position handles container\n const top = elementRect.top - editorRect.top + scrollTop;\n const left = elementRect.left - editorRect.left + scrollLeft;\n const width = elementRect.width;\n const height = elementRect.height;\n const bottom = top + height;\n this.handlesContainer.style.top = top + 'px';\n this.handlesContainer.style.left = left + 'px';\n this.handlesContainer.style.width = width + 'px';\n this.handlesContainer.style.height = height + 'px';\n \n if(bottom < 0){\n this.hideHandles();\n return;\n }\n if(top > editorRect.height){\n this.hideHandles();\n return;\n }\n }\n\n /**\n * Handle mouse down on resize handle\n */\n handleMouseDown(e, handleName) {\n e.preventDefault();\n e.stopPropagation();\n\n this.isResizing = true;\n this.currentHandle = handleName;\n this.startX = e.clientX;\n this.startY = e.clientY;\n this.startWidth = this.activeElement.offsetWidth;\n this.startHeight = this.activeElement.offsetHeight;\n\n // Store initial position\n const elementRect = this.activeElement.getBoundingClientRect();\n this.startLeft = elementRect.left;\n this.startTop = elementRect.top;\n\n // Add resizing class for styling\n this.activeElement.classList.add('resizing');\n document.body.style.cursor = e.target.style.cursor;\n document.body.style.userSelect = 'none';\n }\n\n /**\n * Handle mouse move during resize\n */\n handleMouseMove(e) {\n if (!this.isResizing || !this.activeElement) return;\n\n const deltaX = e.clientX - this.startX;\n const deltaY = e.clientY - this.startY;\n\n let newWidth = this.startWidth;\n let newHeight = this.startHeight;\n\n // Calculate new dimensions based on handle position\n switch (this.currentHandle) {\n case 'nw':\n newWidth = this.startWidth - deltaX;\n newHeight = this.startHeight - deltaY;\n break;\n case 'ne':\n newWidth = this.startWidth + deltaX;\n newHeight = this.startHeight - deltaY;\n break;\n case 'sw':\n newWidth = this.startWidth - deltaX;\n newHeight = this.startHeight + deltaY;\n break;\n case 'se':\n newWidth = this.startWidth + deltaX;\n newHeight = this.startHeight + deltaY;\n break;\n }\n\n // Never let an element grow past the editor's content width (its inner\n // width minus padding) — otherwise tables/images overflow the editor.\n let maxW = this.options.maxWidth;\n const area = this.editor && this.editor.editor;\n if (area) {\n const cs = getComputedStyle(area);\n const avail = area.clientWidth\n - (parseFloat(cs.paddingLeft) || 0)\n - (parseFloat(cs.paddingRight) || 0);\n if (avail > 0) maxW = Math.min(maxW, avail);\n }\n\n // Apply constraints\n newWidth = Math.max(this.options.minWidth, Math.min(maxW, newWidth));\n newHeight = Math.max(this.options.minHeight, Math.min(this.options.maxHeight, newHeight));\n\n // Maintain aspect ratio for images and videos\n if ((this.activeElement.classList.contains('inserted-image') ||\n this.activeElement.classList.contains('inserted-video')) &&\n this.options.maintainAspectRatio) {\n\n const ratioByWidth = newWidth / this.aspectRatio;\n const ratioByHeight = newHeight * this.aspectRatio;\n\n if (Math.abs(newWidth - ratioByHeight) < Math.abs(newHeight - ratioByWidth)) {\n newWidth = ratioByHeight;\n } else {\n newHeight = ratioByWidth;\n }\n // Aspect-ratio math can push width back over the limit — re-clamp.\n if (newWidth > maxW) {\n newWidth = maxW;\n newHeight = newWidth / this.aspectRatio;\n }\n }\n\n // Snap to grid if enabled\n if (this.options.snapToGrid) {\n newWidth = Math.round(newWidth / this.options.gridSize) * this.options.gridSize;\n newHeight = Math.round(newHeight / this.options.gridSize) * this.options.gridSize;\n }\n\n // Apply new dimensions\n this.applyDimensions(newWidth, newHeight);\n this.updateHandlePosition();\n\n // Emit resize event\n this.emit('element-resize', {\n element: this.activeElement,\n width: newWidth,\n height: newHeight,\n handle: this.currentHandle\n });\n }\n\n /**\n * Handle mouse up - end resize\n */\n handleMouseUp(e) {\n if (!this.isResizing) return;\n\n this.isResizing = false;\n this.currentHandle = null;\n \n // Remove resizing class\n if (this.activeElement) {\n this.activeElement.classList.remove('resizing');\n }\n \n // Reset cursor\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n\n // Emit resize complete event\n this.emit('element-resize-complete', {\n element: this.activeElement,\n width: this.activeElement.offsetWidth,\n height: this.activeElement.offsetHeight\n });\n }\n\n /**\n * Apply dimensions to element\n */\n applyDimensions(width, height) {\n if (!this.activeElement) return;\n\n if (this.activeElement.classList.contains('rich-editor-table')) {\n // For tables, set both width and height\n this.activeElement.style.width = width + 'px';\n this.activeElement.style.minWidth = width + 'px';\n this.activeElement.style.height = height + 'px';\n this.activeElement.style.minHeight = height + 'px';\n \n // Calculate cell dimensions\n const rows = this.activeElement.querySelectorAll('tr');\n const cols = rows.length > 0 ? rows[0].querySelectorAll('td, th').length : 0;\n \n if (rows.length > 0 && cols > 0) {\n const cellWidth = Math.floor(width / cols);\n const cellHeight = Math.floor(height / rows.length);\n \n // Apply dimensions to all cells\n const cells = this.activeElement.querySelectorAll('td, th');\n cells.forEach(cell => {\n cell.style.minWidth = cellWidth + 'px';\n cell.style.minHeight = cellHeight + 'px';\n cell.style.height = cellHeight + 'px';\n });\n }\n } else {\n // For images and videos (including iframes)\n this.activeElement.style.width = width + 'px';\n this.activeElement.style.height = height + 'px';\n \n // If it's an iframe, update its attributes too\n if (this.activeElement.tagName === 'IFRAME') {\n this.activeElement.width = width;\n this.activeElement.height = height;\n }\n }\n }\n\n /**\n * Get current active element\n */\n getActiveElement() {\n return this.activeElement;\n }\n\n /**\n * Set active element programmatically\n */\n setActiveElement(element) {\n if (this.isResizableElement(element)) {\n this.showHandles(element);\n }\n }\n\n /**\n * Check and update handles if active element has changed\n */\n checkAndUpdateHandles() {\n if (this.activeElement) {\n // Check if element still exists and is still resizable\n if (!document.body.contains(this.activeElement) || !this.isResizableElement(this.activeElement)) {\n this.hideHandles();\n return;\n }\n \n // Update position if element still exists\n this.updateHandlePosition();\n \n // For tables, also check if size has changed due to content\n if (this.activeElement.classList.contains('rich-editor-table')) {\n this.checkTableSizeChange();\n }\n }\n }\n\n /**\n * Force refresh handles for current active element\n */\n refreshHandles() {\n if (this.activeElement && document.body.contains(this.activeElement)) {\n this.updateHandlePosition();\n }\n }\n\n /**\n * Setup periodic monitoring for table size changes\n */\n setupTableSizeMonitoring(tableElement) {\n // Clear any existing interval\n if (this.tableSizeInterval) {\n clearInterval(this.tableSizeInterval);\n }\n \n // Check table size every 100ms\n this.tableSizeInterval = setInterval(() => {\n if (this.activeElement && this.activeElement.classList.contains('rich-editor-table')) {\n this.checkTableSizeChange();\n } else {\n // Clear interval if no longer monitoring a table\n clearInterval(this.tableSizeInterval);\n this.tableSizeInterval = null;\n }\n }, 100);\n }\n\n /**\n * Check if table size has changed and update handles accordingly\n */\n checkTableSizeChange() {\n if (!this.activeElement || !this.activeElement.classList.contains('rich-editor-table')) {\n return;\n }\n \n const currentWidth = this.activeElement.offsetWidth;\n const currentHeight = this.activeElement.offsetHeight;\n \n // Check if dimensions have changed significantly (more than 1px to avoid floating point issues)\n if (Math.abs(currentWidth - this.lastTableWidth) > 1 || \n Math.abs(currentHeight - this.lastTableHeight) > 1) {\n \n // Update stored dimensions\n this.lastTableWidth = currentWidth;\n this.lastTableHeight = currentHeight;\n \n // Update handle positions\n this.updateHandlePosition();\n \n // Emit size change event\n this.emit('table-size-changed', {\n element: this.activeElement,\n width: currentWidth,\n height: currentHeight,\n previousWidth: this.lastTableWidth,\n previousHeight: this.lastTableHeight\n });\n }\n }\n\n /**\n * Setup mutation observer to watch for DOM changes\n */\n setupMutationObserver() {\n if (typeof MutationObserver !== 'undefined') {\n this.mutationObserver = new MutationObserver((mutations) => {\n let shouldUpdate = false;\n \n mutations.forEach((mutation) => {\n // Check if active element was removed or modified\n if (this.activeElement) {\n if (mutation.type === 'childList') {\n // Check if active element was removed\n if (mutation.removedNodes) {\n for (let node of mutation.removedNodes) {\n if (node === this.activeElement || node.contains(this.activeElement)) {\n shouldUpdate = true;\n break;\n }\n }\n }\n \n // Check if active element was modified\n if (mutation.target === this.activeElement || \n (mutation.target.nodeType === Node.ELEMENT_NODE && \n this.activeElement.contains(mutation.target))) {\n shouldUpdate = true;\n }\n }\n \n // Check for text content changes that might affect table size\n if (mutation.type === 'characterData' && this.activeElement.classList.contains('rich-editor-table')) {\n // Check if the text change is within the active table\n let target = mutation.target;\n while (target && target !== this.activeElement) {\n target = target.parentNode;\n }\n if (target === this.activeElement) {\n shouldUpdate = true;\n }\n }\n \n // Check for attribute changes that might affect size\n if (mutation.type === 'attributes' && this.activeElement.classList.contains('rich-editor-table')) {\n const attributeName = mutation.attributeName;\n // Monitor changes to style attributes that affect size\n if (attributeName === 'style' || attributeName === 'class') {\n shouldUpdate = true;\n }\n }\n }\n });\n \n if (shouldUpdate) {\n // Use setTimeout to ensure DOM is fully updated\n setTimeout(() => {\n this.checkAndUpdateHandles();\n }, 0);\n }\n });\n \n // Start observing the editor content with more comprehensive monitoring\n this.mutationObserver.observe(this.editor.editor, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class'],\n characterData: true, // Monitor text content changes\n characterDataOldValue: true\n });\n }\n }\n\n /**\n * Enable/disable aspect ratio maintenance\n */\n setMaintainAspectRatio(maintain) {\n this.options.maintainAspectRatio = maintain;\n }\n\n /**\n * Set resize constraints\n */\n setConstraints(minWidth, minHeight, maxWidth, maxHeight) {\n this.options.minWidth = minWidth || this.options.minWidth;\n this.options.minHeight = minHeight || this.options.minHeight;\n this.options.maxWidth = maxWidth || this.options.maxWidth;\n this.options.maxHeight = maxHeight || this.options.maxHeight;\n }\n\n /**\n * Destroy module\n */\n destroy() {\n this.hideHandles();\n\n // Remove global event listeners\n if (this._onDocClick) {\n this.editor.editor.removeEventListener('click', this._onEditorClick);\n document.removeEventListener('click', this._onDocClick);\n document.removeEventListener('mousemove', this._onDocMousemove);\n document.removeEventListener('mouseup', this._onDocMouseup);\n window.removeEventListener('scroll', this._onWindowScroll);\n this.editor.editor.removeEventListener('scroll', this._onEditorScroll);\n this._onEditorClick = this._onDocClick = this._onDocMousemove = null;\n this._onDocMouseup = this._onWindowScroll = this._onEditorScroll = null;\n }\n\n if (this.handlesContainer) {\n this.handlesContainer.remove();\n }\n\n // Clear table size monitoring\n if (this.tableSizeInterval) {\n clearInterval(this.tableSizeInterval);\n this.tableSizeInterval = null;\n }\n \n // Disconnect mutation observer\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n this.mutationObserver = null;\n }\n \n super.destroy();\n }\n}\n\nexport default ResizeHandles; ","/**\n * Image Popup Component - Popup for inserting images\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass ImagePopup {\n constructor(options = {}) {\n this.options = {\n onImageInsert: null,\n editor: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.clickOutsideHandler = null;\n this.selectedImageSrc = null;\n this.savedSelection = null; // Save editor selection\n this.resizeHandler = null;\n \n this.createImagePopup();\n }\n\n /**\n * Create image popup\n */\n createImagePopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'image-popup';\n \n const content = document.createElement('div');\n content.className = 'image-popup-content';\n \n // Title\n const title = document.createElement('h3');\n title.textContent = 'Insert image';\n title.className = 'yjd-input-title';\n content.appendChild(title);\n \n // Container\n const uploadContainer = document.createElement('div');\n uploadContainer.className = 'image-input-container';\n\n const textLabel = document.createElement('p');\n textLabel.textContent = 'Your image url';\n textLabel.className = 'yjd-input-label';\n\n const inputgroup1 = document.createElement('div');\n inputgroup1.className = 'yjd-input-upload-group';\n this.inputGroup = inputgroup1; // Store reference\n\n\n // input url\n this.urlInput = document.createElement('input');\n this.urlInput.type = 'url';\n this.urlInput.className = 'yjd-input';\n this.urlInput.placeholder = 'Please enter your image URL';\n \n\n // Hidden file input\n this.fileInput = document.createElement('input');\n this.fileInput.type = 'file';\n this.fileInput.accept = 'image/*';\n this.fileInput.className = 'image-input-hidden'; // ẩn bằng CSS\n this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));\n\n // Custom button\n const customButton = document.createElement('button');\n customButton.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" x2=\"12\" y1=\"3\" y2=\"15\"/></svg>`;\n customButton.className = 'yjd-custom-upload-button';\n this.customButton = customButton;\n customButton.addEventListener('click', () => this.fileInput.click());\n\n // Create preview container\n this.createPreviewContainer();\n\n // Append elements\n inputgroup1.appendChild(this.urlInput);\n inputgroup1.appendChild(this.fileInput);\n inputgroup1.appendChild(customButton);\n uploadContainer.appendChild(textLabel);\n uploadContainer.appendChild(inputgroup1);\n uploadContainer.appendChild(this.previewContainer);\n content.appendChild(uploadContainer);\n this.urlInput.addEventListener('input', () => {\n this.updateInsertButton();\n // Show preview if URL is valid\n const url = this.urlInput.value.trim();\n if (url && this.isValidImageUrl(url)) {\n this.showPreview(url);\n } else {\n this.removePreview();\n }\n if(this.urlInput.value.trim()){\n this.customButton.style.display = 'none';\n }else{\n this.customButton.style.display = 'flex';\n }\n });\n // Buttons\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'yjd-button-container';\n \n const cancelButton = document.createElement('button');\n cancelButton.type = 'button';\n cancelButton.className = 'image-button yjd-button-cancel';\n cancelButton.textContent = 'Cancel';\n cancelButton.addEventListener('click', () => {\n this.hide();\n // Maintain editor focus after popup close\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n this.insertButton = document.createElement('button');\n this.insertButton.type = 'button';\n this.insertButton.className = 'image-button yjd-button-confirm button-disable';\n this.insertButton.textContent = 'Add image';\n this.insertButton.disabled = true;\n this.insertButton.addEventListener('click', () => {\n this.insertImage();\n // Maintain editor focus after insert\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n buttonContainer.appendChild(cancelButton);\n buttonContainer.appendChild(this.insertButton);\n content.appendChild(buttonContainer);\n \n this.popup.appendChild(content);\n appendPopup(this.popup);\n \n // Prevent focus loss when clicking on popup\n if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {\n this.options.editor.preventFocusLoss(this.popup);\n }\n }\n\n async handleFileSelect(e) {\n const file = e.target.files[0];\n if (!file) return;\n \n try {\n const { default: Image } = await import('../formats/image.js');\n this.selectedImageSrc = await Image.handleFileUpload(file);\n this.urlInput.value = '';\n this.showPreview(this.selectedImageSrc);\n this.updateInsertButton();\n } catch (error) {\n alert(error.message);\n }\n }\n\n updateInsertButton() {\n const hasImage = this.selectedImageSrc || this.urlInput.value.trim();\n this.insertButton.disabled = !hasImage;\n this.insertButton.classList.toggle('button-disable', !hasImage);\n }\n\n /**\n * Show image preview\n */\n showPreview(imageSrc) {\n if (!imageSrc) return;\n \n this.imagePreview.src = imageSrc;\n this.previewContainer.style.display = 'block';\n this.selectedImageSrc = imageSrc;\n \n // Hide input group\n this.toggleInputGroup(false);\n \n // Recalculate position after preview is shown to ensure buttons remain visible\n this.recalculatePosition();\n }\n\n /**\n * Remove image preview and show input again\n */\n removePreview() {\n this.selectedImageSrc = null;\n this.previewContainer.style.display = 'none';\n this.imagePreview.src = '';\n \n // Show input group and reset file input\n this.toggleInputGroup(true);\n if (this.fileInput) {\n this.fileInput.value = '';\n }\n \n this.updateInsertButton();\n \n // Recalculate position after preview is removed\n this.recalculatePosition();\n }\n\n /**\n * Toggle input group visibility\n */\n toggleInputGroup(show) {\n if (!this.inputGroup) return;\n \n if (show) {\n this.inputGroup.style.display = 'flex';\n this.inputGroup.style.visibility = 'visible';\n if (this.customButton) {\n this.customButton.style.pointerEvents = 'auto';\n }\n } else {\n this.inputGroup.style.display = 'none';\n this.inputGroup.style.visibility = 'hidden';\n }\n }\n\n /**\n * Create preview container with image and remove button\n */\n createPreviewContainer() {\n this.previewContainer = document.createElement('div');\n this.previewContainer.className = 'image-preview-container';\n this.previewContainer.style.cssText = 'display: none; position: relative;';\n \n // Image preview\n this.imagePreview = document.createElement('img');\n this.imagePreview.className = 'image-preview';\n this.imagePreview.style.cssText = 'max-width: 100%; max-height: 200px; border-radius: 8px; object-fit: contain;';\n \n // Remove button\n this.removeButton = document.createElement('button');\n this.removeButton.className = 'image-remove-button';\n this.removeButton.innerHTML = '×';\n this.removeButton.style.cssText = `\n position: absolute; top: 5px; right: 5px; background: rgba(0,0,0,0.7);\n color: white; border: none; border-radius: 50%; width: 24px; height: 24px;\n cursor: pointer; font-size: 16px; font-weight: bold;\n `;\n this.removeButton.addEventListener('click', () => this.removePreview());\n \n this.previewContainer.appendChild(this.imagePreview);\n this.previewContainer.appendChild(this.removeButton);\n }\n\n /**\n * Check if URL is a valid image URL\n */\n isValidImageUrl(url) {\n try {\n const urlObj = new URL(url);\n const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp'];\n const imageHosts = ['imgur.com', 'images.unsplash.com', 'picsum.photos', 'via.placeholder.com'];\n \n const pathname = urlObj.pathname.toLowerCase();\n const hasImageExtension = imageExtensions.some(ext => pathname.endsWith(ext));\n const isFromImageHost = imageHosts.some(host => urlObj.hostname.includes(host));\n \n return hasImageExtension || isFromImageHost;\n } catch {\n return false;\n }\n }\n\n async insertImage() {\n let src = this.selectedImageSrc || this.urlInput.value.trim();\n const alt = '';\n \n if (!src) return;\n \n // Always validate URL (both file upload and URL input)\n try {\n const { default: Image } = await import('../formats/image.js');\n const isValid = await Image.validateImageUrl(src);\n if (!isValid) {\n alert('Invalid image URL. Please check the URL and try again.');\n return;\n }\n } catch (error) {\n alert('Error validating image URL.');\n return;\n }\n \n // Restore editor selection before inserting\n this.restoreSelection();\n \n if (this.options.onImageInsert) {\n this.options.onImageInsert(src, alt);\n }\n \n this.hide();\n this.reset();\n }\n\n reset() {\n this.fileInput.value = '';\n this.urlInput.value = '';\n this.selectedImageSrc = null;\n \n // Hide preview and show input\n this.previewContainer.style.display = 'none';\n this.imagePreview.src = '';\n this.toggleInputGroup(true);\n \n this.updateInsertButton();\n this.customButton.style.display = 'block';\n }\n\n /**\n * Save current editor selection\n */\n saveSelection() {\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n this.savedSelection = selection.getRangeAt(0).cloneRange();\n }\n }\n\n /**\n * Restore editor selection\n */\n restoreSelection() {\n if (this.savedSelection) {\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(this.savedSelection);\n }\n }\n\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n setupResizeHandler() {\n if (this.resizeHandler) {\n window.removeEventListener('resize', this.resizeHandler);\n }\n \n this.resizeHandler = () => {\n if (this.isVisible) {\n this.recalculatePosition();\n }\n };\n \n window.addEventListener('resize', this.resizeHandler);\n }\n\n removeResizeHandler() {\n if (this.resizeHandler) {\n window.removeEventListener('resize', this.resizeHandler);\n this.resizeHandler = null;\n }\n }\n\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n show(anchor) {\n if (!anchor) return;\n \n // Save current editor selection before showing popup\n this.saveSelection();\n \n // Reset state when showing popup\n this.reset();\n \n // Store anchor for recalculation\n this.currentAnchor = anchor;\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n this.popup.classList.add('visible');\n this.isVisible = true;\n \n this.setupClickOutside();\n }\n\n /**\n * Recalculate popup position to ensure it stays within viewport\n */\n recalculatePosition() {\n if (!this.currentAnchor || !this.isVisible) return;\n \n // Small delay to ensure DOM updates are complete\n setTimeout(() => {\n const position = calculatePopupPosition(this.currentAnchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n }, 10);\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n // Clear saved selection to avoid memory leaks\n this.savedSelection = null;\n }\n\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n }\n}\n\nexport default ImagePopup; ","import Editor from './lib/core/editor.js';\nimport registry from './lib/core/registry.js';\nimport Module from './lib/core/module.js';\nimport { Format, InlineFormat, BlockFormat } from './lib/core/format.js';\nimport StylesLoader from './lib/styles-loader.js';\nimport { renderStatic } from './lib/static.js';\nimport { htmlToMarkdown, markdownToHtml, domToJson, jsonToHtml } from './lib/serialize.js';\n\n// Import formats\nimport Bold from './lib/formats/bold.js';\nimport Italic from './lib/formats/italic.js';\nimport Underline from './lib/formats/underline.js';\nimport Strike from './lib/formats/strike.js';\nimport Subscript from './lib/formats/subscript.js';\nimport Superscript from './lib/formats/superscript.js';\nimport Color from './lib/formats/color.js';\nimport Background from './lib/formats/background.js';\nimport Link from './lib/formats/link.js';\nimport Table from './lib/formats/table.js';\nimport Heading from './lib/formats/heading.js';\nimport FontFamily from './lib/formats/font-family.js';\nimport LineHeight from './lib/formats/line-height.js';\nimport Capitalization from './lib/formats/capitalization.js';\nimport TextAlign from './lib/formats/text-align.js';\nimport List from './lib/formats/list.js';\nimport Indent, { IndentIncrease, IndentDecrease } from './lib/formats/indent.js';\nimport Emoji from './lib/formats/emoji.js';\nimport Image from './lib/formats/image.js';\nimport Video from './lib/formats/video.js';\nimport Tag from './lib/formats/tag.js';\nimport TextSize from './lib/formats/text-size.js';\n\nimport Import from './lib/formats/import.js';\n\n// Import modules\nimport Toolbar from './lib/modules/toolbar.js';\nimport History from './lib/modules/history.js';\nimport BlockToolbar from './lib/modules/block-toolbar.js';\nimport TableToolbar from './lib/modules/table-toolbar.js';\nimport CodeView from './lib/modules/code-view.js';\nimport FindReplace from './lib/modules/find-replace.js';\nimport SlashMenu from './lib/modules/slash-menu.js';\nimport Mention from './lib/modules/mention.js';\n\nimport ResizeHandles from './lib/modules/resize-handles.js';\n\n// Import UI components\nimport ColorPicker from './lib/ui/color-picker.js';\nimport IconUtils from './lib/ui/icons.js';\nimport LinkPopup from './lib/ui/link-popup.js';\nimport TablePopup from './lib/ui/table-popup.js';\nimport TextAlignPicker from './lib/ui/text-align-picker.js';\nimport ListPicker from './lib/ui/list-picker.js';\nimport EmojiPicker from './lib/ui/emoji-picker.js';\nimport ImagePopup from './lib/ui/image-popup.js';\nimport VideoPopup from './lib/ui/video-popup.js';\nimport TagPopup from './lib/ui/tag-popup.js';\n\nimport createCustomButton from './lib/ui/select-button.js';\n\n\n\n// Register default formats\nregistry.register('formats/bold', Bold, true);\nregistry.register('formats/italic', Italic, true);\nregistry.register('formats/underline', Underline, true);\nregistry.register('formats/strike', Strike, true);\nregistry.register('formats/subscript', Subscript, true);\nregistry.register('formats/superscript', Superscript, true);\nregistry.register('formats/color', Color, true);\nregistry.register('formats/background', Background, true);\nregistry.register('formats/link', Link, true);\nregistry.register('formats/table', Table, true);\nregistry.register('formats/heading', Heading, true);\nregistry.register('formats/font-family', FontFamily, true);\nregistry.register('formats/line-height', LineHeight, true);\nregistry.register('formats/capitalization', Capitalization, true);\nregistry.register('formats/text-align', TextAlign, true);\nregistry.register('formats/list', List, true);\nregistry.register('formats/indent', Indent, true);\nregistry.register('formats/indent-increase', IndentIncrease, true);\nregistry.register('formats/indent-decrease', IndentDecrease, true);\nregistry.register('formats/emoji', Emoji, true);\nregistry.register('formats/image', Image, true);\nregistry.register('formats/video', Video, true);\nregistry.register('formats/tag', Tag, true);\nregistry.register('formats/text-size', TextSize, true);\n\nregistry.register('formats/import', Import, true);\n\n// Register default modules\nregistry.register('modules/toolbar', Toolbar, true);\nregistry.register('modules/history', History, true);\nregistry.register('modules/block-toolbar', BlockToolbar, true);\nregistry.register('modules/table-toolbar', TableToolbar, true);\nregistry.register('modules/code-view', CodeView, true);\nregistry.register('modules/find-replace', FindReplace, true);\nregistry.register('modules/slash-menu', SlashMenu, true);\nregistry.register('modules/mention', Mention, true);\n\nregistry.register('modules/resize-handles', ResizeHandles, true);\n\n// Register UI components\nregistry.register('ui/color-picker', ColorPicker, true);\nregistry.register('ui/text-align-picker', TextAlignPicker, true);\nregistry.register('ui/list-picker', ListPicker, true);\nregistry.register('ui/emoji-picker', EmojiPicker, true);\nregistry.register('ui/image-popup', ImagePopup, true);\nregistry.register('ui/video-popup', VideoPopup, true);\nregistry.register('ui/tag-popup', TagPopup, true);\n\nregistry.register('ui/custom-button', createCustomButton, true);\n\n\n\n// Load CSS styles\nStylesLoader.loadStyles().catch(error => {\n console.warn('Could not load Rich Editor styles:', error);\n});\n\n// Main Editor class with registration system\nclass RichEditor extends Editor {\n /**\n * Register a module, format, or theme\n * @param {string|object} path - Registration path\n * @param {*} definition - Class definition\n * @param {boolean} suppressWarning - Suppress overwrite warnings\n */\n static register(path, definition, suppressWarning = false) {\n registry.register(path, definition, suppressWarning);\n }\n\n /**\n * Get registered item\n * @param {string} path - Registration path\n */\n static get(path) {\n return registry.get(path);\n }\n\n /**\n * Create new editor instance\n * @param {string|Element} selector - DOM selector or element\n * @param {object} options - Editor options\n */\n static create(selector, options = {}) {\n return new RichEditor(selector, options);\n }\n\n /**\n * Progressive-enhance a <textarea>: hide it, mount an editor in its place,\n * and keep the textarea's value in sync so existing form submits keep working.\n *\n * const ed = RichEditor.fromTextarea(document.querySelector('#body'), {\n * // any editor option; `format` chooses how the textarea is read/written:\n * format: 'html' | 'markdown', // default 'html'\n * });\n *\n * The textarea's current value seeds the editor (parsed as HTML or Markdown\n * per `format`). On every change the textarea is updated and a native 'input'\n * event is dispatched, so framework bindings / validation keep firing.\n *\n * @param {HTMLTextAreaElement|string} textarea Element or selector.\n * @param {object} [options] Editor options + optional `format`.\n * @returns {RichEditor}\n */\n static fromTextarea(textarea, options = {}) {\n const ta = typeof textarea === 'string' ? document.querySelector(textarea) : textarea;\n if (!ta) throw new Error('RichEditor.fromTextarea: textarea not found');\n\n const format = options.format === 'markdown' ? 'markdown' : 'html';\n const readEditor = (ed) => (format === 'markdown' ? ed.getMarkdown() : ed.getContent());\n\n // Mount point right after the textarea; hide the original.\n const mount = document.createElement('div');\n ta.after(mount);\n ta.style.display = 'none';\n ta.setAttribute('aria-hidden', 'true');\n\n const initial = ta.value || '';\n const editor = new RichEditor(mount, {\n width: '100%',\n ...options,\n // Seed content from the textarea (skip if caller passed explicit content).\n content: options.content != null ? options.content\n : (format === 'markdown' ? markdownToHtml(initial) : initial),\n });\n\n const sync = () => {\n const next = readEditor(editor);\n if (ta.value === next) return;\n ta.value = next;\n ta.dispatchEvent(new Event('input', { bubbles: true }));\n ta.dispatchEvent(new Event('change', { bubbles: true }));\n };\n editor.on('change', sync);\n sync(); // normalise the textarea to the editor's serialization up front.\n\n editor.textarea = ta;\n return editor;\n }\n}\n\n// Export classes for extension. `yjd` is the brand-aligned name; `RichEditor`\n// is kept as an alias for backward compatibility.\nexport {\n RichEditor as default,\n RichEditor,\n RichEditor as yjd,\n Editor,\n Module,\n Format,\n InlineFormat,\n BlockFormat,\n registry\n};\n\n// Export formats\nexport {\n Bold,\n Italic,\n Underline,\n Strike,\n Subscript,\n Superscript,\n Color,\n Background,\n Link,\n Table,\n Heading,\n FontFamily,\n LineHeight,\n Capitalization,\n TextAlign,\n List,\n Indent,\n IndentIncrease,\n IndentDecrease,\n Emoji,\n Image,\n Video,\n Tag,\n TextSize,\n\n Import\n};\n\n// Export modules\nexport {\n Toolbar,\n History,\n BlockToolbar,\n TableToolbar,\n CodeView,\n FindReplace,\n SlashMenu,\n Mention,\n\n ResizeHandles\n};\n\n// Export UI components\nexport {\n ColorPicker,\n IconUtils,\n LinkPopup,\n TablePopup,\n TextAlignPicker,\n ListPicker,\n EmojiPicker,\n ImagePopup,\n VideoPopup,\n TagPopup,\n\n createCustomButton\n};\n\n// Static rendering + serialization helpers\nexport {\n renderStatic,\n htmlToMarkdown,\n markdownToHtml,\n domToJson,\n jsonToHtml\n};\n\n\n\n\n/**\n * Utility function to create editor instance\n * @param {string|Element} selector - DOM selector or element\n * @param {object} options - Editor options\n */\nexport function createEditor(selector, options = {}) {\n return new RichEditor(selector, options);\n} ","import cssText from './styles.css.js';\n\n/**\n * CSS Loader - Load và inject CSS styles vào DOM\n * Thay thế cho việc sử dụng inline styles\n *\n * CSS được import trực tiếp dưới dạng chuỗi (sinh từ styles.css), nên hoạt động\n * cả với native ESM trong trình duyệt lẫn khi đóng gói bằng Rollup/CDN — không\n * còn phụ thuộc vào fetch runtime (vốn làm hỏng việc dùng qua npm/CDN).\n */\nclass StylesLoader {\n static loaded = false;\n static styleElement = null;\n\n /**\n * Load CSS (inject inlined stylesheet vào <head>)\n * Trả về Promise để giữ tương thích với code gọi cũ (.catch/.then).\n */\n static loadStyles() {\n if (this.loaded) return Promise.resolve();\n\n try {\n this.styleElement = document.createElement('style');\n this.styleElement.id = 'rich-editor-styles';\n this.styleElement.textContent = cssText;\n document.head.appendChild(this.styleElement);\n this.loaded = true;\n } catch (error) {\n // Fallback: load minimal styles\n this.loadFallbackStyles();\n }\n\n return Promise.resolve();\n }\n\n /**\n * Load minimal fallback styles nếu không thể load từ file\n */\n static loadFallbackStyles() {\n const fallbackCSS = `\n .yjd-rich-editor { \n position: relative; \n background: #fff; \n border: 1px solid #ddd; \n border-radius: 4px; \n display: flex; \n flex-direction: column; \n font-family: system-ui, sans-serif; \n }\n .yjd-rich-editor .rich-editor-area { \n flex: 1; \n padding: 20px; \n outline: none; \n min-height: 100px; \n }\n .yjd-rich-editor .rich-editor-toolbar { \n display: flex; \n gap: 4px; \n padding: 8px; \n border-bottom: 1px solid #ddd; \n background: #f9f9f9; \n }\n .yjd-rich-editor .rich-editor-toolbar-btn { \n padding: 4px 8px; \n border: 1px solid #ccc; \n border-radius: 3px; \n background: #fff; \n cursor: pointer; \n }\n .yjd-rich-editor .table-grid-selector { \n position: absolute; \n background: white; \n border: 1px solid #ccc; \n border-radius: 4px; \n padding: 10px; \n box-shadow: 0 2px 8px rgba(0,0,0,0.15); \n z-index: 1000; \n display: none; \n }\n .yjd-rich-editor .table-grid-cell { \n width: 20px; \n height: 20px; \n border: 1px solid #ddd; \n cursor: pointer; \n background: white; \n }\n `;\n\n this.styleElement = document.createElement('style');\n this.styleElement.id = 'rich-editor-styles-fallback';\n this.styleElement.textContent = fallbackCSS;\n document.head.appendChild(this.styleElement);\n \n this.loaded = true;\n }\n\n /**\n * Unload styles\n */\n static unloadStyles() {\n if (this.styleElement && this.styleElement.parentNode) {\n this.styleElement.parentNode.removeChild(this.styleElement);\n this.styleElement = null;\n this.loaded = false;\n }\n }\n\n /**\n * Check if styles are loaded\n */\n static isLoaded() {\n return this.loaded;\n }\n\n /**\n * Reload styles\n */\n static async reloadStyles() {\n this.unloadStyles();\n await this.loadStyles();\n }\n\n /**\n * Add custom CSS\n */\n static addCustomCSS(css, id = 'rich-editor-custom') {\n // Remove existing custom styles\n const existing = document.getElementById(id);\n if (existing) {\n existing.remove();\n }\n\n // Add new custom styles\n const style = document.createElement('style');\n style.id = id;\n style.textContent = css;\n document.head.appendChild(style);\n \n }\n}\n\nexport default StylesLoader; ","// AUTO-GENERATED from lib/styles.css by scripts/generate-css.js — do not edit directly.\nexport default \"@keyframes rte-pop-in{0%{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}@keyframes yjd-slash-in{0%{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}.yjd-rich-editor,.yjd-rich-editor .rich-editor-area{background:#fff;position:relative;box-sizing:border-box}.yjd-rich-editor{border:1px solid #d1d5db;border-radius:8px;overflow:hidden;margin:0 auto;display:flex;flex-direction:column}@layer base{.yjd-rich-editor .toolbar-container ::file-selector-button,.yjd-rich-editor .toolbar-container button,.yjd-rich-editor .toolbar-container input,.yjd-rich-editor .toolbar-container optgroup,.yjd-rich-editor .toolbar-container select,.yjd-rich-editor .toolbar-container textarea{font:500 14px/1.4\\\"Inter\\\",Arial,sans-serif!important;letter-spacing:normal!important;font-feature-settings:normal!important;font-variation-settings:normal!important}.yjd-rich-editor .emoji-picker-popup .emoji-button,.yjd-rich-editor .emoji-picker-popup button.emoji-button{font-size:20px!important}}@layer base{.yjd-rich-editor .rich-editor-popup-container ::file-selector-button,.yjd-rich-editor .rich-editor-popup-container button,.yjd-rich-editor .rich-editor-popup-container input,.yjd-rich-editor .rich-editor-popup-container optgroup,.yjd-rich-editor .rich-editor-popup-container select,.yjd-rich-editor .rich-editor-popup-container textarea{font:500 14px/1.4\\\"Inter\\\",Arial,sans-serif!important;letter-spacing:normal!important;font-feature-settings:normal!important;font-variation-settings:normal!important}.yjd-rich-editor .emoji-picker-popup .emoji-button,.yjd-rich-editor .emoji-picker-popup button.emoji-button{font-size:20px!important}}.yjd-rich-editor .rich-editor-area{flex:1 1 auto;min-height:40px;outline:0;overflow-y:auto;border:0;text-align:left;font-family:inherit;color:#2c3e50}.yjd-rich-editor .rich-editor-area[data-placeholder]:before{content:attr(data-placeholder);color:#9ca3af;pointer-events:none;position:absolute;top:0;left:0;right:0;padding:inherit;box-sizing:border-box;font:inherit;font-style:italic;line-height:inherit;display:none;z-index:1}.yjd-rich-editor .custom-select-item-button.current .item-checkmark,.yjd-rich-editor .rich-editor-area div,.yjd-rich-editor .rich-editor-area[data-placeholder].placeholder-visible:before{display:block}.yjd-rich-editor .rich-editor-area b,.yjd-rich-editor .rich-editor-area strong{font-weight:700}.yjd-rich-editor .rich-editor-area em,.yjd-rich-editor .rich-editor-area i{font-style:italic}.yjd-rich-editor .rich-editor-area u{text-decoration:underline}.yjd-rich-editor .rich-editor-area a{color:#2563eb;text-decoration:underline;cursor:pointer;color:var(--rte-accent-ink);text-decoration-color:var(--rte-accent-ring);text-underline-offset:2px}.yjd-content p,.yjd-rich-editor .rich-editor-area p{margin:0 0 1em}.yjd-content h1,.yjd-rich-editor .rich-editor-area h1{font-size:2em;font-weight:700;margin:.67em 0}.yjd-content h2,.yjd-rich-editor .rich-editor-area h2{font-size:1.5em;font-weight:700;margin:.75em 0}.yjd-content h3,.yjd-rich-editor .rich-editor-area h3{font-size:1.25em;font-weight:700;margin:.83em 0}.yjd-content h4,.yjd-rich-editor .rich-editor-area h4{font-size:1.1em;font-weight:700;margin:1em 0}.yjd-content h5,.yjd-rich-editor .rich-editor-area h5{font-size:1em;font-weight:700;margin:1.25em 0}.yjd-content h6,.yjd-rich-editor .rich-editor-area h6{font-size:.875em;font-weight:700;margin:1.5em 0;color:#555}.yjd-content ol,.yjd-content ul{margin:0 0 1em 2em;padding:0}.yjd-rich-editor .rich-editor-area ol,.yjd-rich-editor .rich-editor-area ul{padding:0}.yjd-rich-editor .rich-editor-area li{margin:.25em 0 4px;line-height:1.6}.yjd-rich-editor .rich-editor-area code,.yjd-rich-editor .rich-editor-area pre{font-family:Consolas,Menlo,Monaco,\\\"Courier New\\\",monospace;background:#f1f2f3;overflow-x:auto}.yjd-rich-editor .rich-editor-area pre{border-radius:4px;display:block;margin:1em 0;background-color:#f1f2f3;width:96%;white-space:pre;word-wrap:normal;background:#20242f;color:#ececf5;border-radius:var(--rte-radius-md);padding:14px 16px;border:0}.yjd-rich-editor .rich-editor-area code{display:inline;background:var(--rte-chrome-2);border-radius:6px;padding:2px 6px;font-size:.9em}.yjd-content blockquote{margin:1em 0;padding:4px 12px}.yjd-content blockquote,.yjd-rich-editor .rich-editor-area blockquote{border-left:4px solid #d1d5db;color:#555;font-style:italic;background:#f9fafb}.yjd-rich-editor .rich-editor-area sub{font-size:.75em;vertical-align:sub;line-height:1}.yjd-rich-editor .rich-editor-area sup{font-size:.75em;vertical-align:super;line-height:1}.yjd-rich-editor .rich-editor-area:empty::before{content:\\\"\\\";display:block}.yjd-rich-editor .rich-editor-statusbar{position:relative;z-index:1000;border-top:1px solid #d1d5db;background:#f9fafb;font-size:13px;color:#6b7280;font-family:inherit;min-height:32px;box-sizing:border-box}.yjd-rich-editor .rich-editor-toolbar-container{border-bottom:1px solid #d1d5db;position:relative;z-index:1000}.yjd-rich-editor .rich-editor-toolbar-1,.yjd-rich-editor .rich-editor-toolbar-2{gap:20px;background:#fcfcfc;min-height:48px;box-sizing:border-box;width:100%;z-index:1000;position:relative}.yjd-rich-editor .rich-editor-toolbar-2{display:none;overflow:hidden}.yjd-rich-editor .rich-editor-toolbar-2.visible{display:flex}.yjd-rich-editor .rich-editor-toolbar-btn{width:22px;border-radius:4px;font-size:14px;font-weight:500}.yjd-rich-editor .custom-select-button span{justify-content:left!important}.yjd-rich-editor .custom-select-button:focus{outline:0;border-color:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.1)}.yjd-rich-editor .rich-editor-toolbar-btn span{display:flex!important;align-items:center;justify-content:center}.yjd-rich-editor .rich-editor-toolbar-btn:hover{background:#eee!important;cursor:pointer}.yjd-rich-editor .block-toolbar-btn{border-radius:4px;padding:4px;cursor:pointer;font-size:14px;font-weight:500;align-items:center;justify-content:center;display:inline-flex}.yjd-rich-editor .block-toolbar-btn:hover{background:#eee}.yjd-rich-editor .block-toolbar-btn.active{background:#ccc;color:#136fdf!important}.yjd-rich-editor .block-toolbar-btn.active svg,.yjd-rich-editor .block-toolbar-btn.active svg path{fill:#136fdf!important}.yjd-rich-editor .block-toolbar-container{display:flex;align-items:center;background:#fff;border-radius:6.9px;box-shadow:0 4px 24px rgba(0,0,0,.13)}.yjd-rich-editor .block-toolbar-arrow{position:absolute;bottom:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;filter:drop-shadow(0 2px 4px rgba(0,0,0,.1))}.yjd-rich-editor .table-toolbar-container{display:flex;align-items:center;background:#fff;border-radius:6px;box-shadow:0 4px 24px rgba(0,0,0,.13)}.yjd-rich-editor .table-toolbar-arrow{position:absolute;bottom:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;filter:drop-shadow(0 2px 4px rgba(0,0,0,.1))}.yjd-rich-editor .block-toolbar{display:none;position:absolute;z-index:1001;background:0 0;border:0;border-radius:6.9px;box-sizing:border-box;padding:0;gap:0;flex-direction:column;align-items:center}.yjd-rich-editor .block-toolbar.visible{display:flex}.yjd-rich-editor .color-picker-popup{position:absolute;background:#fff;display:none;border-radius:6.9px;padding:8px;z-index:1000;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .capitalization-select-popup.visible,.yjd-rich-editor .color-picker-popup.visible,.yjd-rich-editor .custom-select-popup.visible,.yjd-rich-editor .font-family-select-popup.visible,.yjd-rich-editor .heading-select-popup.visible,.yjd-rich-editor .line-height-select-popup.visible,.yjd-rich-editor .text-size-select-popup.visible{display:block!important}.yjd-rich-editor .capitalization-select-popup,.yjd-rich-editor .custom-select-popup,.yjd-rich-editor .font-family-select-popup,.yjd-rich-editor .heading-select-popup,.yjd-rich-editor .line-height-select-popup,.yjd-rich-editor .text-size-select-popup{position:absolute;background:#fff;display:none;border:1px solid #d1d5db;border-radius:6.9px;z-index:1002;max-height:200px;overflow-y:auto;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .image-popup,.yjd-rich-editor .link-popup,.yjd-rich-editor .tag-popup,.yjd-rich-editor .video-popup{width:299px}.yjd-rich-editor .item-list{display:flex;flex-direction:column;gap:2px}.yjd-rich-editor .heading-select-popup{width:190px;max-width:100vw}.yjd-rich-editor .capitalization-select-popup{width:150px;max-width:100vw}.yjd-rich-editor .font-family-select-popup{width:190px;max-width:100vw}.yjd-rich-editor .line-height-select-popup{width:80px;max-width:100vw}.yjd-rich-editor .text-size-select-popup{width:150px;max-width:100vw}.yjd-rich-editor .custom-select-item-button{width:100%;border:0;background:0 0;border-radius:6px;cursor:pointer;text-align:left;transition:all .2s ease;font-size:14px;line-height:1.4;color:#111827;display:flex;align-items:center;justify-content:space-between;pointer-events:auto}.yjd-rich-editor .custom-select-item-button:hover{background:#eee}.yjd-rich-editor .custom-select-item-button.current{background:#eee!important}.yjd-rich-editor .custom-select-item-button .item-checkmark{display:none}.yjd-rich-editor .item-text{flex:1}.yjd-rich-editor .dropdown-icon{display:inline-flex!important;align-items:center;justify-content:center;width:16px;height:16px;flex:0 0 auto}.yjd-rich-editor .dropdown-icon svg{width:16px;height:16px;display:block;stroke:currentColor;fill:none}.yjd-rich-editor .color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:6px 4px;margin-bottom:12px}.yjd-rich-editor .color-button{width:24px;height:24px;border:0;border-radius:50%;cursor:pointer;transition:all .2s ease;padding:0;margin:0;pointer-events:auto}.yjd-rich-editor .color-button:hover{transform:scale(1.1);border-color:#374151;box-shadow:0 2px 4px rgba(0,0,0,.1)}.yjd-rich-editor .custom-color-container{position:relative;padding-top:8px;display:flex;align-items:center;gap:8px;z-index:0}.yjd-rich-editor .custom-color-container::before{content:\\\"\\\";position:absolute;top:0;left:-8px;width:calc(100% + 16px);height:0;border-top:1px solid #e5e7eb;z-index:-1}.yjd-rich-editor .custom-color-label{font-size:12px;color:#6b7280;margin:0}.yjd-rich-editor .yjd-input-label,.yjd-rich-editor .yjd-input-title{margin:0;color:#252424;font-size:16px;font-style:normal;line-height:normal}.yjd-rich-editor .yjd-input-label{font-size:14px;font-weight:700}.yjd-rich-editor .yjd-input-group{display:flex;flex-direction:column;gap:8px}.yjd-rich-editor .yjd-input{flex:1 1 auto;min-width:0;max-width:100%;max-height:37px;border-radius:10px;border:1px solid #e1e1e1;color:#252424!important}.yjd-rich-editor .yjd-input:focus{border-color:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.1)}.yjd-rich-editor .yjd-button-container{display:flex;justify-content:flex-end;gap:4px}.yjd-rich-editor .yjd-button-cancel,.yjd-rich-editor .yjd-button-confirm{border-radius:6px;align-items:center;box-shadow:0 1px 3.3px 0 rgba(0,0,0,.04);font-style:normal;line-height:normal;cursor:pointer}.yjd-rich-editor .yjd-button-confirm{background:#181616}.yjd-rich-editor .yjd-button-cancel{border:1px solid #e0e0e0;background:#fff;color:#2a2727}.yjd-rich-editor .button-disable{cursor:not-allowed}.yjd-rich-editor .link-popup{position:absolute;background:#fff;font-family:\\\"Lato\\\",sans-serif;display:none;border:0;border-radius:10px;z-index:1000;margin:0;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .link-popup.visible{display:block!important}.yjd-rich-editor .link-popup-content{display:flex;flex-direction:column;gap:16px;padding:16px}.yjd-rich-editor .yjd-input-upload-group{display:flex;align-items:center;gap:4px}.yjd-rich-editor .yjd-custom-upload-button{display:flex!important;align-items:center;justify-content:center;padding:0;border:1px solid #e1e1e1;background:#fff;width:34px;height:34px;border-radius:10px;cursor:pointer}.yjd-rich-editor .yjd-suggestions-list{display:flex;flex-wrap:wrap;gap:8px;flex:1 1 auto;min-width:0;max-width:100%}.yjd-rich-editor .yjd-suggestion-button{border:0;display:flex;padding:4px 10px;justify-content:center;align-items:center;border-radius:30px;background:#f5f5f5;cursor:pointer;font-size:14px;color:#252424!important}.yjd-rich-editor .yjd-select-input{border-radius:10px;border:1px solid #e1e1e1;max-height:37px;align-items:center;color:#252424!important}.yjd-rich-editor .yjd-select-input:focus{border-color:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.1)}.yjd-rich-editor .table-popup{position:absolute;background:#fff;font-family:\\\"Lato\\\",sans-serif;display:none;border:1px solid #d1d5db;border-radius:6.9px;z-index:1000;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .table-popup.visible{display:block!important}.yjd-rich-editor .table-popup-content{padding:8px;display:flex;flex-direction:column;align-items:center;gap:12px;position:relative}.yjd-rich-editor .table-popup-content>*{position:relative;width:100%}.yjd-rich-editor .table-popup-content>:not(:last-child)::after{content:\\\"\\\";position:absolute;bottom:-8px;left:-8px;right:-8px;height:1px;background-color:#e5e7eb}.yjd-rich-editor .table-grid-selector{display:grid;grid-template-columns:repeat(8,1fr);gap:1px;border-bottom:1px solid #eee}.yjd-rich-editor .table-size-display{display:flex;align-items:center;justify-content:center;font-family:\\\"Lato\\\",sans-serif;color:#252424!important;font-size:12px;font-style:normal;font-weight:400;line-height:normal}.yjd-rich-editor .table-grid-cell{width:20px;height:20px;background:#fff;border:1px solid #eee;cursor:pointer;transition:background-color .1s ease;border-radius:2px}.yjd-rich-editor .table-grid-cell.highlighted,.yjd-rich-editor .table-grid-cell:hover{background:#eee}.yjd-rich-editor .rich-editor-table{border-collapse:collapse;width:100%;max-width:100%;box-sizing:border-box;margin:16px 0;font-size:14px;line-height:1.5;position:relative;display:table;table-layout:fixed}.yjd-rich-editor .rich-editor-table td{box-sizing:border-box;border:1px solid #d1d5db;padding:8px 12px;min-height:28px;vertical-align:top;background:#fff;overflow-wrap:break-word}.yjd-rich-editor .rich-editor-table td:focus{outline-offset:-2px;background:#fefefe}.yjd-rich-editor .table-toolbar{position:absolute;display:none;z-index:1002;background:0 0;border:0;border-radius:6.9px;opacity:0;pointer-events:none}.yjd-rich-editor .table-toolbar-group{display:flex;gap:8px;align-items:center}.yjd-rich-editor .table-toolbar.visible{display:block;opacity:1;pointer-events:auto;transform:translateY(0)}.yjd-rich-editor .table-toolbar-btn{padding:4px;display:inline-flex;align-items:center;justify-content:center;border-radius:4px;font-size:12px;font-weight:500;cursor:pointer;transition:all .15s ease;white-space:nowrap}.yjd-rich-editor .table-toolbar-btn:hover{background:#f3f4f6;color:#111827}.yjd-rich-editor .table-toolbar-btn:active{background:#e5e7eb;border-color:#9ca3af;color:#136fdf!important}@media (max-width:768px){.yjd-rich-editor .table-toolbar-container{flex-wrap:wrap;max-width:200px;gap:2px}.yjd-rich-editor .table-toolbar-btn{height:24px;min-width:24px;font-size:10px;padding:2px 4px}}.yjd-rich-editor .text-align-picker-popup{position:absolute;background:#fff;display:none;border:1px solid #d1d5db;border-radius:6.9px;z-index:1000;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .text-align-picker-popup.visible{display:block!important}.yjd-rich-editor .align-button-container{display:flex}.yjd-rich-editor .align-button .icon-wrapper{display:flex;margin-left:8px;align-items:center;justify-content:center}.yjd-rich-editor .align-button .label-text{color:#252424;font-size:12px;font-style:normal;font-weight:400;line-height:normal}.yjd-rich-editor .align-button{border-radius:3.456px;background:#fff;border:0;align-items:center;display:flex;cursor:pointer;pointer-events:auto}.yjd-rich-editor .align-button:hover{background:#f3f4f6}.yjd-rich-editor .align-button.active{background:#e5e7eb}.yjd-rich-editor .align-button.active svg{color:#1f2937}.yjd-rich-editor .list-picker-popup{position:absolute;background:#fff;display:none;border-radius:6.9px;box-shadow:0 4px 16px rgba(0,0,0,.1);z-index:1000;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .list-picker-popup.visible{display:block!important}.yjd-rich-editor .list-button-container{display:grid;grid-template-columns:repeat(3,1fr)}.yjd-rich-editor .list-button{border-radius:3.46px;display:flex}.yjd-rich-editor .list-button:hover{background:#f3f4f6}.yjd-rich-editor .list-button.active{background:#eee}.yjd-rich-editor .list-button svg{color:#374151}.yjd-rich-editor .list-button.active svg{color:#fff}.yjd-rich-editor .rich-editor-area ul.checklist{list-style:none;padding-left:0}.yjd-rich-editor .rich-editor-area ul.checklist li{position:relative;padding-left:25px;margin-bottom:4px}.yjd-rich-editor .rich-editor-area ul.checklist li input[type=checkbox]{position:absolute;left:0;top:2px;margin:0;cursor:pointer}.yjd-rich-editor .rich-editor-area ol,.yjd-rich-editor .rich-editor-area ul{margin:12px 0;padding-left:20px}.yjd-rich-editor .rich-editor-area .indented,.yjd-rich-editor .rich-editor-area [style*=padding-left]{transition:padding-left .2s ease}.yjd-rich-editor .indent-decrease-btn svg,.yjd-rich-editor .indent-increase-btn svg{width:16px;height:16px;fill:#454545}.yjd-rich-editor .indent-decrease-btn:hover svg,.yjd-rich-editor .indent-increase-btn:hover svg{fill:#454545}.yjd-rich-editor .indent-decrease-btn:disabled,.yjd-rich-editor .indent-increase-btn:disabled{opacity:.5;cursor:not-allowed}.yjd-rich-editor .indent-decrease-btn:disabled svg,.yjd-rich-editor .indent-increase-btn:disabled svg{fill:#9ca3af}.yjd-rich-editor .emoji-picker-popup{position:absolute;z-index:10000;background:#fff;border-radius:8px;padding:8px;display:none;overflow-y:auto;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .emoji-picker-popup.visible{display:block}.yjd-rich-editor .emoji-grid{display:grid;grid-template-columns:repeat(10,1fr);gap:4px;max-height:240px;overflow-y:auto}.yjd-rich-editor .emoji-text-message{margin-top:4px;color:#71787c;text-align:center;font-family:\\\"Segoe UI\\\",Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:normal}.yjd-rich-editor .emoji-button{width:28px;height:28px;border:0;background:0 0;padding:0;border-radius:4px;cursor:pointer;font-size:20px!important;display:flex;align-items:center;justify-content:center;transition:background-color .2s ease;pointer-events:auto}.yjd-rich-editor .emoji-button:hover{background-color:#f3f4f6}.yjd-rich-editor .emoji{font-size:1.1em;line-height:1}.yjd-rich-editor .image-popup{position:absolute;z-index:10000;background:#fff;font-family:\\\"Lato\\\",sans-serif;border-radius:10px;display:none;margin:0;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .image-popup.visible,.yjd-rich-editor .tag-popup.visible,.yjd-rich-editor .video-popup.visible{display:block}.yjd-rich-editor .image-popup-content{display:flex;flex-direction:column;gap:16px;padding:16px}.yjd-rich-editor .image-input-container{gap:8px;display:flex;flex-direction:column}.yjd-rich-editor .image-input-hidden{display:none}.yjd-rich-editor .video-popup{position:absolute;z-index:10000;background:#fff;font-family:\\\"Lato\\\",sans-serif;border-radius:10px;display:none;margin:0;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .video-popup-content{display:flex;flex-direction:column;gap:16px;padding:16px}.yjd-rich-editor .video-input-container{gap:8px;display:flex;flex-direction:column}.yjd-rich-editor .inserted-image,.yjd-rich-editor .inserted-video{max-width:100%;height:auto;margin:4px 0;border-radius:4px;display:block}.yjd-rich-editor .inserted-video{background:#000}.yjd-rich-editor .tag-popup{position:absolute;z-index:10000;background:#fff;font-family:\\\"Lato\\\",sans-serif;border-radius:10px;display:none;margin:0;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tag-popup{max-height:80vh;overflow-y:auto}.tag-suggestions-container{max-height:200px;overflow-y:auto}.yjd-rich-editor .tag-popup-content{display:flex;flex-direction:column;gap:16px;padding:16px}.yjd-rich-editor .tag-type-container{gap:8px;display:flex;flex-direction:column}.yjd-rich-editor .custom-tag{display:inline-block;padding:2px 6px;border-radius:12px;font-size:.85em;font-weight:500;cursor:pointer;margin:0 2px}.yjd-rich-editor .custom-tag.tag-mention{background:#dbeafe;color:#1d4ed8}.yjd-rich-editor .custom-tag.tag-hashtag{background:#dcfce7;color:#166534}.yjd-rich-editor .custom-tag.tag-custom{background:#fef3c7;color:#92400e}.yjd-rich-editor .custom-tag:hover{opacity:.8}.yjd-rich-editor .import-popup{position:absolute;z-index:10000;background:#fff;border:1px solid #d1d5db;border-radius:8px;display:none;width:400px;max-width:90vw;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .import-popup.visible{display:block}.yjd-rich-editor .import-popup-content{padding:20px}.yjd-rich-editor .import-popup-title{margin:0 0 20px;font-size:18px;font-weight:600;color:#1f2937}.yjd-rich-editor .import-type-container{margin-bottom:20px}.yjd-rich-editor .import-input-label{display:block;margin-bottom:8px;font-size:14px;font-weight:500;color:#374151}.yjd-rich-editor .import-type-select{border:1px solid #d1d5db;border-radius:6px;font-size:14px;background:#fff;color:#374151}.yjd-rich-editor .import-type-select:focus{border-color:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.1)}.yjd-rich-editor .import-file-input{width:100%;padding:10px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:14px;background:#fff;color:#374151;margin-bottom:16px;cursor:pointer;box-sizing:border-box}.yjd-rich-editor .import-file-input:disabled{background-color:#f3f4f6;cursor:not-allowed;opacity:.5}.yjd-rich-editor .import-file-input:focus{outline:0;border-color:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.1)}.yjd-rich-editor .import-file-info{padding:12px;background-color:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;margin-bottom:16px;font-size:14px;color:#374151}.yjd-rich-editor .import-file-info div{margin-bottom:4px}.yjd-rich-editor .import-file-info div:last-child{margin-bottom:0}.yjd-rich-editor .import-button-container{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.yjd-rich-editor .import-button{padding:10px 20px;border:1px solid #d1d5db;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s}.yjd-rich-editor .import-button:hover{background-color:#f3f4f6}.yjd-rich-editor .import-button.import-button-main{background-color:#3b82f6;color:#fff;border-color:#3b82f6}.yjd-rich-editor .import-button.import-button-main:hover{background-color:#2563eb}.yjd-rich-editor .import-button.import-button-main:disabled{background-color:#9ca3af;border-color:#9ca3af;cursor:not-allowed}.yjd-rich-editor .import-button.cancel-button{background-color:#fff;color:#6b7280}.yjd-rich-editor .import-button.cancel-button:hover,.yjd-rich-editor .imported-table tr:hover{background-color:#f9fafb}.yjd-rich-editor .imported-content{margin:16px 0;padding:16px;border:1px dashed #d1d5db;border-radius:6px;background-color:#f9fafb}.yjd-rich-editor .imported-content.html-content{border-color:#3b82f6;background-color:#eff6ff}.yjd-rich-editor .imported-content.excel-content{border-color:#10b981;background-color:#f0fdf4}.yjd-rich-editor .imported-content.text-content{border-color:#f59e0b;background-color:#fffbeb}.yjd-rich-editor .imported-table{width:100%;border-collapse:collapse;font-size:14px;background:#fff;border-radius:4px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.1)}.yjd-rich-editor .imported-table td,.yjd-rich-editor .imported-table th{padding:12px;text-align:left;border-bottom:1px solid #e5e7eb}.yjd-rich-editor .imported-table th{background-color:#f3f4f6;font-weight:600;color:#374151}.yjd-rich-editor .imported-content p{margin:8px 0;line-height:1.6;color:#374151}.yjd-rich-editor .imported-content p:first-child{margin-top:0}.yjd-rich-editor .imported-content p:last-child{margin-bottom:0}.yjd-rich-editor .code-view-textarea{width:100%;height:500px;font-family:\\\"Consolas\\\",\\\"Monaco\\\",\\\"Courier New\\\",monospace;font-size:14px;line-height:1.5;padding:16px;background-color:#f8fafc;color:#374151;resize:vertical;border:0;outline:0;white-space:pre;word-wrap:break-word;tab-size:2;box-sizing:border-box}@media (max-width:768px){.yjd-rich-editor .code-view-textarea{font-size:13px;padding:12px}.yjd-rich-editor.code-view-active::after{top:5px;right:5px;font-size:11px;padding:3px 6px}}.yjd-rich-editor .resize-handles-container{position:absolute;pointer-events:none;z-index:997;display:none}.yjd-rich-editor .resize-handles-container.active{display:block}.yjd-rich-editor .resize-handle{position:absolute;width:8px;height:8px;background-color:#3b82f6;pointer-events:auto;box-shadow:0 2px 4px rgba(0,0,0,.2);transition:all .2s ease;z-index:999}.yjd-rich-editor .resize-handle:hover{background-color:#2563eb;transform:scale(1.2);box-shadow:0 2px 6px rgba(0,0,0,.3)}.yjd-rich-editor .resize-handle-nw{top:-4px;left:-4px;cursor:nw-resize}.yjd-rich-editor .resize-handle-ne{top:-4px;right:-4px;cursor:ne-resize}.yjd-rich-editor .resize-handle-sw{bottom:-4px;left:-4px;cursor:sw-resize}.yjd-rich-editor .resize-handle-se{bottom:-4px;right:-4px;cursor:se-resize}.yjd-rich-editor .inserted-image.resizing,.yjd-rich-editor .inserted-video.resizing,.yjd-rich-editor .rich-editor-table.resizing{outline:2px solid #3b82f6;outline-offset:2px;opacity:.8}.yjd-rich-editor .inserted-image.resizing,.yjd-rich-editor .inserted-video.resizing{-webkit-user-select:none;user-select:none;pointer-events:none}.yjd-rich-editor .rich-editor-table.resizing{-webkit-user-select:none;user-select:none}.yjd-rich-editor .rich-editor-table.resizing td{pointer-events:none}.yjd-rich-editor .inserted-image:hover,.yjd-rich-editor .inserted-video:hover,.yjd-rich-editor .rich-editor-table:hover{outline:1px solid #3b82f6;outline-offset:1px;cursor:pointer}@media (max-width:768px){.yjd-rich-editor .resize-handle{width:12px;height:12px}.yjd-rich-editor .resize-handle-nw{top:-6px;left:-6px}.yjd-rich-editor .resize-handle-ne{top:-6px;right:-6px}.yjd-rich-editor .resize-handle-sw{bottom:-6px;left:-6px}.yjd-rich-editor .resize-handle-se{bottom:-6px;right:-6px}}@media (max-width:849px){.yjd-rich-editor{margin:0;width:100%!important;max-width:100%!important}.yjd-rich-editor .rich-editor-area{padding:12px;min-height:150px;font-size:14px;line-height:1.4}.yjd-rich-editor .rich-editor-statusbar{padding:6px 8px;font-size:11px;flex-wrap:wrap;gap:8px}.yjd-rich-editor .editor-dropdown,.yjd-rich-editor .editor-popup{position:fixed!important;top:50%!important;left:50%!important;transform:translate(-50%,-50%)!important;width:90%!important;max-width:320px!important;max-height:80vh!important;overflow-y:auto;z-index:10000}.yjd-rich-editor .editor-popup::before{content:\\\"\\\";position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);z-index:-1}.yjd-rich-editor .color-picker{width:100%!important;max-width:280px!important}.yjd-rich-editor .color-picker-grid{grid-template-columns:repeat(6,1fr)!important;gap:8px!important}.yjd-rich-editor .color-picker-item{width:32px!important;height:32px!important}.yjd-rich-editor .table-popup{max-width:300px!important}.yjd-rich-editor .table-size-grid{grid-template-columns:repeat(8,1fr)!important;gap:2px!important}.yjd-rich-editor .table-size-cell{width:24px!important;height:24px!important}.yjd-rich-editor .list-picker{width:100%!important;max-width:250px!important}.yjd-rich-editor .list-picker-btn{padding:12px!important;font-size:14px!important}.yjd-rich-editor .text-align-picker{width:100%!important;max-width:200px!important}.yjd-rich-editor .image-popup,.yjd-rich-editor .import-popup,.yjd-rich-editor .link-popup,.yjd-rich-editor .video-popup{width:90%!important;max-width:350px!important}.yjd-rich-editor .media-upload-area{min-height:100px!important;padding:20px!important}.yjd-rich-editor .tag-popup{width:90%!important;max-width:320px!important}.yjd-rich-editor .emoji-picker{width:100%!important;max-width:300px!important;max-height:350px!important}.yjd-rich-editor .emoji-grid{grid-template-columns:repeat(8,1fr)!important;gap:4px!important}.yjd-rich-editor .emoji-item{width:32px!important;height:32px!important;font-size:18px!important}.yjd-rich-editor .code-view-textarea{font-size:12px!important;padding:8px!important;line-height:1.3!important}.yjd-rich-editor .rich-editor-area h1{font-size:1.5em!important;margin:.5em 0!important}.yjd-rich-editor .rich-editor-area h2{font-size:1.3em!important;margin:.5em 0!important}.yjd-rich-editor .rich-editor-area h3{font-size:1.1em!important;margin:.5em 0!important}.yjd-rich-editor .rich-editor-area p{margin:.8em 0!important}.yjd-rich-editor .rich-editor-area ol,.yjd-rich-editor .rich-editor-area ul{padding-left:20px!important;margin:.8em 0!important}.yjd-rich-editor .rich-editor-area blockquote{padding-left:12px!important;margin:.8em 0!important;font-size:14px!important}.yjd-rich-editor .rich-editor-area table{font-size:13px!important;overflow-x:auto!important;display:block!important;white-space:nowrap!important}.yjd-rich-editor .rich-editor-area td,.yjd-rich-editor .rich-editor-area th{padding:6px 8px!important;min-width:60px!important}}@media (max-width:290px){.yjd-rich-editor .emoji-picker{max-width:calc(100vw - 20px)!important}.yjd-rich-editor .emoji-grid{grid-template-columns:repeat(auto-fit,minmax(30px,1fr))!important}}@media print{.yjd-rich-editor .rich-editor-statusbar,.yjd-rich-editor .rich-editor-toolbar-1,.yjd-rich-editor .rich-editor-toolbar-2{display:none!important}.yjd-rich-editor{border:0!important;box-shadow:none!important}.yjd-rich-editor .rich-editor-area{padding:0!important;background:#fff!important;color:#000!important}}.yjd-rich-editor .rich-editor-popup-container{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1005;overflow:visible;max-height:90%;overflow-y:auto}.yjd-rich-editor .capitalization-select-popup button,.yjd-rich-editor .color-picker-popup button,.yjd-rich-editor .custom-color-input,.yjd-rich-editor .custom-select-popup button,.yjd-rich-editor .custom-tag,.yjd-rich-editor .emoji-picker-popup button,.yjd-rich-editor .font-family-select-popup button,.yjd-rich-editor .heading-select-popup button,.yjd-rich-editor .image-input-hidden,.yjd-rich-editor .image-popup button,.yjd-rich-editor .image-popup input,.yjd-rich-editor .image-popup select,.yjd-rich-editor .import-csv-input,.yjd-rich-editor .import-file-input,.yjd-rich-editor .import-html-input,.yjd-rich-editor .import-input,.yjd-rich-editor .import-json-input,.yjd-rich-editor .import-markdown-input,.yjd-rich-editor .import-popup button,.yjd-rich-editor .import-popup input,.yjd-rich-editor .import-popup select,.yjd-rich-editor .import-text-input,.yjd-rich-editor .import-toml-input,.yjd-rich-editor .import-type-select,.yjd-rich-editor .import-url-input,.yjd-rich-editor .import-xml-input,.yjd-rich-editor .import-yaml-input,.yjd-rich-editor .line-height-select-popup button,.yjd-rich-editor .link-popup button,.yjd-rich-editor .link-popup input,.yjd-rich-editor .link-popup select,.yjd-rich-editor .list-button,.yjd-rich-editor .list-picker-popup button,.yjd-rich-editor .table-popup button,.yjd-rich-editor .table-popup input,.yjd-rich-editor .table-toolbar-btn,.yjd-rich-editor .tag-popup button,.yjd-rich-editor .tag-popup input,.yjd-rich-editor .tag-popup select,.yjd-rich-editor .tag-type-select,.yjd-rich-editor .tag-value-input,.yjd-rich-editor .text-align-picker-popup button,.yjd-rich-editor .text-size-select-popup button,.yjd-rich-editor .video-input-hidden,.yjd-rich-editor .video-popup button,.yjd-rich-editor .video-popup input,.yjd-rich-editor .video-popup select,.yjd-rich-editor .yjd-button-cancel,.yjd-rich-editor .yjd-button-confirm,.yjd-rich-editor .yjd-custom-upload-button,.yjd-rich-editor .yjd-input,.yjd-rich-editor .yjd-select-input,.yjd-rich-editor .yjd-suggestion-button{pointer-events:auto!important}.yjd-rich-editor .rich-editor-popup-container>*{pointer-events:auto}.yjd-rich-editor .rich-editor-popup-container .color-picker-popup,.yjd-rich-editor .rich-editor-popup-container .custom-select-popup,.yjd-rich-editor .rich-editor-popup-container .emoji-picker-popup,.yjd-rich-editor .rich-editor-popup-container .image-popup,.yjd-rich-editor .rich-editor-popup-container .import-popup,.yjd-rich-editor .rich-editor-popup-container .link-popup,.yjd-rich-editor .rich-editor-popup-container .list-picker-popup,.yjd-rich-editor .rich-editor-popup-container .table-popup,.yjd-rich-editor .rich-editor-popup-container .tag-popup,.yjd-rich-editor .rich-editor-popup-container .text-align-picker-popup,.yjd-rich-editor .rich-editor-popup-container .video-popup{position:absolute!important;z-index:1001!important}.yjd-rich-editor{--rte-bg:#ffffff;--rte-chrome:#fbfbfd;--rte-chrome-2:#f2f2f7;--rte-border:#e9e9f1;--rte-border-strong:#dadae6;--rte-ink:#20242f;--rte-muted:#767c8e;--rte-accent:#6d5efc;--rte-accent-ink:#5a48ee;--rte-accent-weak:#efedff;--rte-accent-ring:rgba(109, 94, 252, 0.28);--rte-danger:#e5484d;--rte-radius:14px;--rte-radius-md:11px;--rte-radius-sm:8px;--rte-shadow-sm:0 1px 2px rgba(20, 24, 46, 0.06);--rte-shadow:0 12px 32px -8px rgba(20, 24, 46, 0.20), 0 4px 10px -4px rgba(20, 24, 46, 0.10);--rte-t:140ms cubic-bezier(0.4, 0, 0.2, 1);border:1px solid var(--rte-border)!important;border-radius:var(--rte-radius)!important;background:var(--rte-bg);color:var(--rte-ink);box-shadow:var(--rte-shadow-sm);font-family:system-ui,-apple-system,BlinkMacSystemFont,\\\"Segoe UI\\\",Roboto,sans-serif}.yjd-rich-editor .rich-editor-popup-container button,.yjd-rich-editor .rich-editor-popup-container input,.yjd-rich-editor .rich-editor-popup-container select,.yjd-rich-editor .rich-editor-popup-container textarea,.yjd-rich-editor .toolbar-container button,.yjd-rich-editor .toolbar-container input,.yjd-rich-editor .toolbar-container select,.yjd-rich-editor .toolbar-container textarea{font-family:system-ui,-apple-system,BlinkMacSystemFont,\\\"Segoe UI\\\",Roboto,sans-serif!important}.yjd-rich-editor .rich-editor-toolbar-container{background:var(--rte-chrome);border-bottom:1px solid var(--rte-border)}.yjd-rich-editor .rich-editor-toolbar-1,.yjd-rich-editor .rich-editor-toolbar-2{display:flex;align-items:center;column-gap:0;row-gap:4px;padding:8px 12px}.yjd-rich-editor .rich-editor-toolbar-2{justify-content:flex-start}.yjd-rich-editor .rich-editor-toolbar-2.visible{border-top:1px solid var(--rte-border)}.yjd-rich-editor .toolbar-group{display:flex;align-items:center;gap:2px;border-right:none;margin:0;padding:0}.yjd-rich-editor .toolbar-group+.toolbar-group{margin-left:12px}.yjd-rich-editor .rich-editor-toolbar-btn{color:var(--rte-ink)}.yjd-rich-editor .custom-select-button,.yjd-rich-editor .rich-editor-toolbar-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-width:32px;height:32px;padding:0 8px;border:1px solid transparent;border-radius:var(--rte-radius-sm);background:0 0;cursor:pointer;transition:background var(--rte-t),color var(--rte-t),box-shadow var(--rte-t),border-color var(--rte-t)}.yjd-rich-editor .rich-editor-toolbar-btn:hover{background:var(--rte-chrome-2)}.yjd-rich-editor .custom-select-button:hover{background:var(--rte-chrome-2);color:var(--rte-ink)}.yjd-rich-editor .rich-editor-toolbar-btn.active{background:var(--rte-accent-weak);border-color:transparent}.yjd-rich-editor .rich-editor-toolbar-btn:disabled,.yjd-rich-editor .rich-editor-toolbar-btn[disabled]{opacity:.38!important;cursor:not-allowed!important}.yjd-rich-editor .custom-select-button:focus-visible,.yjd-rich-editor .rich-editor-toolbar-btn:focus-visible{outline:0;border-color:var(--rte-accent);box-shadow:0 0 0 3px var(--rte-accent-ring)}.yjd-rich-editor .rich-editor-statusbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:7px 14px;background:var(--rte-chrome);border-top:1px solid var(--rte-border)}.yjd-rich-editor .rich-editor-breadcrumb,.yjd-rich-editor .wordcount{color:var(--rte-muted);font-size:12px;letter-spacing:.01em}.yjd-rich-editor .rich-editor-area{color:var(--rte-ink);font-size:16px;line-height:1.75;padding:22px 24px}.yjd-rich-editor .rich-editor-area[data-placeholder].placeholder-visible:before{color:var(--rte-muted);opacity:.55}.yjd-rich-editor .rich-editor-area blockquote{border-left:3px solid var(--rte-accent);background:var(--rte-accent-weak);border-radius:0 var(--rte-radius-sm) var(--rte-radius-sm)0;padding:8px 16px;color:var(--rte-ink);margin:12px 0}.yjd-rich-editor .rich-editor-area pre code{background:0 0;color:inherit;padding:0}.yjd-rich-editor .capitalization-select-popup,.yjd-rich-editor .color-picker-popup,.yjd-rich-editor .custom-select-popup,.yjd-rich-editor .emoji-picker-popup,.yjd-rich-editor .font-family-select-popup,.yjd-rich-editor .heading-select-popup,.yjd-rich-editor .image-popup,.yjd-rich-editor .import-popup,.yjd-rich-editor .line-height-select-popup,.yjd-rich-editor .link-popup,.yjd-rich-editor .table-popup,.yjd-rich-editor .tag-popup,.yjd-rich-editor .text-align-picker-popup,.yjd-rich-editor .text-size-select-popup,.yjd-rich-editor .video-popup{background:var(--rte-bg);border:1px solid var(--rte-border);box-shadow:var(--rte-shadow)}.yjd-rich-editor .custom-select-item-button{color:var(--rte-ink);transition:background var(--rte-t),color var(--rte-t)}.yjd-rich-editor .custom-select-item-button:hover{background:var(--rte-chrome-2)}.yjd-rich-editor .custom-select-item-button.current .item-checkmark{color:var(--rte-accent-ink)}.yjd-rich-editor .yjd-input{transition:border-color var(--rte-t),box-shadow var(--rte-t)}.yjd-rich-editor .yjd-input:focus{outline:0}.yjd-rich-editor .yjd-input-title{color:var(--rte-ink);font-weight:600}.yjd-rich-editor .yjd-input-label{color:var(--rte-muted)}.yjd-rich-editor .yjd-button-confirm{border:0}.yjd-rich-editor .yjd-button-cancel{background:var(--rte-chrome-2)}.yjd-rich-editor .yjd-button-cancel:hover{background:#e9e9f2}.yjd-rich-editor .yjd-custom-upload-button{border-radius:var(--rte-radius-sm);border:1px dashed var(--rte-border-strong);color:var(--rte-accent-ink);background:var(--rte-accent-weak);transition:background var(--rte-t),border-color var(--rte-t)}.yjd-rich-editor .yjd-custom-upload-button:hover{border-color:var(--rte-accent)}.yjd-rich-editor .block-toolbar-container,.yjd-rich-editor .table-toolbar-container{background:var(--rte-bg);border:1px solid var(--rte-border);border-radius:var(--rte-radius-md);box-shadow:var(--rte-shadow);padding:4px;gap:2px}.yjd-rich-editor .block-toolbar-btn,.yjd-rich-editor .table-toolbar-btn{border:1px solid transparent;border-radius:var(--rte-radius-sm);background:0 0;color:var(--rte-ink);transition:background var(--rte-t),color var(--rte-t)}.yjd-rich-editor .block-toolbar-btn:hover,.yjd-rich-editor .table-toolbar-btn:hover{background:var(--rte-chrome-2)}.yjd-rich-editor .block-toolbar-btn.active{background:var(--rte-accent-weak);color:var(--rte-accent-ink)}.yjd-rich-editor .block-toolbar-btn.active .icon svg,.yjd-rich-editor .block-toolbar-btn.active .icon svg path{fill:var(--rte-accent-ink)}.yjd-rich-editor .rich-editor-table td{border:1px solid var(--rte-border-strong)!important}.yjd-rich-editor .rich-editor-table td:focus{outline:0;box-shadow:inset 0 0 0 2px var(--rte-accent);background:var(--rte-accent-weak)}.yjd-rich-editor .table-grid-cell.highlighted{background:var(--rte-accent);border-color:var(--rte-accent)}.yjd-rich-editor .resize-handle{background:var(--rte-accent)!important;border:2px solid #fff!important;border-radius:50%!important;box-shadow:var(--rte-shadow-sm)!important}.yjd-rich-editor .yjd-find-replace{position:absolute;top:10px;right:10px;z-index:1100;display:none;flex-direction:column;gap:8px;padding:10px;background:var(--rte-bg);border:1px solid var(--rte-border);border-radius:var(--rte-radius-md);box-shadow:var(--rte-shadow);font:13px/1.4 system-ui,-apple-system,sans-serif;max-width:calc(100% - 20px)}.yjd-rich-editor .yjd-find-replace.open{display:flex}.yjd-rich-editor .yjd-find-row{display:flex;align-items:center;gap:6px}.yjd-rich-editor .yjd-find-input{flex:1 1 auto;min-width:150px;transition:border-color var(--rte-t),box-shadow var(--rte-t)}.yjd-rich-editor .yjd-find-input:focus{outline:0}.yjd-rich-editor .yjd-find-count{min-width:46px;text-align:center;color:var(--rte-muted);font-variant-numeric:tabular-nums}.yjd-rich-editor .yjd-find-btn{border:1px solid var(--rte-border);background:var(--rte-chrome);color:var(--rte-ink);cursor:pointer}.yjd-rich-editor .yjd-find-btn:hover{background:var(--rte-chrome-2)}.yjd-rich-editor .yjd-find-btn:focus-visible{outline:0;border-color:var(--rte-accent);box-shadow:0 0 0 3px var(--rte-accent-ring)}.yjd-rich-editor .yjd-find-icon{width:30px;padding:0;font-size:15px;line-height:1}.yjd-rich-editor .yjd-find-toggle.active{background:var(--rte-accent-weak);color:var(--rte-accent-ink);border-color:transparent}.yjd-rich-editor .yjd-find-replace .yjd-find-btn.yjd-find-close:hover{background:#fdecec;color:var(--rte-danger);border-color:#f6caca}.yjd-rich-editor .yjd-find-replace-input{flex:1 1 auto}.yjd-rich-editor .yjd-find-row .yjd-find-btn:not(.yjd-find-icon){flex:0 0 auto}.yjd-rich-editor mark.yjd-find-hit{background:#fff1b8;color:inherit;border-radius:3px}.yjd-rich-editor mark.yjd-find-hit.active{background:var(--rte-accent);color:#fff}@media (prefers-reduced-motion:reduce){.yjd-rich-editor *{transition-duration:.01ms!important;animation-duration:.01ms!important}}.yjd-rich-editor .block-toolbar-btn svg [fill=\\\"#454545\\\"],.yjd-rich-editor .icon svg [fill=\\\"#010101\\\"],.yjd-rich-editor .icon svg [fill=\\\"#231F20\\\"],.yjd-rich-editor .icon svg [fill=\\\"#454545\\\"],.yjd-rich-editor .icon svg[fill=\\\"#454545\\\"],.yjd-rich-editor .table-toolbar-btn svg [fill=\\\"#454545\\\"]{fill:currentColor}.yjd-rich-editor .block-toolbar-btn .icon,.yjd-rich-editor .rich-editor-toolbar-btn .icon,.yjd-rich-editor .table-toolbar-btn .icon{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;color:inherit}.yjd-rich-editor .block-toolbar-btn .icon svg,.yjd-rich-editor .rich-editor-toolbar-btn .icon svg,.yjd-rich-editor .table-toolbar-btn .icon svg{width:auto!important;height:16px!important;max-width:18px;display:block}.yjd-rich-editor .rich-editor-toolbar-btn .icon svg,.yjd-rich-editor .rich-editor-toolbar-btn .icon svg path{fill:currentColor}.yjd-rich-editor .rich-editor-toolbar-btn{color:#4b5060}.yjd-rich-editor .rich-editor-toolbar-btn:hover{color:var(--rte-ink)}.yjd-rich-editor .rich-editor-toolbar-btn.active,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg [fill]{color:var(--rte-accent-ink)!important;fill:var(--rte-accent-ink)!important}.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg path{fill:var(--rte-accent-ink)!important}.yjd-rich-editor .custom-select-button,.yjd-rich-editor .rich-editor-toolbar-btn{box-shadow:none}.yjd-rich-editor .custom-select-button{border-color:var(--rte-border);background:var(--rte-bg);color:var(--rte-ink);font-weight:500}.yjd-rich-editor .custom-select-button:hover{border-color:var(--rte-border-strong);background:var(--rte-bg)}.yjd-rich-editor .dropdown-icon{color:var(--rte-muted)}.yjd-rich-editor .capitalization-select-popup,.yjd-rich-editor .color-picker-popup,.yjd-rich-editor .custom-select-popup,.yjd-rich-editor .font-family-select-popup,.yjd-rich-editor .heading-select-popup,.yjd-rich-editor .image-popup,.yjd-rich-editor .import-popup,.yjd-rich-editor .line-height-select-popup,.yjd-rich-editor .link-popup,.yjd-rich-editor .table-popup,.yjd-rich-editor .tag-popup,.yjd-rich-editor .text-size-select-popup,.yjd-rich-editor .video-popup{border-color:var(--rte-border);border-radius:var(--rte-radius-md);box-shadow:0 10px 30px -10px rgba(20,24,46,.18),0 2px 6px -2px rgba(20,24,46,.08)}.yjd-rich-editor .emoji-picker-popup,.yjd-rich-editor .text-align-picker-popup{border-color:var(--rte-border);box-shadow:0 10px 30px -10px rgba(20,24,46,.18),0 2px 6px -2px rgba(20,24,46,.08)}.yjd-rich-editor .emoji-picker-popup{border-radius:var(--rte-radius-md)}.yjd-rich-editor .capitalization-select-popup,.yjd-rich-editor .custom-select-popup,.yjd-rich-editor .font-family-select-popup,.yjd-rich-editor .heading-select-popup,.yjd-rich-editor .line-height-select-popup,.yjd-rich-editor .text-size-select-popup{padding:6px}.yjd-rich-editor .capitalization-select-popup.visible,.yjd-rich-editor .color-picker-popup.visible,.yjd-rich-editor .custom-select-popup.visible,.yjd-rich-editor .font-family-select-popup.visible,.yjd-rich-editor .heading-select-popup.visible,.yjd-rich-editor .image-popup.visible,.yjd-rich-editor .line-height-select-popup.visible,.yjd-rich-editor .link-popup.visible,.yjd-rich-editor .table-popup.visible,.yjd-rich-editor .tag-popup.visible,.yjd-rich-editor .text-align-picker-popup.visible,.yjd-rich-editor .text-size-select-popup.visible,.yjd-rich-editor .video-popup.visible,.yjd-rich-editor .yjd-find-replace.open{animation:rte-pop-in 90ms ease-out}.yjd-rich-editor .yjd-input{height:36px;border:1px solid var(--rte-border);border-radius:var(--rte-radius-sm);background:var(--rte-bg);color:var(--rte-ink)}.yjd-rich-editor .yjd-select-input{height:36px;background:var(--rte-bg);font-size:14px}.yjd-rich-editor .yjd-find-input{height:36px;border:1px solid var(--rte-border);border-radius:var(--rte-radius-sm);background:var(--rte-bg);color:var(--rte-ink)}.yjd-rich-editor .yjd-find-input::placeholder,.yjd-rich-editor .yjd-input::placeholder,.yjd-rich-editor .yjd-select-input::placeholder{color:var(--rte-muted);opacity:.7}.yjd-rich-editor .yjd-find-input:focus,.yjd-rich-editor .yjd-input:focus{border-color:var(--rte-accent);box-shadow:0 0 0 3px var(--rte-accent-ring)}.yjd-rich-editor .yjd-button-cancel,.yjd-rich-editor .yjd-button-confirm,.yjd-rich-editor .yjd-find-btn{height:36px;padding:0 16px;font-size:14px;font-weight:500;border-radius:var(--rte-radius-sm);box-shadow:none;transition:background var(--rte-t),color var(--rte-t),border-color var(--rte-t)}.yjd-rich-editor .yjd-button-confirm{background:var(--rte-accent);border:1px solid var(--rte-accent);color:#fff}.yjd-rich-editor .yjd-button-confirm:hover{background:var(--rte-accent-ink);border-color:var(--rte-accent-ink)}.yjd-rich-editor .yjd-button-cancel{background:0 0;border:1px solid var(--rte-border);color:var(--rte-muted)}.yjd-rich-editor .yjd-button-cancel:hover{background:var(--rte-chrome-2);color:var(--rte-ink)}.yjd-rich-editor .yjd-find-btn:not(.yjd-find-icon){background:0 0;border:1px solid var(--rte-border);color:var(--rte-ink)}.yjd-rich-editor .yjd-find-btn:not(.yjd-find-icon):hover{background:var(--rte-chrome-2)}.yjd-rich-editor .yjd-find-btn.yjd-find-icon{height:32px;width:32px;color:var(--rte-muted);background:0 0;border:1px solid transparent}.yjd-rich-editor .yjd-find-btn.yjd-find-icon:hover{background:var(--rte-chrome-2);color:var(--rte-ink)}@media (prefers-reduced-motion:reduce){.yjd-rich-editor .color-picker-popup.visible,.yjd-rich-editor .yjd-find-replace.open,.yjd-rich-editor [class$=-popup].visible,.yjd-rich-editor [class*=select-popup].visible{animation:none!important}}.yjd-rich-editor .icon svg{display:block;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;overflow:visible}.yjd-rich-editor .icon svg line,.yjd-rich-editor .icon svg path,.yjd-rich-editor .icon svg polyline,.yjd-rich-editor .icon svg rect{fill:none!important;stroke:currentColor}.yjd-rich-editor .icon svg [fill=currentColor],.yjd-rich-editor .list-button svg [fill=currentColor],.yjd-rich-editor .table-toolbar-btn svg [fill=currentColor]{fill:currentColor!important;stroke:none!important}.yjd-rich-editor .rich-editor-toolbar-btn .icon svg{color:#565b6b}.yjd-rich-editor .rich-editor-toolbar-btn:hover .icon svg{color:var(--rte-ink)}.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg line,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg path,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg polyline,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg rect{color:var(--rte-accent-ink)!important;stroke:var(--rte-accent-ink)!important;fill:none!important}.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg [fill=currentColor]{fill:var(--rte-accent-ink)!important;stroke:none!important}.yjd-rich-editor .block-toolbar-btn.active .icon svg,.yjd-rich-editor .block-toolbar-btn.active .icon svg path{stroke:var(--rte-accent-ink)!important;fill:none!important}.yjd-rich-editor .list-picker-popup{background:var(--rte-bg);border:1px solid var(--rte-border);border-radius:var(--rte-radius-md);box-shadow:var(--rte-shadow);padding:6px}.yjd-rich-editor .list-picker-popup.visible{animation:rte-pop-in 90ms ease-out}.yjd-rich-editor .list-button-container{display:flex;align-items:center;gap:2px}.yjd-rich-editor .list-button{display:inline-flex;align-items:center;justify-content:center;width:34px;height:32px;padding:0;border:1px solid transparent;border-radius:var(--rte-radius-sm);background:0 0;color:#565b6b;cursor:pointer;transition:background var(--rte-t),color var(--rte-t)}.yjd-rich-editor .list-button:hover{background:var(--rte-chrome-2);color:var(--rte-ink)}.yjd-rich-editor .list-button.active{background:var(--rte-accent-weak);color:var(--rte-accent-ink)}.yjd-rich-editor .icon svg,.yjd-rich-editor .list-button svg,.yjd-rich-editor .table-toolbar-btn svg{width:auto!important;height:16px!important;max-width:20px;fill:none!important}.yjd-rich-editor .list-button svg{stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.yjd-rich-editor .table-toolbar-btn svg{display:block}.yjd-rich-editor .table-toolbar-btn{color:#565b6b}.yjd-rich-editor .table-toolbar-btn:hover{color:var(--rte-ink)}.yjd-rich-editor .align-button{border-radius:var(--rte-radius-sm);background:0 0;color:var(--rte-ink);transition:background var(--rte-t),color var(--rte-t)}.yjd-rich-editor .align-button:hover{background:var(--rte-chrome-2);border-color:transparent}.yjd-rich-editor .align-button.active{background:var(--rte-accent-weak);color:var(--rte-accent-ink);border-color:transparent}.yjd-rich-editor .align-button svg,.yjd-rich-editor .table-toolbar-btn svg,.yjd-slash-icon svg{stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.yjd-rich-editor .align-button svg{width:auto!important;height:16px!important;color:inherit;fill:none!important}.yjd-rich-editor .align-button.active svg{color:var(--rte-accent-ink)}.yjd-rich-editor .yjd-custom-upload-button svg{width:17px;height:17px;fill:none;stroke:var(--rte-accent-ink)}.yjd-rich-editor .text-align-picker-popup{min-width:168px}.yjd-rich-editor .align-button,.yjd-rich-editor .align-button .label-text{white-space:nowrap}.yjd-rich-editor .rich-editor-toolbar-1{flex-wrap:nowrap!important;overflow:hidden}.yjd-rich-editor .rich-editor-toolbar-2{border-top:1px solid var(--rte-border)}.yjd-rich-editor .custom-select-button,.yjd-rich-editor .rich-editor-toolbar-btn,.yjd-rich-editor .toolbar-group{flex:0 0 auto}.yjd-rich-editor .block-toolbar-arrow,.yjd-rich-editor .table-toolbar-arrow{display:none!important}.yjd-slash-menu{position:absolute;z-index:2000;min-width:264px;max-width:320px;max-height:320px;overflow-y:auto;padding:6px;background:#fff;border:1px solid #e9e9f1;border-radius:12px;box-shadow:0 12px 32px -8px rgba(20,24,46,.2),0 4px 10px -4px rgba(20,24,46,.1);font:14px/1.4 system-ui,-apple-system,BlinkMacSystemFont,\\\"Segoe UI\\\",Roboto,sans-serif;animation:yjd-slash-in 90ms ease-out}.yjd-slash-item{display:flex;align-items:center;gap:10px;width:100%;padding:8px 10px;border:0;background:0 0;border-radius:8px;cursor:pointer;text-align:left;color:#20242f}.yjd-slash-item.active,.yjd-slash-item:hover{background:#efedff;color:#5a48ee}.yjd-slash-icon{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;flex:0 0 auto;border-radius:7px;background:#f2f2f7;color:inherit}.yjd-slash-item.active .yjd-slash-icon{background:#fff}.yjd-slash-icon svg{width:auto;height:16px;fill:none}.yjd-slash-icon svg [fill=currentColor]{fill:currentColor;stroke:none}.yjd-slash-text{display:flex;flex-direction:column;line-height:1.25;min-width:0}.yjd-slash-label{font-weight:500}.yjd-slash-hint{font-size:12px;color:#767c8e}.yjd-slash-item.active .yjd-slash-hint{color:#8b7ff0}@media (prefers-reduced-motion:reduce){.yjd-slash-menu{animation:none}}.yjd-rich-editor .rich-editor-toolbar-btn.background-btn,.yjd-rich-editor .rich-editor-toolbar-btn.color-btn{position:relative}.yjd-rich-editor .rich-editor-toolbar-btn .rte-swatch{position:absolute;left:50%;bottom:5px;transform:translateX(-50%);width:16px;height:3px;border-radius:2px;background:var(--rte-ink, #20242f);pointer-events:none;box-shadow:0 0 0 1px rgba(0,0,0,.06)}.yjd-rich-editor .rich-editor-toolbar-btn.background-btn .rte-swatch{background:0 0;box-shadow:inset 0 0 0 1px var(--rte-border, #e9e9f1)}.yjd-rich-editor .rich-editor-toolbar-btn.background-btn.has-color .rte-swatch{box-shadow:0 0 0 1px rgba(0,0,0,.06)}.yjd-rich-editor .rich-editor-toolbar-btn.background-btn .icon,.yjd-rich-editor .rich-editor-toolbar-btn.color-btn .icon{transform:translateY(-1px)}.yjd-rich-editor .link-popup.link-popup--inline{width:auto;min-width:320px;max-width:min(92vw,420px);padding:10px}.yjd-rich-editor .link-popup--inline .link-popup-content{display:flex;flex-direction:column;gap:8px}.yjd-rich-editor .link-popup--inline .link-popup-row{display:flex;align-items:center;gap:8px}.yjd-rich-editor .link-popup--inline .link-popup-row .yjd-input{flex:1;min-width:0;margin:0}.yjd-rich-editor .link-popup--inline .link-popup-apply{flex:0 0 auto;white-space:nowrap;padding:8px 16px;margin:0}.yjd-rich-editor .custom-select-item-button{min-height:34px;padding:6px 10px!important;gap:10px;border-radius:var(--rte-radius-sm)!important}.yjd-rich-editor .custom-select-item-button.current,.yjd-rich-editor .custom-select-item-button:hover{background:var(--rte-accent-weak)!important;color:var(--rte-accent-ink)!important}.yjd-rich-editor .custom-select-item-button.current{font-weight:600}.yjd-rich-editor .font-family-select-popup .custom-select-item-button,.yjd-rich-editor .heading-select-popup .custom-select-item-button,.yjd-rich-editor .text-size-select-popup .custom-select-item-button{line-height:1.15}.yjd-rich-editor .import-type-select,.yjd-rich-editor .tag-type-select,.yjd-rich-editor .yjd-select-input,.yjd-rich-editor select{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;box-sizing:border-box;padding:10px 36px 10px 12px;font:500 14px/1.4 system-ui,-apple-system,\\\"Segoe UI\\\",Roboto,sans-serif;color:var(--rte-ink);background-color:var(--rte-bg);background-image:url(\\\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='16'%20height='16'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23767c8e'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='m6%209%206%206%206-6'/%3E%3C/svg%3E\\\");background-repeat:no-repeat;background-position:right 12px center;background-size:16px;border:1px solid var(--rte-border-strong);border-radius:var(--rte-radius-sm);cursor:pointer;transition:border-color var(--rte-t),box-shadow var(--rte-t)}.yjd-rich-editor .import-type-select:hover,.yjd-rich-editor .tag-type-select:hover,.yjd-rich-editor .yjd-select-input:hover,.yjd-rich-editor select:hover{border-color:var(--rte-accent)}.yjd-rich-editor .import-type-select:focus,.yjd-rich-editor .tag-type-select:focus,.yjd-rich-editor .yjd-select-input:focus,.yjd-rich-editor select:focus{outline:0;border-color:var(--rte-accent);box-shadow:0 0 0 3px var(--rte-accent-ring)}.yjd-rich-editor .yjd-find-input,.yjd-rich-editor .yjd-input{box-sizing:border-box;padding:10px 12px;font-size:14px;line-height:1.4}.yjd-rich-editor .table-grid-cell:hover{background:var(--rte-accent-weak)!important;border-color:var(--rte-accent)!important}.yjd-rich-editor .rich-editor-toolbar-1{justify-content:flex-start!important}.yjd-rich-editor .rich-editor-toolbar-1 .more-btn{margin-left:auto!important}@media (max-width:640px){.yjd-rich-editor .rich-editor-toolbar-1{flex-wrap:nowrap!important;overflow-x:auto!important;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none;justify-content:flex-start!important;padding:6px 8px;scroll-padding:8px}.yjd-rich-editor .rich-editor-toolbar-1::-webkit-scrollbar{display:none}.yjd-rich-editor .rich-editor-toolbar-1 .more-btn,.yjd-rich-editor .rich-editor-toolbar-2{display:none!important}.yjd-rich-editor .toolbar-group{flex:0 0 auto}.yjd-rich-editor .toolbar-group+.toolbar-group{margin-left:8px}.yjd-rich-editor .custom-select-button,.yjd-rich-editor .rich-editor-toolbar-btn{min-width:32px;height:32px;padding:0 8px;flex:0 0 auto}.yjd-rich-editor .custom-select-button{max-width:none}}.yjd-rich-editor .rich-editor-toolbar-btn.rte-hidden{display:none!important}.yjd-rich-editor .rich-editor-area>:first-child{margin-top:0!important}.yjd-rich-editor .rich-editor-area>:last-child{margin-bottom:0!important}.yjd-rich-editor .align-button-container{flex-direction:row!important;width:auto!important;height:auto!important;gap:4px}.yjd-rich-editor .align-button{width:34px!important;min-height:34px!important;height:34px!important;padding:0!important;gap:0!important;justify-content:center!important}.yjd-rich-editor .text-align-picker-popup{width:auto!important}.yjd-rich-editor .rich-editor-toolbar-2{flex-wrap:nowrap!important;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none}.yjd-rich-editor .rich-editor-toolbar-2::-webkit-scrollbar{display:none}.yjd-rich-editor .rich-editor-toolbar-2 .toolbar-group{flex:0 0 auto}.yjd-rich-editor .custom-select-header{padding:8px 12px 6px;font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--rte-muted);border-bottom:1px solid var(--rte-border);margin-bottom:4px;white-space:nowrap}.yjd-rich-editor .custom-select-button .select-lead-icon{width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--rte-muted);margin-right:4px}.yjd-rich-editor .custom-select-button .select-lead-icon svg{width:16px;height:16px;display:block;stroke:currentColor;fill:none}.yjd-rich-editor .custom-select-button{white-space:nowrap;overflow:hidden}.yjd-rich-editor .custom-select-button .button-text{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.yjd-rich-editor .custom-select-button .dropdown-icon,.yjd-rich-editor .custom-select-button .select-lead-icon{flex:0 0 auto}.yjd-rich-editor .text-align-picker-popup{padding:6px!important;border-radius:var(--rte-radius-md)!important}.yjd-rich-editor .align-button-container{padding:0!important}.yjd-rich-editor .rich-editor-breadcrumb,.yjd-rich-editor .wordcount{color:#5b6373}.yjd-rich-editor .yjd-find-btn{display:inline-flex;align-items:center;justify-content:center}.yjd-rich-editor .yjd-find-icon .icon,.yjd-rich-editor .yjd-find-icon .icon svg{width:16px;height:16px;display:block}.yjd-rich-editor .yjd-find-icon .icon svg{stroke:currentColor;fill:none}.mention{display:inline;padding:0 2px;border-radius:4px;background:#efedff;color:#5a48ee;font-weight:500;text-decoration:none;white-space:nowrap}.mention[data-trigger=\\\"#\\\"]{background:#e8f3ff;color:#1f6feb}.yjd-mention-menu{position:absolute;z-index:2000;min-width:220px;max-width:320px;max-height:280px;overflow-y:auto;padding:6px;background:#fff;border:1px solid #e9e9f1;border-radius:12px;box-shadow:0 12px 32px -8px rgba(20,24,46,.2),0 4px 10px -4px rgba(20,24,46,.1);font:14px/1.4 system-ui,-apple-system,BlinkMacSystemFont,\\\"Segoe UI\\\",Roboto,sans-serif;animation:yjd-slash-in 90ms ease-out}.yjd-mention-item{display:flex;align-items:center;gap:10px;width:100%;padding:7px 10px;border:0;background:0 0;border-radius:8px;cursor:pointer;text-align:left;color:#20242f}.yjd-mention-item.active,.yjd-mention-item:hover{background:#efedff;color:#5a48ee}.yjd-mention-avatar{width:26px;height:26px;border-radius:50%;object-fit:cover;flex:0 0 auto;background:#f2f2f7}.yjd-mention-name{font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media (prefers-reduced-motion:reduce){.yjd-mention-menu{animation:none}}.yjd-content{font-family:-apple-system,BlinkMacSystemFont,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif;font-size:16px;line-height:1.6;color:#20242f;word-wrap:break-word}.yjd-content>:first-child{margin-top:0}.yjd-content>:last-child{margin-bottom:0}.yjd-content li{margin:.25em 0}.yjd-content a{color:#2563eb;text-decoration:underline}.yjd-content code,.yjd-content pre{font-family:Consolas,Menlo,Monaco,\\\"Courier New\\\",monospace;background:#f1f2f3;padding:2px 6px;border-radius:4px}.yjd-content pre{padding:12px 14px;border-radius:6px;margin:1em 0;overflow-x:auto;white-space:pre}.yjd-content pre code{background:0 0;padding:0}.yjd-content img{max-width:100%;height:auto;border-radius:4px}.yjd-content hr{border:0;border-top:1px solid #d1d5db;margin:1.5em 0}.yjd-content table{border-collapse:collapse;width:100%;margin:1em 0}.yjd-content table td,.yjd-content table th{border:1px solid #d1d5db;padding:8px 10px;text-align:left}\";\n"],"names":["registry","constructor","this","modules","Map","formats","themes","ui","register","path","def","suppressWarning","Object","entries","forEach","key","value","type","name","split","get","set","has","undefined","getAll","unregister","delete","clear","getAllItems","items","Array","from","keys","Module","static","editor","options","DEFAULTS","events","on","event","handler","push","off","handlers","index","indexOf","splice","emit","data","error","destroy","onContentChange","onSelectionChange","range","execFormat","command","document","execCommand","e","setStyleWithCSS","enabled","queryFormatState","queryCommandState","SAFE_URL_SCHEMES","TRUSTED_IFRAME_PREFIXES","isSafeUrl","url","allowDataImage","trimmed","trim","stripped","replace","schemeMatch","match","scheme","toLowerCase","test","includes","FORBIDDEN_TAGS","Set","cleanElement","el","attrs","attributes","attr","startsWith","removeAttribute","tagName","sanitizeHtml","html","doc","DOMParser","parseFromString","elements","body","querySelectorAll","tag","remove","src","getAttribute","some","prefix","innerHTML","htmlToMarkdown","root","createElement","blocksToMd","parent","depth","out","childNodes","node","nodeType","t","textContent","repeat","inline","c","map","l","join","listToMd","rows","length","cells","r","children","head","slice","tableToMd","imgToMd","nodeBlock","ordered","i","li","marker","pad","text","nested","ch","inlineNode","classList","contains","id","markdownToHtml","md","lines","isList","line","h","inlineMd","code","escapeHtml","q","parseTable","next","parseList","para","indentOf","start","baseIndent","ind","m","row","s","_","a","href","String","domToJson","content","nodeToJson","filter","Boolean","obj","kids","jsonToHtml","json","isArray","jsonNodeToHtml","n","k","v","inner","Editor","placeholder","theme","height","width","maxWidth","maxHeight","features","emoji","image","table","wordCount","breadcrumb","selector","querySelector","toolbarBtns","statusbarEls","dropdownMenus","popupInstances","currentInstance","instanceId","generateInstanceId","instances","init","Date","now","Math","random","toString","substr","createStructure","loadModules","loadFormats","setupEventListeners","updateStatusbar","wrapper","className","cssSize","style","minHeight","position","contentEditable","setAttribute","ariaLabel","direction","getDefaultContent","appendChild","popupContainer","top","left","pointerEvents","zIndex","createStatusbar","updatePlaceholderVisibility","isHtmlContent","pattern","wrapTextInParagraph","saved","_getAutosaved","statusbar","wordcount","modulesToLoad","toolbar","toolbar1","toolbar2","mention","moduleName","ModuleClass","moduleInstance","getContainer","toolbarContainer","insertBefore","handleToolbarClick","formatsToLoad","formatName","FormatClass","_markActive","addEventListener","_onDocSelectionChange","activeElement","_scheduleSelectionUpdate","ensureEditorHasContent","ctrlKey","metaKey","altKey","b","u","shiftKey","preventDefault","toggleFormat","setTimeout","handlePaste","dataTransfer","types","dt","imageFile","files","find","f","placeCaretAtPoint","clientX","clientY","insertImageFile","maxLength","inputType","incoming","_remainingChars","markdown","handleMarkdownShortcut","updateHistoryButtons","focus","_emitChange","module","getContent","onChange","_scheduleAutosave","isEditorEmpty","selInEditor","isSelectionInEditableArea","window","getSelection","paragraph","setCursorToElement","_clearStickyInlineFormats","updateToolbarButtonStates","cmd","ensureParagraphForEditing","lastChild","element","createRange","selection","firstChild","Node","TEXT_NODE","setStart","collapse","removeAllRanges","addRange","_selUpdateQueued","requestAnimationFrame","rangeCount","getRangeAt","isInEditableArea","collapsed","_lastRange","cloneRange","updateToolbarAccessibility","startContainer","endContainer","startInEditor","isNodeInEditableArea","endInEditor","currentNode","parentNode","getModule","setButtonDisabled","sel","anchorNode","parentElement","tagInfo","classes","unshift","codeView","isInCodeView","tmp","getCurrentContent","words","chars","label","max","setContent","processedContent","getHTML","setHTML","getJSON","setJSON","getMarkdown","setMarkdown","getText","isEmpty","insertText","insertHTML","clipboard","clipboardData","imageItem","it","kind","file","getAsFile","getData","remaining","pasteAsPlainText","Infinity","selLen","isCollapsed","cfg","accept","endsWith","reason","maxSize","size","makeImg","extra","ImageClass","create","img","upload","reader","FileReader","onload","ev","target","result","outerHTML","readAsDataURL","placeholderId","round","performance","_upCounter","ph","opacity","Promise","resolve","then","catch","err","x","y","caretRangeFromPoint","caretPositionFromPoint","pos","offsetNode","offset","block","pre","selectNodeContents","setEnd","startOffset","blockTag","listType","history","saveBeforeFormat","deleteContents","replaceWith","caret","setDirection","dir","d","setButtonActive","getDirection","toggleDirection","_autosaveCfg","_autosaveMemo","autosave","debounce","localStorage","getItem","clearTimeout","_autosaveTimer","setItem","clearAutosave","removeItem","clearFormatting","historyModule","setBlockType","BLOCK","insertHorizontalRule","_isBlockEmpty","insertBlock","blockEl","topBlock","wasEmpty","nextSibling","p","setReadOnly","readOnly","_readOnly","toggle","buttons","isReadOnly","getFormat","definition","button","undo","redo","registryKey","bold","italic","underline","strike","subscript","superscript","color","background","link","heading","capitalization","list","video","import","_getFormatInstance","_fmtCache","inst","createForEditor","original","formatInstance","isActive","updateButtonText","TextSizeClass","updateButtonTextStatic","updateColorSwatches","getButton","canUndo","canRedo","setState","btn","disabled","apply","swatch","add","removeProperty","ColorClass","getCurrentColor","BgClass","preventFocusLoss","allowedSelector","closest","getCurrentInstance","maintainFocus","callback","editorInstance","getPopupContainer","getPopupInstance","popupType","setPopupInstance","popupInstance","getPopupInstanceById","editorId","getInstanceById","getAllInstances","destroyPopupInstances","removeEventListener","removeChild","Format","domNode","getOffsetWithin","container","walker","createTreeWalker","NodeFilter","SHOW_TEXT","nextNode","InlineFormat","super","formatNode","createTextNode","insertNode","newRange","contents","extractContents","removeAtCursor","removeFromSelection","findFormatNode","absoluteOffset","beforeText","afterText","fragment","createDocumentFragment","beforeNode","cloneNode","zwspNode","afterNode","setStartAfter","ELEMENT_NODE","findFormatNodesInRange","nodes","commonAncestorContainer","SHOW_ELEMENT","acceptNode","intersectsNode","FILTER_ACCEPT","FILTER_SKIP","altTags","alternativeTagNames","BlockFormat","blocks","getBlockElements","createBlockAtCursor","convertBlock","removeBlockFormat","blockNode","existingBlock","getBlockElement","cssText","newBlock","shouldPreserveClass","replaceChild","startBlock","endBlock","nextElementSibling","endOffset","current","isBlockElement","getNextBlockElement","self","toUpperCase","hasValue","renderStatic","safe","Bold","Italic","Underline","Strike","Subscript","removeSuperscriptBeforeApply","Superscript","removeSubscriptBeforeApply","S","Icons","close","check","dropdown","more","IconUtils","getIcon","iconName","createIconElement","iconElement","display","alignItems","justifyContent","verticalAlign","hasIcon","getIconNames","appendPopup","popup","getPopupDimensions","rect","getBoundingClientRect","offsetWidth","offsetHeight","computedStyle","getComputedStyle","visibility","originalDisplay","originalVisibility","originalPosition","originalTop","originalLeft","originalZIndex","tempRect","computedWidth","parseInt","computedHeight","calculatePopupPosition","anchor","offsetX","offsetY","preferTop","preferLeft","anchorRect","wrapperRect","popupWidth","popupHeight","topPosition","bottom","scrollY","scrollX","innerWidth","innerHeight","setPopupPosition","ColorPicker","colors","customColorEnabled","onColorSelect","isVisible","currentColor","clickOutsideHandler","createColorPicker","createColorGrid","createCustomColorInput","grid","colorButton","backgroundColor","dataset","title","stopPropagation","selectColor","customContainer","noColorButton","noColorIcon","whiteButton","border","blackButton","customColorButton","font","customInput","click","setupClickOutside","hide","removeClickOutside","show","Color","currentEditor","colorPicker","applyColorToCurrentSelection","savedRanges","showColorPicker","Background","applyBackgroundToCurrentSelection","originalCurrent","format","backgroundButton","LinkPopup","onLinkSelect","urlInput","textInput","createPopup","textGroup","applyBtn","onclick","handleOk","_refocusEditor","onKey","onkeydown","raw","existingLink","selectedText","hasSelection","closeOnClickOutside","Link","linkPopup","linkData","insertLink","savedRange","linkElement","rel","showPopup","getCurrentLink","TablePopup","maxRows","maxCols","onTableSelect","selectedRows","selectedCols","sizeDisplay","createSizeDisplay","createGridSelector","col","cell","highlightGrid","selectSize","handleInsert","cols","updateSizeDisplay","cellRow","cellCol","Table","tablePopup","tableData","insertTable","tableElement","createTableElement","firstCell","cellSpacing","cellPadding","tbody","minWidth","padding","borderCollapse","margin","tableButton","getCurrentTable","richEditor","resizeHandles","hideHandles","CustomSelect","onItemSelect","displayProperty","valueProperty","currentValue","initialized","createSelect","header","createItemList","checkIconSvg","item","itemButton","getItemValue","itemText","getItemDisplay","checkmark","selectItem","updateItems","existingList","setupScrollHandler","scrollHandler","updatePosition","removeScrollHandler","ed","editorEl","_savedRange","checkInit","highlightCurrentItem","currentAnchor","setCurrentValue","getCurrentValue","Heading","customSelect","tagMap","getTagMap","values","tagData","applyTagToCurrentSelection","H1","H2","H3","H4","H5","H6","P","PRE","BLOCKQUOTE","getTagDisplayName","currentTag","getCurrentTag","displayName","headingButton","updateText","headingFormat","blocksArray","findBlockIndex","idx","charOffsetFromBlockStart","startBlockIndex","endBlockIndex","startCharOffset","endCharOffset","newBlocks","resolvePositionByCharOffset","charOffset","len","nodeValue","min","setCursorAtStartOfBlock","firstTextNode","showTagPicker","FontFamily","fontMap","getFontMap","fontData","applyFontFamilyToCurrentSelection","setupSelectionListener","updateTimeout","debouncedUpdate","isSameNode","selectionListener","Arial","Helvetica","Georgia","Verdana","Impact","getFontDisplayName","currentFont","getCurrentFont","fontFamilyButton","fontFamily","fontFamilyFormat","moveCaretInside","textNode","fontNormalized","styleFont","isCaretInsideFontSpan","currentSpan","caretPos","textBefore","textAfter","newSpan","span1","span2","span3","anchorButton","showFontPicker","setCurrentFont","LineHeight","heightMap","getHeightMap","heightData","applyLineHeightToCurrentSelection","getHeightDisplayName","num","parseFloat","isNaN","currentHeight","getCurrentHeight","lineHeightButton","lineHeight","lineHeightFormat","blockParent","blockElements","getBlockElementsInRange","heightSpan","blockTags","cloneContents","actualNode","showHeightPicker","normalizeHeightValue","fontSize","lineHeightPx","relative","toFixed","numValue","normalized","Capitalization","capMap","getCapitalizationMap","capData","applyCapitalizationToCurrentSelection","capitalize","uppercase","lowercase","getCapitalizationDisplayName","currentCap","getCurrentCapitalization","capitalizationButton","fontVariant","textTransform","capFormat","hasCapitalizationStyling","computed","isCapitalizationElement","findAncestorCapitalizationElement","setElementCapitalizationStyle","frag","span","transformText","char","toRemove","childElementCount","removeEmptyElements","removeExistingCapitalization","elementsToProcess","maybeAdd","addAncestors","startNode","unwrapElement","showCapitalizationPicker","setCurrentCapitalization","currentCapitalization","toggleUppercase","newStyle","toggleLowercase","toggleCapitalize","toggleSmallCaps","TextAlignPicker","alignments","icon","onAlignSelect","currentAlignment","createAlignPicker","createAlignmentButtons","buttonContainer","alignment","alignButton","iconSvg","charAt","selectAlignment","updateCurrentAlignment","getSelectedBlockElements","firstBlock","textAlign","updateToolbarButtonIcon","default","TextAlign","alignPicker","applyAlignToCurrentSelection","originalRange","caretContainer","caretOffset","applyAlignmentToBlock","newCaretRange","getIconNameForAlignment","center","right","justify","svgContent","iconSpan","currentBlock","blockRange","compareBoundaryPoints","Range","END_TO_START","START_TO_END","showAlignPicker","getCurrentAlignment","ListPicker","listTypes","onListSelect","currentListType","createListPicker","createListTypeButtons","listButton","selectListType","updateCurrentListType","getCurrentListType","updateButtonStates","bullet","roman","alpha","getParentList","getListType","listElement","listStyleType","List","listPicker","applyListToCurrentSelection","createListFromSelection","convertBlocksToList","listItem","toggleOrChangeListType","firstListItem","newListType","removeListFormatting","changeListType","newList","newItem","firstElementChild","firstParagraph","getIconNameForListType","nextBlock","showListPicker","Indent","paddingLeft","applyIndentToCurrentSelection","applyIndentToBlock","currentIndent","newIndent","blockElement","getCurrentIndentLevel","IndentIncrease","IndentDecrease","EmojiPicker","emojis","onEmojiSelect","createEmojiPicker","detectOS","platform","navigator","getEmojiShortcutMessage","createEmojiGrid","emojiTextMessage","emojiGrid","emojiButton","selectEmoji","Emoji","emojiPicker","insertEmojiAtCurrentPosition","emojiParent","emojiElement","zeroWidthSpace","showEmojiPicker","getEmojiElement","emojiInSelection","Image","alt","insertImageAtCurrentPosition","imageElement","spaceNode","openFilePicker","input","getImageElement","imageInSelection","handleFileUpload","reject","Error","onerror","validateImageUrl","hasValidExtension","ext","VideoPopup","onVideoInsert","selectedVideoSrc","savedSelection","createVideoPopup","uploadContainer","textLabel","inputgroup1","inputGroup","updateInsertButton","isValidVideoUrl","showPreview","removePreview","customButton","fileInput","handleFileSelect","createPreviewContainer","previewContainer","cancelButton","insertButton","insertVideo","Video","alert","message","hasVideo","videoSrc","videoPreview","toggleInputGroup","recalculatePosition","controls","muted","removeButton","urlObj","URL","videoExtensions","videoHosts","pathname","hasVideoExtension","isFromVideoHost","host","hostname","validateVideoUrl","restoreSelection","reset","saveSelection","videoPopup","insertVideoAtCurrentPosition","isYouTubeUrl","createYouTubeEmbed","videoId","getYouTubeVideoId","iframe","videoElement","showVideoPopup","getVideoElement","videoButton","videoInSelection","onloadedmetadata","TagPopup","onTagInsert","selectedTagType","createTagPopup","group1","typeLabel","typeSelect","updateSuggestions","group2","contentLabel","contentInput","insertTag","group3","suggestionsContainer","suggestionsLabel","suggestionsList","getSuggestions","suggestion","suggestionButton","tagType","hashtag","custom","hasContent","Tag","tagPopup","tagContent","insertTagAtCurrentPosition","displayText","tagElement","showTagPopup","getTagElement","tagButton","TextSize","sizeMap","getSizeMap","sizeData","applyTextSizeToCurrentSelection","getSizeDisplayName","currentSize","getCurrentSize","textSizeButton","getCurrentSizeStatic","sizeToCss","sizeFormat","normalize","newFont","font1","font2","font3","zwsp","fontEl","showSizePicker","val","queryCommandValue","queryFormatValue","normalizeCssSizeToExecSize","px","steps","closestIndex","minDiff","diff","abs","ImportPopup","onImport","selectedFile","fileType","createImportPopup","typeContainer","updateFileInput","fileInfo","importButton","processImport","selectedType","acceptTypes","getAcceptTypes","updateImportButton","excel","pdf","word","setSelectedFile","formatFileSize","bytes","floor","log","pow","readAsText","csvContent","parseCSV","Import","importPopup","insertImportedContent","contentElement","processHtmlContent","processExcelContent","processTextContent","htmlContent","cleanHtmlContent","thead","headerRow","cellData","th","td","allowedTags","allowedAttrs","elementsToRemove","showImportPopup","getSupportedTypes","extensions","mimeTypes","createCustomButton","leadIcon","textSpan","dropdownIcon","setProperty","fontWeight","cursor","flex","newText","Toolbar","group","toolbar2Visible","preloadIcons","createToolbarContainer","flowGroups","groupContainer","addButton","addMoreButton","moreBtn","ResizeObserver","_ro","_scheduleReflow","observe","reflow","_setupKeyboardNav","_focusableButtons","offsetParent","_updateRoving","tabIndex","btns","cur","_reflowQueued","g","matchMedia","matches","_syncMoreButton","cs","avail","clientWidth","paddingRight","total","budget","used","cut","w","createToolbar","toolbarItems","customButtons","config","iconButtons","toggleToolbar2","isDisabled","setButtonTitle","isToolbar2Visible","callbacks","disconnect","History","delay","maxStack","userOnly","stack","lastSave","saveState","_onInput","handleInput","_onKeydownSave","_onToolbarClick","_onUndoRedo","setupMutationObserver","mutationObserver","MutationObserver","mutations","shouldSave","mutation","mutationTimeout","childList","subtree","attributeFilter","timestamp","shift","state","restoreState","onHistoryChange","getOffsetInEditor","selectionState","getNodeAtOffset","endNode","totalOffset","targetOffset","currentOffset","nodeLength","getState","stackLength","currentIndex","action","CustomEvent","detail","dispatchEvent","forceSave","originalLastSave","BlockToolbar","showOnSelection","showOnEnter","blockToolbar","currentSelection","currentCursorPosition","originalTags","createBlockToolbar","meta","handleCommand","arrow","_onEditorMouseup","handleSelectionChange","_onEditorKeydown","showAtCursorAfterEnter","_onDocMousedown","_onWindowScroll","updateToolbarPosition","_onEditorScroll","_onEditorKeyup","showAtSelection","editorRect","scrollTop","pageYOffset","documentElement","scrollLeft","pageXOffset","showAt","ensureCursorAtEndOfLine","getCursorPositionAfterEnter","textLength","lastTextNode","showAtCursor","ensureToolbarInViewport","editorArea","toolbarRect2","arrowLeft","arrowDirection","borderTop","borderBottom","borderLeft","borderRight","updateToolbarAt","selectInstance","updateButtonState","originalTag","formatClass","TableToolbar","fadeDelay","tableToolbar","currentTable","currentCell","hideTimeout","createTableToolbar","buttonGroups","groupDiv","_onEditorClick","clickedCell","clickedTable","showAtTable","tableCell","clearHideTimeout","showTableProfile","deleteTable","insertRowAbove","insertRowBelow","deleteRow","insertColumnRight","insertColumnLeft","deleteColumn","currentRow","newRow","createNewRow","checkAndUpdateHandles","cellIndex","newCell","createNewCell","targetCell","tableInfo","cellCount","CodeView","isCodeView","originalContent","codeTextarea","disabledModules","toggleCodeView","showNormalView","showCodeView","updateToolbarButton","formatHTML","disableOtherFeatures","updateOriginalContent","updatedHTML","enableOtherFeatures","disableEditorEvents","disableOtherModules","hideAllPopups","enableEditorEvents","enableOtherModules","disable","enable","formatted","formattedLines","tagStack","closeTagMatch","currentLevel","openTagMatch","buttonTitle","FindReplace","hits","activeIndex","caseSensitive","isOpen","buildPanel","bindEvents","panel","mkInput","cls","mkBtn","svg","findRow","replaceRow","findInput","replaceInput","countEl","prevBtn","nextBtn","caseBtn","closeBtn","replaceBtn","replaceAllBtn","append","_onKeydown","open","runSearch","navigate","replaceCurrent","replaceAll","selText","select","clearHighlights","escapeRegex","term","updateCount","regex","RegExp","textNodes","ranges","lastIndex","exec","mark","surroundContents","highlightActive","scroll","scrollIntoView","at","repl","SlashMenu","query","filtered","commands","buildCommands","buildMenu","hint","run","menu","move","choose","_onDocPointer","slashNode","slashStart","render","mh","delta","del","Mention","sources","_buildSources","renderItem","source","trigger","triggers","_loadFor","_t","avatar_url","space","after","ResizeHandles","maintainAspectRatio","snapToGrid","gridSize","handles","isResizing","startX","startY","startWidth","startHeight","currentHandle","aspectRatio","createHandles","handlesContainer","handle","createHandle","borderRadius","boxShadow","handleMouseDown","handleElementClick","_onDocClick","isClickOnResizableElement","isClickOnHandle","_onDocMousemove","handleMouseMove","_onDocMouseup","handleMouseUp","updateHandlePosition","resizableElement","findResizableElement","showHandles","isResizableElement","isImage","isVideo","isTable","lastTableWidth","lastTableHeight","setupTableSizeMonitoring","tableSizeInterval","clearInterval","elementRect","handleName","startLeft","startTop","userSelect","deltaX","deltaY","newWidth","newHeight","maxW","area","ratioByWidth","ratioByHeight","applyDimensions","cellWidth","cellHeight","getActiveElement","setActiveElement","checkTableSizeChange","refreshHandles","setInterval","currentWidth","previousWidth","previousHeight","shouldUpdate","removedNodes","attributeName","characterData","characterDataOldValue","setMaintainAspectRatio","maintain","setConstraints","ImagePopup","onImageInsert","selectedImageSrc","resizeHandler","createImagePopup","isValidImageUrl","insertImage","hasImage","imageSrc","imagePreview","imageExtensions","imageHosts","hasImageExtension","isFromImageHost","setupResizeHandler","removeResizeHandler","loadStyles","loaded","styleElement","loadFallbackStyles","unloadStyles","isLoaded","reloadStyles","addCustomCSS","css","existing","getElementById","RichEditor","fromTextarea","textarea","ta","mount","initial","sync","Event","bubbles","createEditor"],"mappings":"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"}
1
+ {"version":3,"file":"rich-editor.esm.js","sources":["../lib/core/registry.js","../lib/core/module.js","../lib/utils/exec-command.js","../lib/utils/sanitize.js","../lib/serialize.js","../lib/ui/icons.js","../lib/core/editor.js","../lib/core/format.js","../lib/static.js","../lib/utils/history-helper.js","../lib/formats/bold.js","../lib/formats/italic.js","../lib/formats/underline.js","../lib/formats/strike.js","../lib/formats/subscript.js","../lib/formats/superscript.js","../lib/utils/popup-helper.js","../lib/ui/color-picker.js","../lib/formats/color.js","../lib/formats/background.js","../lib/ui/link-popup.js","../lib/formats/link.js","../lib/ui/table-popup.js","../lib/formats/table.js","../lib/ui/customselect.js","../lib/formats/heading.js","../lib/formats/font-family.js","../lib/formats/line-height.js","../lib/formats/capitalization.js","../lib/ui/text-align-picker.js","../lib/formats/text-align.js","../lib/ui/list-picker.js","../lib/formats/list.js","../lib/formats/indent.js","../lib/ui/emoji-picker.js","../lib/formats/emoji.js","../lib/formats/image.js","../lib/ui/video-popup.js","../lib/formats/video.js","../lib/ui/tag-popup.js","../lib/formats/tag.js","../lib/formats/text-size.js","../lib/ui/import-popup.js","../lib/formats/import.js","../lib/ui/select-button.js","../lib/modules/toolbar.js","../lib/modules/history.js","../lib/modules/block-toolbar.js","../lib/modules/table-toolbar.js","../lib/modules/code-view.js","../lib/modules/find-replace.js","../lib/modules/slash-menu.js","../lib/modules/mention.js","../lib/modules/resize-handles.js","../lib/ui/image-popup.js","../index.js","../lib/styles-loader.js","../lib/styles.css.js"],"sourcesContent":["/**\n * Registry system - Inspired by Quill's registration system\n * Manages registration and retrieval of modules, formats, themes, and UI components\n */\nclass Registry {\n constructor() {\n this.modules = new Map();\n this.formats = new Map();\n this.themes = new Map();\n this.ui = new Map();\n }\n\n /**\n * Register a module, format, theme, or UI component\n * @param {string|object} path - Registration path or object with multiple registrations\n * @param {*} def - Definition to register\n * @param {boolean} suppressWarning - Suppress overwrite warnings\n */\n register(path, def, suppressWarning = false) {\n if (typeof path === 'object') {\n // Bulk registration\n Object.entries(path).forEach(([key, value]) => {\n this.register(key, value, suppressWarning);\n });\n return;\n }\n\n const [type, name] = path.split('/');\n \n // Only warn when genuinely replacing one definition with a DIFFERENT one.\n // Re-registering the same class (common when several editors share the\n // singleton registry) is a no-op, not a mistake.\n if (!suppressWarning) {\n const existing = this.get(path);\n if (existing && existing !== def) {\n console.warn(`Overwriting ${path}`);\n }\n }\n\n switch (type) {\n case 'modules':\n this.modules.set(name, def);\n break;\n case 'formats':\n this.formats.set(name, def);\n break;\n case 'themes':\n this.themes.set(name, def);\n break;\n case 'ui':\n this.ui.set(name, def);\n break;\n default:\n console.warn(`Unknown registry type: ${type}`);\n }\n }\n\n /**\n * Get a registered item\n * @param {string} path - Registration path\n * @returns {*}\n */\n get(path) {\n const [type, name] = path.split('/');\n \n switch (type) {\n case 'modules':\n return this.modules.get(name);\n case 'formats':\n return this.formats.get(name);\n case 'themes':\n return this.themes.get(name);\n case 'ui':\n return this.ui.get(name);\n default:\n return null;\n }\n }\n\n /**\n * Check if an item is registered\n * @param {string} path - Registration path\n * @returns {boolean}\n */\n has(path) {\n return this.get(path) !== null && this.get(path) !== undefined;\n }\n\n /**\n * Get all registered items of a type\n * @param {string} type - Type to get (modules, formats, themes, ui)\n * @returns {Map}\n */\n getAll(type) {\n switch (type) {\n case 'modules':\n return new Map(this.modules);\n case 'formats':\n return new Map(this.formats);\n case 'themes':\n return new Map(this.themes);\n case 'ui':\n return new Map(this.ui);\n default:\n return new Map();\n }\n }\n\n /**\n * Unregister an item\n * @param {string} path - Registration path\n */\n unregister(path) {\n const [type, name] = path.split('/');\n \n switch (type) {\n case 'modules':\n this.modules.delete(name);\n break;\n case 'formats':\n this.formats.delete(name);\n break;\n case 'themes':\n this.themes.delete(name);\n break;\n case 'ui':\n this.ui.delete(name);\n break;\n }\n }\n\n /**\n * Clear all registrations\n */\n clear() {\n this.modules.clear();\n this.formats.clear();\n this.themes.clear();\n this.ui.clear();\n }\n\n /**\n * Get all registered items for debugging\n */\n getAllItems() {\n const items = {};\n items.modules = Array.from(this.modules.keys());\n items.formats = Array.from(this.formats.keys());\n items.themes = Array.from(this.themes.keys());\n items.ui = Array.from(this.ui.keys());\n return items;\n }\n}\n\n// Create singleton instance\nconst registry = new Registry();\n\nexport default registry; ","/**\n * Base Module class - Inspired by Quill's architecture\n * All editor modules should extend this class\n */\nexport default class Module {\n static DEFAULTS = {};\n\n constructor(editor, options = {}) {\n this.editor = editor;\n this.options = { ...this.constructor.DEFAULTS, ...options };\n this.events = new Map();\n }\n\n /**\n * Add event listener\n * @param {string} event - Event name\n * @param {function} handler - Event handler\n */\n on(event, handler) {\n if (!this.events.has(event)) {\n this.events.set(event, []);\n }\n this.events.get(event).push(handler);\n }\n\n /**\n * Remove event listener\n * @param {string} event - Event name\n * @param {function} handler - Event handler\n */\n off(event, handler) {\n if (this.events.has(event)) {\n const handlers = this.events.get(event);\n const index = handlers.indexOf(handler);\n if (index > -1) {\n handlers.splice(index, 1);\n }\n }\n }\n\n /**\n * Emit event\n * @param {string} event - Event name\n * @param {*} data - Event data\n */\n emit(event, data) {\n if (this.events.has(event)) {\n this.events.get(event).forEach(handler => {\n try {\n handler(data);\n } catch (error) {\n console.error(`Error in event handler for ${event}:`, error);\n }\n });\n }\n }\n\n /**\n * Called when module is being destroyed\n * Override this method to cleanup resources\n */\n destroy() {\n this.events.clear();\n }\n\n /**\n * Called when editor content changes\n * Override this method to respond to content changes\n */\n onContentChange() {\n // Override in subclasses\n }\n\n /**\n * Called when selection changes\n * Override this method to respond to selection changes\n */\n onSelectionChange(range) {\n // Override in subclasses\n }\n} ","/**\n * execCommand wrapper — single migration point for the deprecated\n * document.execCommand / queryCommand* family.\n *\n * document.execCommand is deprecated. It still works in every current browser\n * and remains the most reliable way to toggle inline formatting across complex\n * selections, so we keep using it for now — but ONLY through this module.\n * Centralizing it here means:\n * - consistent try/catch (these APIs throw in detached/edge cases),\n * - one place to add feature detection or a fallback,\n * - one place to perform a future Range-API migration without touching every\n * format file.\n *\n * Prefer these helpers over calling document.execCommand directly.\n */\n\n/**\n * Execute a formatting command.\n * @param {string} command - execCommand command name (e.g. 'bold', 'foreColor')\n * @param {string|null} [value] - command value, when applicable\n * @returns {boolean} true if the command ran without throwing\n */\nexport function execFormat(command, value = null) {\n try {\n // Omit the value argument when none is given. Passing null explicitly makes\n // some commands stringify it (e.g. insertHorizontalRule would set id=\"null\").\n return value == null\n ? document.execCommand(command, false)\n : document.execCommand(command, false, value);\n } catch (e) {\n console.warn(`execCommand('${command}') failed:`, e);\n return false;\n }\n}\n\n/**\n * Enable/disable styleWithCSS (so commands emit inline styles instead of\n * deprecated presentational tags like <font>). Safe to call before each\n * styling command.\n * @param {boolean} [enabled=true]\n */\nexport function setStyleWithCSS(enabled = true) {\n return execFormat('styleWithCSS', enabled);\n}\n\n/**\n * Query whether a command is currently active for the selection.\n * @param {string} command\n * @returns {boolean}\n */\nexport function queryFormatState(command) {\n try {\n return document.queryCommandState(command);\n } catch (e) {\n return false;\n }\n}\n\n/**\n * Query the current value of a command for the selection.\n * @param {string} command\n * @returns {string} empty string if unsupported/unavailable\n */\nexport function queryFormatValue(command) {\n try {\n return document.queryCommandValue(command) || '';\n } catch (e) {\n return '';\n }\n}\n\nexport default { execFormat, setStyleWithCSS, queryFormatState, queryFormatValue };\n","/**\n * Sanitize utilities - dependency-free XSS protection helpers.\n *\n * These functions provide a defense-in-depth layer for the few places where\n * the editor turns user-supplied strings into live DOM (links, images, video,\n * HTML import, code view). They are intentionally conservative: anything that\n * is not provably safe is rejected.\n */\n\n// URL schemes that are safe to use as navigable links / resource sources.\nconst SAFE_URL_SCHEMES = ['http:', 'https:', 'mailto:', 'tel:', 'ftp:'];\n\n// Trusted iframe embed prefixes (used by the video feature).\nconst TRUSTED_IFRAME_PREFIXES = [\n 'https://www.youtube.com/embed/',\n 'https://www.youtube-nocookie.com/embed/',\n 'https://player.vimeo.com/video/'\n];\n\n/**\n * Determine whether a URL is safe to assign to href/src.\n *\n * Relative URLs, anchors and path-only references (no scheme) are considered\n * safe. URLs with an explicit scheme are only allowed if the scheme is in the\n * whitelist. `javascript:`, `vbscript:`, `data:text/html`, etc. are rejected.\n *\n * @param {string} url - URL to validate\n * @param {object} [options]\n * @param {boolean} [options.allowDataImage] - allow `data:image/*` (except SVG)\n * @returns {boolean}\n */\nexport function isSafeUrl(url, { allowDataImage = false } = {}) {\n if (typeof url !== 'string') return false;\n\n const trimmed = url.trim();\n if (trimmed === '') return false;\n\n // Strip control/whitespace characters that are commonly used to smuggle\n // schemes past naive validators (e.g. \"java\\tscript:alert(1)\").\n const stripped = trimmed.replace(/[\\u0000-\\u0020\\u007F-\\u009F]/g, '');\n\n // Detect a leading scheme. If there is none it is a relative URL → safe.\n const schemeMatch = stripped.match(/^([a-z][a-z0-9+.-]*):/i);\n if (!schemeMatch) {\n return true;\n }\n\n const scheme = schemeMatch[1].toLowerCase() + ':';\n\n if (scheme === 'data:') {\n if (!allowDataImage) return false;\n // Allow raster image data URIs only. SVG can carry script, so it is denied.\n return /^data:image\\//i.test(stripped) && !/^data:image\\/svg/i.test(stripped);\n }\n\n return SAFE_URL_SCHEMES.includes(scheme);\n}\n\n/**\n * Return the URL if it is safe, otherwise an empty string.\n * @param {string} url\n * @param {object} [options] - same options as isSafeUrl\n * @returns {string}\n */\nexport function sanitizeUrl(url, options) {\n return isSafeUrl(url, options) ? url.trim() : '';\n}\n\n// Tags that are never allowed in sanitized HTML.\nconst FORBIDDEN_TAGS = new Set([\n 'SCRIPT', 'STYLE', 'OBJECT', 'EMBED', 'LINK', 'META', 'BASE',\n 'FORM', 'INPUT', 'BUTTON', 'TEXTAREA', 'SELECT', 'OPTION', 'NOSCRIPT'\n]);\n\n/**\n * Clean a single element node in place: drop event-handler attributes,\n * unsafe href/src URLs and dangerous inline styles.\n * @param {Element} el\n */\nfunction cleanElement(el) {\n const attrs = Array.from(el.attributes);\n for (const attr of attrs) {\n const name = attr.name.toLowerCase();\n const value = attr.value;\n\n // Strip all inline event handlers (onclick, onerror, onload, ...).\n if (name.startsWith('on')) {\n el.removeAttribute(attr.name);\n continue;\n }\n\n // Validate URL-bearing attributes.\n if (name === 'href' || name === 'src' || name === 'xlink:href') {\n const allowDataImage = el.tagName === 'IMG';\n if (!isSafeUrl(value, { allowDataImage })) {\n el.removeAttribute(attr.name);\n }\n continue;\n }\n\n // Reject styles that can execute script (legacy IE expression / url(javascript:)).\n if (name === 'style' && /expression\\s*\\(|javascript:/i.test(value)) {\n el.removeAttribute(attr.name);\n }\n }\n}\n\n/**\n * Sanitize an HTML string and return safe HTML.\n *\n * Parsing is done with DOMParser so that no scripts execute and no network\n * resources load during sanitization (the parsed document is inert).\n *\n * @param {string} html - untrusted HTML\n * @returns {string} sanitized HTML\n */\nexport function sanitizeHtml(html) {\n if (typeof html !== 'string' || html === '') return '';\n\n const doc = new DOMParser().parseFromString(html, 'text/html');\n const elements = Array.from(doc.body.querySelectorAll('*'));\n\n for (const el of elements) {\n const tag = el.tagName;\n\n if (FORBIDDEN_TAGS.has(tag)) {\n el.remove();\n continue;\n }\n\n // Iframes are only allowed when pointing at a trusted embed host.\n if (tag === 'IFRAME') {\n const src = el.getAttribute('src') || '';\n const trusted = TRUSTED_IFRAME_PREFIXES.some(prefix => src.startsWith(prefix));\n if (!trusted) {\n el.remove();\n continue;\n }\n }\n\n cleanElement(el);\n }\n\n return doc.body.innerHTML;\n}\n\n/**\n * Sanitize an already-parsed DOM subtree in place (event handlers + unsafe URLs).\n * Use when content is built via the DOM rather than from an HTML string.\n * @param {Element} root\n */\nexport function sanitizeNode(root) {\n if (!root) return;\n const elements = Array.from(root.querySelectorAll('*'));\n for (const el of elements) {\n if (FORBIDDEN_TAGS.has(el.tagName)) {\n el.remove();\n continue;\n }\n cleanElement(el);\n }\n}\n\nexport default { isSafeUrl, sanitizeUrl, sanitizeHtml, sanitizeNode };\n","/**\n * Serialization for yjd content — HTML <-> Markdown and HTML <-> JSON.\n *\n * Targeted at the HTML yjd emits (headings, inline marks, lists, links,\n * images, blockquote, code, tables, hr, and mention tokens). Browser-only\n * (uses the DOM). Zero dependencies.\n *\n * import { htmlToMarkdown, markdownToHtml, domToJson, jsonToHtml } from '.../serialize.js'\n */\n\n/* ============================ HTML -> Markdown ============================ */\n\nexport function htmlToMarkdown(html) {\n const root = document.createElement('div');\n root.innerHTML = html || '';\n return blocksToMd(root, 0).replace(/\\n{3,}/g, '\\n\\n').trim() + '\\n';\n}\n\nfunction blocksToMd(parent, depth) {\n let out = '';\n parent.childNodes.forEach((node) => { out += nodeBlock(node, depth); });\n return out;\n}\n\nfunction nodeBlock(node, depth) {\n if (node.nodeType === 3) {\n const t = node.textContent.replace(/\\s+/g, ' ');\n return t.trim() ? t + '\\n\\n' : '';\n }\n if (node.nodeType !== 1) return '';\n const tag = node.tagName;\n switch (tag) {\n case 'H1': case 'H2': case 'H3': case 'H4': case 'H5': case 'H6':\n return '#'.repeat(+tag[1]) + ' ' + inline(node) + '\\n\\n';\n case 'P': case 'DIV': {\n const c = inline(node);\n return c.trim() ? c + '\\n\\n' : '';\n }\n case 'BLOCKQUOTE':\n return inline(node).split('\\n').map((l) => '> ' + l).join('\\n') + '\\n\\n';\n case 'PRE':\n return '```\\n' + node.textContent.replace(/\\n$/, '') + '\\n```\\n\\n';\n case 'UL': return listToMd(node, depth, false) + '\\n';\n case 'OL': return listToMd(node, depth, true) + '\\n';\n case 'HR': return '---\\n\\n';\n case 'TABLE': return tableToMd(node) + '\\n';\n case 'FIGURE': return blocksToMd(node, depth);\n case 'IMG': return imgToMd(node) + '\\n\\n';\n case 'BR': return '\\n';\n default:\n return inline(node) + '\\n\\n';\n }\n}\n\nfunction listToMd(node, depth, ordered) {\n let out = '', i = 1;\n node.childNodes.forEach((li) => {\n if (li.nodeType !== 1 || li.tagName !== 'LI') return;\n const marker = ordered ? (i++) + '. ' : '- ';\n const pad = ' '.repeat(depth);\n let text = '', nested = '';\n li.childNodes.forEach((ch) => {\n if (ch.nodeType === 1 && (ch.tagName === 'UL' || ch.tagName === 'OL')) {\n nested += listToMd(ch, depth + 1, ch.tagName === 'OL');\n } else {\n text += inlineNode(ch);\n }\n });\n out += pad + marker + text.trim() + '\\n' + nested;\n });\n return out;\n}\n\nfunction tableToMd(node) {\n const rows = [...node.querySelectorAll('tr')];\n if (!rows.length) return '';\n const cells = (r) => [...r.children].map((c) => inline(c).replace(/\\|/g, '\\\\|').trim());\n const head = cells(rows[0]);\n let out = '| ' + head.join(' | ') + ' |\\n| ' + head.map(() => '---').join(' | ') + ' |\\n';\n rows.slice(1).forEach((r) => { out += '| ' + cells(r).join(' | ') + ' |\\n'; });\n return out;\n}\n\nfunction imgToMd(node) {\n return `![${node.getAttribute('alt') || ''}](${node.getAttribute('src') || ''})`;\n}\n\nfunction inline(node) {\n let out = '';\n node.childNodes.forEach((ch) => { out += inlineNode(ch); });\n return out;\n}\n\nfunction inlineNode(node) {\n if (node.nodeType === 3) return node.textContent;\n if (node.nodeType !== 1) return '';\n const tag = node.tagName;\n if (node.classList && node.classList.contains('mention')) {\n const id = node.getAttribute('data-id') || '';\n const name = (node.textContent || '').replace(/^[@#]/, '');\n const trig = (node.textContent || '@')[0];\n return `${trig}[${name}](${id})`;\n }\n if (node.classList && node.classList.contains('yjd-file-chip')) {\n const url = node.getAttribute('href') || '';\n const nameEl = node.querySelector ? node.querySelector('.yjd-file-name') : null;\n const name = node.getAttribute('data-name') || (nameEl && nameEl.textContent) || 'file';\n const size = node.getAttribute('data-size') || '';\n return `[${size ? `${name} (${size})` : name}](${url})`;\n }\n switch (tag) {\n case 'B': case 'STRONG': return '**' + inline(node) + '**';\n case 'I': case 'EM': return '*' + inline(node) + '*';\n case 'S': case 'STRIKE': case 'DEL': return '~~' + inline(node) + '~~';\n case 'U': return '<u>' + inline(node) + '</u>';\n case 'CODE': return '`' + node.textContent + '`';\n case 'A': return '[' + inline(node) + '](' + (node.getAttribute('href') || '') + ')';\n case 'IMG': return imgToMd(node);\n case 'BR': return ' \\n';\n default: return inline(node); // spans (colour/font) → keep text only\n }\n}\n\n/* ============================ Markdown -> HTML ============================ */\n\nexport function markdownToHtml(md) {\n const lines = (md || '').replace(/\\r\\n/g, '\\n').split('\\n');\n let html = '', i = 0;\n const isList = (l) => /^\\s*([-*+]|\\d+\\.)\\s+/.test(l);\n while (i < lines.length) {\n const line = lines[i];\n if (/^\\s*$/.test(line)) { i++; continue; }\n if (/^---+$/.test(line.trim())) { html += '<hr>'; i++; continue; }\n const h = line.match(/^(#{1,6})\\s+(.*)$/);\n if (h) { html += `<h${h[1].length}>${inlineMd(h[2])}</h${h[1].length}>`; i++; continue; }\n if (/^```/.test(line)) {\n i++; let code = '';\n while (i < lines.length && !/^```/.test(lines[i])) { code += lines[i] + '\\n'; i++; }\n i++; html += '<pre>' + escapeHtml(code.replace(/\\n$/, '')) + '</pre>'; continue;\n }\n if (/^>\\s?/.test(line)) {\n const q = [];\n while (i < lines.length && /^>\\s?/.test(lines[i])) { q.push(lines[i].replace(/^>\\s?/, '')); i++; }\n html += '<blockquote>' + inlineMd(q.join(' ')) + '</blockquote>'; continue;\n }\n if (/^\\|.*\\|\\s*$/.test(line) && i + 1 < lines.length && /^\\|[\\s:|-]+\\|\\s*$/.test(lines[i + 1])) {\n const r = parseTable(lines, i); html += r.html; i = r.next; continue;\n }\n if (isList(line)) { const r = parseList(lines, i, 0); html += r.html; i = r.next; continue; }\n const para = [line]; i++;\n while (i < lines.length && !/^\\s*$/.test(lines[i]) && !/^(#{1,6}\\s|>|```)/.test(lines[i]) &&\n !/^---+$/.test(lines[i].trim()) && !isList(lines[i])) { para.push(lines[i]); i++; }\n html += '<p>' + inlineMd(para.join('\\n').trim()) + '</p>';\n }\n return html;\n}\n\nfunction indentOf(l) { return (l.match(/^(\\s*)/)[1] || '').length; }\n\nfunction parseList(lines, start, baseIndent) {\n const ordered = /^\\s*\\d+\\./.test(lines[start]);\n let i = start, html = '<' + (ordered ? 'ol' : 'ul') + '>';\n while (i < lines.length) {\n const l = lines[i];\n if (/^\\s*$/.test(l)) { i++; continue; }\n const ind = indentOf(l);\n const m = l.match(/^\\s*([-*+]|\\d+\\.)\\s+(.*)$/);\n if (!m || ind < baseIndent) break;\n if (ind > baseIndent) { // nested list belongs to previous <li>\n const r = parseList(lines, i, ind);\n html = html.replace(/<\\/li>$/, r.html + '</li>');\n i = r.next; continue;\n }\n html += '<li>' + inlineMd(m[2]) + '</li>';\n i++;\n }\n return { html: html + '</' + (ordered ? 'ol' : 'ul') + '>', next: i };\n}\n\nfunction parseTable(lines, start) {\n const row = (l) => l.trim().replace(/^\\||\\|$/g, '').split('|').map((c) => c.trim());\n const head = row(lines[start]);\n let i = start + 2, body = '';\n while (i < lines.length && /^\\|.*\\|\\s*$/.test(lines[i])) {\n body += '<tr>' + row(lines[i]).map((c) => `<td>${inlineMd(c)}</td>`).join('') + '</tr>';\n i++;\n }\n const thead = '<tr>' + head.map((c) => `<td><b>${inlineMd(c)}</b></td>`).join('') + '</tr>';\n return { html: `<table class=\"rich-editor-table\"><tbody>${thead}${body}</tbody></table>`, next: i };\n}\n\nfunction inlineMd(s) {\n // images, mentions, links, then marks. Order matters.\n return s\n .replace(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/g, (_, a, src) => `<img class=\"inserted-image\" src=\"${attr(src)}\" alt=\"${attr(a)}\" style=\"max-width:100%;height:auto\">`)\n .replace(/([@#])\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_, t, name, id) => `<span class=\"mention\" data-id=\"${attr(id)}\">${t}${escapeHtml(name)}</span>`)\n .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_, t, href) => `<a href=\"${attr(href)}\" target=\"_blank\" rel=\"noopener noreferrer\">${escapeHtml(t)}</a>`)\n .replace(/\\*\\*([^*]+)\\*\\*/g, '<b>$1</b>')\n .replace(/(^|[^*])\\*([^*]+)\\*/g, '$1<i>$2</i>')\n .replace(/~~([^~]+)~~/g, '<s>$1</s>')\n .replace(/`([^`]+)`/g, (_, c) => '<code>' + escapeHtml(c) + '</code>')\n .replace(/\\n/g, '<br>');\n}\n\nfunction escapeHtml(s) { return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }\nfunction attr(s) { return String(s).replace(/\"/g, '&quot;').replace(/</g, '&lt;'); }\n\n/* ============================== HTML <-> JSON ============================= */\n\nexport function domToJson(html) {\n const root = document.createElement('div');\n root.innerHTML = html || '';\n return { type: 'doc', content: [...root.childNodes].map(nodeToJson).filter(Boolean) };\n}\n\nfunction nodeToJson(node) {\n if (node.nodeType === 3) {\n const text = node.textContent;\n return text ? { text } : null;\n }\n if (node.nodeType !== 1) return null;\n const obj = { tag: node.tagName.toLowerCase() };\n if (node.attributes.length) {\n obj.attrs = {};\n for (const a of node.attributes) obj.attrs[a.name] = a.value;\n }\n const kids = [...node.childNodes].map(nodeToJson).filter(Boolean);\n if (kids.length) obj.content = kids;\n return obj;\n}\n\nexport function jsonToHtml(json) {\n const nodes = json && json.content ? json.content : (Array.isArray(json) ? json : []);\n return nodes.map(jsonNodeToHtml).join('');\n}\n\nfunction jsonNodeToHtml(n) {\n if (n == null) return '';\n if (n.text != null) return escapeHtml(n.text);\n if (!n.tag) return '';\n const attrs = n.attrs\n ? Object.entries(n.attrs).map(([k, v]) => ` ${k}=\"${attr(v)}\"`).join('')\n : '';\n const inner = (n.content || []).map(jsonNodeToHtml).join('');\n const VOID = new Set(['img', 'hr', 'br', 'input']);\n if (VOID.has(n.tag)) return `<${n.tag}${attrs}>`;\n return `<${n.tag}${attrs}>${inner}</${n.tag}>`;\n}\n","/**\n * Inline Icons — a single, cohesive outline icon set (Lucide-style).\n * Every icon is a 24×24, stroke-based glyph using `currentColor`, so they all\n * share one visual weight and follow the button's text/accent colour.\n */\nconst S = (body) =>\n `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${body}</svg>`;\n\nexport const Icons = {\n // --- Text formatting ---\n bold: S('<path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/><path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/>'),\n italic: S('<line x1=\"19\" x2=\"10\" y1=\"4\" y2=\"4\"/><line x1=\"14\" x2=\"5\" y1=\"20\" y2=\"20\"/><line x1=\"15\" x2=\"9\" y1=\"4\" y2=\"20\"/>'),\n underline: S('<path d=\"M6 4v6a6 6 0 0 0 12 0V4\"/><line x1=\"4\" x2=\"20\" y1=\"20\" y2=\"20\"/>'),\n strike: S('<path d=\"M16 4H9a3 3 0 0 0-2.83 4\"/><path d=\"M14 12a4 4 0 0 1 0 8H6\"/><line x1=\"4\" x2=\"20\" y1=\"12\" y2=\"12\"/>'),\n subscript: S('<path d=\"m4 5 8 8\"/><path d=\"m12 5-8 8\"/><path d=\"M20 19h-4c0-1.5.44-2 1.5-2.5S20 15.33 20 14c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07\"/>'),\n superscript: S('<path d=\"m4 19 8-8\"/><path d=\"m12 19-8-8\"/><path d=\"M20 12h-4c0-1.5.44-2 1.5-2.5S20 8.33 20 7c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07\"/>'),\n\n // --- Alignment ---\n 'align-left': S('<line x1=\"21\" x2=\"3\" y1=\"6\" y2=\"6\"/><line x1=\"15\" x2=\"3\" y1=\"12\" y2=\"12\"/><line x1=\"17\" x2=\"3\" y1=\"18\" y2=\"18\"/>'),\n 'align-center': S('<line x1=\"21\" x2=\"3\" y1=\"6\" y2=\"6\"/><line x1=\"17\" x2=\"7\" y1=\"12\" y2=\"12\"/><line x1=\"19\" x2=\"5\" y1=\"18\" y2=\"18\"/>'),\n 'align-right': S('<line x1=\"21\" x2=\"3\" y1=\"6\" y2=\"6\"/><line x1=\"21\" x2=\"9\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"7\" y1=\"18\" y2=\"18\"/>'),\n 'align-justify': S('<line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"/>'),\n\n // --- Lists ---\n 'list-bullet': S('<line x1=\"8\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"8\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"21\" y1=\"18\" y2=\"18\"/><circle cx=\"3.5\" cy=\"6\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"3.5\" cy=\"12\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"3.5\" cy=\"18\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/>'),\n 'list-ordered': S('<line x1=\"10\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"10\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"10\" x2=\"21\" y1=\"18\" y2=\"18\"/><path d=\"M4 6h1v4\"/><path d=\"M4 10h2\"/><path d=\"M6 18H4c0-1 2-2 2-3s-1-1.5-2-1\"/>'),\n 'list-alpha': S('<line x1=\"10\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"10\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"10\" x2=\"21\" y1=\"18\" y2=\"18\"/><path d=\"M4 10V8a1 1 0 0 1 2 0v2\"/><path d=\"M4 9h2\"/><path d=\"M4 14h1.5a1 1 0 0 1 0 2H4l2-2\"/>'),\n 'list-roman': S('<line x1=\"10\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"10\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"10\" x2=\"21\" y1=\"18\" y2=\"18\"/><path d=\"M5 7v3\"/><path d=\"M4 14h2l-1 4\"/>'),\n list: S('<line x1=\"8\" x2=\"21\" y1=\"6\" y2=\"6\"/><line x1=\"8\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"21\" y1=\"18\" y2=\"18\"/><circle cx=\"3.5\" cy=\"6\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"3.5\" cy=\"12\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"3.5\" cy=\"18\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/>'),\n\n // --- Indentation ---\n 'indent-increase': S('<polyline points=\"3 8 7 12 3 16\"/><line x1=\"21\" x2=\"11\" y1=\"6\" y2=\"6\"/><line x1=\"21\" x2=\"11\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"11\" y1=\"18\" y2=\"18\"/>'),\n 'indent-decrease': S('<polyline points=\"7 8 3 12 7 16\"/><line x1=\"21\" x2=\"11\" y1=\"6\" y2=\"6\"/><line x1=\"21\" x2=\"11\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"11\" y1=\"18\" y2=\"18\"/>'),\n\n // --- Media ---\n image: S('<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.1-3.1a2 2 0 0 0-2.8 0L6 21\"/>'),\n video: S('<path d=\"m22 8-6 4 6 4V8Z\"/><rect width=\"14\" height=\"12\" x=\"2\" y=\"6\" rx=\"2\" ry=\"2\"/>'),\n file: S('<path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\"/>'),\n\n // --- Table ---\n table: S('<path d=\"M12 3v18\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/>'),\n 'table-profile': S('<path d=\"M15 3v18\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M21 9H3\"/><path d=\"M21 15H3\"/>'),\n 'add-row-above': S('<rect x=\"3\" y=\"13\" width=\"18\" height=\"8\" rx=\"2\"/><line x1=\"12\" x2=\"12\" y1=\"3\" y2=\"9\"/><line x1=\"9\" x2=\"15\" y1=\"6\" y2=\"6\"/>'),\n 'add-row-below': S('<rect x=\"3\" y=\"3\" width=\"18\" height=\"8\" rx=\"2\"/><line x1=\"12\" x2=\"12\" y1=\"15\" y2=\"21\"/><line x1=\"9\" x2=\"15\" y1=\"18\" y2=\"18\"/>'),\n 'add-col-left': S('<rect x=\"13\" y=\"3\" width=\"8\" height=\"18\" rx=\"2\"/><line x1=\"3\" x2=\"9\" y1=\"12\" y2=\"12\"/><line x1=\"6\" x2=\"6\" y1=\"9\" y2=\"15\"/>'),\n 'add-col-right': S('<rect x=\"3\" y=\"3\" width=\"8\" height=\"18\" rx=\"2\"/><line x1=\"15\" x2=\"21\" y1=\"12\" y2=\"12\"/><line x1=\"18\" x2=\"18\" y1=\"9\" y2=\"15\"/>'),\n 'delete-row': S('<rect x=\"3\" y=\"9\" width=\"18\" height=\"6\" rx=\"2\"/><line x1=\"9\" x2=\"15\" y1=\"12\" y2=\"12\"/>'),\n 'delete-col': S('<rect x=\"9\" y=\"3\" width=\"6\" height=\"18\" rx=\"2\"/><line x1=\"12\" x2=\"12\" y1=\"9\" y2=\"15\"/>'),\n 'delete-table': S('<path d=\"M3 6h18\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\"/><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\"/><line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\"/>'),\n\n // --- Colour ---\n color: S('<path d=\"M5.5 19 12 5l6.5 14\"/><path d=\"M8 14h8\"/>'),\n background: S('<path d=\"m9 11-6 6v3h9l3-3\"/><path d=\"m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4\"/>'),\n 'no-color': S('<circle cx=\"12\" cy=\"12\" r=\"9\"/><line x1=\"5.6\" x2=\"18.4\" y1=\"5.6\" y2=\"18.4\"/>'),\n 'custom-color': S('<path d=\"M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.93 0 1.65-.75 1.65-1.69 0-.43-.18-.83-.44-1.12-.29-.29-.44-.65-.44-1.13a1.64 1.64 0 0 1 1.67-1.67h2c3.05 0 5.55-2.5 5.55-5.55C22 6 17.5 2 12 2z\"/><circle cx=\"8.5\" cy=\"7.5\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"6.5\" cy=\"12.5\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"13.5\" cy=\"6.5\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"17.5\" cy=\"10.5\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/>'),\n\n // --- History ---\n undo: S('<path d=\"M9 14 4 9l5-5\"/><path d=\"M4 9h10.5a5.5 5.5 0 0 1 0 11H11\"/>'),\n redo: S('<path d=\"m15 14 5-5-5-5\"/><path d=\"M20 9H9.5a5.5 5.5 0 0 0 0 11H13\"/>'),\n\n // --- Insert ---\n link: S('<path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/>'),\n emoji: S('<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M8 14s1.5 2 4 2 4-2 4-2\"/><line x1=\"9\" x2=\"9.01\" y1=\"9\" y2=\"9\"/><line x1=\"15\" x2=\"15.01\" y1=\"9\" y2=\"9\"/>'),\n tag: S('<path d=\"M12.6 2.6A2 2 0 0 0 11.2 2H4a2 2 0 0 0-2 2v7.2a2 2 0 0 0 .6 1.4l8.7 8.7a2.4 2.4 0 0 0 3.4 0l6.6-6.6a2.4 2.4 0 0 0 0-3.4z\"/><circle cx=\"7.5\" cy=\"7.5\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/>'),\n import: S('<path d=\"M12 3v12\"/><path d=\"m8 11 4 4 4-4\"/><path d=\"M8 5H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-4\"/>'),\n code: S('<polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/>'),\n 'code-view': S('<path d=\"m18 16 4-4-4-4\"/><path d=\"m6 8-4 4 4 4\"/><path d=\"m14.5 4-5 16\"/>'),\n 'clear-format': S('<path d=\"M4 7V4h16v3\"/><path d=\"M5 20h6\"/><path d=\"M13 4 8 20\"/><path d=\"m15 15 5 5\"/><path d=\"m20 15-5 5\"/>'),\n 'horizontal-rule': S('<path d=\"M5 12h14\"/>'),\n find: S('<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>'),\n 'chevron-up': S('<path d=\"m18 15-6-6-6 6\"/>'),\n 'chevron-down': S('<path d=\"m6 9 6 6 6-6\"/>'),\n close: S('<path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/>'),\n 'text-direction': S('<path d=\"M8 3 4 7l4 4\"/><path d=\"M4 7h16\"/><path d=\"m16 21 4-4-4-4\"/><path d=\"M20 17H4\"/>'),\n\n // --- UI / utility ---\n check: S('<polyline points=\"20 6 9 17 4 12\"/>'),\n dropdown: S('<path d=\"m6 9 6 6 6-6\"/>'),\n more: S('<circle cx=\"12\" cy=\"12\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"19\" cy=\"12\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"5\" cy=\"12\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\"/>'),\n theme: S('<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/><path d=\"m4.9 4.9 1.4 1.4\"/><path d=\"m17.7 17.7 1.4 1.4\"/><path d=\"M2 12h2\"/><path d=\"M20 12h2\"/><path d=\"m6.3 17.7-1.4 1.4\"/><path d=\"m19.1 4.9-1.4 1.4\"/>'),\n\n // --- Typography (dropdown triggers; mostly shown as text) ---\n heading: S('<path d=\"M4 12h8\"/><path d=\"M4 18V6\"/><path d=\"M12 18V6\"/><path d=\"m17 12 3-2v8\"/>'),\n 'font-family': S('<polyline points=\"4 7 4 4 20 4 20 7\"/><line x1=\"9\" x2=\"15\" y1=\"20\" y2=\"20\"/><line x1=\"12\" x2=\"12\" y1=\"4\" y2=\"20\"/>'),\n 'line-height': S('<path d=\"M3 5h12\"/><path d=\"M3 12h12\"/><path d=\"M3 19h12\"/><path d=\"M19 5v14\"/><path d=\"m16.5 7.5 2.5-2.5 2.5 2.5\"/><path d=\"m16.5 16.5 2.5 2.5 2.5-2.5\"/>'),\n capitalization: S('<path d=\"M4 18 8 8l4 10\"/><path d=\"M5.5 14h5\"/><path d=\"M16 18a3 3 0 1 0 0-6 3 3 0 0 0-3 3v3\"/><path d=\"M19 12v6\"/>'),\n 'text-size': S('<path d=\"M21 14h-5\"/><path d=\"M16 16v-3.5a2.5 2.5 0 0 1 5 0V16\"/><path d=\"M4.5 13h6\"/><path d=\"m3 16 4.5-9 4.5 9\"/>')\n};\n\n/**\n * Icon utility functions\n */\nexport class IconUtils {\n /**\n * Get icon SVG content by name\n * @param {string} iconName - Name of the icon\n * @returns {string} SVG content or empty string if not found\n */\n static getIcon(iconName) {\n return Icons[iconName] || '';\n }\n\n /**\n * Create icon element with proper styling\n * @param {string} iconName - Name of the icon\n * @param {Object} options - Options for icon styling\n * @returns {HTMLElement} Icon element\n */\n static createIconElement(iconName, options = {}) {\n const iconElement = document.createElement('span');\n iconElement.className = `icon icon-${iconName}`;\n\n // Apply default styles\n iconElement.style.display = 'inline-flex';\n iconElement.style.alignItems = 'center';\n iconElement.style.justifyContent = 'center';\n iconElement.style.width = options.width || '16px';\n iconElement.style.height = options.height || '16px';\n iconElement.style.verticalAlign = 'middle';\n\n // Set SVG content\n iconElement.innerHTML = this.getIcon(iconName);\n\n return iconElement;\n }\n\n /**\n * Check if icon exists\n * @param {string} iconName - Name of the icon\n * @returns {boolean} True if icon exists\n */\n static hasIcon(iconName) {\n return iconName in Icons;\n }\n\n /**\n * Get all available icon names\n * @returns {string[]} Array of icon names\n */\n static getIconNames() {\n return Object.keys(Icons);\n }\n}\n\n// Export default for backward compatibility\nexport default IconUtils;\n","import registry from './registry.js';\nimport Module from './module.js';\nimport { execFormat, queryFormatState } from '../utils/exec-command.js';\nimport { sanitizeHtml } from '../utils/sanitize.js';\nimport { htmlToMarkdown, markdownToHtml, domToJson, jsonToHtml } from '../serialize.js';\nimport IconUtils from '../ui/icons.js';\n\n/**\n * Main Editor class - Inspired by Quill's architecture\n * This replaces the monolithic EditorCore class\n */\nexport default class Editor {\n static DEFAULTS = {\n placeholder: 'Start typing...',\n theme: 'light',\n height: 400,\n width: 800,\n maxWidth: 1200,\n maxHeight: 800,\n content: null, // Default content for the editor\n features: {\n emoji: true,\n image: true,\n table: true,\n wordCount: true,\n breadcrumb: true\n }\n };\n\n // Static reference to current editor instance\n static currentInstance = null;\n // Static map to track all editor instances\n static instances = new Map();\n\n constructor(selector, options = {}) {\n this.options = {\n ...Editor.DEFAULTS,\n ...options,\n // Deep-merge `features` so a partial override (e.g. { wordCount: false }\n // to hide the bottom bar) keeps the other defaults instead of wiping them.\n features: { ...Editor.DEFAULTS.features, ...(options.features || {}) }\n };\n this.root = typeof selector === 'string' ? document.querySelector(selector) : selector;\n this.modules = new Map();\n this.formats = new Map();\n this.registry = registry;\n this.events = new Map(); // Add event system\n \n // State management\n this.toolbarBtns = {};\n this.statusbarEls = {};\n this.dropdownMenus = {};\n \n // Popup management - each editor has its own popup instances\n this.popupInstances = new Map();\n \n // Set as current instance\n Editor.currentInstance = this;\n \n // Register this instance\n const instanceId = this.generateInstanceId();\n this.instanceId = instanceId;\n Editor.instances.set(instanceId, this);\n \n this.init();\n }\n\n /**\n * Generate unique instance ID\n */\n generateInstanceId() {\n return 'editor_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);\n }\n\n /**\n * Initialize editor\n */\n init() {\n this.createStructure();\n this.loadModules();\n this.loadFormats();\n this.setupEventListeners();\n this.updateStatusbar();\n }\n\n /**\n * Create basic DOM structure - extracted from EditorCore.init()\n * TODO: Copy implementation from EditorCore.init()\n */\n createStructure() {\n // Create wrapper\n this.wrapper = document.createElement('div');\n this.wrapper.className = 'yjd-rich-editor';\n \n // Apply dynamic sizing. A number is treated as pixels; a string (e.g.\n // '100%') is applied verbatim so the editor can size responsively to its\n // container instead of a fixed width.\n const cssSize = (v) => (typeof v === 'number' ? v + 'px' : v);\n this.wrapper.style.width = cssSize(this.options.width);\n this.wrapper.style.maxWidth = cssSize(this.options.maxWidth);\n this.wrapper.style.minHeight = cssSize(this.options.height);\n this.wrapper.style.maxHeight = cssSize(this.options.maxHeight);\n \n // Set position relative for popup positioning\n this.wrapper.style.position = 'relative';\n\n // Create editor area\n this.editor = document.createElement('div');\n this.editor.className = 'rich-editor-area';\n this.editor.contentEditable = true;\n this.editor.setAttribute('data-placeholder', this.options.placeholder);\n\n // Accessibility: expose the editable region to assistive technology\n this.editor.setAttribute('role', 'textbox');\n this.editor.setAttribute('aria-multiline', 'true');\n this.editor.setAttribute('aria-label', this.options.ariaLabel || this.options.placeholder || 'Rich text editor');\n\n // Text direction (RTL support)\n if (this.options.direction) {\n this.editor.setAttribute('dir', this.options.direction === 'rtl' ? 'rtl' : 'ltr');\n }\n \n // Force browser to create <p> tags instead of <div> when pressing Enter\n execFormat('defaultParagraphSeparator', 'p');\n \n // Add default content\n this.editor.innerHTML = this.getDefaultContent();\n \n this.wrapper.appendChild(this.editor);\n\n // Create popup container\n this.popupContainer = document.createElement('div');\n this.popupContainer.className = 'rich-editor-popup-container';\n this.popupContainer.style.position = 'absolute';\n this.popupContainer.style.top = '0';\n this.popupContainer.style.left = '0';\n this.popupContainer.style.width = '100%';\n this.popupContainer.style.height = '100%';\n this.popupContainer.style.pointerEvents = 'none';\n this.popupContainer.style.zIndex = '1000';\n this.wrapper.appendChild(this.popupContainer);\n\n // Create statusbar if needed\n if (this.options.features.wordCount || this.options.features.breadcrumb) {\n this.createStatusbar();\n }\n\n // Add wrapper to root\n this.root.appendChild(this.wrapper);\n \n // Initialize placeholder visibility\n this.updatePlaceholderVisibility();\n }\n\n /**\n * Check if content is HTML or plain text\n * @param {string} content - Content to check\n * @returns {boolean} True if content appears to be HTML\n */\n isHtmlContent(content) {\n if (!content || typeof content !== 'string') {\n return false;\n }\n \n // Trim whitespace for checking\n const trimmed = content.trim();\n \n // Check for common HTML patterns\n const htmlPatterns = [\n /<[^>]+>/, // Contains HTML tags\n /&[a-zA-Z]+;/, // Contains HTML entities\n /&#\\d+;/, // Contains numeric HTML entities\n ];\n \n return htmlPatterns.some(pattern => pattern.test(trimmed));\n }\n\n /**\n * Wrap plain text content in a paragraph tag\n * @param {string} content - Content to wrap\n * @returns {string} Wrapped content\n */\n wrapTextInParagraph(content) {\n if (!content || typeof content !== 'string') {\n return '<p><br></p>';\n }\n \n const trimmed = content.trim();\n \n // If content is already HTML, return as is\n if (this.isHtmlContent(trimmed)) {\n return trimmed;\n }\n \n // If content is empty, return empty paragraph\n if (trimmed === '') {\n return '<p><br></p>';\n }\n \n // Wrap plain text in paragraph tag\n return `<p>${trimmed}</p>`;\n }\n\n /**\n * Get default content for editor\n */\n getDefaultContent() {\n // If custom content is provided in options, use it\n if (this.options.content) {\n return this.wrapTextInParagraph(this.options.content);\n }\n \n // Restore an autosaved draft if available\n const saved = this._getAutosaved();\n if (saved != null && saved !== '') {\n return saved;\n }\n\n // Return completely empty content to show placeholder\n return '';\n }\n\n /**\n * Create statusbar - extracted from EditorCore\n * TODO: Copy implementation from EditorCore.init()\n */\n createStatusbar() {\n this.statusbar = document.createElement('div');\n this.statusbar.className = 'rich-editor-statusbar';\n\n // Create breadcrumb and word count elements\n this.statusbarEls.breadcrumb = document.createElement('span');\n this.statusbarEls.breadcrumb.className = 'rich-editor-breadcrumb';\n\n this.statusbarEls.wordcount = document.createElement('span');\n this.statusbarEls.wordcount.className = 'wordcount';\n\n this.statusbar.appendChild(this.statusbarEls.breadcrumb);\n this.statusbar.appendChild(this.statusbarEls.wordcount);\n this.wrapper.appendChild(this.statusbar);\n }\n\n /**\n * Load and initialize modules\n */\n loadModules() {\n // Determine which modules to load\n let modulesToLoad;\n \n // Check if user provided toolbar configuration\n const hasToolbarConfig = this.options.toolbar || this.options.toolbar1 || this.options.toolbar2;\n \n if (hasToolbarConfig) {\n // User wants custom toolbar - load only basic modules\n modulesToLoad = this.options.modules || ['toolbar', 'history'];\n } else {\n // No toolbar config - load full feature set\n modulesToLoad = this.options.modules || ['toolbar', 'history', 'block-toolbar', 'table-toolbar', 'code-view', 'theme-switcher', 'resize-handles', 'find-replace', 'slash-menu', 'mention'];\n }\n\n // @mention is inert without a source, so load it whenever configured —\n // even alongside a custom toolbar that otherwise loads only basics.\n if (this.options.mention && !modulesToLoad.includes('mention')) {\n modulesToLoad.push('mention');\n }\n\n \n modulesToLoad.forEach(moduleName => {\n const ModuleClass = this.registry.get(`modules/${moduleName}`);\n if (ModuleClass) {\n // For toolbar module, pass all options so it can detect toolbar config\n const moduleOptions = moduleName === 'toolbar' ? this.options : (this.options[moduleName] || this.options);\n const moduleInstance = new ModuleClass(this, moduleOptions);\n this.modules.set(moduleName, moduleInstance);\n \n // Insert toolbar before editor\n if (moduleName === 'toolbar' && moduleInstance.getContainer) {\n const toolbarContainer = moduleInstance.getContainer();\n this.wrapper.insertBefore(toolbarContainer, this.editor);\n \n // Listen for toolbar events\n moduleInstance.on('toolbar-click', (data) => {\n this.handleToolbarClick(data);\n });\n }\n \n } else {\n }\n });\n }\n\n /**\n * Load and initialize formats\n */\n loadFormats() {\n // Determine which formats to load\n let formatsToLoad;\n \n // Check if user provided toolbar configuration\n const hasToolbarConfig = this.options.toolbar || this.options.toolbar1 || this.options.toolbar2;\n \n if (hasToolbarConfig) {\n // User wants custom toolbar - load only basic formats\n formatsToLoad = this.options.formats || ['bold', 'italic', 'underline', 'strike'];\n } else {\n // No toolbar config - load full feature set\n formatsToLoad = this.options.formats || [\n 'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript',\n 'color', 'background', 'text-align', 'text-size', 'link',\n 'code', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', \n 'paragraph', 'pre'\n ];\n }\n \n \n formatsToLoad.forEach(formatName => {\n const FormatClass = this.registry.get(`formats/${formatName}`);\n if (FormatClass) {\n this.formats.set(formatName, FormatClass);\n } else {\n }\n });\n }\n\n /**\n * Setup event listeners - extracted from EditorCore\n * TODO: Copy implementation from EditorCore.bindEvents()\n */\n setupEventListeners() {\n // Track the active editor: whenever the user interacts with THIS editor\n // (pointer or focus anywhere inside its wrapper — editor area, toolbar,\n // popups), make it the current instance. This fixes multi-instance bugs\n // where helpers resolved to the last-CREATED editor instead of the\n // last-INTERACTED one.\n this._markActive = () => { Editor.currentInstance = this; };\n this.wrapper.addEventListener('pointerdown', this._markActive, true);\n this.wrapper.addEventListener('focusin', this._markActive, true);\n\n // Basic input event. onContentChange() already runs ensureEditorHasContent()\n // and updateStatusbar() (via _emitChange), so we don't duplicate them here.\n this.editor.addEventListener('input', () => {\n this.updatePlaceholderVisibility();\n this.onContentChange();\n });\n\n // Selection changes (caret move, selection) — coalesced to one run per\n // animation frame so rapid events don't each trigger a full toolbar pass.\n this._onDocSelectionChange = () => {\n if (document.activeElement === this.editor || this.editor.contains(document.activeElement)) {\n this._scheduleSelectionUpdate();\n }\n };\n document.addEventListener('selectionchange', this._onDocSelectionChange);\n\n // Mouse up: selectionchange already fires for this; just ensure a refresh\n // (still rAF-throttled, no setTimeout).\n this.editor.addEventListener('mouseup', () => this._scheduleSelectionUpdate());\n\n // Click inside the editor: keep a valid editable block.\n this.editor.addEventListener('click', () => {\n this.ensureEditorHasContent();\n });\n\n // Image context menu (right-click)\n this.editor.addEventListener('contextmenu', (e) => {\n // Image context menu functionality removed - methods don't exist\n });\n\n // Formatting keyboard shortcuts (Ctrl/Cmd + B/I/U, Ctrl/Cmd + K for link)\n this.editor.addEventListener('keydown', (e) => {\n if (!(e.ctrlKey || e.metaKey) || e.altKey) return;\n const shortcuts = { b: 'bold', i: 'italic', u: 'underline', k: 'link' };\n const command = shortcuts[e.key.toLowerCase()];\n if (!command) return;\n // Don't hijack shift-modified combos (e.g. Ctrl+Shift+...) except plain ones\n if (e.shiftKey) return;\n e.preventDefault();\n this.toggleFormat(command);\n });\n\n // Handle keydown events to ensure content structure\n this.editor.addEventListener('keydown', (e) => {\n // Check for delete/backspace operations that might empty the editor\n if (e.key === 'Delete' || e.key === 'Backspace') {\n // Use setTimeout to check after the deletion occurs\n setTimeout(() => {\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n }, 0);\n }\n });\n\n // Handle paste events — sanitize pasted HTML (prevents XSS and strips\n // messy markup from Word/Google Docs). Set options.pasteAsPlainText to\n // always paste as plain text instead.\n this.editor.addEventListener('paste', (e) => {\n this.handlePaste(e);\n });\n\n // Allow dropping (needed for the drop event to fire with files) and show a\n // drop-zone highlight while files are dragged over the editor.\n this.editor.addEventListener('dragover', (e) => {\n if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('Files')) {\n e.preventDefault();\n this.editor.classList.add('yjd-drag-over');\n }\n });\n this.editor.addEventListener('dragleave', (e) => {\n // Only clear when the pointer actually leaves the editor (not child nodes).\n if (!e.relatedTarget || !this.editor.contains(e.relatedTarget)) {\n this.editor.classList.remove('yjd-drag-over');\n }\n });\n\n // Handle drop events (drag and drop) — insert dropped image files\n this.editor.addEventListener('drop', (e) => {\n this.editor.classList.remove('yjd-drag-over');\n const dt = e.dataTransfer;\n const files = dt && dt.files ? Array.from(dt.files) : [];\n const imageFile = files.find(f => f.type && f.type.startsWith('image/'));\n if (imageFile) {\n e.preventDefault();\n this.placeCaretAtPoint(e.clientX, e.clientY);\n this.insertImageFile(imageFile);\n return;\n }\n // Non-image files become attachments when a file hook is configured.\n const attachment = files.find(f => f && f.name);\n if (attachment && this.options.file) {\n e.preventDefault();\n this.placeCaretAtPoint(e.clientX, e.clientY);\n this.insertFileAttachment(attachment);\n return;\n }\n // Check content after a normal drop operation\n setTimeout(() => {\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n }, 0);\n });\n\n // Enforce character limit (maxLength) on insertion-type input\n if (this.options.maxLength) {\n this.editor.addEventListener('beforeinput', (e) => {\n if (!e.inputType || !e.inputType.startsWith('insert')) return;\n if (e.inputType === 'insertFromPaste') return; // handled in handlePaste\n const incoming = e.data ? e.data.length : 1;\n if (this._remainingChars() < incoming) {\n e.preventDefault();\n }\n });\n }\n\n // Markdown shortcuts (# heading, - bullet, 1. ordered, > quote) on space\n if (this.options.markdown !== false) {\n this.editor.addEventListener('keydown', (e) => {\n if (e.key === ' ' && !e.ctrlKey && !e.metaKey && !e.altKey) {\n this.handleMarkdownShortcut(e);\n }\n });\n }\n\n // Enter-to-submit (e.g. a comment box). Enter submits, Shift+Enter inserts\n // a newline — UNLESS an autocomplete popup is open, in which case Enter is\n // left for the popup to choose its item. Configured via options.submit.\n if (this.options.submit && typeof this.options.submit.onEnter === 'function') {\n const submitCfg = this.options.submit;\n this.editor.addEventListener('keydown', (e) => {\n if (e.key !== 'Enter' || e.isComposing) return;\n if (e.shiftKey) return; // Shift+Enter → newline (browser default)\n if (this.isMenuOpen()) return; // let the popup handle Enter\n e.preventDefault();\n submitCfg.onEnter(this.getContent(), this);\n });\n }\n\n // Handle cut events\n this.editor.addEventListener('cut', () => {\n // Check content after cut operation\n setTimeout(() => {\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n }, 0);\n });\n\n // Focus editor on load\n setTimeout(() => {\n // Ensure editor has proper content structure on load\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n // Set the initial undo/redo dimmed state (nothing to undo yet).\n this.updateHistoryButtons();\n this.focus();\n }, 100);\n\n // Handle focus events to ensure content structure\n this.editor.addEventListener('focus', () => {\n // Ensure there's always a paragraph element for editing when focusing\n setTimeout(() => {\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n }, 0);\n });\n }\n\n /**\n * Handle content changes\n */\n onContentChange() {\n // Check if editor is empty and create a paragraph element if needed\n this.ensureEditorHasContent();\n this._emitChange();\n }\n\n /**\n * Notify listeners of a content change WITHOUT running the empty-content\n * reset (used when an intentionally-empty block — e.g. a fresh heading from\n * a markdown shortcut — must not be wiped by ensureEditorHasContent).\n */\n _emitChange() {\n this.modules.forEach(module => {\n if (typeof module.onContentChange === 'function') {\n module.onContentChange();\n }\n });\n\n // Keep the status bar (word/char count + breadcrumb) in sync after every\n // mutation, including programmatic ones (find/replace, undo/redo, toolbar).\n this.updateStatusbar();\n\n // Get current content\n const content = this.getContent();\n\n // Call onChange callback if provided\n if (this.options.onChange && typeof this.options.onChange === 'function') {\n this.options.onChange(content);\n }\n\n // Persist draft if autosave is enabled\n this._scheduleAutosave(content);\n\n // Warn when the serialized content exceeds maxContentSize (bytes). Guards\n // against, e.g., pasting huge base64 images. Fires once per crossing.\n if (this.options.maxContentSize) {\n const size = content.length;\n const over = size > this.options.maxContentSize;\n if (over && !this._overflowed) {\n this._overflowed = true;\n this.emit('content:overflow', { size, max: this.options.maxContentSize });\n } else if (!over) {\n this._overflowed = false;\n }\n }\n\n // Emit change events ('change' is the documented name; 'text-change' kept\n // for backward compatibility).\n this.emit('change', content);\n this.emit('text-change', content);\n }\n\n /**\n * Ensure editor always has a paragraph element for editing\n * This prevents users from editing directly in the editor container\n */\n ensureEditorHasContent() {\n if (!this.isEditorEmpty()) return;\n\n // Only act when the caret/selection is actually inside this editor — avoids\n // clearing formats or stealing focus based on a selection elsewhere.\n const selInEditor = this.isSelectionInEditableArea(window.getSelection());\n\n // Rebuild to a clean paragraph when needed — this strips leftover empty\n // formatting tags (e.g. <b><i><u>) that survive a \"delete all\".\n if (this.editor.innerHTML !== '<p><br></p>') {\n const paragraph = document.createElement('p');\n paragraph.innerHTML = '<br>';\n this.editor.innerHTML = '';\n this.editor.appendChild(paragraph);\n this.setCursorToElement(paragraph);\n this.editor.focus();\n }\n\n // Clearing the DOM is not enough: browsers keep a \"pending\" inline-format\n // state for the next typed character. Toggle off any active inline format\n // so new text isn't unexpectedly bold/italic/underline/strikethrough.\n if (selInEditor || document.activeElement === this.editor) {\n this._clearStickyInlineFormats();\n }\n\n this.updateToolbarButtonStates();\n this.updateStatusbar();\n }\n\n /**\n * Turn off any active inline formatting command so the next typed character\n * starts unformatted. Only affects a collapsed caret (no DOM mutation).\n */\n _clearStickyInlineFormats() {\n ['bold', 'italic', 'underline', 'strikeThrough'].forEach((cmd) => {\n if (queryFormatState(cmd)) execFormat(cmd);\n });\n }\n\n /**\n * Ensure there's always a paragraph element available for editing\n * This prevents users from editing directly in the editor container\n */\n ensureParagraphForEditing() {\n const children = this.editor.children;\n \n // If editor has no children, create a paragraph\n if (children.length === 0) {\n const paragraph = document.createElement('p');\n paragraph.innerHTML = '<br>';\n this.editor.appendChild(paragraph);\n this.setCursorToElement(paragraph);\n return;\n }\n \n // Check if the last child is a block element that can contain text\n const lastChild = children[children.length - 1];\n const blockTags = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'PRE', 'ARTICLE', 'SECTION', 'MAIN', 'ASIDE'];\n \n // Only add paragraph if the last child is not a block element that can contain text\n if (!blockTags.includes(lastChild.tagName)) {\n // Add a paragraph element at the end for editing\n const paragraph = document.createElement('p');\n paragraph.innerHTML = '<br>';\n this.editor.appendChild(paragraph);\n }\n }\n\n /**\n * Check if editor is empty or contains only empty elements\n */\n isEditorEmpty() {\n // Real text content → not empty (ignore zero-width spaces).\n const text = this.editor.textContent;\n if (text && text.replace(/\\u200B/g, '').trim() !== '') return false;\n\n // Embedded/void media counts as content even with no text.\n if (this.editor.querySelector('img, table, hr, video, iframe, audio, figure')) {\n return false;\n }\n\n // Otherwise empty — including the case where only empty formatting tags\n // remain (e.g. <p><b><i><u><br></u></i></b></p> after deleting everything).\n return true;\n }\n\n /**\n * Set cursor position to a specific element\n */\n setCursorToElement(element) {\n const range = document.createRange();\n const selection = window.getSelection();\n \n // Try to set cursor at the beginning of the element\n if (element.firstChild && element.firstChild.nodeType === Node.TEXT_NODE) {\n range.setStart(element.firstChild, 0);\n } else {\n range.setStart(element, 0);\n }\n \n range.collapse(true);\n \n selection.removeAllRanges();\n selection.addRange(range);\n }\n\n /**\n * Coalesce selection-driven UI updates to one run per animation frame.\n */\n _scheduleSelectionUpdate() {\n if (this._selUpdateQueued) return;\n this._selUpdateQueued = true;\n requestAnimationFrame(() => {\n this._selUpdateQueued = false;\n this.onSelectionChange();\n });\n }\n\n /**\n * Handle selection changes\n */\n onSelectionChange() {\n const selection = window.getSelection();\n const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;\n \n // Check if selection is within rich-editor-area\n const isInEditableArea = this.isSelectionInEditableArea(selection);\n\n // If the selection is inside this editor, it is the active instance.\n if (isInEditableArea) {\n Editor.currentInstance = this;\n // Remember the last real (non-collapsed) selection so popups (colour,\n // etc.) can restore it even if a tap clears the live selection on mobile.\n if (range && !range.collapsed) {\n this._lastRange = range.cloneRange();\n }\n }\n\n // Update all modules with selection info\n this.modules.forEach(module => {\n if (typeof module.onSelectionChange === 'function') {\n module.onSelectionChange(range, isInEditableArea);\n }\n });\n \n // Update toolbar button states\n this.updateToolbarButtonStates();\n \n // Update toolbar buttons accessibility\n this.updateToolbarAccessibility(isInEditableArea);\n \n // Update statusbar when selection changes\n this.updateStatusbar();\n }\n\n /**\n * Check if current selection is within the rich-editor-area\n */\n isSelectionInEditableArea(selection) {\n if (!selection || selection.rangeCount === 0) {\n return false;\n }\n\n const range = selection.getRangeAt(0);\n const startContainer = range.startContainer;\n const endContainer = range.endContainer;\n \n // Check if both start and end containers are within rich-editor-area\n const startInEditor = this.isNodeInEditableArea(startContainer);\n const endInEditor = this.isNodeInEditableArea(endContainer);\n \n return startInEditor && endInEditor;\n }\n\n /**\n * Check if a node is within the rich-editor-area\n */\n isNodeInEditableArea(node) {\n if (!node) return false;\n \n // Traverse up the DOM tree to find rich-editor-area\n let currentNode = node.nodeType === Node.TEXT_NODE ? node.parentNode : node;\n \n while (currentNode && currentNode !== document.body) {\n if (currentNode === this.editor || \n (currentNode.classList && currentNode.classList.contains('rich-editor-area'))) {\n return true;\n }\n currentNode = currentNode.parentNode;\n }\n \n return false;\n }\n\n /**\n * Update toolbar accessibility based on selection location\n */\n updateToolbarAccessibility(isInEditableArea) {\n const toolbar = this.getModule('toolbar');\n if (!toolbar) return;\n \n // List of commands that should be disabled when outside editable area\n // Note: undo/redo are NOT in this list - they should always work\n const editingCommands = [\n 'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript',\n 'color', 'background', 'link', 'table', 'heading', \n 'font-family', 'line-height', 'capitalization', 'text-align', 'list',\n 'indent-increase', 'indent-decrease', 'text-size'\n ];\n \n editingCommands.forEach(command => {\n toolbar.setButtonDisabled(command, !isInEditableArea);\n });\n \n // These commands should always be enabled regardless of selection location\n const alwaysEnabledCommands = ['more', 'undo', 'redo', 'code-view', 'theme'];\n alwaysEnabledCommands.forEach(command => {\n toolbar.setButtonDisabled(command, false);\n });\n }\n\n /**\n * Update statusbar - extracted from EditorCore\n * TODO: Copy implementation from EditorCore.updateStatusbar()\n */\n updateStatusbar() {\n if (!this.statusbar) return;\n\n const sel = window.getSelection();\n if (!sel) return;\n\n // Update breadcrumb\n if (this.statusbarEls.breadcrumb && this.options.features.breadcrumb) {\n const currentNode = sel.anchorNode;\n // Only reflect a selection that lives inside THIS editor. On a page with\n // several editors the selection is global, so without this guard each\n // statusbar would walk up to <body> and show another editor's path.\n if (!currentNode || !this.editor.contains(currentNode)) {\n this.statusbarEls.breadcrumb.textContent = 'editor';\n } else {\n const path = [];\n let element = currentNode?.nodeType === 3 ? currentNode.parentElement : currentNode;\n\n while (element && element !== this.editor && element !== document.body) {\n if (element.tagName) {\n let tagInfo = element.tagName.toLowerCase();\n if (element.className && typeof element.className === 'string') {\n const classes = element.className.trim();\n if (classes) {\n tagInfo += '.' + classes.split(' ').join('.');\n }\n }\n if (element.id) {\n tagInfo += '#' + element.id;\n }\n path.unshift(tagInfo);\n }\n element = element.parentElement;\n }\n\n this.statusbarEls.breadcrumb.textContent = path.length > 0 ? path.join(' > ') : 'editor';\n }\n }\n\n // Update word count\n if (this.statusbarEls.wordcount && this.options.features.wordCount) {\n // While in HTML code-view, count the rendered text of the source being\n // edited (not the stale visual content that's hidden behind it).\n let text;\n const codeView = this.getModule('code-view');\n if (codeView && typeof codeView.isInCodeView === 'function' && codeView.isInCodeView()) {\n const tmp = document.createElement('div');\n tmp.innerHTML = codeView.getCurrentContent ? codeView.getCurrentContent() : '';\n text = tmp.textContent || '';\n } else {\n text = this.editor.textContent || '';\n }\n const words = text.trim() ? text.trim().split(/\\s+/).length : 0;\n const chars = text.length;\n const charsNoSpaces = text.replace(/\\s/g, '').length;\n \n let label = `${words} words, ${chars} chars (${charsNoSpaces} no spaces)`;\n if (this.options.maxLength) {\n label += ` • ${Math.max(0, this.options.maxLength - chars)} left`;\n }\n this.statusbarEls.wordcount.textContent = label;\n }\n }\n\n /**\n * Focus editor\n */\n focus() {\n if (this.editor) {\n this.editor.focus();\n }\n }\n\n /**\n * Get editor content\n */\n getContent() {\n return this.editor.innerHTML;\n }\n\n /**\n * Set editor content\n */\n setContent(html) {\n // Wrap plain text content in paragraph tag if needed\n const processedContent = this.wrapTextInParagraph(html);\n this.editor.innerHTML = processedContent;\n this.onContentChange();\n }\n\n /* ----- Export / import in common formats (HTML · JSON · Markdown) ----- */\n\n /** HTML string (alias of getContent). */\n getHTML() { return this.getContent(); }\n /** Set content from an HTML string (sanitised). */\n setHTML(html) { this.setContent(sanitizeHtml(html || '')); }\n\n /** Structured JSON document `{ type:'doc', content:[…] }`. */\n getJSON() { return domToJson(this.getContent()); }\n /** Set content from a JSON document (produced by getJSON). */\n setJSON(json) { this.setContent(sanitizeHtml(jsonToHtml(json))); }\n\n /** Markdown string. */\n getMarkdown() { return htmlToMarkdown(this.getContent()); }\n /** Set content from a Markdown string. */\n setMarkdown(md) { this.setContent(sanitizeHtml(markdownToHtml(md || ''))); }\n\n /**\n * Get the plain text content of the editor (no markup).\n * @returns {string}\n */\n getText() {\n return this.editor.textContent || '';\n }\n\n /**\n * Whether the editor has no meaningful content.\n * @returns {boolean}\n */\n isEmpty() {\n return this.isEditorEmpty();\n }\n\n /**\n * Clear all content, leaving an empty paragraph.\n */\n clear() {\n this.editor.innerHTML = '<p><br></p>';\n this.onContentChange();\n this.updatePlaceholderVisibility();\n }\n\n /**\n * Insert plain text at the current caret position.\n * @param {string} text\n */\n insertText(text) {\n if (typeof text !== 'string') return;\n this.focus();\n execFormat('insertText', text);\n this.onContentChange();\n }\n\n /**\n * Insert HTML at the current caret position (sanitized to prevent XSS).\n * @param {string} html\n */\n insertHTML(html) {\n if (typeof html !== 'string') return;\n this.focus();\n execFormat('insertHTML', sanitizeHtml(html));\n this.onContentChange();\n }\n\n /**\n * Handle a paste event: sanitize pasted HTML, or paste as plain text.\n * @param {ClipboardEvent} e\n */\n handlePaste(e) {\n const clipboard = e.clipboardData || window.clipboardData;\n if (!clipboard) return; // let the browser handle it\n\n // Pasted image file (screenshot, copied image) → insert as image.\n const items = clipboard.items ? Array.from(clipboard.items) : [];\n const imageItem = items.find(it => it.kind === 'file' && it.type && it.type.startsWith('image/'));\n if (imageItem) {\n const file = imageItem.getAsFile();\n if (file) {\n e.preventDefault();\n this.insertImageFile(file);\n return;\n }\n }\n\n let html = clipboard.getData('text/html');\n let text = clipboard.getData('text/plain');\n\n // Nothing useful to insert ourselves → fall back to default behavior\n if (!html && !text) return;\n\n e.preventDefault();\n\n // Enforce character limit on paste (force plain text trimmed to remaining)\n if (this.options.maxLength) {\n const remaining = this._remainingChars();\n if (remaining <= 0) return;\n const plain = (text || '').slice(0, remaining);\n execFormat('insertText', plain);\n } else if (!this.options.pasteAsPlainText && html) {\n execFormat('insertHTML', sanitizeHtml(html));\n } else if (text) {\n execFormat('insertText', text);\n }\n\n setTimeout(() => {\n this.ensureEditorHasContent();\n this.updatePlaceholderVisibility();\n this.onContentChange();\n }, 0);\n }\n\n /**\n * Number of characters that can still be added before hitting maxLength\n * (accounts for the current selection being replaced). Infinity if no limit.\n */\n _remainingChars() {\n if (!this.options.maxLength) return Infinity;\n const sel = window.getSelection();\n const selLen = sel && !sel.isCollapsed ? sel.toString().length : 0;\n return this.options.maxLength - (this.getText().length - selLen);\n }\n\n /**\n * Read an image File and insert it (as a base64 data URL) at the caret.\n * @param {File} file\n */\n insertImageFile(file) {\n if (!file || !file.type || !file.type.startsWith('image/')) return;\n\n const cfg = this.options.image || {};\n // Validate against optional accept / maxSize before doing anything.\n if (cfg.accept && cfg.accept !== 'image/*') {\n const ok = cfg.accept.split(',').some(a => {\n a = a.trim();\n return a.endsWith('/*') ? file.type.startsWith(a.slice(0, -1)) : file.type === a;\n });\n if (!ok) { this.emit('image:error', { file, reason: 'type' }); return; }\n }\n if (cfg.maxSize && file.size > cfg.maxSize) {\n this.emit('image:error', { file, reason: 'size' });\n return;\n }\n\n // Build a real <img> from a src (validates via the Image format).\n const makeImg = (src, extra = '') => {\n const ImageClass = this.registry.get('formats/image');\n if (ImageClass && typeof ImageClass.create === 'function') {\n const img = ImageClass.create(src);\n if (!img) return null;\n if (extra) img.setAttribute('data-state', extra);\n return img;\n }\n return null;\n };\n\n // No upload hook → embed as base64 (backward-compatible default).\n if (typeof cfg.upload !== 'function') {\n const reader = new FileReader();\n reader.onload = (ev) => {\n const html = (makeImg(ev.target.result) || {}).outerHTML\n || `<img src=\"${ev.target.result}\" class=\"inserted-image\" style=\"max-width:100%\" contenteditable=\"false\">`;\n this.focus();\n execFormat('insertHTML', html);\n this.onContentChange();\n };\n reader.readAsDataURL(file);\n return;\n }\n\n // Upload hook: insert a visible loading placeholder (spinner + filename),\n // await the URL, then replace the placeholder with the real <img>.\n const placeholderId = 'rte-up-' + Math.round(performance.now()) + '-' + (this._upCounter = (this._upCounter || 0) + 1);\n const escName = (file.name || 'image')\n .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n const phHTML =\n `<span class=\"yjd-upload\" id=\"${placeholderId}\" contenteditable=\"false\" data-state=\"uploading\">` +\n `<span class=\"yjd-spinner\" aria-hidden=\"true\"></span>` +\n `<span class=\"yjd-upload-label\">${escName}</span>` +\n `</span>`;\n this.focus();\n execFormat('insertHTML', phHTML);\n this.emit('image:upload', { file });\n\n Promise.resolve(cfg.upload(file)).then((url) => {\n const el = this.editor.querySelector('#' + placeholderId);\n if (!el) return;\n if (url) {\n const img = makeImg(url);\n if (img) { el.replaceWith(img); } else { el.remove(); }\n this.emit('image:uploaded', { file, url });\n } else {\n el.remove();\n }\n this.onContentChange();\n }).catch((err) => {\n const el = this.editor.querySelector('#' + placeholderId);\n if (el) el.remove();\n this.emit('image:error', { file, reason: 'upload', error: err });\n this.onContentChange();\n });\n }\n\n /**\n * Human-readable byte size, e.g. 24576 -> \"24 KB\".\n */\n static formatBytes(bytes) {\n if (!bytes && bytes !== 0) return '';\n const u = ['B', 'KB', 'MB', 'GB'];\n let i = 0, n = bytes;\n while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; }\n return `${n >= 10 || i === 0 ? Math.round(n) : n.toFixed(1)} ${u[i]}`;\n }\n\n /**\n * Open the native picker for a non-image attachment, then insert it as a\n * file chip via the options.file.upload hook.\n */\n openFileAttachmentPicker() {\n const cfg = this.options.file || {};\n const sel = window.getSelection();\n const savedRange = sel && sel.rangeCount ? sel.getRangeAt(0).cloneRange() : null;\n\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = cfg.accept || '*/*';\n input.style.display = 'none';\n input.addEventListener('change', () => {\n const file = input.files && input.files[0];\n if (file) {\n this.focus();\n const s = window.getSelection();\n if (savedRange) { s.removeAllRanges(); s.addRange(savedRange); }\n else if (!s.rangeCount || !this.editor.contains(s.anchorNode)) {\n const r = document.createRange();\n r.selectNodeContents(this.editor); r.collapse(false);\n s.removeAllRanges(); s.addRange(r);\n }\n this.insertFileAttachment(file);\n }\n input.remove();\n });\n document.body.appendChild(input);\n input.click();\n }\n\n /**\n * Insert a non-image File as a \"file chip\" — a contenteditable=false anchor\n * (icon + name + size) that serializes to a Markdown link `[name (size)](url)`.\n * Uploads via options.file.upload(file) -> string url | { url, name, size };\n * with no hook it falls back to an inline data: URL (like images do).\n * @param {File} file\n */\n insertFileAttachment(file) {\n if (!file) return;\n const cfg = this.options.file || {};\n\n // Validate accept / maxSize, mirroring insertImageFile.\n if (cfg.accept && cfg.accept !== '*/*') {\n const name = (file.name || '').toLowerCase();\n const ok = cfg.accept.split(',').some(a => {\n a = a.trim().toLowerCase();\n if (!a) return false;\n if (a.startsWith('.')) return name.endsWith(a);\n if (a.endsWith('/*')) return (file.type || '').startsWith(a.slice(0, -1));\n return file.type === a;\n });\n if (!ok) { this.emit('file:error', { file, reason: 'type' }); return; }\n }\n if (cfg.maxSize && file.size > cfg.maxSize) {\n this.emit('file:error', { file, reason: 'size' });\n return;\n }\n\n const ico = IconUtils && typeof IconUtils.getIcon === 'function'\n ? (IconUtils.getIcon('file') || '') : '';\n const esc = (s) => String(s == null ? '' : s)\n .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n\n // Build a chip anchor element. `meta` may override name/size from the hook.\n const makeChip = (url, meta = {}, state = '') => {\n const name = meta.name || file.name || 'file';\n const size = meta.size != null ? meta.size : Editor.formatBytes(file.size);\n const a = document.createElement('a');\n a.className = 'yjd-file-chip';\n a.setAttribute('contenteditable', 'false');\n a.setAttribute('href', url || '#');\n a.setAttribute('target', '_blank');\n a.setAttribute('rel', 'noopener noreferrer');\n a.setAttribute('data-name', name);\n if (size) a.setAttribute('data-size', size);\n if (state) a.setAttribute('data-state', state);\n // While uploading, the icon slot shows a spinner instead of the file glyph.\n const icoHTML = state === 'uploading'\n ? '<span class=\"yjd-spinner\" aria-hidden=\"true\"></span>' : ico;\n a.innerHTML =\n `<span class=\"yjd-file-ico\" contenteditable=\"false\">${icoHTML}</span>` +\n `<span class=\"yjd-file-name\">${esc(name)}</span>` +\n (size ? `<span class=\"yjd-file-size\">${esc(size)}</span>` : '');\n return a;\n };\n\n const insertChipHTML = (html) => {\n this.focus();\n execFormat('insertHTML', html + '&nbsp;');\n this.onContentChange();\n };\n\n // No upload hook → inline data URL (works offline, persists in the HTML).\n if (typeof cfg.upload !== 'function') {\n const reader = new FileReader();\n reader.onload = (ev) => insertChipHTML(makeChip(ev.target.result).outerHTML);\n reader.onerror = () => this.emit('file:error', { file, reason: 'read' });\n reader.readAsDataURL(file);\n return;\n }\n\n // Upload hook: insert a placeholder chip, await the URL, then fill it in.\n const id = 'rte-file-' + Math.round(performance.now()) + '-' + (this._fileCounter = (this._fileCounter || 0) + 1);\n const ph = makeChip('#', {}, 'uploading');\n ph.id = id;\n ph.style.opacity = '0.6';\n insertChipHTML(ph.outerHTML);\n this.emit('file:upload', { file });\n\n Promise.resolve(cfg.upload(file)).then((res) => {\n const el = this.editor.querySelector('#' + id);\n if (!el) return;\n const url = typeof res === 'string' ? res : (res && res.url);\n if (!url) { el.remove(); this.onContentChange(); return; }\n const name = (res && res.name) || el.getAttribute('data-name');\n const size = (res && res.size) || el.getAttribute('data-size');\n el.setAttribute('href', url);\n el.setAttribute('data-name', name);\n if (size) el.setAttribute('data-size', size);\n el.style.opacity = '';\n el.removeAttribute('data-state');\n el.removeAttribute('id');\n const icoEl = el.querySelector('.yjd-file-ico');\n if (icoEl) icoEl.innerHTML = ico; // swap spinner back to the file icon\n const nameEl = el.querySelector('.yjd-file-name');\n const sizeEl = el.querySelector('.yjd-file-size');\n if (nameEl) nameEl.textContent = name;\n if (sizeEl && size) sizeEl.textContent = size;\n this.emit('file:uploaded', { file, url, name, size });\n this.onContentChange();\n }).catch((err) => {\n const el = this.editor.querySelector('#' + id);\n if (el) el.remove();\n this.emit('file:error', { file, reason: 'upload', error: err });\n this.onContentChange();\n });\n }\n\n /**\n * Place the caret at the given viewport coordinates (used for drag-drop).\n */\n placeCaretAtPoint(x, y) {\n let range = null;\n if (document.caretRangeFromPoint) {\n range = document.caretRangeFromPoint(x, y);\n } else if (document.caretPositionFromPoint) {\n const pos = document.caretPositionFromPoint(x, y);\n if (pos) {\n range = document.createRange();\n range.setStart(pos.offsetNode, pos.offset);\n }\n }\n if (range) {\n range.collapse(true);\n const sel = window.getSelection();\n sel.removeAllRanges();\n sel.addRange(range);\n }\n }\n\n /**\n * Transform a markdown marker at the start of the current block when space\n * is pressed: \"# \" → H1..H6, \"- \"/\"* \" → bullet list, \"1. \" → ordered list,\n * \"> \" → blockquote.\n * @param {KeyboardEvent} e\n */\n handleMarkdownShortcut(e) {\n const sel = window.getSelection();\n if (!sel || !sel.isCollapsed || !sel.rangeCount) return;\n const range = sel.getRangeAt(0);\n\n // Find the nearest plain block (P/DIV) containing the caret.\n let block = range.startContainer;\n block = block.nodeType === Node.TEXT_NODE ? block.parentElement : block;\n while (block && block !== this.editor && block.tagName !== 'P' && block.tagName !== 'DIV') {\n block = block.parentElement;\n }\n if (!block || block === this.editor) return;\n\n // Text from block start to the caret = the marker the user typed.\n const pre = document.createRange();\n pre.selectNodeContents(block);\n pre.setEnd(range.startContainer, range.startOffset);\n const marker = pre.toString();\n\n const blockMap = { '#': 'h1', '##': 'h2', '###': 'h3', '####': 'h4', '#####': 'h5', '######': 'h6', '>': 'blockquote' };\n const blockTag = blockMap[marker];\n const listType = (marker === '-' || marker === '*') ? 'ul'\n : /^\\d+\\.$/.test(marker) ? 'ol' : null;\n if (!blockTag && !listType) return;\n\n e.preventDefault();\n\n const history = this.getModule('history');\n if (history && typeof history.saveBeforeFormat === 'function') history.saveBeforeFormat();\n\n // Remove the marker text the user typed.\n pre.deleteContents();\n\n if (blockTag) {\n // Replace the block element directly (execCommand formatBlock is\n // unreliable on a now-empty block).\n const el = document.createElement(blockTag);\n while (block.firstChild) el.appendChild(block.firstChild);\n // Ensure a <br> placeholder so the empty block stays focusable/visible\n if (el.textContent === '' && !el.querySelector('*')) {\n el.innerHTML = '<br>';\n }\n block.replaceWith(el);\n const caret = document.createRange();\n caret.selectNodeContents(el);\n caret.collapse(true);\n sel.removeAllRanges();\n sel.addRange(caret);\n } else {\n const caret = document.createRange();\n caret.selectNodeContents(block);\n caret.collapse(true);\n sel.removeAllRanges();\n sel.addRange(caret);\n execFormat(listType === 'ul' ? 'insertUnorderedList' : 'insertOrderedList');\n }\n // Use _emitChange (not onContentChange) so a fresh empty heading/quote\n // isn't wiped by the empty-content reset.\n this._emitChange();\n this.updatePlaceholderVisibility();\n }\n\n /**\n * Set text direction ('ltr' | 'rtl').\n */\n setDirection(dir) {\n const d = dir === 'rtl' ? 'rtl' : 'ltr';\n this.editor.setAttribute('dir', d);\n const toolbar = this.getModule('toolbar');\n if (toolbar) toolbar.setButtonActive('text-direction', d === 'rtl');\n }\n\n /**\n * @returns {'ltr'|'rtl'} Current text direction.\n */\n getDirection() {\n return this.editor.getAttribute('dir') === 'rtl' ? 'rtl' : 'ltr';\n }\n\n /**\n * Toggle between LTR and RTL.\n */\n toggleDirection() {\n this.setDirection(this.getDirection() === 'rtl' ? 'ltr' : 'rtl');\n }\n\n /**\n * Normalized autosave config ({ key, debounce }) or null when disabled.\n */\n _autosaveCfg() {\n if (this._autosaveMemo !== undefined) return this._autosaveMemo;\n const a = this.options.autosave;\n if (!a) { this._autosaveMemo = null; return null; }\n this._autosaveMemo = {\n key: (typeof a === 'object' && a.key) ? a.key : 'yjd-autosave',\n debounce: (typeof a === 'object' && a.debounce) ? a.debounce : 1000\n };\n return this._autosaveMemo;\n }\n\n /** Read previously autosaved content (or null). */\n _getAutosaved() {\n const cfg = this._autosaveCfg();\n if (!cfg) return null;\n try { return localStorage.getItem(cfg.key); } catch (e) { return null; }\n }\n\n /** Debounced write of content to localStorage. */\n _scheduleAutosave(content) {\n const cfg = this._autosaveCfg();\n if (!cfg) return;\n clearTimeout(this._autosaveTimer);\n this._autosaveTimer = setTimeout(() => {\n try { localStorage.setItem(cfg.key, content); } catch (e) { /* storage unavailable */ }\n }, cfg.debounce);\n }\n\n /** Remove the autosaved draft from storage. */\n clearAutosave() {\n const cfg = this._autosaveCfg();\n if (!cfg) return;\n try { localStorage.removeItem(cfg.key); } catch (e) { /* ignore */ }\n }\n\n /**\n * True when an autocomplete/popup that captures Enter is open (mention, slash\n * command, emoji). Used by submit.onEnter so Enter chooses the item instead\n * of submitting. App code can call it too.\n * @returns {boolean}\n */\n isMenuOpen() {\n const m = this.modules.get('mention');\n if (m && m.isOpen) return true;\n const s = this.modules.get('slash-menu');\n if (s && s.isOpen) return true;\n // Any visible portaled popup (emoji, link, image, table…).\n const sel = '.yjd-mention-menu, .yjd-slash-menu, .emoji-picker-popup.visible, .link-popup, .image-popup, .video-popup, .tag-popup';\n return [...document.querySelectorAll(sel)].some((el) => {\n if (!el.offsetParent && getComputedStyle(el).position !== 'fixed') return false;\n return getComputedStyle(el).display !== 'none';\n });\n }\n\n /**\n * Remove inline formatting (and links) from the current selection.\n */\n clearFormatting() {\n const historyModule = this.getModule('history');\n if (historyModule && typeof historyModule.saveBeforeFormat === 'function') {\n historyModule.saveBeforeFormat();\n }\n this.focus();\n execFormat('removeFormat');\n execFormat('unlink');\n this.onContentChange();\n this.updateToolbarButtonStates();\n }\n\n /**\n * Convert the current block to a given type.\n * @param {('p'|'h1'|'h2'|'h3'|'h4'|'h5'|'h6'|'blockquote'|'pre'|'ul'|'ol')} type\n */\n setBlockType(type) {\n const sel = window.getSelection();\n if (!sel || !sel.rangeCount) return;\n let block = sel.getRangeAt(0).startContainer;\n block = block.nodeType === Node.TEXT_NODE ? block.parentElement : block;\n const BLOCK = /^(P|DIV|H[1-6]|BLOCKQUOTE|PRE|LI)$/;\n while (block && block !== this.editor && !BLOCK.test(block.tagName)) {\n block = block.parentElement;\n }\n if (!block || block === this.editor) return;\n\n const history = this.getModule('history');\n if (history && typeof history.saveBeforeFormat === 'function') history.saveBeforeFormat();\n\n if (type === 'ul' || type === 'ol') {\n const r = document.createRange();\n r.selectNodeContents(block);\n r.collapse(true);\n sel.removeAllRanges();\n sel.addRange(r);\n execFormat(type === 'ul' ? 'insertUnorderedList' : 'insertOrderedList');\n } else {\n const el = document.createElement(type);\n while (block.firstChild) el.appendChild(block.firstChild);\n if (el.textContent === '' && !el.querySelector('*')) el.innerHTML = '<br>';\n block.replaceWith(el);\n const r = document.createRange();\n r.selectNodeContents(el);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n }\n this._emitChange();\n this.updatePlaceholderVisibility();\n }\n\n /**\n * Insert a horizontal rule at the current caret position.\n */\n insertHorizontalRule() {\n const historyModule = this.getModule('history');\n if (historyModule && typeof historyModule.saveBeforeFormat === 'function') {\n historyModule.saveBeforeFormat();\n }\n this.focus();\n execFormat('insertHorizontalRule');\n this.onContentChange();\n }\n\n /**\n * Whether a block element has no real (text/media) content.\n */\n _isBlockEmpty(el) {\n if (!el) return true;\n if (el.querySelector && el.querySelector('img, table, hr, video, iframe, audio, figure')) {\n return false;\n }\n return (el.textContent || '').replace(/\\u200B/g, '').trim() === '';\n }\n\n /**\n * Insert a block-level element at the editor's top level, next to the block\n * containing the caret — never nested inside a heading or inline formatting\n * tag (which would be invalid HTML). Removes the source block if it became\n * empty, and guarantees an editable paragraph after the inserted block.\n * @param {HTMLElement} blockEl\n */\n insertBlock(blockEl) {\n const sel = window.getSelection();\n let topBlock = null;\n if (sel && sel.rangeCount) {\n const range = sel.getRangeAt(0);\n if (!range.collapsed) range.deleteContents();\n let node = range.startContainer;\n node = node.nodeType === Node.TEXT_NODE ? node.parentNode : node;\n while (node && node !== this.editor && node.parentNode !== this.editor) {\n node = node.parentNode;\n }\n if (node && node.parentNode === this.editor) topBlock = node;\n }\n\n if (topBlock) {\n const wasEmpty = this._isBlockEmpty(topBlock);\n if (topBlock.nextSibling) {\n this.editor.insertBefore(blockEl, topBlock.nextSibling);\n } else {\n this.editor.appendChild(blockEl);\n }\n // Remove the originating block if it held only the caret / empty format tags\n if (wasEmpty) topBlock.remove();\n } else {\n this.editor.appendChild(blockEl);\n }\n\n // Guarantee an editable paragraph after the inserted block\n if (!blockEl.nextSibling) {\n const p = document.createElement('p');\n p.innerHTML = '<br>';\n this.editor.appendChild(p);\n }\n }\n\n /**\n * Enable/disable read-only mode.\n * @param {boolean} readOnly\n */\n setReadOnly(readOnly) {\n this._readOnly = !!readOnly;\n this.editor.contentEditable = this._readOnly ? 'false' : 'true';\n this.editor.setAttribute('aria-readonly', this._readOnly ? 'true' : 'false');\n this.wrapper.classList.toggle('read-only', this._readOnly);\n\n // Disable/enable toolbar interaction\n const toolbar = this.getModule('toolbar');\n if (toolbar && toolbar.buttons) {\n toolbar.buttons.forEach((_, command) => {\n toolbar.setButtonDisabled(command, this._readOnly);\n });\n }\n }\n\n /**\n * @returns {boolean} Whether the editor is in read-only mode.\n */\n isReadOnly() {\n return !!this._readOnly;\n }\n\n /**\n * Get module instance\n */\n getModule(name) {\n return this.modules.get(name);\n }\n\n /**\n * Get format class\n */\n getFormat(name) {\n return this.formats.get(name);\n }\n\n /**\n * Register new items\n */\n register(path, definition, suppressWarning = false) {\n this.registry.register(path, definition, suppressWarning);\n }\n\n /**\n * Handle toolbar button clicks\n */\n handleToolbarClick(data) {\n const { command, button, value } = data;\n \n // Set this editor as current instance for the duration of this command\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = this;\n \n // Emit toolbar-click event for modules to listen\n this.emit('toolbar-click', data);\n \n // Commands that should always work regardless of selection location\n const alwaysAllowedCommands = ['more', 'undo', 'redo', 'code-view', 'theme', 'text-direction', 'find'];\n\n if (alwaysAllowedCommands.includes(command)) {\n // These commands can execute regardless of selection location\n switch (command) {\n case 'more':\n // More command is handled by toolbar module itself\n return;\n case 'undo':\n this.undo();\n return;\n case 'redo':\n this.redo();\n return;\n case 'code-view':\n // Code view command is handled by CodeView module itself\n // The module listens to 'toolbar-click' events and handles it internally\n return;\n case 'text-direction':\n this.toggleDirection();\n return;\n case 'find':\n // Find/replace module listens to 'toolbar-click' and opens its panel\n return;\n }\n }\n \n // For all other commands, check if current selection is in editable area\n const selection = window.getSelection();\n const isInEditableArea = this.isSelectionInEditableArea(selection);\n \n if (!isInEditableArea) {\n console.warn(`Command '${command}' blocked: Selection outside editable area`);\n return;\n }\n \n // Handle formatting commands (only when selection is in editable area)\n switch (command) {\n case 'bold':\n case 'italic': \n case 'underline':\n case 'strike':\n case 'subscript':\n case 'superscript':\n case 'color':\n case 'background':\n case 'link':\n case 'table':\n case 'heading':\n case 'font-family':\n case 'line-height':\n case 'capitalization':\n case 'text-align':\n case 'text-size':\n case 'list':\n case 'indent-increase':\n case 'indent-decrease':\n case 'emoji':\n case 'image':\n case 'video':\n case 'tag':\n\n case 'import':\n this.toggleFormat(command);\n break;\n case 'file':\n this.openFileAttachmentPicker();\n break;\n case 'clear-format':\n this.clearFormatting();\n break;\n case 'horizontal-rule':\n this.insertHorizontalRule();\n break;\n default:\n console.warn(`Unknown command: ${command}`);\n }\n }\n\n /**\n * Toggle format on current selection\n */\n toggleFormat(formatName) {\n // Save state before applying format\n const historyModule = this.getModule('history');\n if (historyModule && typeof historyModule.saveBeforeFormat === 'function') {\n historyModule.saveBeforeFormat();\n }\n\n // Map format names to registry keys\n const formatMap = {\n 'bold': 'bold',\n 'italic': 'italic', \n 'underline': 'underline',\n 'strike': 'strike',\n 'subscript': 'subscript',\n 'superscript': 'superscript',\n 'color': 'color',\n 'background': 'background',\n 'link': 'link',\n 'table': 'table',\n 'heading': 'heading',\n 'font-family': 'font-family',\n 'line-height': 'line-height',\n 'capitalization': 'capitalization',\n 'text-align': 'text-align',\n 'text-size': 'text-size',\n 'list': 'list',\n 'indent-increase': 'indent-increase',\n 'indent-decrease': 'indent-decrease',\n 'emoji': 'emoji',\n 'image': 'image',\n 'video': 'video',\n 'tag': 'tag',\n\n 'import': 'import'\n };\n \n const registryKey = formatMap[formatName];\n if (!registryKey) {\n console.warn(`Unknown format: ${formatName}`);\n return;\n }\n \n const FormatClass = this.registry.get(`formats/${registryKey}`);\n if (!FormatClass) {\n return;\n }\n \n // Create format instance and toggle\n const formatInstance = new FormatClass();\n formatInstance.toggle();\n \n // Update button state\n this.updateToolbarButtonStates();\n \n // Trigger content change for formats that modify content immediately\n // (like bold, italic, underline, etc. that use execCommand)\n const immediateFormats = ['bold', 'italic', 'underline', 'strike', 'subscript', 'superscript'];\n if (immediateFormats.includes(formatName)) {\n // Use setTimeout to ensure DOM changes are complete\n setTimeout(() => {\n this.onContentChange();\n }, 0);\n }\n }\n\n /**\n * Get a cached format instance for state checks (created once per editor).\n */\n _getFormatInstance(formatName) {\n if (!this._fmtCache) this._fmtCache = new Map();\n if (this._fmtCache.has(formatName)) return this._fmtCache.get(formatName);\n const FormatClass = this.registry.get(`formats/${formatName}`);\n if (!FormatClass) return null;\n let inst;\n if (FormatClass.createForEditor) {\n inst = FormatClass.createForEditor(this.instanceId);\n } else {\n const original = Editor.currentInstance;\n Editor.currentInstance = this;\n inst = new FormatClass();\n Editor.currentInstance = original;\n }\n this._fmtCache.set(formatName, inst);\n return inst;\n }\n\n /**\n * Update toolbar button states based on current selection\n */\n updateToolbarButtonStates() {\n const toolbar = this.getModule('toolbar');\n if (!toolbar) return;\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n \n // Check if selection is in editable area\n const isInEditableArea = this.isSelectionInEditableArea(selection);\n \n const formats = ['heading', 'font-family', 'line-height', 'capitalization', 'text-align', 'list', 'indent-increase', 'indent-decrease', 'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript', 'color', 'background', 'link', 'table', 'text-size'];\n \n formats.forEach(formatName => {\n // Only check format state if selection is in editable area\n if (isInEditableArea) {\n // Reuse a cached instance per format (was creating 19 instances on\n // every caret move — wasteful garbage). isActive() reads live\n // selection/DOM, so a cached instance is safe.\n const formatInstance = this._getFormatInstance(formatName);\n if (formatInstance) {\n toolbar.setButtonActive(formatName, formatInstance.isActive());\n if (formatName === 'line-height' && typeof formatInstance.updateButtonText === 'function') {\n formatInstance.updateButtonText();\n }\n }\n } else {\n // Clear active state for buttons when outside editable area\n toolbar.setButtonActive(formatName, false);\n }\n });\n\n // Special handling for text-size: always update button text to show current size\n if (isInEditableArea) {\n const TextSizeClass = this.registry.get('formats/text-size');\n if (TextSizeClass && typeof TextSizeClass.updateButtonTextStatic === 'function') {\n TextSizeClass.updateButtonTextStatic(this.instanceId);\n }\n }\n\n this.updateColorSwatches();\n this.updateHistoryButtons();\n }\n\n /**\n * Undo/redo are always visible (no show/hide flicker); they're just dimmed\n * and disabled when there's nothing to act on.\n */\n updateHistoryButtons() {\n const toolbar = this.getModule('toolbar');\n if (!toolbar || typeof toolbar.getButton !== 'function') return;\n const history = this.getModule('history');\n const canUndo = !!(history && typeof history.canUndo === 'function' && history.canUndo());\n const canRedo = !!(history && typeof history.canRedo === 'function' && history.canRedo());\n const setState = (btn, enabled) => {\n if (!btn) return;\n btn.classList.remove('rte-hidden'); // always shown now\n btn.disabled = !enabled;\n btn.classList.toggle('is-disabled', !enabled);\n };\n setState(toolbar.getButton('undo'), canUndo);\n setState(toolbar.getButton('redo'), canRedo);\n }\n\n /**\n * Reflect the colour at the caret on the toolbar's colour/background swatch\n * bars. Falls back to the CSS default (via clearing the inline style) when no\n * explicit colour is applied.\n */\n updateColorSwatches() {\n const toolbar = this.getModule('toolbar');\n if (!toolbar || typeof toolbar.getButton !== 'function') return;\n\n const apply = (name, color) => {\n const btn = toolbar.getButton(name);\n if (!btn) return;\n const swatch = btn.querySelector('.rte-swatch');\n if (!swatch) return;\n if (color) {\n swatch.style.background = color;\n btn.classList.add('has-color');\n } else {\n swatch.style.removeProperty('background');\n btn.classList.remove('has-color');\n }\n };\n\n const ColorClass = this.registry.get('formats/color');\n if (ColorClass && typeof ColorClass.getCurrentColor === 'function') {\n apply('color', ColorClass.getCurrentColor());\n }\n const BgClass = this.registry.get('formats/background');\n if (BgClass && typeof BgClass.getCurrentColor === 'function') {\n apply('background', BgClass.getCurrentColor());\n }\n }\n\n /**\n * Undo last action\n */\n undo() {\n const history = this.getModule('history');\n if (history && typeof history.undo === 'function') {\n history.undo();\n } else {\n execFormat('undo');\n }\n }\n\n /**\n * Redo last undone action\n */\n redo() {\n const history = this.getModule('history');\n if (history && typeof history.redo === 'function') {\n history.redo();\n } else {\n execFormat('redo');\n }\n }\n\n /**\n * Add event listener\n * @param {string} event - Event name\n * @param {function} handler - Event handler\n */\n on(event, handler) {\n if (!this.events.has(event)) {\n this.events.set(event, []);\n }\n this.events.get(event).push(handler);\n }\n\n /**\n * Remove event listener\n * @param {string} event - Event name\n * @param {function} handler - Event handler\n */\n off(event, handler) {\n if (this.events.has(event)) {\n const handlers = this.events.get(event);\n const index = handlers.indexOf(handler);\n if (index > -1) {\n handlers.splice(index, 1);\n }\n }\n }\n\n /**\n * Emit event\n * @param {string} event - Event name\n * @param {*} data - Event data\n */\n emit(event, data) {\n if (this.events.has(event)) {\n this.events.get(event).forEach(handler => {\n try {\n handler(data);\n } catch (error) {\n console.error(`Error in event handler for ${event}:`, error);\n }\n });\n }\n }\n\n /**\n * Prevent focus loss when clicking on UI elements\n * @param {HTMLElement} element - Element to attach listener to\n * @param {string} allowedSelector - CSS selector for elements that should allow normal click behavior\n */\n preventFocusLoss(element, allowedSelector = 'button, input, select, textarea, [contenteditable]') {\n if (!element) return;\n \n element.addEventListener('mousedown', (e) => {\n // Allow normal behavior for interactive elements\n if (e.target.closest(allowedSelector)) {\n return;\n }\n \n // Prevent default behavior for non-interactive areas\n e.preventDefault();\n \n // Restore focus to editor after event processing\n setTimeout(() => {\n this.focus();\n }, 0);\n });\n }\n\n /**\n * Get current editor instance\n * @returns {Editor|null} Current editor instance\n */\n static getCurrentInstance() {\n return Editor.currentInstance;\n }\n\n /**\n * Utility function to maintain editor focus after UI interactions\n * @param {Function} callback - Function to execute before maintaining focus\n * @param {Editor} editor - Editor instance to maintain focus on\n */\n static maintainFocus(callback, editor = null) {\n if (typeof callback === 'function') {\n callback();\n }\n const editorInstance = editor || Editor.getCurrentInstance();\n if (editorInstance) {\n setTimeout(() => editorInstance.focus(), 0);\n }\n }\n\n /**\n * Get popup container for this editor instance\n * @returns {HTMLElement} Popup container element\n */\n getPopupContainer() {\n return this.popupContainer;\n }\n\n /**\n * Get popup container from current editor instance\n * @returns {HTMLElement|null} Popup container element or null if no current instance\n */\n static getPopupContainer() {\n const currentInstance = Editor.getCurrentInstance();\n return currentInstance ? currentInstance.getPopupContainer() : null;\n }\n\n /**\n * Get popup instance for this editor\n * @param {string} popupType - Type of popup (e.g., 'link', 'image', 'table')\n * @returns {Object|null} Popup instance or null if not found\n */\n getPopupInstance(popupType) {\n return this.popupInstances.get(popupType);\n }\n\n /**\n * Set popup instance for this editor\n * @param {string} popupType - Type of popup\n * @param {Object} popupInstance - Popup instance\n */\n setPopupInstance(popupType, popupInstance) {\n this.popupInstances.set(popupType, popupInstance);\n }\n\n /**\n * Get popup instance by editor ID and popup type\n * @param {string} editorId - Editor instance ID\n * @param {string} popupType - Type of popup\n * @returns {Object|null} Popup instance or null if not found\n */\n static getPopupInstanceById(editorId, popupType) {\n const editor = Editor.instances.get(editorId);\n return editor ? editor.getPopupInstance(popupType) : null;\n }\n\n /**\n * Get editor instance by ID\n * @param {string} editorId - Editor instance ID\n * @returns {Editor|null} Editor instance or null if not found\n */\n static getInstanceById(editorId) {\n return Editor.instances.get(editorId);\n }\n\n /**\n * Get all editor instances\n * @returns {Map} Map of all editor instances\n */\n static getAllInstances() {\n return Editor.instances;\n }\n\n /**\n * Destroy popup instances for this editor\n */\n destroyPopupInstances() {\n this.popupInstances.forEach((popupInstance, popupType) => {\n if (popupInstance && typeof popupInstance.destroy === 'function') {\n popupInstance.destroy();\n }\n });\n this.popupInstances.clear();\n }\n\n /**\n * Update placeholder visibility based on editor content\n */\n updatePlaceholderVisibility() {\n // Use isEditorEmpty() (text AND media) rather than textContent alone, so an\n // image/table-only editor doesn't keep showing the placeholder.\n if (this.isEditorEmpty()) {\n this.editor.classList.add('placeholder-visible');\n } else {\n this.editor.classList.remove('placeholder-visible');\n }\n }\n\n /**\n * Destroy editor\n */\n destroy() {\n // Remove active-tracking listeners\n if (this._markActive) {\n this.wrapper.removeEventListener('pointerdown', this._markActive, true);\n this.wrapper.removeEventListener('focusin', this._markActive, true);\n this._markActive = null;\n }\n\n // Remove the document-level selection listener\n if (this._onDocSelectionChange) {\n document.removeEventListener('selectionchange', this._onDocSelectionChange);\n this._onDocSelectionChange = null;\n }\n if (this._fmtCache) this._fmtCache.clear();\n\n // Cancel any pending autosave write\n clearTimeout(this._autosaveTimer);\n\n // Destroy all modules\n this.modules.forEach(module => {\n if (typeof module.destroy === 'function') {\n module.destroy();\n }\n });\n\n // Destroy popup instances\n this.destroyPopupInstances();\n\n // Remove DOM elements\n if (this.wrapper && this.wrapper.parentNode) {\n this.wrapper.parentNode.removeChild(this.wrapper);\n }\n\n // Clear references\n this.modules.clear();\n this.formats.clear();\n this.events.clear(); // Clear events\n \n // Remove from instances map\n Editor.instances.delete(this.instanceId);\n \n // Clear current instance if this was the current one\n if (Editor.currentInstance === this) {\n Editor.currentInstance = null;\n }\n }\n} ","import { execFormat, queryFormatState } from '../utils/exec-command.js';\n\n/**\n * Base Format class - Inspired by Quill's architecture\n * All text formats should extend this class\n */\nexport class Format {\n static formatName = '';\n static tagName = '';\n static className = '';\n\n constructor(domNode) {\n this.domNode = domNode;\n }\n\n /**\n * Create a new format node\n * @param {*} value - Format value\n * @returns {HTMLElement}\n */\n static create(value) {\n const node = document.createElement(this.tagName);\n if (this.className) {\n node.className = this.className;\n }\n return node;\n }\n\n getOffsetWithin(container, range) {\n let offset = 0;\n const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);\n let currentNode;\n\n while ((currentNode = walker.nextNode())) {\n if (currentNode === range.startContainer) {\n return offset + range.startOffset;\n }\n offset += currentNode.textContent.length;\n }\n\n return offset;\n }\n\n /**\n * Check if format is active at current selection\n * @returns {boolean}\n */\n} \n\n/**\n * Inline Format - for formats like bold, italic, underline\n * Handles inline formatting that wraps text within the same line/block\n */\nexport class InlineFormat extends Format {\n /**\n * Create inline format element\n * @param {*} value - Format value\n * @returns {HTMLElement}\n */\n static create(value) {\n const node = super.create(value);\n return node;\n }\n\n /**\n * Apply inline format to selection\n * Wraps selected text or inserts format marker at cursor\n * @param {*} value - Format value\n */\n apply(value) {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0); \n if (range.collapsed) {\n // No selection - insert format marker at cursor\n const formatNode = this.constructor.create(value);\n formatNode.appendChild(document.createTextNode('\\u200B')); // Zero-width space\n range.insertNode(formatNode);\n \n // Position cursor inside the format node\n const newRange = document.createRange();\n newRange.setStart(formatNode.firstChild, 1);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n // Has selection - wrap selected content\n const contents = range.extractContents();\n const formatNode = this.constructor.create(value);\n formatNode.appendChild(contents);\n range.insertNode(formatNode);\n \n // Select the formatted content\n const newRange = document.createRange();\n newRange.selectNodeContents(formatNode);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n }\n\n /**\n * Remove inline format from selection\n * Unwraps formatted content or removes format at cursor\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n \n if (range.collapsed) {\n // Handle cursor position\n this.removeAtCursor(range, selection);\n } else {\n // Handle selection\n this.removeFromSelection(range, selection);\n }\n }\n\n /**\n * Remove format at cursor position\n * @param {Range} range - Current range\n * @param {Selection} selection - Current selection\n */\n removeAtCursor(range, selection) {\n const container = range.startContainer;\n const formatNode = this.findFormatNode(container);\n \n if (!formatNode || !formatNode.parentNode) return;\n\n const text = formatNode.textContent;\n const absoluteOffset = this.getOffsetWithin(formatNode, range);\n\n // Split the format node at cursor position\n const beforeText = text.slice(0, absoluteOffset);\n const afterText = text.slice(absoluteOffset);\n \n const fragment = document.createDocumentFragment();\n \n if (beforeText) {\n const beforeNode = formatNode.cloneNode(false);\n beforeNode.textContent = beforeText;\n fragment.appendChild(beforeNode);\n }\n \n // Insert zero-width space as marker\n const zwspNode = document.createTextNode('\\u200B');\n fragment.appendChild(zwspNode);\n \n if (afterText) {\n const afterNode = formatNode.cloneNode(false);\n afterNode.textContent = afterText;\n fragment.appendChild(afterNode);\n }\n\n formatNode.replaceWith(fragment);\n\n // Position cursor after the marker\n const newRange = document.createRange();\n newRange.setStartAfter(zwspNode);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n\n /**\n * Remove format from selection\n */\n removeFromSelection(range, selection) {\n const formatName = this.constructor.formatName;\n execFormat(formatName);\n if (formatName === 'strike') {\n execFormat('strikeThrough');\n }\n }\n\n\n\n /**\n * Find the format node containing the given node\n * @param {Node} node - DOM node\n * @returns {Element|null} Format node\n */\n findFormatNode(node) {\n while (node && node !== document.body) {\n if (node.nodeType === Node.ELEMENT_NODE && \n node.tagName === this.constructor.tagName) {\n return node;\n }\n node = node.parentNode;\n }\n return null;\n }\n\n /**\n * Find all format nodes within a range\n * @param {Range} range - Selection range\n * @returns {Element[]} Array of format nodes\n */\n findFormatNodesInRange(range) {\n const nodes = [];\n const walker = document.createTreeWalker(\n range.commonAncestorContainer,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node) => {\n if (node.tagName === this.constructor.tagName && \n range.intersectsNode(node)) {\n return NodeFilter.FILTER_ACCEPT;\n }\n return NodeFilter.FILTER_SKIP;\n }\n }\n );\n\n let node;\n while ((node = walker.nextNode())) {\n nodes.push(node);\n }\n\n return nodes;\n }\n\n /**\n * Check if inline format is active at current selection\n * @returns {boolean}\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n let node = range.startContainer;\n\n const tagName = this.constructor.tagName;\n const altTags = this.constructor.alternativeTagNames || [];\n const formatName = this.constructor.formatName;\n\n // Đặc biệt với một số lệnh hỗ trợ execCommand\n const commandSupported = ['bold', 'italic', 'underline'];\n if (commandSupported.includes(formatName?.toLowerCase())) {\n return queryFormatState(formatName);\n }\n\n // Kiểm tra DOM tag\n while (node && node !== document.body) {\n if (\n node.nodeType === Node.ELEMENT_NODE &&\n (node.tagName === tagName ||\n altTags.includes(node.tagName))\n ) {\n return true;\n }\n node = node.parentNode;\n }\n\n return false;\n }\n\n}\n\n/**\n * Block Format - for formats like headers, paragraphs, alignment\n * Handles block-level formatting that affects entire blocks/paragraphs\n */\nexport class BlockFormat extends Format {\n /**\n * Create block format element\n * @param {*} value - Format value\n * @returns {HTMLElement}\n */\n static create(value) {\n const node = super.create(value);\n return node;\n }\n\n /**\n * Apply block format to selection\n * Converts current block(s) or creates new block\n * @param {*} value - Format value\n */\n apply(value) {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const blocks = this.getBlockElements(range);\n\n if (blocks.length === 0) {\n // No block found - create new one\n this.createBlockAtCursor(range, value);\n } else {\n // Convert existing blocks\n blocks.forEach(block => {\n this.convertBlock(block, value);\n });\n }\n }\n\n /**\n * Remove block format from selection\n * Converts blocks back to default (paragraph) or removes formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const blocks = this.getBlockElements(range);\n\n blocks.forEach(block => {\n this.removeBlockFormat(block);\n });\n }\n\n /**\n * Create new block at cursor position\n * @param {Range} range - Current range\n * @param {*} value - Format value\n */\n createBlockAtCursor(range, value) {\n const blockNode = this.constructor.create(value);\n \n // Try to preserve style from existing block if cursor is inside one\n const existingBlock = this.getBlockElement(range.startContainer);\n if (existingBlock && existingBlock.style && existingBlock.style.cssText) {\n blockNode.style.cssText = existingBlock.style.cssText;\n }\n \n if (range.collapsed) {\n // No selection - create empty block\n blockNode.appendChild(document.createTextNode(''));\n range.insertNode(blockNode);\n \n // Position cursor inside the block\n const newRange = document.createRange();\n newRange.setStart(blockNode, 0);\n newRange.collapse(true);\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n // Has selection - wrap in block\n const contents = range.extractContents();\n blockNode.appendChild(contents);\n range.insertNode(blockNode);\n \n // Select the content in the block\n const newRange = document.createRange();\n newRange.selectNodeContents(blockNode);\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n }\n\n /**\n * Convert existing block to new format\n * @param {Element} block - Block element to convert\n * @param {*} value - Format value\n */\n convertBlock(block, value) {\n const newBlock = this.constructor.create(value);\n \n // Copy all child nodes\n while (block.firstChild) {\n newBlock.appendChild(block.firstChild);\n }\n \n // Copy relevant attributes\n if (block.className && this.shouldPreserveClass(block.className)) {\n newBlock.className = block.className;\n }\n \n // Copy style attributes to preserve formatting like text-align\n if (block.style && block.style.cssText) {\n newBlock.style.cssText = block.style.cssText;\n }\n \n // Replace the block\n block.parentNode.replaceChild(newBlock, block);\n }\n\n /**\n * Remove block format (convert to paragraph)\n * @param {Element} block - Block element\n */\n removeBlockFormat(block) {\n const paragraph = document.createElement('P');\n \n // Move all child nodes to paragraph\n while (block.firstChild) {\n paragraph.appendChild(block.firstChild);\n }\n \n // Copy style attributes to preserve formatting like text-align\n if (block.style && block.style.cssText) {\n paragraph.style.cssText = block.style.cssText;\n }\n \n // Replace the block\n block.parentNode.replaceChild(paragraph, block);\n }\n\n /**\n * Get block elements in range\n * @param {Range} range - Selection range\n * @returns {Element[]} Array of block elements\n */\n getBlockElements(range) {\n const blocks = [];\n let startBlock = this.getBlockElement(range.startContainer);\n let endBlock = this.getBlockElement(range.endContainer);\n\n // Nếu endBlock ngay sau startBlock và selection kết thúc ở vị trí 0 của endBlock\n if (startBlock && endBlock && startBlock.nextElementSibling === endBlock) {\n const endAtStartOfEndBlock =\n range.endContainer === endBlock &&\n range.endOffset === 0;\n if (endAtStartOfEndBlock) {\n endBlock = startBlock;\n }\n }\n\n if (startBlock === endBlock) {\n if (startBlock) blocks.push(startBlock);\n } else {\n // Multiple blocks\n let current = startBlock;\n while (current && current !== endBlock) {\n if (this.isBlockElement(current)) {\n blocks.push(current);\n }\n current = this.getNextBlockElement(current);\n }\n if (endBlock && this.isBlockElement(endBlock)) {\n blocks.push(endBlock);\n }\n }\n\n return blocks.filter((block, index, self) =>\n self.indexOf(block) === index\n );\n }\n\n /**\n * Get block element containing node\n * @param {Node} node - DOM node\n * @returns {Element|null} Block element\n */\n getBlockElement(node) {\n while (node && node.nodeType !== Node.ELEMENT_NODE) {\n node = node.parentNode;\n }\n\n while (node && node !== document.body) {\n if (this.isBlockElement(node)) {\n return node;\n }\n node = node.parentNode;\n }\n\n return null;\n }\n\n /**\n * Check if element is a block element\n * @param {Element} element - DOM element\n * @returns {boolean}\n */\n isBlockElement(element) {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;\n \n const blockTags = [\n 'P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', \n 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI', 'SECTION', 'ARTICLE'\n ];\n return blockTags.includes(element.tagName.toUpperCase());\n }\n\n /**\n * Get next block element\n * @param {Element} element - Current element\n * @returns {Element|null} Next block element\n */\n getNextBlockElement(element) {\n let next = element.nextElementSibling;\n while (next) {\n if (this.isBlockElement(next)) {\n return next;\n }\n next = next.nextElementSibling;\n }\n return null;\n }\n\n /**\n * Check if block format is active at current selection\n * @param {*} value - Optional specific value to check\n * @returns {boolean}\n */\n isActive(value = null) {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n const block = this.getBlockElement(range.startContainer);\n \n if (!block) return false;\n\n // Check if block matches our format\n if (block.tagName === this.constructor.tagName) {\n return value ? this.hasValue(block, value) : true;\n }\n\n return false;\n }\n\n /**\n * Check if block has specific value\n * Override in subclasses for specific value checking\n * @param {Element} block - Block element\n * @param {*} value - Value to check\n * @returns {boolean}\n */\n hasValue(block, value) {\n return true; // Default implementation\n }\n\n /**\n * Check if class should be preserved during conversion\n * Override in subclasses for specific class handling\n * @param {string} className - Class name\n * @returns {boolean}\n */\n shouldPreserveClass(className) {\n return false; // Default: don't preserve classes\n }\n} ","/**\n * renderStatic — paint stored HTML into a read-only view that looks exactly\n * like the editor's content area, without booting an editor.\n *\n * It sanitizes the HTML (same allowlist the editor uses on paste/setContent)\n * and tags the host element with `.yjd-content`, the class the stylesheet uses\n * to style typography, lists, tables, images, and mention tokens. Load the\n * editor stylesheet (or StylesLoader.loadStyles()) on the page for it to match.\n *\n * import { renderStatic } from '@oix1987/yjd/core';\n * renderStatic(post.body_html, document.querySelector('#post'));\n *\n * @param {string} html Stored HTML (untrusted — it is sanitized).\n * @param {Element} [target] Element to render into. If omitted, a fresh\n * <div.yjd-content> is created and returned.\n * @returns {Element} the element the content was rendered into.\n */\nimport { sanitizeHtml } from './utils/sanitize.js';\n\nexport function renderStatic(html, target) {\n const safe = sanitizeHtml(html || '');\n const el = target || document.createElement('div');\n el.classList.add('yjd-content');\n el.innerHTML = safe;\n return el;\n}\n\nexport default renderStatic;\n","import Editor from '../core/editor.js';\n\n/**\n * Helper function to save history state before applying format\n * This should be called by all format operations to ensure proper undo/redo functionality\n */\nexport function saveBeforeFormat() {\n const editor = Editor.getCurrentInstance();\n if (editor) {\n const historyModule = editor.getModule('history');\n if (historyModule && typeof historyModule.saveBeforeFormat === 'function') {\n historyModule.saveBeforeFormat();\n }\n }\n}\n\n/**\n * Helper function to trigger content change after format operations\n * This ensures onChange callback is called when formatting is applied\n */\nexport function triggerContentChange() {\n const editor = Editor.getCurrentInstance();\n if (editor && typeof editor.onContentChange === 'function') {\n // Use setTimeout to ensure the DOM changes are complete\n setTimeout(() => {\n editor.onContentChange();\n }, 0);\n }\n}\n\n/**\n * Helper function to save before format and trigger content change\n * This is a convenience function that combines both operations\n */\nexport function saveBeforeFormatAndTriggerChange() {\n saveBeforeFormat();\n triggerContentChange();\n}\n\n/**\n * Helper function to check if history module is available\n */\nexport function hasHistoryModule() {\n const editor = Editor.getCurrentInstance();\n if (editor) {\n const historyModule = editor.getModule('history');\n return historyModule && typeof historyModule.saveBeforeFormat === 'function';\n }\n return false;\n} ","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport { execFormat, queryFormatState } from '../utils/exec-command.js';\n\n/**\n * Bold Format - Handles bold text formatting\n */\nclass Bold extends InlineFormat {\n static formatName = 'bold';\n static tagName = 'B';\n static alternativeTagNames = ['STRONG'];\n\n /**\n * Apply bold formatting\n */\n apply() {\n // Save state before applying format\n saveBeforeFormat();\n execFormat('bold');\n }\n\n /**\n * Remove bold formatting\n */\n remove() {\n execFormat('bold');\n }\n\n /**\n * Toggle bold formatting\n */\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n execFormat('bold');\n }\n\n /**\n * Check if bold formatting is active\n */\n isActive() {\n return queryFormatState('bold');\n }\n\n\n}\n\n\nexport default Bold;\n","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\n\n/**\n * Italic Format - Handles italic text formatting\n */\nclass Italic extends InlineFormat {\n static formatName = 'italic';\n static tagName = 'I';\n static alternativeTagNames = ['EM'];\n\n /**\n * Toggle italic formatting\n */\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n\n if (this.isActive()) {\n this.remove();\n } else {\n this.apply();\n }\n }\n}\n\nexport default Italic;\n","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\n\n/**\n * Underline Format - Handles underline text formatting\n * Extracted from FormatManager.js logic\n */\nclass Underline extends InlineFormat {\n static formatName = 'underline';\n static tagName = 'U';\n static alternativeTagNames = ['SPAN'];\n\n /**\n * Toggle underline formatting\n */\n\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n\n if (this.isActive()) {\n this.remove();\n } else {\n this.apply();\n }\n }\n\n}\n\nexport default Underline; ","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\n\n/**\n * Strike Format - Handles strikethrough text formatting\n * Extracted from FormatManager.js logic\n */\nclass Strike extends InlineFormat {\n static formatName = 'strike';\n static tagName = 'S';\n static alternativeTagNames = ['STRIKE', 'DEL'];\n\n /**\n * Toggle strikethrough formatting\n */\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n\n if (this.isActive()) {\n this.remove();\n } else {\n this.apply();\n }\n }\n\n\n \n}\n\nexport default Strike; ","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport registry from '../core/registry.js';\n/**\n * Subscript Format - Handles subscript text formatting\n * Creates <sub> elements for subscript text\n */\nclass Subscript extends InlineFormat {\n static formatName = 'subscript';\n static tagName = 'SUB';\n\n removeSuperscriptBeforeApply() {\n // Resolved via registry (not a static import) to avoid a circular\n // dependency between subscript.js and superscript.js.\n const Superscript = registry.get('formats/superscript');\n if (!Superscript) return;\n const superscript = new Superscript();\n if (superscript.isActive()) {\n superscript.remove();\n }\n }\n /**\n * Toggle subscript formatting\n */\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n\n if (this.isActive()) {\n this.remove();\n } else {\n this.removeSuperscriptBeforeApply();\n this.apply();\n }\n }\n\n\n}\n\nexport default Subscript; ","import { InlineFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport registry from '../core/registry.js';\n/**\n * Superscript Format - Handles superscript text formatting\n * Creates <sup> elements for superscript text\n */\nclass Superscript extends InlineFormat {\n static formatName = 'superscript';\n static tagName = 'SUP';\n\n /**\n * Toggle superscript formatting\n */\n removeSubscriptBeforeApply() {\n // Resolved via registry (not a static import) to avoid a circular\n // dependency between superscript.js and subscript.js.\n const Subscript = registry.get('formats/subscript');\n if (!Subscript) return;\n const subscript = new Subscript();\n if (subscript.isActive()) {\n subscript.remove();\n }\n }\n toggle() {\n // Save state before applying format\n saveBeforeFormat();\n\n if (this.isActive()) {\n this.remove();\n } else {\n // Ensure mutual exclusivity: remove subscript before applying superscript\n this.removeSubscriptBeforeApply();\n this.apply();\n }\n }\n}\n\nexport default Superscript; ","/**\n * Popup Helper Utility\n * Helps popups append to the yjd-rich-editor instead of document.body\n * Now supports multiple editor instances with separate popup containers\n */\nimport Editor from '../core/editor.js';\n\n/**\n * Get the appropriate container for popups\n * @param {string} editorId - Optional editor instance ID\n * @returns {HTMLElement} Container element for popups\n */\nexport function getPopupContainer(editorId = null) {\n let editor;\n \n if (editorId) {\n // Get specific editor instance\n editor = Editor.getInstanceById(editorId);\n } else {\n // Try to get current editor instance\n editor = Editor.getCurrentInstance();\n }\n \n if (editor) {\n return editor.getPopupContainer();\n }\n \n // Fallback to document.body if no editor instance\n return document.body;\n}\n\n/**\n * Append popup to the appropriate container\n * @param {HTMLElement} popup - Popup element to append\n * @param {string} editorId - Optional editor instance ID\n */\nexport function appendPopup(popup, editorId = null) {\n const container = getPopupContainer(editorId);\n \n // Remove from current parent if exists\n if (popup.parentNode) {\n popup.parentNode.removeChild(popup);\n }\n \n container.appendChild(popup);\n \n // Note: pointer-events are now controlled by CSS rules\n // Popup containers have pointer-events: none by default\n // Interactive elements inside popups have pointer-events: auto\n}\n\n/**\n * Get popup dimensions by temporarily showing it if needed\n * @param {HTMLElement} popup - Popup element\n * @returns {Object} Object with width and height\n */\nfunction getPopupDimensions(popup) {\n if (!popup) return { width: 300, height: 200 };\n \n // Try getBoundingClientRect first\n const rect = popup.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n return { width: rect.width, height: rect.height };\n }\n \n // Try offsetWidth/offsetHeight\n if (popup.offsetWidth > 0 && popup.offsetHeight > 0) {\n return { width: popup.offsetWidth, height: popup.offsetHeight };\n }\n \n // Check if popup is hidden\n const computedStyle = window.getComputedStyle(popup);\n const isHidden = computedStyle.display === 'none' || computedStyle.visibility === 'hidden';\n \n if (isHidden) {\n // Temporarily show popup to get dimensions\n const originalDisplay = popup.style.display;\n const originalVisibility = popup.style.visibility;\n const originalPosition = popup.style.position;\n const originalTop = popup.style.top;\n const originalLeft = popup.style.left;\n const originalZIndex = popup.style.zIndex;\n \n // Make popup visible but off-screen\n popup.style.display = 'block';\n popup.style.visibility = 'visible';\n popup.style.position = 'absolute';\n popup.style.top = '-9999px';\n popup.style.left = '-9999px';\n popup.style.zIndex = '-1';\n \n // Force reflow\n popup.offsetHeight;\n \n // Get dimensions\n const tempRect = popup.getBoundingClientRect();\n const width = tempRect.width > 0 ? tempRect.width : 300;\n const height = tempRect.height > 0 ? tempRect.height : 200;\n \n // Restore original styles\n popup.style.display = originalDisplay;\n popup.style.visibility = originalVisibility;\n popup.style.position = originalPosition;\n popup.style.top = originalTop;\n popup.style.left = originalLeft;\n popup.style.zIndex = originalZIndex;\n \n return { width, height };\n }\n \n // Last resort: try computed styles\n const computedWidth = parseInt(computedStyle.width);\n const computedHeight = parseInt(computedStyle.height);\n \n return {\n width: computedWidth > 0 ? computedWidth : 300,\n height: computedHeight > 0 ? computedHeight : 200\n };\n}\n\n/**\n * Calculate position for popup relative to anchor element\n * @param {HTMLElement} anchor - Anchor element\n * @param {HTMLElement} popup - Popup element\n * @param {Object} options - Positioning options\n * @returns {Object} Position object with top and left values\n */\nexport function calculatePopupPosition(anchor, popup, options = {}) {\n const {\n offsetX = 0,\n offsetY = 5,\n preferTop = false,\n preferLeft = false\n } = options;\n\n const anchorRect = anchor.getBoundingClientRect();\n const container = getPopupContainer();\n const isInWrapper = container.classList.contains('rich-editor-popup-container');\n \n let top, left;\n \n if (isInWrapper) {\n // Position relative to wrapper\n const wrapperRect = container.getBoundingClientRect();\n \n // Calculate position relative to wrapper\n top = anchorRect.top - wrapperRect.top + anchorRect.height + offsetY;\n left = anchorRect.left - wrapperRect.left + offsetX;\n \n // Get popup dimensions using the helper function\n const { width: popupWidth, height: popupHeight } = getPopupDimensions(popup);\n\n \n // Check if popup would overflow bottom of wrapper\n if (top + popupHeight > wrapperRect.height && !preferTop) {\n // Try to position above the anchor\n const topPosition = anchorRect.top - wrapperRect.top - popupHeight - offsetY;\n if (topPosition >= 0) {\n top = topPosition;\n } else {\n // If still doesn't fit, try to center it vertically within the wrapper\n top = Math.max(offsetY, (wrapperRect.height - popupHeight) / 2);\n }\n }\n \n // Check if popup would overflow right of wrapper\n if (left + popupWidth + 5 > wrapperRect.width && !preferLeft) {\n left = wrapperRect.width - popupWidth - offsetX -15;\n }\n \n // Ensure popup doesn't go off-screen\n if (left < 0) left = offsetX;\n if (top < 0) top = offsetY;\n \n } else {\n // Fallback to document.body positioning\n top = anchorRect.bottom + window.scrollY + offsetY;\n left = anchorRect.left + window.scrollX + offsetX;\n\n \n // Get popup dimensions using the helper function\n const { width: popupWidth, height: popupHeight } = getPopupDimensions(popup);\n \n // Check if popup would overflow right edge\n if (left + popupWidth > window.innerWidth && !preferLeft) {\n left = window.innerWidth - popupWidth - offsetX;\n }\n \n // Check if popup would overflow bottom edge\n if (top + popupHeight > window.innerHeight + window.scrollY && !preferTop) {\n // Try to position above the anchor\n const topPosition = anchorRect.top + window.scrollY - popupHeight - offsetY;\n if (topPosition >= window.scrollY) {\n top = topPosition;\n } else {\n // If still doesn't fit, try to center it vertically within the viewport\n top = Math.max(window.scrollY + offsetY, window.scrollY + (window.innerHeight - popupHeight) / 2);\n }\n }\n \n // Ensure popup doesn't go off-screen\n if (left < 0) left = offsetX;\n if (top < 0) top = offsetY;\n }\n \n return { top, left };\n}\n\n/**\n * Set popup position\n * @param {HTMLElement} popup - Popup element\n * @param {Object} position - Position object with top and left values\n */\nexport function setPopupPosition(popup, position) {\n popup.style.position = 'absolute';\n popup.style.top = `${position.top}px`;\n popup.style.left = `${position.left}px`;\n popup.style.zIndex = '1000';\n}\n","/**\n * Color Picker Component - Simple color picker with popup\n */\nimport IconUtils from './icons.js';\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass ColorPicker {\n constructor(options = {}) {\n this.options = {\n colors: [\n '#000000', '#333333', '#666666', '#999999', '#cccccc', '#eeeeee',\n '#ff0000', '#ff6600', '#ffcc00', '#ffff00', '#99ff00', '#00ff00',\n '#00ffcc', '#00ccff', '#0066ff', '#0000ff', '#6600ff', '#cc00ff',\n '#ff00cc', '#ff0066', '#800000', '#ff8000', '#808000', '#008000',\n '#008080', '#0080ff', '#004080', '#800080', '#804080', '#ff0080'\n ],\n customColorEnabled: true,\n onColorSelect: null,\n editor: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.currentColor = '#000000';\n this.clickOutsideHandler = null;\n \n this.createColorPicker();\n }\n\n /**\n * Create color picker popup\n */\n createColorPicker() {\n // Create popup\n this.popup = document.createElement('div');\n this.popup.className = 'color-picker-popup';\n \n // Create color grid\n this.createColorGrid();\n \n // Create custom color input if enabled\n if (this.options.customColorEnabled) {\n this.createCustomColorInput();\n }\n \n // Add popup to container\n appendPopup(this.popup);\n \n // Prevent focus loss when clicking on popup\n if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {\n this.options.editor.preventFocusLoss(this.popup);\n }\n }\n\n /**\n * Create color grid\n */\n createColorGrid() {\n const grid = document.createElement('div');\n grid.className = 'color-grid';\n \n this.options.colors.forEach(color => {\n const colorButton = document.createElement('button');\n colorButton.type = 'button';\n colorButton.className = 'color-button';\n colorButton.style.backgroundColor = color;\n colorButton.dataset.color = color;\n colorButton.title = color;\n \n colorButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectColor(color);\n // Maintain editor focus after color selection\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n grid.appendChild(colorButton);\n });\n \n this.popup.appendChild(grid);\n }\n\n /**\n * Create custom color input\n */\n createCustomColorInput() {\n const customContainer = document.createElement('div');\n customContainer.className = 'custom-color-container';\n \n // No color button\n const noColorButton = document.createElement('button');\n noColorButton.type = 'button';\n noColorButton.className = 'color-button no-color-button';\n noColorButton.title = 'No Color';\n noColorButton.style.backgroundColor = 'transparent';\n \n // Add icon to button\n const noColorIcon = IconUtils.createIconElement('no-color', {\n width: '24',\n height: '24'\n });\n noColorButton.appendChild(noColorIcon);\n \n noColorButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectColor('transparent');\n // Maintain editor focus after color selection\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n // White button\n const whiteButton = document.createElement('button');\n whiteButton.type = 'button';\n whiteButton.className = 'color-button white-button';\n whiteButton.style.backgroundColor = '#ffffff';\n whiteButton.style.border = '1px solid #ccc';\n whiteButton.title = 'White';\n \n whiteButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectColor('#ffffff');\n // Maintain editor focus after color selection\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n // Black button\n const blackButton = document.createElement('button');\n blackButton.type = 'button';\n blackButton.className = 'color-button black-button';\n blackButton.style.backgroundColor = '#000000';\n blackButton.title = 'Black';\n \n blackButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectColor('#000000');\n // Maintain editor focus after color selection\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n // Custom color button with hidden input\n const customColorButton = document.createElement('button');\n customColorButton.type = 'button';\n customColorButton.className = 'color-button custom-color-button';\n customColorButton.title = 'Custom Color';\n customColorButton.style.backgroundColor = 'transparent';\n customColorButton.style.border = '1px solid #ccc';\n customColorButton.style.font = 'none !important';\n // Add icon to button\n const iconElement = IconUtils.createIconElement('custom-color', {\n width: '16px',\n height: '16px'\n });\n customColorButton.appendChild(iconElement);\n \n const customInput = document.createElement('input');\n customInput.type = 'color';\n customInput.className = 'custom-color-input';\n customInput.value = this.currentColor;\n customInput.style.visibility = 'hidden';\n customInput.style.pointerEvents = 'none'; // ngăn không cho click\n customInput.style.opacity = '0'; // ẩn hẳn về mặt thị giác\n customColorButton.addEventListener('click', (e) => {\n customInput.style.visibility = 'visible';\n customInput.style.pointerEvents = 'auto';\n customInput.style.opacity = '1';\n e.preventDefault();\n e.stopPropagation();\n customInput.click();\n\n });\n \n customInput.addEventListener('change', (e) => {\n customInput.style.visibility = 'hidden';\n customInput.style.pointerEvents = 'none'; // ngăn không cho click\n customInput.style.opacity = '0'; // ẩn hẳn về mặt thị giác\n this.selectColor(e.target.value);\n // Maintain editor focus after color selection\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n customContainer.appendChild(noColorButton);\n customContainer.appendChild(whiteButton);\n customContainer.appendChild(blackButton);\n customContainer.appendChild(customColorButton);\n customContainer.appendChild(customInput);\n \n this.popup.appendChild(customContainer);\n }\n\n /**\n * Setup click outside handler\n */\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n // Add slight delay to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n /**\n * Remove click outside handler\n */\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n /**\n * Show color picker popup\n * @param {HTMLElement} anchor - Element to position popup relative to\n */\n show(anchor) {\n if (!anchor) return;\n \n // Ensure popup is in DOM\n if (!document.body.contains(this.popup)) {\n appendPopup(this.popup);\n }\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup by adding visible class\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Setup click outside handler\n this.setupClickOutside();\n }\n\n /**\n * Hide color picker popup\n */\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n /**\n * Select color and trigger callback\n * @param {string} color - Selected color\n */\n selectColor(color) {\n this.currentColor = color;\n \n if (this.options.onColorSelect) {\n this.options.onColorSelect(color);\n }\n \n this.hide();\n }\n\n /**\n * Destroy color picker\n */\n destroy() {\n this.removeClickOutside();\n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n }\n}\n\nexport default ColorPicker; ","import { InlineFormat } from '../core/format.js';\nimport ColorPicker from '../ui/color-picker.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\nimport { execFormat, setStyleWithCSS } from '../utils/exec-command.js';\n\n/**\n * Color Format - Handles text color formatting\n */\nclass Color extends InlineFormat {\n static formatName = 'color';\n static tagName = 'SPAN';\n static attribute = 'color';\n\n // Selection saved when the picker opens, restored before applying — so the\n // colour still lands on the right text after a tap clears the live selection\n // (mobile/touch) or focus moves to the picker.\n static savedRanges = new Map();\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Color format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a color picker instance\n let colorPicker = currentEditor.getPopupInstance('color');\n \n if (!colorPicker) {\n // Create new color picker instance for this editor\n const editorId = this.editorId;\n colorPicker = new ColorPicker({\n onColorSelect: (color) => {\n Color.applyColorToCurrentSelection(color, editorId);\n },\n editor: Editor.getCurrentInstance()\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('color', colorPicker);\n }\n \n this.colorPicker = colorPicker;\n }\n\n /**\n * Static method to apply color to current selection\n */\n static applyColorToCurrentSelection(color, editorId = null) {\n const selection = window.getSelection();\n // Restore the selection captured when the picker opened (a tap on the\n // picker may have collapsed the live selection, especially on mobile).\n const saved = editorId != null ? Color.savedRanges.get(editorId) : null;\n if (saved) {\n selection.removeAllRanges();\n selection.addRange(saved);\n }\n if (editorId != null) Color.savedRanges.delete(editorId);\n\n if (!selection || !selection.rangeCount || selection.isCollapsed) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n setStyleWithCSS(true);\n // 'transparent' is the picker's \"reset to default\" entry — clear the colour\n // back to the editor default rather than painting an explicit colour.\n execFormat('foreColor', color === 'transparent' ? 'inherit' : color);\n\n // Refresh toolbar state (active highlight + swatch) and notify listeners.\n setTimeout(() => {\n const currentEditor = Editor.getCurrentInstance();\n if (currentEditor) {\n if (typeof currentEditor.updateToolbarButtonStates === 'function') {\n currentEditor.updateToolbarButtonStates();\n }\n if (typeof currentEditor.onContentChange === 'function') {\n currentEditor.onContentChange();\n }\n }\n }, 0);\n }\n\n /**\n * Toggle color formatting - shows/hides color picker\n */\n toggle() {\n if (this.colorPicker.isVisible) {\n this.colorPicker.hide();\n } else {\n this.showColorPicker();\n }\n }\n\n /**\n * Show color picker positioned relative to color button on toolbar\n */\n showColorPicker() {\n // Find color button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n\n // Capture the current selection so we can apply the colour to it even if a\n // tap on the picker clears the live selection. Fall back to the editor's\n // last non-collapsed range (mobile clears the selection on touchstart).\n const sel = window.getSelection();\n if (sel && sel.rangeCount && !sel.isCollapsed) {\n Color.savedRanges.set(this.editorId, sel.getRangeAt(0).cloneRange());\n } else if (editor._lastRange) {\n Color.savedRanges.set(this.editorId, editor._lastRange.cloneRange());\n }\n\n const toolbar = editor.getModule('toolbar');\n let colorButton = null;\n \n if (toolbar) {\n colorButton = toolbar.getButton('color');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!colorButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n colorButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.color-btn');\n }\n }\n \n // Final fallback: find any color button in the current editor's wrapper\n if (!colorButton) {\n colorButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.color-btn');\n }\n \n if (!colorButton) {\n console.warn('Color button not found for editor:', this.editorId);\n return;\n }\n \n this.colorPicker.show(colorButton);\n }\n\n /**\n * Check if color formatting is active in current selection\n */\n isActive() {\n return !!Color.getCurrentColor();\n }\n\n /**\n * Return the explicit text colour applied at the current selection, or null\n * when the text uses the editor's default colour. We look for an EXPLICIT\n * inline colour (the `<span style=\"color:…\">` / `<font color>` that the\n * editor inserts) rather than comparing the computed colour to a hardcoded\n * default — the default depends on the active theme, so a hardcoded compare\n * made the button look permanently \"active\".\n */\n static getCurrentColor() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n let node = selection.getRangeAt(0).startContainer;\n if (node.nodeType === Node.TEXT_NODE) node = node.parentNode;\n\n while (node && node.nodeType === Node.ELEMENT_NODE) {\n if (node.classList && node.classList.contains('rich-editor-area')) break;\n if (node.style && node.style.color && node.style.color !== 'inherit') {\n return node.style.color;\n }\n if (node.tagName === 'FONT' && node.getAttribute('color')) {\n return node.getAttribute('color');\n }\n node = node.parentNode;\n }\n return null;\n }\n}\n\nexport default Color; ","import { InlineFormat } from '../core/format.js';\nimport ColorPicker from '../ui/color-picker.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\nimport { execFormat, setStyleWithCSS } from '../utils/exec-command.js';\n\n/**\n * Background Color Format - Handles text background color formatting\n * Now supports multiple editor instances with separate popup instances\n */\nclass Background extends InlineFormat {\n static formatName = 'background';\n static tagName = 'SPAN';\n static attribute = 'background-color';\n\n // Selection saved when the picker opens, restored before applying (see Color).\n static savedRanges = new Map();\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Background format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a background color picker instance\n let colorPicker = currentEditor.getPopupInstance('background');\n \n if (!colorPicker) {\n // Create new color picker instance for this editor\n colorPicker = new ColorPicker({\n onColorSelect: (color) => {\n Background.applyBackgroundToCurrentSelection(color, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('background', colorPicker);\n }\n \n this.colorPicker = colorPicker;\n }\n\n /**\n * Create a new Background format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Background} Background format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Background();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Static method to apply background color to current selection\n * @param {string} color - Background color value\n * @param {string} editorId - Editor instance ID\n */\n static applyBackgroundToCurrentSelection(color, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for background color application');\n return;\n }\n \n const selection = window.getSelection();\n // Restore the selection captured when the picker opened (a tap on the\n // picker may have collapsed the live selection, especially on mobile).\n const saved = editorId != null ? Background.savedRanges.get(editorId) : null;\n if (saved) {\n selection.removeAllRanges();\n selection.addRange(saved);\n }\n if (editorId != null) Background.savedRanges.delete(editorId);\n\n if (!selection || !selection.rangeCount || selection.isCollapsed) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n setStyleWithCSS(true);\n execFormat('backColor', color === 'transparent' ? 'inherit' : color);\n\n // Refresh toolbar state (active highlight + swatch) and notify listeners.\n setTimeout(() => {\n if (editor) {\n if (typeof editor.updateToolbarButtonStates === 'function') {\n editor.updateToolbarButtonStates();\n }\n if (typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }\n }, 0);\n }\n\n /**\n * Toggle background color formatting - shows/hides color picker\n */\n toggle() {\n if (this.colorPicker.isVisible) {\n this.colorPicker.hide();\n } else {\n this.showColorPicker();\n }\n }\n\n /**\n * Show color picker positioned relative to background button on toolbar\n */\n showColorPicker() {\n // Find background button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n\n // Capture the selection so the colour applies even if a tap clears it.\n // Fall back to the editor's last non-collapsed range (mobile touch clears it).\n const sel = window.getSelection();\n if (sel && sel.rangeCount && !sel.isCollapsed) {\n Background.savedRanges.set(this.editorId, sel.getRangeAt(0).cloneRange());\n } else if (editor._lastRange) {\n Background.savedRanges.set(this.editorId, editor._lastRange.cloneRange());\n }\n\n const toolbar = editor.getModule('toolbar');\n let backgroundButton = null;\n \n if (toolbar) {\n backgroundButton = toolbar.getButton('background');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!backgroundButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n backgroundButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.background-btn');\n }\n }\n \n // Final fallback: find any background button in the current editor's wrapper\n if (!backgroundButton) {\n backgroundButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.background-btn');\n }\n \n if (!backgroundButton) {\n console.warn('Background button not found for editor:', this.editorId);\n return;\n }\n \n this.colorPicker.show(backgroundButton);\n }\n\n /**\n * Check if background color formatting is active in current selection\n */\n isActive() {\n return !!Background.getCurrentColor();\n }\n\n /**\n * Return the explicit background colour applied at the current selection, or\n * null when none is set. Looks for an explicit inline background (the span\n * the editor inserts) instead of comparing the computed colour to hardcoded\n * white/transparent — which misfired against themed backgrounds.\n */\n static getCurrentColor() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n let node = selection.getRangeAt(0).startContainer;\n if (node.nodeType === Node.TEXT_NODE) node = node.parentNode;\n\n while (node && node.nodeType === Node.ELEMENT_NODE) {\n if (node.classList && node.classList.contains('rich-editor-area')) break;\n if (node.style && node.style.backgroundColor && node.style.backgroundColor !== 'inherit') {\n return node.style.backgroundColor;\n }\n node = node.parentNode;\n }\n return null;\n }\n}\n\nexport default Background; ","/**\n * Link Popup Component — a compact, inline link input that appears right at the\n * selected text (Notion/Medium style). Shows just a URL field + Apply; the\n * display-text field only appears when no text is selected.\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass LinkPopup {\n constructor(options = {}) {\n this.options = {\n onLinkSelect: null,\n editor: null,\n ...options\n };\n\n this.popup = null;\n this.isVisible = false;\n this.urlInput = null;\n this.textInput = null;\n\n this.createPopup();\n }\n\n createPopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'link-popup link-popup--inline';\n\n const content = document.createElement('div');\n content.className = 'link-popup-content';\n\n // Display-text field — only shown when there's no selected text to link.\n this.textGroup = document.createElement('div');\n this.textGroup.className = 'link-popup-row';\n this.textInput = document.createElement('input');\n this.textInput.type = 'text';\n this.textInput.className = 'yjd-input';\n this.textInput.placeholder = 'Text to display';\n this.textGroup.appendChild(this.textInput);\n\n // URL row: input + Apply.\n const row = document.createElement('div');\n row.className = 'link-popup-row';\n\n this.urlInput = document.createElement('input');\n this.urlInput.type = 'text';\n this.urlInput.className = 'yjd-input';\n this.urlInput.placeholder = 'Paste or type a link…';\n\n this.applyBtn = document.createElement('button');\n this.applyBtn.type = 'button';\n this.applyBtn.className = 'yjd-button-confirm link-popup-apply';\n this.applyBtn.textContent = 'Apply';\n this.applyBtn.onclick = () => { this.handleOk(); this._refocusEditor(); };\n\n row.appendChild(this.urlInput);\n row.appendChild(this.applyBtn);\n\n content.appendChild(this.textGroup);\n content.appendChild(row);\n this.popup.appendChild(content);\n\n const onKey = (e) => {\n if (e.key === 'Enter') { e.preventDefault(); this.handleOk(); this._refocusEditor(); }\n if (e.key === 'Escape') { this.hide(); this._refocusEditor(); }\n };\n this.urlInput.onkeydown = onKey;\n this.textInput.onkeydown = onKey;\n\n appendPopup(this.popup);\n\n // Prevent focus loss when clicking on popup\n if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {\n this.options.editor.preventFocusLoss(this.popup);\n }\n }\n\n _refocusEditor() {\n if (this.options.editor) setTimeout(() => this.options.editor.focus(), 0);\n }\n\n handleOk() {\n const raw = this.urlInput.value.trim();\n if (!raw) { this.urlInput.focus(); return; }\n\n // Friendly normalisation: bare domains get https://; keep anchors,\n // root-relative paths and explicit schemes (mailto:, tel:, …) as-is.\n let url = raw;\n const hasScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url);\n if (!hasScheme && !url.startsWith('/') && !url.startsWith('#')) {\n url = 'https://' + url;\n }\n\n const text = this.textInput.value.trim();\n if (this.options.onLinkSelect) this.options.onLinkSelect({ url, text });\n this.hide();\n }\n\n show(anchor, existingLink = null, selectedText = '') {\n if (!anchor) return;\n\n const hasSelection = !!selectedText;\n this.urlInput.value = existingLink ? existingLink.url : '';\n this.textInput.value = selectedText || (existingLink ? existingLink.text : '');\n // No need to ask for display text when text is already selected.\n this.textGroup.style.display = hasSelection ? 'none' : '';\n\n const position = calculatePopupPosition(anchor, this.popup, { offsetY: 8, offsetX: 0 });\n setPopupPosition(this.popup, position);\n\n this.popup.classList.add('visible');\n this.isVisible = true;\n\n setTimeout(() => this.urlInput.focus(), 60);\n setTimeout(() => {\n document.addEventListener('click', this.closeOnClickOutside);\n }, 100);\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n document.removeEventListener('click', this.closeOnClickOutside);\n }\n\n closeOnClickOutside = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n }\n\n destroy() {\n document.removeEventListener('click', this.closeOnClickOutside);\n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n }\n}\n\nexport default LinkPopup;\n","import { InlineFormat } from '../core/format.js';\nimport LinkPopup from '../ui/link-popup.js';\nimport Editor from '../core/editor.js';\nimport { isSafeUrl } from '../utils/sanitize.js';\n\n/**\n * Link Format - Simple link insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Link extends InlineFormat {\n static formatName = 'link';\n static tagName = 'A';\n \n // Map to store saved ranges for each editor instance\n static savedRanges = new Map();\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Link format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a link popup instance\n let linkPopup = currentEditor.getPopupInstance('link');\n \n if (!linkPopup) {\n // Create new popup instance for this editor\n linkPopup = new LinkPopup({\n onLinkSelect: (linkData) => {\n Link.insertLink(linkData, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('link', linkPopup);\n }\n \n this.linkPopup = linkPopup;\n }\n\n /**\n * Create a new Link format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Link} Link format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Link();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Insert link at saved cursor position\n * @param {Object} linkData - Link data with url and text\n * @param {string} editorId - Editor instance ID\n */\n static insertLink(linkData, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for link insertion');\n return;\n }\n\n // Block unsafe URL schemes (javascript:, data:text/html, vbscript:, ...)\n if (!isSafeUrl(linkData.url)) {\n console.warn('Blocked unsafe link URL:', linkData.url);\n return;\n }\n\n // Get saved range for this specific editor\n const savedRange = Link.savedRanges.get(editorId);\n if (!savedRange) {\n console.warn('No saved range found for editor:', editorId);\n return;\n }\n \n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(savedRange);\n \n const range = selection.getRangeAt(0);\n \n if (range.collapsed) {\n // No selection - insert link at cursor\n const linkElement = document.createElement('A');\n linkElement.href = linkData.url;\n linkElement.target = '_blank';\n linkElement.rel = 'noopener noreferrer';\n linkElement.textContent = linkData.text || linkData.url;\n range.insertNode(linkElement);\n } else {\n // Has selection - wrap existing content with link while preserving styles\n const fragment = range.extractContents();\n const linkElement = document.createElement('A');\n linkElement.href = linkData.url;\n linkElement.target = '_blank';\n linkElement.rel = 'noopener noreferrer';\n \n // Move all nodes from fragment to link element\n while (fragment.firstChild) {\n linkElement.appendChild(fragment.firstChild);\n }\n \n range.insertNode(linkElement);\n }\n \n // Position cursor after link\n const newRange = document.createRange();\n newRange.setStartAfter(range.endContainer);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n \n // Clear saved range for this editor\n Link.savedRanges.delete(editorId);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }\n\n /**\n * Toggle link popup\n */\n toggle() {\n if (this.linkPopup.isVisible) {\n this.linkPopup.hide();\n } else {\n this.showPopup();\n }\n }\n\n /**\n * Show link popup\n */\n showPopup() {\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) {\n console.warn('No editor found for ID:', this.editorId);\n return;\n }\n\n // Save the current range and anchor the popup right at the selected text.\n const selection = window.getSelection();\n let rect = null;\n if (selection && selection.rangeCount > 0) {\n const range = selection.getRangeAt(0).cloneRange();\n Link.savedRanges.set(this.editorId, range);\n rect = range.getBoundingClientRect();\n // A collapsed caret can report an empty rect — fall back to its element.\n if (!rect || (rect.width === 0 && rect.height === 0)) {\n const node = range.startContainer.nodeType === Node.TEXT_NODE\n ? range.startContainer.parentElement\n : range.startContainer;\n if (node && node.getBoundingClientRect) rect = node.getBoundingClientRect();\n }\n }\n\n const existingLink = this.getCurrentLink();\n let selectedText = '';\n if (selection && !selection.isCollapsed) {\n selectedText = selection.toString().trim();\n }\n\n // Anchor at the selection rect (virtual anchor); fall back to the toolbar\n // button when there's no usable rect.\n let anchor = null;\n if (rect && (rect.width || rect.height)) {\n anchor = { getBoundingClientRect: () => rect };\n } else {\n const toolbar = editor.getModule('toolbar');\n anchor = toolbar && (toolbar.getButton('link')\n || editor.wrapper.querySelector('.rich-editor-toolbar-btn[data-command=\"link\"]'));\n }\n if (!anchor) {\n console.warn('No anchor for link popup, editor:', this.editorId);\n return;\n }\n\n this.linkPopup.show(anchor, existingLink, selectedText);\n }\n\n /**\n * Get current link if cursor is in one\n */\n getCurrentLink() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n \n let node = selection.getRangeAt(0).startContainer;\n \n // Find parent link element\n while (node && node !== document.body) {\n if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'A') {\n return {\n url: node.href || '',\n text: node.textContent || ''\n };\n }\n node = node.parentNode;\n }\n \n return null;\n }\n\n /**\n * Check if cursor is in a link\n */\n isActive() {\n return this.getCurrentLink() !== null;\n }\n}\n\nexport default Link; ","/**\n * Table Popup Component - Interactive table size picker\n */\nimport { PopupPositioning } from '../utils/popup-positioning.js';\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass TablePopup {\n constructor(options = {}) {\n this.options = {\n maxRows: 8,\n maxCols: 8,\n onTableSelect: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.selectedRows = 1;\n this.selectedCols = 1;\n this.grid = null;\n this.sizeDisplay = null;\n \n this.createPopup();\n }\n\n createPopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'table-popup';\n \n const content = document.createElement('div');\n content.className = 'table-popup-content';\n \n // Create size display text\n this.createSizeDisplay();\n \n // Create grid selector\n this.createGridSelector();\n \n content.appendChild(this.grid);\n content.appendChild(this.sizeDisplay);\n this.popup.appendChild(content);\n appendPopup(this.popup);\n }\n\n createSizeDisplay() {\n this.sizeDisplay = document.createElement('div');\n this.sizeDisplay.className = 'table-size-display';\n \n }\n createGridSelector() {\n this.grid = document.createElement('div');\n this.grid.className = 'table-grid-selector';\n \n // Create grid of cells\n for (let row = 1; row <= this.options.maxRows; row++) {\n for (let col = 1; col <= this.options.maxCols; col++) {\n const cell = document.createElement('div');\n cell.className = 'table-grid-cell';\n cell.dataset.row = row;\n cell.dataset.col = col;\n \n // Mouse events\n cell.addEventListener('mouseenter', () => {\n this.highlightGrid(row, col);\n });\n \n cell.addEventListener('click', () => {\n this.selectSize(row, col);\n this.handleInsert();\n });\n \n this.grid.appendChild(cell);\n }\n }\n \n // Reset hover when leaving grid\n this.grid.addEventListener('mouseleave', () => {\n this.highlightGrid(1, 1);\n });\n }\n\n highlightGrid(rows, cols) {\n this.selectedRows = rows;\n this.selectedCols = cols;\n \n // Update size display text\n this.updateSizeDisplay(rows, cols);\n \n // Update grid visual\n const cells = this.grid.querySelectorAll('.table-grid-cell');\n cells.forEach(cell => {\n const cellRow = parseInt(cell.dataset.row);\n const cellCol = parseInt(cell.dataset.col);\n \n if (cellRow <= rows && cellCol <= cols) {\n cell.classList.add('highlighted');\n } else {\n cell.classList.remove('highlighted');\n }\n });\n }\n\n updateSizeDisplay(rows, cols) {\n if (this.sizeDisplay) {\n this.sizeDisplay.textContent = `${rows}x${cols}`;\n }\n }\n\n selectSize(rows, cols) {\n this.selectedRows = rows;\n this.selectedCols = cols;\n this.updateSizeDisplay(rows, cols);\n }\n\n handleInsert() {\n if (this.options.onTableSelect) {\n this.options.onTableSelect({\n rows: this.selectedRows,\n cols: this.selectedCols\n });\n }\n \n this.hide();\n }\n\n show(anchor) {\n if (!anchor) return;\n \n // Reset selection\n this.selectedRows = 1;\n this.selectedCols = 1;\n this.highlightGrid(1, 1);\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Click outside to close\n setTimeout(() => {\n document.addEventListener('click', this.closeOnClickOutside);\n }, 100);\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n document.removeEventListener('click', this.closeOnClickOutside);\n }\n\n closeOnClickOutside = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n }\n\n destroy() {\n document.removeEventListener('click', this.closeOnClickOutside);\n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n }\n}\n\nexport default TablePopup; ","import { BlockFormat } from '../core/format.js';\nimport TablePopup from '../ui/table-popup.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Table Format - HTML table insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Table extends BlockFormat {\n static formatName = 'table';\n static tagName = 'TABLE';\n static savedRanges = new Map(); // Map to store saved ranges for each editor\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Table format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a table popup instance\n let tablePopup = currentEditor.getPopupInstance('table');\n \n if (!tablePopup) {\n // Create new table popup instance for this editor\n tablePopup = new TablePopup({\n onTableSelect: (tableData) => {\n Table.insertTable(tableData, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('table', tablePopup);\n }\n \n this.tablePopup = tablePopup;\n }\n\n /**\n * Create a new Table format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Table} Table format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Table();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Insert table at saved cursor position\n * @param {Object} tableData - Table data with rows and cols\n * @param {string} editorId - Editor instance ID\n */\n static insertTable(tableData, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for table insertion');\n return;\n }\n \n // Get saved range for this editor\n const savedRange = Table.savedRanges.get(editorId);\n if (!savedRange) return;\n \n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(savedRange);\n \n const range = selection.getRangeAt(0);\n\n // Create table HTML\n const tableElement = Table.createTableElement(tableData.rows, tableData.cols);\n\n // Clear any selected content\n if (!range.collapsed) {\n range.deleteContents();\n }\n\n // Insert the table as a top-level block (not nested inside a heading or\n // inline formatting tags, which would produce invalid HTML).\n if (typeof editor.insertBlock === 'function') {\n editor.insertBlock(tableElement);\n } else {\n range.insertNode(tableElement);\n }\n\n // Position cursor in first cell\n const firstCell = tableElement.querySelector('td');\n if (firstCell) {\n const newRange = document.createRange();\n newRange.setStart(firstCell, 0);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n \n // Clear saved range for this editor\n Table.savedRanges.delete(editorId);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }\n\n /**\n * Create table element\n */\n static createTableElement(rows, cols) {\n const table = document.createElement('table');\n table.className = 'rich-editor-table';\n table.cellSpacing = '0';\n table.cellPadding = '0';\n table.border = '1';\n \n const tbody = document.createElement('tbody');\n \n for (let r = 0; r < rows; r++) {\n const row = document.createElement('tr');\n \n for (let c = 0; c < cols; c++) {\n const cell = document.createElement('td');\n cell.innerHTML = '<br>'; // empty placeholder (keeps height, not counted as content)\n cell.style.minWidth = '50px';\n cell.style.minHeight = '24px';\n cell.style.padding = '4px 8px';\n cell.style.border = '1px solid #ddd';\n cell.style.verticalAlign = 'top';\n \n // Make cells editable\n cell.contentEditable = 'true';\n \n row.appendChild(cell);\n }\n \n tbody.appendChild(row);\n }\n \n table.appendChild(tbody);\n \n // Add table styles\n table.style.borderCollapse = 'collapse';\n table.style.width = '100%';\n table.style.margin = '10px 0';\n \n return table;\n }\n\n /**\n * Toggle table popup\n */\n toggle() {\n if (this.tablePopup.isVisible) {\n this.tablePopup.hide();\n } else {\n this.showPopup();\n }\n }\n\n /**\n * Show table popup\n */\n showPopup() {\n // Lưu vị trí con trỏ hiện tại cho editor này\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n Table.savedRanges.set(this.editorId, selection.getRangeAt(0).cloneRange());\n }\n \n // Find table button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let tableButton = null;\n \n if (toolbar) {\n tableButton = toolbar.getButton('table');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!tableButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n tableButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.table-btn');\n }\n }\n \n // Final fallback: find any table button in the current editor's wrapper\n if (!tableButton) {\n tableButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.table-btn');\n }\n \n if (!tableButton) {\n console.warn('Table button not found for editor:', this.editorId);\n return;\n }\n \n this.tablePopup.show(tableButton);\n }\n\n /**\n * Check if cursor is in a table\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n \n let node = selection.getRangeAt(0).startContainer;\n \n // Find parent table element\n while (node && node !== document.body) {\n if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'TABLE') {\n return true;\n }\n node = node.parentNode;\n }\n \n return false;\n }\n\n /**\n * Get current table if cursor is in one\n */\n getCurrentTable() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n \n let node = selection.getRangeAt(0).startContainer;\n \n // Find parent table element\n while (node && node !== document.body) {\n if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'TABLE') {\n return node;\n }\n node = node.parentNode;\n }\n \n return null;\n }\n\n /**\n * Apply table formatting (not applicable for this format)\n */\n apply() {\n this.showPopup();\n }\n\n /**\n * Remove table formatting\n */\n remove() {\n const table = this.getCurrentTable();\n if (table) {\n // Hide resize handles before removing table\n if (window.richEditor && window.richEditor.resizeHandles) {\n window.richEditor.resizeHandles.hideHandles();\n }\n table.parentNode.removeChild(table);\n }\n }\n}\n\nexport default Table; ","import IconUtils from './icons.js';\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Custom Select Component - Reusable dropdown/popup select component\n */\nclass CustomSelect {\n constructor(options = {}) {\n this.options = {\n items: [], // Array of items to display\n onItemSelect: null, // Callback when item is selected\n displayProperty: 'label', // Property to display as text\n valueProperty: 'value', // Property to use as value\n className: 'custom-select', // CSS class for the popup\n title: '', // Optional header label shown at the top of the popup\n width: 200, // Popup width\n height: 280, // Popup height\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.currentValue = null;\n this.clickOutsideHandler = null;\n this.initialized = false;\n \n this.createSelect();\n }\n\n /**\n * Create select popup\n */\n createSelect() {\n // Create popup\n this.popup = document.createElement('div');\n this.popup.className = `${this.options.className}-popup`;\n\n // Optional header so it's clear what the dropdown controls.\n if (this.options.title) {\n const header = document.createElement('div');\n header.className = 'custom-select-header';\n header.textContent = this.options.title;\n this.popup.appendChild(header);\n }\n\n // Add popup to container\n appendPopup(this.popup);\n \n // Initialize async\n this.init();\n }\n\n /**\n * Initialize component with async operations\n */\n async init() {\n // Create item list\n await this.createItemList();\n this.initialized = true;\n }\n\n /**\n * Create item list\n */\n async createItemList() {\n const list = document.createElement('div');\n list.className = 'item-list';\n \n // Get check icon\n const checkIconSvg = IconUtils.getIcon('check');\n \n this.options.items.forEach(item => {\n const itemButton = document.createElement('button');\n itemButton.type = 'button';\n itemButton.className = 'custom-select-item-button';\n itemButton.dataset.value = this.getItemValue(item);\n \n // Create item content with text and checkmark\n const itemText = document.createElement('div');\n itemText.className = 'item-text';\n itemText.innerHTML = this.getItemDisplay(item);\n \n const checkmark = document.createElement('span');\n checkmark.className = 'item-checkmark';\n checkmark.innerHTML = checkIconSvg || '';\n \n itemButton.appendChild(itemText);\n itemButton.appendChild(checkmark);\n \n itemButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectItem(item);\n });\n \n list.appendChild(itemButton);\n });\n \n this.popup.appendChild(list);\n }\n\n /**\n * Get display text for item\n */\n getItemDisplay(item) {\n return item[this.options.displayProperty] || item.toString();\n }\n\n /**\n * Get value for item\n */\n getItemValue(item) {\n return item[this.options.valueProperty] || item[this.options.displayProperty] || item;\n }\n\n /**\n * Update items in the select\n */\n async updateItems(items) {\n this.options.items = items;\n \n // Remove existing list\n const existingList = this.popup.querySelector('.item-list');\n if (existingList) {\n existingList.remove();\n }\n \n // Create new list\n await this.createItemList();\n }\n\n /**\n * Setup click outside handler\n */\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n // Don't hide if clicking on block toolbar or its buttons\n if (e.target.closest('.block-toolbar')) {\n return;\n }\n \n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n // Add slight delay to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n /**\n * Remove click outside handler\n */\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n \n /**\n * Setup scroll handler to update popup position\n */\n setupScrollHandler() {\n if (this.scrollHandler) {\n window.removeEventListener('scroll', this.scrollHandler);\n }\n \n this.scrollHandler = () => {\n if (this.isVisible) {\n this.updatePosition();\n }\n };\n \n window.addEventListener('scroll', this.scrollHandler);\n }\n \n /**\n * Remove scroll handler\n */\n removeScrollHandler() {\n if (this.scrollHandler) {\n window.removeEventListener('scroll', this.scrollHandler);\n this.scrollHandler = null;\n }\n }\n\n /**\n * Show select popup\n */\n async show(anchor) {\n if (!anchor) return;\n\n // Capture the editor selection NOW so the format applies to the right place\n // even if a tap on the popup clears the live selection (mobile/touch).\n // Prefer the LIVE selection when it's inside the editor — including a\n // collapsed caret (needed for \"apply then keep typing\"). Only fall back to\n // the last real range when the selection is genuinely gone/outside.\n const sel = window.getSelection();\n const ed = Editor.getCurrentInstance && Editor.getCurrentInstance();\n const editorEl = ed && ed.editor;\n if (sel && sel.rangeCount && editorEl && editorEl.contains(sel.anchorNode)) {\n this._savedRange = sel.getRangeAt(0).cloneRange();\n } else {\n this._savedRange = ed && ed._lastRange ? ed._lastRange.cloneRange() : null;\n }\n\n // Wait for initialization if not ready\n if (!this.initialized) {\n await new Promise(resolve => {\n const checkInit = () => {\n if (this.initialized) {\n resolve();\n } else {\n setTimeout(checkInit, 10);\n }\n };\n checkInit();\n });\n }\n \n // Ensure popup is in DOM\n if (!document.body.contains(this.popup)) {\n appendPopup(this.popup);\n }\n \n // Update current selection highlight\n this.highlightCurrentItem(this.currentValue);\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup by adding visible class\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Setup click outside handler\n this.setupClickOutside();\n \n // Setup scroll handler to update position\n this.setupScrollHandler();\n \n // Store reference to anchor for potential repositioning\n this.currentAnchor = anchor;\n }\n\n /**\n * Hide select popup\n */\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n this.currentAnchor = null;\n }\n \n /**\n * Update popup position based on current anchor\n */\n updatePosition() {\n if (this.isVisible && this.currentAnchor) {\n // Calculate and set popup position\n const position = calculatePopupPosition(this.currentAnchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n }\n }\n\n /**\n * Set current value\n */\n setCurrentValue(value) {\n this.currentValue = value;\n this.highlightCurrentItem(value);\n }\n\n /**\n * Highlight current item in the list\n */\n highlightCurrentItem(value) {\n // Remove previous highlights\n this.popup.querySelectorAll('.custom-select-item-button.current').forEach(btn => {\n btn.classList.remove('current');\n });\n \n // Highlight current item - find by comparing dataset.value directly\n if (value != null) {\n const buttons = this.popup.querySelectorAll('.custom-select-item-button');\n for (const button of buttons) {\n if (button.dataset.value === value.toString()) {\n button.classList.add('current');\n break;\n }\n }\n }\n }\n\n /**\n * Select item and trigger callback\n */\n selectItem(item) {\n const value = this.getItemValue(item);\n this.currentValue = value;\n\n // Restore the selection captured when the popup opened, so the format\n // applies even if the tap cleared the live selection (mobile/touch).\n if (this._savedRange) {\n const s = window.getSelection();\n s.removeAllRanges();\n s.addRange(this._savedRange);\n }\n\n if (this.options.onItemSelect) {\n this.options.onItemSelect(value, item);\n }\n\n this.hide();\n }\n\n /**\n * Get current selected value\n */\n getCurrentValue() {\n return this.currentValue;\n }\n\n /**\n * Destroy select component\n */\n destroy() {\n this.removeClickOutside();\n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n }\n}\n\nexport default CustomSelect; ","import { BlockFormat } from '../core/format.js';\nimport CustomSelect from '../ui/customselect.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Heading Format - Handles heading and paragraph formatting\n * Now supports multiple editor instances with separate popup instances\n */\nclass Heading extends BlockFormat {\n static formatName = 'heading';\n static tagName = 'H1'; // Default tag, will be overridden\n \n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Heading format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a heading select instance\n let customSelect = currentEditor.getPopupInstance('heading');\n \n if (!customSelect) {\n // Create new custom select instance for this editor\n const tagMap = Heading.getTagMap();\n const items = Object.values(tagMap).map(tagData => ({\n value: tagData.tag,\n label: tagData.element,\n title: tagData.title\n }));\n\n customSelect = new CustomSelect({\n items: items,\n displayProperty: 'label',\n valueProperty: 'value',\n className: 'heading-select',\n onItemSelect: (value, item) => {\n Heading.applyTagToCurrentSelection(value, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('heading', customSelect);\n }\n \n this.customSelect = customSelect;\n }\n\n /**\n * Create a new Heading format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Heading} Heading format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Heading();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Get display name for tag\n * @param {string} tag - HTML tag name\n * @returns {string} Display name\n */\n static getTagMap() {\n return {\n 'H1': { tag: 'H1', element: '<h1 style=\"margin:0\">Heading 1</h1>', title: 'Heading 1' },\n 'H2': { tag: 'H2', element: '<h2 style=\"margin:0\">Heading 2</h2>', title: 'Heading 2' },\n 'H3': { tag: 'H3', element: '<h3 style=\"margin:0\">Heading 3</h3>', title: 'Heading 3' },\n 'H4': { tag: 'H4', element: '<h4 style=\"margin:0\">Heading 4</h4>', title: 'Heading 4' },\n 'H5': { tag: 'H5', element: '<h5 style=\"margin:0\">Heading 5</h5>', title: 'Heading 5' },\n 'H6': { tag: 'H6', element: '<h6 style=\"margin:0\">Heading 6</h6>', title: 'Heading 6' },\n 'P': { tag: 'P', element: '<p style=\"margin:0\">Paragraph</p>', title: 'Paragraph' },\n 'PRE': { tag: 'PRE', element: '<pre style=\"margin:0\">Code</pre>', title: 'Preformatted' },\n 'BLOCKQUOTE': { tag: 'BLOCKQUOTE', element: '<blockquote style=\"margin:0\">Quote</blockquote>', title: 'Quote' }\n };\n }\n\n static getTagDisplayName(tag) {\n const tagMap = this.getTagMap();\n return tagMap[tag]?.title || 'Paragraph';\n }\n\n /**\n * Update custom button text based on current tag\n */\n updateButtonText() {\n const currentTag = this.getCurrentTag();\n const displayName = Heading.getTagDisplayName(currentTag || 'P');\n \n // Find heading button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let headingButton = null;\n \n if (toolbar) {\n headingButton = toolbar.getButton('heading');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!headingButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n headingButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.heading-btn');\n }\n }\n \n // Final fallback: find any heading button in the current editor's wrapper\n if (!headingButton) {\n headingButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.heading-btn');\n }\n \n if (headingButton && headingButton.updateText) {\n headingButton.updateText(displayName);\n } else if (headingButton) {\n headingButton.textContent = displayName;\n }\n }\n\n /**\n * Create element with specific tag\n * @param {string} tag - HTML tag name (H1, H2, P, etc.)\n * @returns {HTMLElement}\n */\n static create(tag = 'P') {\n const node = document.createElement(tag.toUpperCase());\n return node;\n }\n\n /**\n * Static method to apply tag to current selection\n * @param {string} tag - HTML tag name\n * @param {string} editorId - Editor instance ID\n */\n static applyTagToCurrentSelection(tag, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for heading application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n const headingFormat = Heading.createForEditor(editorId);\n if (headingFormat) {\n headingFormat.apply(tag);\n \n // Update button text after applying\n headingFormat.updateButtonText();\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply heading format with specified tag\n * @param {string} tag - HTML tag name (H1, H2, P, etc.)\n */\n apply(tag = 'P') {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Lưu selection trước khi đổi\n const range = selection.getRangeAt(0);\n const isCollapsed = range.collapsed; // true = không bôi đen gì\n\n const blocks = this.getBlockElements(range);\n\n if (blocks.length === 0) {\n // Không có block - tạo mới\n const newBlock = this.createBlockAtCursor(range, tag);\n \n // Sau khi tạo block mới → đặt con trỏ vào block\n const newRange = document.createRange();\n newRange.setStart(newBlock, 0);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n // selection hiện tại\n const selection = window.getSelection();\n if (!selection.rangeCount) return;\n const range = selection.getRangeAt(0);\n const isCollapsed = range.collapsed;\n\n // đảm bảo blocks là mảng\n const blocksArray = Array.from(blocks);\n \n\n // tìm block chứa 1 node\n function findBlockIndex(node, blocks) {\n while (node && node.nodeType !== 9 /*document*/) {\n const idx = blocks.indexOf(node);\n if (idx !== -1) return idx;\n node = node.parentNode;\n }\n return -1;\n }\n\n // tính số ký tự từ đầu block tới vị trí (sử dụng Range.toString())\n function charOffsetFromBlockStart(block, container, offset) {\n const r = document.createRange();\n r.setStart(block, 0);\n r.setEnd(container, offset);\n return r.toString().length;\n }\n\n const startBlockIndex = findBlockIndex(range.startContainer, blocksArray);\n const endBlockIndex = findBlockIndex(range.endContainer, blocksArray);\n\n let startCharOffset = 0, endCharOffset = 0;\n if (startBlockIndex !== -1) {\n startCharOffset = charOffsetFromBlockStart(blocksArray[startBlockIndex], range.startContainer, range.startOffset);\n }\n if (!isCollapsed && endBlockIndex !== -1) {\n endCharOffset = charOffsetFromBlockStart(blocksArray[endBlockIndex], range.endContainer, range.endOffset);\n }\n\n // --- Thực hiện convert và lấy lại node mới trả về (nếu convertBlock trả về node mới)\n const newBlocks = blocksArray.map(b => {\n const newNode = this.convertBlock(b, tag);\n return newNode || b; // nếu convertBlock trả về undefined thì dùng lại b (convert in-place)\n });\n\n // helper: từ charOffset tìm text node + offset bên trong nó; nếu không tìm thì trả về block để set ở cuối\n function resolvePositionByCharOffset(block, charOffset) {\n const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT, null, false);\n let node;\n let remaining = charOffset;\n while ((node = walker.nextNode())) {\n const len = node.nodeValue.length;\n if (remaining <= len) return { node, offset: remaining };\n remaining -= len;\n }\n // không tìm text node phù hợp => đặt ở cuối block\n return { node: block, offset: block.childNodes.length };\n }\n\n // tái tạo range\n const newRange = document.createRange();\n\n if (isCollapsed) {\n const idx = (startBlockIndex !== -1 ? startBlockIndex : 0);\n const pos = resolvePositionByCharOffset(newBlocks[idx], startCharOffset);\n if (pos.node.nodeType === Node.TEXT_NODE) newRange.setStart(pos.node, pos.offset);\n else newRange.setStart(pos.node, Math.max(0, pos.offset));\n newRange.collapse(true);\n } else {\n if (startBlockIndex === -1 || endBlockIndex === -1) {\n // fallback: nếu không nằm trong blocks thì giữ range cũ (hoặc handle theo logic của bạn)\n selection.removeAllRanges();\n selection.addRange(range);\n return;\n }\n const s = resolvePositionByCharOffset(newBlocks[startBlockIndex], startCharOffset);\n const e = resolvePositionByCharOffset(newBlocks[endBlockIndex], endCharOffset);\n\n // setStart/setEnd chấp nhận text node + offset hoặc element + childIndex\n if (s.node.nodeType === Node.TEXT_NODE) newRange.setStart(s.node, s.offset);\n else newRange.setStart(s.node, Math.min(s.offset, s.node.childNodes.length));\n\n if (e.node.nodeType === Node.TEXT_NODE) newRange.setEnd(e.node, e.offset);\n else newRange.setEnd(e.node, Math.min(e.offset, e.node.childNodes.length));\n }\n\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n } \n\n\n /**\n * Create new block at cursor position\n * @param {Range} range - Current range\n * @param {string} tag - HTML tag name\n */\n createBlockAtCursor(range, tag) {\n const blockNode = this.constructor.create(tag);\n \n // Try to preserve style from existing block if cursor is inside one\n const existingBlock = this.getBlockElement(range.startContainer);\n if (existingBlock && existingBlock.style && existingBlock.style.cssText) {\n blockNode.style.cssText = existingBlock.style.cssText;\n }\n \n if (range.collapsed) {\n // No selection - create empty block\n blockNode.appendChild(document.createTextNode(''));\n range.insertNode(blockNode);\n \n // Position cursor inside the block\n const newRange = document.createRange();\n newRange.setStart(blockNode, 0);\n newRange.collapse(true);\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n // Has selection - wrap in block\n const contents = range.extractContents();\n blockNode.appendChild(contents);\n range.insertNode(blockNode);\n \n // Select the content in the block\n const newRange = document.createRange();\n newRange.selectNodeContents(blockNode);\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n }\n\n /**\n * Convert existing block to new format\n * @param {Element} block - Block element to convert\n * @param {string} tag - HTML tag name\n * @returns {Element} - The new block element\n */\n convertBlock(block, tag) {\n const newBlock = this.constructor.create(tag);\n \n // Copy all child nodes\n while (block.firstChild) {\n newBlock.appendChild(block.firstChild);\n }\n \n // Copy relevant attributes\n if (block.className && this.shouldPreserveClass(block.className)) {\n newBlock.className = block.className;\n }\n \n // Copy style attributes to preserve formatting like text-align\n if (block.style && block.style.cssText) {\n newBlock.style.cssText = block.style.cssText;\n }\n \n // Replace the block\n block.parentNode.replaceChild(newBlock, block);\n \n return newBlock;\n }\n\n /**\n * Set cursor at start of block (fallback method)\n * @param {Element} block - Block element\n */\n setCursorAtStartOfBlock(block) {\n const selection = window.getSelection();\n const range = document.createRange();\n \n // Find first text node or position at start of block\n const walker = document.createTreeWalker(\n block,\n NodeFilter.SHOW_TEXT,\n null,\n false\n );\n \n const firstTextNode = walker.nextNode();\n if (firstTextNode) {\n range.setStart(firstTextNode, 0);\n range.collapse(true);\n } else {\n range.setStart(block, 0);\n range.collapse(true);\n }\n\n selection.removeAllRanges();\n selection.addRange(range);\n }\n\n /**\n * Toggle heading format - shows/hides tag picker\n */\n async toggle() {\n if (this.customSelect.isVisible) {\n this.customSelect.hide();\n } else {\n await this.showTagPicker();\n }\n }\n\n /**\n * Show custom select positioned relative to heading button on toolbar\n */\n async showTagPicker() {\n // Find heading button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let headingButton = null;\n \n if (toolbar) {\n headingButton = toolbar.getButton('heading');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!headingButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n headingButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.heading-btn');\n }\n }\n \n // Final fallback: find any heading button in the current editor's wrapper\n if (!headingButton) {\n headingButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.heading-btn');\n }\n \n if (!headingButton) {\n console.warn('Heading button not found for editor:', this.editorId);\n return;\n }\n \n // Update current selection before showing\n const currentTag = this.getCurrentTag();\n if (currentTag) {\n this.customSelect.setCurrentValue(currentTag);\n }\n \n await this.customSelect.show(headingButton);\n }\n\n /**\n * Check if heading format is active - always return false (no active state)\n * Only update button text to show current tag\n * @param {string} tag - Optional specific tag to check\n * @returns {boolean}\n */\n isActive(tag = null) {\n // Always update button text to show current tag\n this.updateButtonText();\n \n // Never show active state for heading button\n return false;\n }\n\n /**\n * Get current tag of the selection\n * @returns {string|null} Current tag name or null\n */\n getCurrentTag() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n const block = this.getBlockElement(range.startContainer);\n \n if (!block) return null;\n\n const headingTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'PRE', 'BLOCKQUOTE'];\n if (headingTags.includes(block.tagName)) {\n return block.tagName;\n }\n\n return null;\n }\n}\n\nexport default Heading; ","import { InlineFormat } from '../core/format.js';\nimport CustomSelect from '../ui/customselect.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\nimport { execFormat } from '../utils/exec-command.js';\n\n/**\n * Font Family Format - Handles font family formatting\n * Now supports multiple editor instances with separate popup instances\n */\nclass FontFamily extends InlineFormat {\n static formatName = 'fontFamily';\n static tagName = 'SPAN';\n \n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for FontFamily format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a font family select instance\n let customSelect = currentEditor.getPopupInstance('font-family');\n \n if (!customSelect) {\n // Create new custom select instance for this editor\n const fontMap = FontFamily.getFontMap();\n const items = Object.values(fontMap).map(fontData => ({\n value: fontData.font,\n label: fontData.element,\n title: fontData.title\n }));\n\n customSelect = new CustomSelect({\n items: items,\n displayProperty: 'label',\n valueProperty: 'value',\n className: 'font-family-select',\n onItemSelect: (value, item) => {\n FontFamily.applyFontFamilyToCurrentSelection(value, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('font-family', customSelect);\n }\n \n this.customSelect = customSelect;\n \n // Set up event listener for selection changes\n this.setupSelectionListener();\n }\n\n /**\n * Create a new FontFamily format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {FontFamily} FontFamily format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new FontFamily();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Set up event listener for selection changes to update button text\n */\n setupSelectionListener() {\n // Use a debounced function to avoid too many updates\n let updateTimeout;\n const debouncedUpdate = () => {\n clearTimeout(updateTimeout);\n updateTimeout = setTimeout(() => {\n // Only update if selection is in this editor\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n const range = selection.getRangeAt(0);\n const editor = Editor.getInstanceById(this.editorId);\n if (editor && (editor.editor.contains(range.startContainer) || editor.editor.isSameNode(range.startContainer))) {\n this.updateButtonText();\n }\n }\n }, 50); // 50ms delay\n };\n\n // Listen for selection changes\n document.addEventListener('selectionchange', debouncedUpdate);\n \n // Also listen for mouseup and keyup events for immediate feedback\n document.addEventListener('mouseup', debouncedUpdate);\n document.addEventListener('keyup', debouncedUpdate);\n \n // Store the listener for cleanup\n this.selectionListener = debouncedUpdate;\n }\n\n /**\n * Get font map with different font families\n */\n static getFontMap() {\n return {\n 'Arial': { \n font: 'Arial, sans-serif', \n element: '<span style=\"font-family: Arial, sans-serif\">Arial</span>', \n title: 'Arial' \n },\n 'Helvetica': { \n font: 'Helvetica, Arial, sans-serif', \n element: '<span style=\"font-family: Helvetica, Arial, sans-serif\">Helvetica</span>', \n title: 'Helvetica' \n },\n 'Times New Roman': { \n font: '\"Times New Roman\", Times, serif', \n element: '<span style=\"font-family: \\'Times New Roman\\', Times, serif\">Times New Roman</span>', \n title: 'Times New Roman' \n },\n 'Georgia': { \n font: 'Georgia, serif', \n element: '<span style=\"font-family: Georgia, serif\">Georgia</span>', \n title: 'Georgia' \n },\n 'Verdana': { \n font: 'Verdana, Geneva, sans-serif', \n element: '<span style=\"font-family: Verdana, Geneva, sans-serif\">Verdana</span>', \n title: 'Verdana' \n },\n 'Courier New': { \n font: '\"Courier New\", Courier, monospace', \n element: '<span style=\"font-family: \\'Courier New\\', Courier, monospace\">Courier New</span>', \n title: 'Courier New' \n },\n 'Trebuchet MS': { \n font: '\"Trebuchet MS\", Helvetica, sans-serif', \n element: '<span style=\"font-family: \\'Trebuchet MS\\', Helvetica, sans-serif\">Trebuchet MS</span>', \n title: 'Trebuchet MS' \n },\n 'Comic Sans MS': { \n font: '\"Comic Sans MS\", cursive', \n element: '<span style=\"font-family: \\'Comic Sans MS\\', cursive\">Comic Sans MS</span>', \n title: 'Comic Sans MS' \n },\n 'Impact': { \n font: 'Impact, Charcoal, sans-serif', \n element: '<span style=\"font-family: Impact, Charcoal, sans-serif\">Impact</span>', \n title: 'Impact' \n },\n 'Lucida Console': { \n font: '\"Lucida Console\", Monaco, monospace', \n element: '<span style=\"font-family: \\'Lucida Console\\', Monaco, monospace\">Lucida Console</span>', \n title: 'Lucida Console' \n }\n };\n }\n\n\n /**\n * Get display name for font\n * @param {string} font - Font family value\n * @returns {string} Display name\n */\n static getFontDisplayName(font) {\n const fontMap = this.getFontMap();\n // Find by font value\n for (const [key, value] of Object.entries(fontMap)) {\n if (value.font === font || key === font) {\n return value.title;\n }\n }\n return 'Arial';\n }\n\n /**\n * Update custom button text based on current font\n */\n updateButtonText() {\n const currentFont = this.getCurrentFont();\n const displayName = FontFamily.getFontDisplayName(currentFont || 'Arial, sans-serif');\n \n // Find font-family button in the specific editor's toolbar using editorId\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let fontFamilyButton = null;\n \n if (toolbar) {\n fontFamilyButton = toolbar.getButton('font-family');\n }\n \n // Fallback: find button by class in the specific editor's toolbar\n if (!fontFamilyButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n fontFamilyButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.font-family-btn');\n }\n }\n \n // Final fallback: find any font-family button in the specific editor's wrapper\n if (!fontFamilyButton) {\n fontFamilyButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.font-family-btn');\n }\n \n if (fontFamilyButton && fontFamilyButton.updateText) {\n fontFamilyButton.updateText(displayName);\n } else if (fontFamilyButton) {\n fontFamilyButton.textContent = displayName;\n }\n }\n\n /**\n * Create element with specific font family\n * @param {string} font - Font family value\n * @returns {HTMLElement}\n */\n static create(font = 'Arial, sans-serif') {\n const node = document.createElement('span');\n node.style.fontFamily = font;\n return node;\n }\n\n /**\n * Static method to apply font family to current selection\n * @param {string} font - Font family value\n * @param {string} editorId - Editor instance ID\n */\n static applyFontFamilyToCurrentSelection(font, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for font family application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n const fontFamilyFormat = FontFamily.createForEditor(editorId);\n if (fontFamilyFormat) {\n fontFamilyFormat.apply(font);\n \n // Update button text after applying\n fontFamilyFormat.updateButtonText();\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply font family format with specified font\n * @param {string} font - Font family value\n */\n apply(font = 'Arial, sans-serif') {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n const range = selection.getRangeAt(0);\n\n function isCaretInsideFontSpan(selection, font) {\n if (!selection.rangeCount) return false;\n const range = selection.getRangeAt(0);\n let node = range.startContainer;\n\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n\n const fontNormalized = font.split(',')[0].trim().toLowerCase();\n\n while (node && node.nodeType === Node.ELEMENT_NODE) {\n if (node.tagName === 'SPAN') {\n const styleFont = node.style.fontFamily;\n if (styleFont) {\n const styleFontNormalized = styleFont.split(',')[0].trim().toLowerCase();\n if (styleFontNormalized === fontNormalized) {\n if (\n node.childNodes.length === 1 &&\n node.firstChild.nodeType === Node.TEXT_NODE &&\n node.firstChild.textContent === '\\u200B'\n ) {\n return true; // Đang trong span marker rồi\n }\n return true; // Đang trong span font-family đó\n }\n }\n }\n node = node.parentNode;\n }\n return false;\n }\n\n // Hàm đặt caret vào bên trong span mới\n function moveCaretInside(el) {\n const sel = window.getSelection();\n const range = document.createRange();\n const textNode = el.firstChild;\n range.setStart(textNode, textNode.length);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n\n if (range.collapsed) {\n if (isCaretInsideFontSpan(selection, font)) {\n // Đã ở trong span font rồi, không cần làm gì thêm\n return;\n }\n\n let node = range.startContainer;\n let offset = range.startOffset;\n\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n\n const currentSpan = node.closest && node.closest('span');\n\n // Trường hợp 1: caret trong span rỗng chứa \\u200B\n if (currentSpan && currentSpan.textContent === \"\\u200B\") {\n currentSpan.style.fontFamily = font;\n return;\n }\n\n // Trường hợp 2: caret trong span có text thật\n if (currentSpan && currentSpan.firstChild && currentSpan.firstChild.nodeType === Node.TEXT_NODE) {\n const textNode = currentSpan.firstChild;\n const caretPos = range.startOffset;\n\n const textBefore = textNode.data.slice(0, caretPos);\n const textAfter = textNode.data.slice(caretPos);\n\n const parent = currentSpan.parentNode;\n\n if (caretPos === 0) {\n // Chèn span mới trước currentSpan\n const newSpan = document.createElement('span');\n newSpan.style.fontFamily = font;\n newSpan.appendChild(document.createTextNode('\\u200B'));\n parent.insertBefore(newSpan, currentSpan);\n moveCaretInside(newSpan);\n } else if (caretPos === textNode.data.length) {\n // Chèn span mới sau currentSpan\n const newSpan = document.createElement('span');\n newSpan.style.fontFamily = font;\n newSpan.appendChild(document.createTextNode('\\u200B'));\n parent.insertBefore(newSpan, currentSpan.nextSibling);\n moveCaretInside(newSpan);\n } else {\n // Tách thành 3 span\n const span1 = document.createElement('span');\n span1.style.fontFamily = currentSpan.style.fontFamily;\n span1.appendChild(document.createTextNode(textBefore));\n\n const span2 = document.createElement('span');\n span2.style.fontFamily = font;\n span2.appendChild(document.createTextNode('\\u200B'));\n\n const span3 = document.createElement('span');\n span3.style.fontFamily = currentSpan.style.fontFamily;\n span3.appendChild(document.createTextNode(textAfter));\n\n parent.insertBefore(span1, currentSpan);\n parent.insertBefore(span2, currentSpan);\n parent.insertBefore(span3, currentSpan);\n parent.removeChild(currentSpan);\n\n moveCaretInside(span2);\n }\n return;\n }\n\n // Trường hợp 3: không ở trong span nào → tạo mới\n const newSpan = document.createElement('span');\n newSpan.style.fontFamily = font;\n newSpan.appendChild(document.createTextNode('\\u200B'));\n range.insertNode(newSpan);\n moveCaretInside(newSpan);\n\n } else {\n // Có selection → áp dụng fontName\n execFormat('fontName', font);\n }\n}\n\n \n /**\n * Toggle font family format - shows/hides font picker\n */\n async toggle(anchorButton = null) {\n if (this.customSelect.isVisible) {\n this.customSelect.hide();\n } else {\n await this.showFontPicker(anchorButton);\n }\n }\n\n /**\n * Show custom select positioned relative to font family button on toolbar\n */\n async showFontPicker(anchorButton = null) {\n // Use provided anchor button or find the default toolbar button\n let fontFamilyButton = anchorButton;\n \n if (!fontFamilyButton) {\n // Find font-family button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n \n if (toolbar) {\n fontFamilyButton = toolbar.getButton('font-family');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!fontFamilyButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n fontFamilyButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.font-family-btn');\n }\n }\n \n // Final fallback: find any font-family button in the current editor's wrapper\n if (!fontFamilyButton) {\n fontFamilyButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.font-family-btn');\n }\n }\n \n if (!fontFamilyButton) return;\n \n // Update current selection before showing\n const currentFont = this.getCurrentFont();\n if (currentFont) {\n this.customSelect.setCurrentValue(currentFont);\n }\n \n await this.customSelect.show(fontFamilyButton);\n }\n\n /**\n * Check if font family format is active - always return false (no active state)\n * Only update button text to show current font\n * @param {string} font - Optional specific font to check\n * @returns {boolean}\n */\n isActive(font = null) {\n // Always update button text to show current font\n this.updateButtonText();\n \n // Never show active state for font family button\n return false;\n }\n\n /**\n * Get current font family of the selection\n * @returns {string|null} Current font family or null\n */\n getCurrentFont() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n let currentNode = range.startContainer;\n \n // If text node, get parent element\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentElement;\n }\n \n // Get the specific editor instance\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return 'Arial, sans-serif';\n \n // Check if the selection is within this editor\n if (!editor.editor.contains(currentNode) && !editor.editor.isSameNode(currentNode)) {\n // Selection is not in this editor, return default\n return 'Arial, sans-serif';\n }\n \n // Find element with font-family style\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const element = currentNode;\n \n // Priority 1: Check if this element has explicit inline font-family\n if (element.style.fontFamily) {\n return element.style.fontFamily;\n }\n \n // Priority 2: Check computed font-family\n const computedStyle = window.getComputedStyle(element);\n const fontFamily = computedStyle.fontFamily;\n if (fontFamily && fontFamily !== 'initial' && fontFamily !== 'inherit') {\n return fontFamily;\n }\n }\n currentNode = currentNode.parentElement;\n }\n\n // Default fallback\n return 'Arial, sans-serif';\n }\n\n /**\n * Set current font for future typing\n * @param {string} font - Font family value\n */\n setCurrentFont(font) {\n // Store for future typing operations\n this.currentFont = font;\n }\n}\n\nexport default FontFamily; ","import { InlineFormat } from '../core/format.js';\nimport CustomSelect from '../ui/customselect.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Line Height Format - Handles line height formatting\n * Now supports multiple editor instances with separate popup instances\n */\nclass LineHeight extends InlineFormat {\n static formatName = 'lineHeight';\n static tagName = 'SPAN';\n \n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for LineHeight format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a line height select instance\n let customSelect = currentEditor.getPopupInstance('line-height');\n \n if (!customSelect) {\n // Create new custom select instance for this editor\n const heightMap = LineHeight.getHeightMap();\n const items = Object.values(heightMap).map(heightData => ({\n value: heightData.height,\n label: heightData.element,\n title: heightData.title\n }));\n\n customSelect = new CustomSelect({\n items: items,\n displayProperty: 'label',\n valueProperty: 'value',\n className: 'line-height-select',\n onItemSelect: (value, item) => {\n LineHeight.applyLineHeightToCurrentSelection(value, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('line-height', customSelect);\n }\n \n this.customSelect = customSelect;\n \n // Set up event listener for selection changes\n this.setupSelectionListener();\n }\n\n /**\n * Create a new LineHeight format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {LineHeight} LineHeight format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new LineHeight();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Set up event listener for selection changes to update button text\n */\n setupSelectionListener() {\n // Use a debounced function to avoid too many updates\n let updateTimeout;\n const debouncedUpdate = () => {\n clearTimeout(updateTimeout);\n updateTimeout = setTimeout(() => {\n // Only update if selection is in this editor\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n const range = selection.getRangeAt(0);\n const editor = Editor.getInstanceById(this.editorId);\n if (editor && (editor.editor.contains(range.startContainer) || editor.editor.isSameNode(range.startContainer))) {\n this.updateButtonText();\n }\n }\n }, 50); // 50ms delay\n };\n\n // Listen for selection changes\n document.addEventListener('selectionchange', debouncedUpdate);\n \n // Also listen for mouseup and keyup events for immediate feedback\n document.addEventListener('mouseup', debouncedUpdate);\n document.addEventListener('keyup', debouncedUpdate);\n \n // Store the listener for cleanup\n this.selectionListener = debouncedUpdate;\n }\n\n /**\n * Get height map with different line heights\n */\n static getHeightMap() {\n return {\n '1.0': { \n height: '1', \n element: '<span>1.0</span>', \n title: '1.0' \n },\n '1.2': { \n height: '1.2', \n element: '<span>1.2</span>', \n title: '1.2' \n },\n '1.5': { \n height: '1.5', \n element: '<span>1.5</span>', \n title: '1.5' \n },\n '1.8': { \n height: '1.8', \n element: '<span>1.8</span>', \n title: '1.8' \n },\n '2.0': { \n height: '2', \n element: '<span>2.0</span>', \n title: '2.0' \n },\n '2.5': { \n height: '2.5', \n element: '<span>2.5</span>', \n title: '2.5' \n },\n '3.0': { \n height: '3', \n element: '<span>3.0</span>', \n title: '3.0' \n }\n };\n }\n\n /**\n * Get display name for line height\n * @param {string} height - Line height value\n * @returns {string} Display name\n */\n static getHeightDisplayName(height) {\n const heightMap = this.getHeightMap();\n if (heightMap[height]?.title) return heightMap[height].title;\n // Always show the applied value rather than a placeholder\n const num = parseFloat(height);\n if (!isNaN(num)) return String(num);\n return 'Normal';\n }\n\n /**\n * Update custom button text based on current line height\n */\n updateButtonText() {\n const currentHeight = this.getCurrentHeight();\n const displayName = LineHeight.getHeightDisplayName(currentHeight || '1.15');\n \n // Find line-height button in the specific editor's toolbar using editorId\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let lineHeightButton = null;\n \n if (toolbar) {\n lineHeightButton = toolbar.getButton('line-height');\n }\n \n // Fallback: find button by class in the specific editor's toolbar\n if (!lineHeightButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n lineHeightButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.line-height-btn');\n }\n }\n \n // Final fallback: find any line-height button in the specific editor's wrapper\n if (!lineHeightButton) {\n lineHeightButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.line-height-btn');\n }\n \n if (lineHeightButton && lineHeightButton.updateText) {\n lineHeightButton.updateText(displayName);\n } else if (lineHeightButton) {\n lineHeightButton.textContent = displayName;\n }\n }\n\n /**\n * Create element with specific line height\n * @param {string} height - Line height value\n * @returns {HTMLElement}\n */\n static create(height = '1.15') {\n const node = document.createElement('span');\n node.style.lineHeight = height;\n return node;\n }\n\n /**\n * Static method to apply line height to current selection\n * @param {string} height - Line height value\n * @param {string} editorId - Editor instance ID\n */\n static applyLineHeightToCurrentSelection(height, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for line height application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n const lineHeightFormat = LineHeight.createForEditor(editorId);\n if (lineHeightFormat) {\n lineHeightFormat.apply(height);\n \n // Update button text after applying\n lineHeightFormat.updateButtonText();\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply line height format with specified height\n * @param {string} height - Line height value\n */\n apply(height = '1.15') {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n // Hàm đặt caret vào bên trong span mới\n function moveCaretInside(el) {\n const sel = window.getSelection();\n const range = document.createRange();\n const textNode = el.firstChild;\n range.setStart(textNode, textNode.length);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n // Save state before applying format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n \n if (range.collapsed) {\n // No selection - set style for future typing\n let node = range.startContainer;\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n\n // Tìm phần tử block cha gần nhất (div, p, li, ...)\n const blockParent = node.closest('div, p, li, section, article') || node;\n blockParent.style.lineHeight = height;\n moveCaretInside(blockParent);\n \n return;\n }\n\n // Apply to block elements if possible for better line height effect\n const blockElements = this.getBlockElementsInRange(range);\n \n if (blockElements.length > 0) {\n // Apply to block elements\n blockElements.forEach(block => {\n block.style.lineHeight = height;\n });\n } else {\n // Fallback: wrap in span with line-height\n const heightSpan = this.constructor.create(height);\n \n try {\n const contents = range.extractContents();\n heightSpan.appendChild(contents);\n range.insertNode(heightSpan);\n \n // Select the content in the span\n const newRange = document.createRange();\n newRange.selectNodeContents(heightSpan);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } catch (error) {\n console.warn('Failed to apply line height manually:', error);\n }\n }\n }\n\n /**\n * Get block elements within the range\n * @param {Range} range - Selection range\n * @returns {Array} Array of block elements\n */\n getBlockElementsInRange(range) {\n const blockElements = [];\n const blockTags = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI'];\n \n // Create a fragment of the selection\n const fragment = range.cloneContents();\n \n // Get all potential block elements in the fragment\n const walker = document.createTreeWalker(\n fragment,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node) => {\n return blockTags.includes(node.tagName) ? \n NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;\n }\n }\n );\n\n // Get corresponding elements from the actual document\n let node = walker.nextNode();\n while (node) {\n // Find the actual element in the document that corresponds to this fragment node\n const actualNode = range.commonAncestorContainer.querySelector(\n `${node.tagName.toLowerCase()}:not([data-processed])`\n );\n if (actualNode && range.intersectsNode(actualNode)) {\n blockElements.push(actualNode);\n // Mark as processed to avoid duplicates\n actualNode.setAttribute('data-processed', 'true');\n }\n node = walker.nextNode();\n }\n\n // Clean up the temporary attribute\n blockElements.forEach(el => el.removeAttribute('data-processed'));\n\n // If no block elements found in selection, get the closest parent block element\n if (blockElements.length === 0) {\n let currentNode = range.startContainer;\n \n // If text node, get parent element\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentElement;\n }\n \n // Find parent block element\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE && \n blockTags.includes(currentNode.tagName)) {\n blockElements.push(currentNode);\n break;\n }\n currentNode = currentNode.parentElement;\n }\n }\n \n return blockElements;\n }\n\n /**\n * Toggle line height format - shows/hides height picker\n */\n async toggle() {\n if (this.customSelect.isVisible) {\n this.customSelect.hide();\n } else {\n await this.showHeightPicker();\n }\n }\n\n /**\n * Show custom select positioned relative to line height button on toolbar\n */\n async showHeightPicker() {\n // Find line-height button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let lineHeightButton = null;\n \n if (toolbar) {\n lineHeightButton = toolbar.getButton('line-height');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!lineHeightButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n lineHeightButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.line-height-btn');\n }\n }\n \n // Final fallback: find any line-height button in the current editor's wrapper\n if (!lineHeightButton) {\n lineHeightButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.line-height-btn');\n }\n \n if (!lineHeightButton) {\n console.warn('Line-height button not found for editor:', this.editorId);\n return;\n }\n \n // Update current selection before showing\n const currentHeight = this.getCurrentHeight();\n if (currentHeight) {\n this.customSelect.setCurrentValue(currentHeight);\n }\n \n await this.customSelect.show(lineHeightButton);\n }\n\n /**\n * Check if line height format is active - always return false (no active state)\n * Only update button text to show current height\n * @param {string} height - Optional specific height to check\n * @returns {boolean}\n */\n isActive(height = null) {\n // Always update button text to show current height\n this.updateButtonText();\n \n // Never show active state for line height button\n return false;\n }\n\n /**\n * Get current line height of the selection\n * @returns {string|null} Current line height or null\n */\n getCurrentHeight() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n let currentNode = range.startContainer;\n \n // If text node, get parent element\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentElement;\n }\n \n // Get the specific editor instance\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return '1.15';\n \n // Check if the selection is within this editor\n if (!editor.editor.contains(currentNode) && !editor.editor.isSameNode(currentNode)) {\n // Selection is not in this editor, return default\n return '1.15';\n }\n \n // Find element with line-height style\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const element = currentNode;\n \n // Priority 1: Check if this element has explicit inline line-height\n if (element.style.lineHeight) {\n const height = element.style.lineHeight;\n return this.normalizeHeightValue(height);\n }\n \n // Priority 2: Check computed line-height\n const computedStyle = window.getComputedStyle(element);\n const lineHeight = computedStyle.lineHeight;\n \n if (lineHeight && lineHeight !== 'normal' && lineHeight !== 'initial' && lineHeight !== 'inherit') {\n // Convert pixel values to relative values if possible\n if (lineHeight.endsWith('px')) {\n const fontSize = parseFloat(computedStyle.fontSize);\n const lineHeightPx = parseFloat(lineHeight);\n if (fontSize > 0) {\n const relative = (lineHeightPx / fontSize).toFixed(2);\n return this.normalizeHeightValue(relative);\n }\n }\n return this.normalizeHeightValue(lineHeight);\n }\n }\n currentNode = currentNode.parentElement;\n }\n\n // Default fallback\n return '1.15';\n }\n\n /**\n * Normalize height value to match heightMap keys\n * @param {string} height - Raw height value\n * @returns {string} Normalized height value\n */\n normalizeHeightValue(height) {\n if (!height) return '1.15';\n \n // Convert to number and back to string to normalize\n const numValue = parseFloat(height);\n if (isNaN(numValue)) return '1.15';\n \n // Round to 1 decimal place and convert back to string\n const normalized = numValue.toFixed(1);\n \n // Check if this normalized value exists in our heightMap\n const heightMap = this.constructor.getHeightMap();\n if (heightMap[normalized]) {\n return normalized;\n }\n \n // If not in map, return the original value\n return height;\n }\n\n\n /**\n * Clean up event listeners to prevent memory leaks\n */\n destroy() {\n if (this.selectionListener) {\n document.removeEventListener('selectionchange', this.selectionListener);\n document.removeEventListener('mouseup', this.selectionListener);\n document.removeEventListener('keyup', this.selectionListener);\n this.selectionListener = null;\n }\n }\n}\n\nexport default LineHeight; ","import { InlineFormat } from '../core/format.js';\nimport CustomSelect from '../ui/customselect.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Capitalization Format - Handles text capitalization\n * Now supports multiple editor instances with separate popup instances\n */\nclass Capitalization extends InlineFormat {\n static formatName = 'capitalization';\n static tagName = 'SPAN';\n \n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Capitalization format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a capitalization select instance\n let customSelect = currentEditor.getPopupInstance('capitalization');\n \n if (!customSelect) {\n // Create new custom select instance for this editor\n const capMap = Capitalization.getCapitalizationMap();\n const items = Object.values(capMap).map(capData => ({\n value: capData.style,\n label: capData.element,\n title: capData.title\n }));\n\n customSelect = new CustomSelect({\n items: items,\n displayProperty: 'label',\n valueProperty: 'value',\n className: 'capitalization-select',\n onItemSelect: (value, item) => {\n Capitalization.applyCapitalizationToCurrentSelection(value, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('capitalization', customSelect);\n }\n \n this.customSelect = customSelect;\n }\n\n /**\n * Create a new Capitalization format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Capitalization} Capitalization format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Capitalization();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Get capitalization map with different text transformations\n */\n static getCapitalizationMap() {\n return {\n 'capitalize': { \n style: 'capitalize', \n element: '<span>Capitalize</span>', \n title: 'Capitalize' \n },\n 'uppercase': { \n style: 'uppercase', \n element: '<span>UPPERCASE</span>', \n title: 'UPPERCASE' \n },\n 'lowercase': { \n style: 'lowercase', \n element: '<span>lowercase</span>', \n title: 'lowercase' \n },\n 'small-caps': { \n style: 'small-caps', \n element: '<span>Small Caps</span>', \n title: 'Small Caps' \n }\n };\n }\n\n /**\n * Get display name for capitalization\n * @param {string} style - Text transform value\n * @returns {string} Display name\n */\n static getCapitalizationDisplayName(style) {\n const capMap = this.getCapitalizationMap();\n return capMap[style]?.title || 'Capitalization';\n }\n\n /**\n * Update custom button text based on current capitalization\n */\n updateButtonText() {\n const currentCap = this.getCurrentCapitalization();\n const displayName = Capitalization.getCapitalizationDisplayName(currentCap || 'none');\n \n // Find capitalization button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let capitalizationButton = null;\n \n if (toolbar) {\n capitalizationButton = toolbar.getButton('capitalization');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!capitalizationButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n capitalizationButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.capitalization-btn');\n }\n }\n \n // Final fallback: find any capitalization button in the current editor's wrapper\n if (!capitalizationButton) {\n capitalizationButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.capitalization-btn');\n }\n \n if (capitalizationButton && capitalizationButton.updateText) {\n capitalizationButton.updateText(displayName);\n } else if (capitalizationButton) {\n capitalizationButton.textContent = displayName;\n }\n }\n\n /**\n * Create element with specific text transformation\n * @param {string} style - Text transform value\n * @returns {HTMLElement}\n */\n static create(style = 'none') {\n const node = document.createElement('span');\n if (style === 'small-caps') {\n node.style.fontVariant = 'small-caps';\n } else {\n node.style.textTransform = style;\n }\n return node;\n }\n\n /**\n * Static method to apply capitalization to current selection\n * @param {string} style - Text transform value\n * @param {string} editorId - Editor instance ID\n */\n static applyCapitalizationToCurrentSelection(style, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for capitalization application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n const capFormat = Capitalization.createForEditor(editorId);\n if (capFormat) {\n capFormat.apply(style);\n \n // Update button text after applying\n capFormat.updateButtonText();\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Check if an element has capitalization-related inline or computed styles\n * @param {Element} element\n * @returns {boolean}\n */\n hasCapitalizationStyling(element) {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;\n if (element.style.fontVariant === 'small-caps') return true;\n if (element.style.textTransform && element.style.textTransform !== 'none') return true;\n const computed = window.getComputedStyle(element);\n if (computed.fontVariant === 'small-caps') return true;\n if (computed.textTransform && computed.textTransform !== 'none') return true;\n return false;\n }\n\n /**\n * Determine whether an element is our capitalization wrapper (inline styles only)\n * @param {Element} element\n * @returns {boolean}\n */\n isCapitalizationElement(element) {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;\n if (element.style.fontVariant === 'small-caps') return true;\n if (element.style.textTransform && element.style.textTransform !== 'none') return true;\n return false;\n }\n\n /**\n * Find nearest ancestor element that is a capitalization wrapper\n * @param {Node} node\n * @returns {Element|null}\n */\n findAncestorCapitalizationElement(node) {\n let current = node;\n if (!current) return null;\n if (current.nodeType === Node.TEXT_NODE) current = current.parentElement;\n while (current && current !== document.body) {\n if (this.isCapitalizationElement(current)) return current;\n current = current.parentElement;\n }\n return null;\n }\n\n /**\n * Apply capitalization style directly to an element\n * @param {Element} element\n * @param {string} style\n */\n setElementCapitalizationStyle(element, style) {\n if (!element) return;\n if (style === 'small-caps') {\n element.style.fontVariant = 'small-caps';\n element.style.textTransform = '';\n } else if (style === 'none') {\n element.style.fontVariant = '';\n element.style.textTransform = '';\n } else {\n element.style.fontVariant = '';\n element.style.textTransform = style;\n }\n }\n\n /**\n * Apply capitalization format with specified style\n * @param {string} style - Text transform value\n */\n apply(style = 'none') {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Lưu trạng thái trước khi format\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n if (range.collapsed) return;\n\n // Small Caps is a visual style (not a one-way text transform): wrap the\n // selected text in a span so it can render — and be toggled off — properly.\n if (style === 'small-caps') {\n const frag = range.extractContents();\n const span = document.createElement('span');\n span.style.fontVariant = 'small-caps';\n span.appendChild(frag);\n range.insertNode(span);\n const r = document.createRange();\n r.selectNodeContents(span);\n selection.removeAllRanges();\n selection.addRange(r);\n return;\n }\n\n // Hàm đổi chữ theo style\n function transformText(text, style) {\n switch (style) {\n case 'uppercase':\n return text.toUpperCase();\n case 'lowercase':\n return text.toLowerCase();\n case 'capitalize':\n text =text.toLowerCase();\n return text.replace(/\\b\\w/g, char => char.toUpperCase());\n default:\n return text; // 'none' hoặc không đổi\n }\n }\n function removeEmptyElements(node) {\n if (!node) return;\n\n // Duyệt cây DOM từ node này xuống\n const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT, null);\n const toRemove = [];\n\n while (walker.nextNode()) {\n const el = walker.currentNode;\n // Nếu không có text hoặc chỉ toàn khoảng trắng & không có element con\n if (!el.textContent.trim() && el.childElementCount === 0) {\n toRemove.push(el);\n }\n }\n\n toRemove.forEach(el => el.remove());\n }\n\n // Nếu có selection: đổi text bên trong\n const contents = range.extractContents();\n const walker = document.createTreeWalker(contents, NodeFilter.SHOW_TEXT, null);\n\n while (walker.nextNode()) {\n const textNode = walker.currentNode;\n textNode.textContent = transformText(textNode.textContent, style);\n }\n\n range.deleteContents();\n range.insertNode(contents);\n removeEmptyElements(range.commonAncestorContainer);\n\n // Giữ nguyên selection\n selection.removeAllRanges();\n selection.addRange(range);\n }\n\n /**\n * Remove existing capitalization formatting from range\n * @param {Range} range - Selection range\n */\n removeExistingCapitalization(range) {\n const root = range.commonAncestorContainer;\n const elementsToProcess = new Set();\n\n // Helper to maybe add element\n const maybeAdd = (el) => {\n if (el && el.nodeType === Node.ELEMENT_NODE && this.hasCapitalizationStyling(el) && range.intersectsNode(el)) {\n elementsToProcess.add(el);\n }\n };\n\n // Include the root if applicable\n if (root && root.nodeType === Node.ELEMENT_NODE) {\n maybeAdd(root);\n }\n\n // Walk descendants\n const walker = document.createTreeWalker(\n root,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node) => (range.intersectsNode(node) && this.hasCapitalizationStyling(node)) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP\n }\n );\n let node;\n while ((node = walker.nextNode())) {\n elementsToProcess.add(node);\n }\n\n // Include ancestors from start and end containers\n const addAncestors = (startNode) => {\n let current = startNode.nodeType === Node.TEXT_NODE ? startNode.parentElement : startNode;\n while (current && current !== document.body) {\n maybeAdd(current);\n current = current.parentElement;\n }\n };\n addAncestors(range.startContainer);\n addAncestors(range.endContainer);\n\n Array.from(elementsToProcess).forEach(element => {\n // Clear text transform and font variant styles\n element.style.textTransform = '';\n element.style.fontVariant = '';\n \n // If element has no other styles, unwrap it\n if (!element.style.cssText.trim() && !element.className) {\n this.unwrapElement(element);\n }\n });\n }\n\n /**\n * Unwrap an element, moving its children to its parent\n * @param {Element} element - Element to unwrap\n */\n unwrapElement(element) {\n const parent = element.parentNode;\n if (!parent) return;\n\n while (element.firstChild) {\n parent.insertBefore(element.firstChild, element);\n }\n parent.removeChild(element);\n }\n\n /**\n * Toggle capitalization format - shows/hides capitalization picker\n */\n async toggle() {\n if (this.customSelect.isVisible) {\n this.customSelect.hide();\n } else {\n await this.showCapitalizationPicker();\n }\n }\n\n /**\n * Show custom select positioned relative to capitalization button on toolbar\n */\n async showCapitalizationPicker() {\n // Find capitalization button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let capitalizationButton = null;\n \n if (toolbar) {\n capitalizationButton = toolbar.getButton('capitalization');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!capitalizationButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n capitalizationButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.capitalization-btn');\n }\n }\n \n // Final fallback: find any capitalization button in the current editor's wrapper\n if (!capitalizationButton) {\n capitalizationButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.capitalization-btn');\n }\n \n if (!capitalizationButton) {\n console.warn('Capitalization button not found for editor:', this.editorId);\n return;\n }\n \n // Update current selection before showing\n const currentCap = this.getCurrentCapitalization();\n if (currentCap) {\n this.customSelect.setCurrentValue(currentCap);\n }\n \n await this.customSelect.show(capitalizationButton);\n }\n\n /**\n * Check if capitalization format is active - always return false (no active state)\n * Only update button text to show current capitalization\n * @param {string} style - Optional specific style to check\n * @returns {boolean}\n */\n isActive(style = null) {\n // Always update button text to show current capitalization\n this.updateButtonText();\n \n // Never show active state for capitalization button\n return false;\n }\n\n /**\n * Get current capitalization of the selection\n * @returns {string|null} Current text transform or null\n */\n getCurrentCapitalization() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n let currentNode = range.startContainer;\n \n // If text node, get parent element\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentElement;\n }\n \n // Find element with text-transform or font-variant style\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const element = currentNode;\n \n // Priority 1: Check if this element has explicit inline styles\n if (element.style.fontVariant === 'small-caps') {\n return 'small-caps';\n }\n if (element.style.textTransform && element.style.textTransform !== 'none') {\n return element.style.textTransform;\n }\n \n // Priority 2: Check computed styles\n const computedStyle = window.getComputedStyle(element);\n if (computedStyle.fontVariant === 'small-caps') {\n return 'small-caps';\n }\n if (computedStyle.textTransform && computedStyle.textTransform !== 'none') {\n return computedStyle.textTransform;\n }\n }\n currentNode = currentNode.parentElement;\n }\n\n // Default fallback\n return 'none';\n }\n\n /**\n * Set current capitalization for future typing\n * @param {string} style - Text transform value\n */\n setCurrentCapitalization(style) {\n // Store for future typing operations\n this.currentCapitalization = style;\n }\n\n /**\n * Quick toggle methods for common capitalizations\n */\n static toggleUppercase() {\n const cap = new Capitalization();\n const current = cap.getCurrentCapitalization();\n const newStyle = current === 'uppercase' ? 'none' : 'uppercase';\n Capitalization.applyCapitalizationToCurrentSelection(newStyle);\n }\n\n static toggleLowercase() {\n const cap = new Capitalization();\n const current = cap.getCurrentCapitalization();\n const newStyle = current === 'lowercase' ? 'none' : 'lowercase';\n Capitalization.applyCapitalizationToCurrentSelection(newStyle);\n }\n\n static toggleCapitalize() {\n const cap = new Capitalization();\n const current = cap.getCurrentCapitalization();\n const newStyle = current === 'capitalize' ? 'none' : 'capitalize';\n Capitalization.applyCapitalizationToCurrentSelection(newStyle);\n }\n\n static toggleSmallCaps() {\n const cap = new Capitalization();\n const current = cap.getCurrentCapitalization();\n const newStyle = current === 'small-caps' ? 'none' : 'small-caps';\n Capitalization.applyCapitalizationToCurrentSelection(newStyle);\n }\n}\n\nexport default Capitalization; ","import IconUtils from './icons.js';\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\n/**\n * Text Align Picker Component - Popup for selecting text alignment\n */\nclass TextAlignPicker {\n constructor(options = {}) {\n this.options = {\n alignments: [\n { value: 'left', label: 'Align Left', icon: 'align-left' },\n { value: 'center', label: 'Align Center', icon: 'align-center' },\n { value: 'right', label: 'Align Right', icon: 'align-right' },\n { value: 'justify', label: 'Justify', icon: 'align-justify' }\n ],\n onAlignSelect: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.currentAlignment = 'left';\n this.clickOutsideHandler = null;\n \n this.createAlignPicker();\n }\n\n /**\n * Create text align picker popup\n */\n createAlignPicker() {\n // Create popup\n this.popup = document.createElement('div');\n this.popup.className = 'text-align-picker-popup';\n \n // Create alignment buttons\n this.createAlignmentButtons();\n \n // Add popup to container\n appendPopup(this.popup);\n }\n\n /**\n * Create alignment buttons\n */\n async createAlignmentButtons() {\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'align-button-container';\n \n // Icons are now inline, no need to preload\n \n // Create buttons\n for (const alignment of this.options.alignments) {\n const alignButton = document.createElement('button');\n alignButton.type = 'button';\n alignButton.className = 'align-button';\n alignButton.dataset.alignment = alignment.value;\n alignButton.title = alignment.label;\n \n // Icon only — the label lives in the tooltip (title) above.\n const iconSvg = IconUtils.getIcon(alignment.icon);\n if (iconSvg) {\n alignButton.innerHTML = `<span class=\"icon-wrapper\">${iconSvg}</span>`;\n } else {\n alignButton.textContent = alignment.label.charAt(0);\n }\n \n alignButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectAlignment(alignment.value);\n });\n \n buttonContainer.appendChild(alignButton);\n }\n \n this.popup.appendChild(buttonContainer);\n }\n\n /**\n * Setup click outside handler\n */\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n // Add slight delay to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n /**\n * Remove click outside handler\n */\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n /**\n * Show text align picker popup\n * @param {HTMLElement} anchor - Element to position popup relative to\n */\n show(anchor) {\n if (!anchor) return;\n \n // Ensure popup is in DOM\n if (!document.body.contains(this.popup)) {\n appendPopup(this.popup);\n }\n \n // Update current alignment state\n this.updateCurrentAlignment();\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup by adding visible class\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Setup click outside handler\n this.setupClickOutside();\n }\n\n /**\n * Hide text align picker popup\n */\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n /**\n * Select alignment and trigger callback\n * @param {string} alignment - Selected alignment\n */\n selectAlignment(alignment) {\n this.currentAlignment = alignment;\n \n if (this.options.onAlignSelect) {\n this.options.onAlignSelect(alignment);\n }\n \n this.hide();\n }\n\n /**\n * Update current alignment state based on selection\n */\n updateCurrentAlignment() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n try {\n const range = selection.getRangeAt(0);\n\n // Lấy tất cả block trong vùng chọn\n const blocks = this.getSelectedBlockElements(range);\n\n // Nếu có block → lấy block đầu tiên, nếu không thì fallback về block bao quanh\n const firstBlock = blocks.length > 0\n ? blocks[0]\n : this.getBlockElement(range.commonAncestorContainer);\n\n if (firstBlock) {\n const textAlign = window.getComputedStyle(firstBlock).textAlign;\n this.currentAlignment =\n textAlign === 'left' || textAlign === 'start' || !textAlign\n ? 'left'\n : textAlign;\n } else {\n this.currentAlignment = 'left';\n }\n\n // Cập nhật trạng thái nút trong popup\n const buttons = this.popup.querySelectorAll('.align-button');\n buttons.forEach(button => {\n if (button.dataset.alignment === this.currentAlignment) {\n button.classList.add('active');\n } else {\n button.classList.remove('active');\n }\n });\n\n // Cập nhật icon trên toolbar\n this.updateToolbarButtonIcon(this.currentAlignment);\n } catch (error) {\n console.error('Error updating current alignment:', error);\n }\n }\n\n /**\n * Lấy tất cả block element trong vùng chọn\n */\n getSelectedBlockElements(range) {\n const blocks = [];\n const startBlock = this.getBlockElement(range.startContainer);\n const endBlock = this.getBlockElement(range.endContainer);\n\n if (startBlock) blocks.push(startBlock);\n\n if (startBlock && endBlock && startBlock !== endBlock) {\n let current = startBlock;\n while (current && current !== endBlock) {\n current = current.nextElementSibling;\n if (current && this.getBlockElement(current) && !blocks.includes(current)) {\n blocks.push(current);\n }\n }\n if (endBlock && !blocks.includes(endBlock)) {\n blocks.push(endBlock);\n }\n }\n\n return blocks;\n }\n\n /**\n * Update toolbar button icon based on alignment\n * @param {string} alignment - Current alignment\n */\n updateToolbarButtonIcon(alignment) {\n // Import TextAlign class to use its static method\n import('../formats/text-align.js').then(module => {\n const TextAlign = module.default;\n TextAlign.updateToolbarButtonIcon(alignment);\n }).catch(error => {\n console.warn('Could not import TextAlign class:', error);\n });\n }\n\n /**\n * Get the block element containing the given node\n */\n getBlockElement(node) {\n if (!node) return null;\n \n let currentNode = node;\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.parentNode;\n }\n return null;\n }\n\n /**\n * Destroy text align picker\n */\n destroy() {\n this.removeClickOutside();\n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n }\n}\n\nexport default TextAlignPicker; ","import { BlockFormat } from '../core/format.js';\nimport TextAlignPicker from '../ui/text-align-picker.js';\nimport IconUtils from '../ui/icons.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\nimport { execFormat } from '../utils/exec-command.js';\n\n/**\n * Text Align Format - Handles text alignment formatting\n * Now supports multiple editor instances with separate popup instances\n */\nclass TextAlign extends BlockFormat {\n static formatName = 'text-align';\n static tagName = 'P';\n static attribute = 'style';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for TextAlign format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a text align picker instance\n let alignPicker = currentEditor.getPopupInstance('text-align');\n \n if (!alignPicker) {\n // Create new text align picker instance for this editor\n alignPicker = new TextAlignPicker({\n onAlignSelect: (alignment) => {\n TextAlign.applyAlignToCurrentSelection(alignment, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('text-align', alignPicker);\n }\n \n this.alignPicker = alignPicker;\n }\n\n /**\n * Create a new TextAlign format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {TextAlign} TextAlign format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new TextAlign();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create block element with text alignment\n * @param {string} value - Alignment value (left, center, right, justify)\n * @returns {HTMLElement}\n */\n static create(value) {\n const node = document.createElement(this.tagName);\n if (value && value !== 'left') {\n node.style.textAlign = value;\n }\n return node;\n }\n\n /**\n * Static method to apply alignment to current selection or cursor position\n * @param {string} alignment - Alignment value\n * @param {string} editorId - Editor instance ID\n */\n static applyAlignToCurrentSelection(alignment, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for text alignment application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n // Lưu vị trí caret trước khi thay đổi\n const originalRange = selection.getRangeAt(0);\n const caretContainer = originalRange.endContainer;\n const caretOffset = originalRange.endOffset;\n try {\n const range = selection.getRangeAt(0);\n const blockElements = TextAlign.getSelectedBlockElements(range);\n \n if (blockElements.length === 0) {\n // If no block elements found, create one\n execFormat('formatBlock', 'p');\n const newRange = selection.getRangeAt(0);\n const newBlocks = TextAlign.getSelectedBlockElements(newRange);\n newBlocks.forEach(block => {\n TextAlign.applyAlignmentToBlock(block, alignment);\n });\n } else {\n // Apply alignment to existing blocks\n blockElements.forEach(block => {\n TextAlign.applyAlignmentToBlock(block, alignment);\n });\n }\n \n // Update toolbar button icon after applying alignment\n TextAlign.updateToolbarButtonIcon(alignment, editorId);\n // Khôi phục caret\n selection.removeAllRanges();\n const newCaretRange = document.createRange();\n newCaretRange.setStart(caretContainer, caretOffset);\n newCaretRange.collapse(true);\n selection.addRange(newCaretRange);\n\n } catch (error) {\n console.error('Error applying text alignment:', error);\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply alignment to a specific block element\n */\n static applyAlignmentToBlock(block, alignment) {\n if (alignment === 'left') {\n block.style.textAlign = '';\n } else {\n block.style.textAlign = alignment;\n }\n }\n\n /**\n * Get icon name for alignment value\n * @param {string} alignment - Alignment value\n * @returns {string} Icon name\n */\n static getIconNameForAlignment(alignment) {\n const iconMap = {\n 'left': 'align-left',\n 'center': 'align-center',\n 'right': 'align-right',\n 'justify': 'align-justify'\n };\n return iconMap[alignment] || 'align-center';\n }\n\n /**\n * Update toolbar button icon based on alignment\n * @param {string} alignment - Current alignment\n * @param {string} editorId - Editor instance ID\n */\n static updateToolbarButtonIcon(alignment, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let button = null;\n \n if (toolbar) {\n button = toolbar.getButton('text-align');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!button) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n button = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-align-btn');\n }\n }\n \n // Final fallback: find any text-align button in the current editor's wrapper\n if (!button) {\n button = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-align-btn');\n }\n \n if (!button) return;\n\n const iconName = TextAlign.getIconNameForAlignment(alignment);\n const titleMap = {\n 'left': 'Align Left',\n 'center': 'Align Center', \n 'right': 'Align Right',\n 'justify': 'Justify'\n };\n \n // Update button title\n button.title = titleMap[alignment] || 'Text Alignment';\n \n // Update icon\n const svgContent = IconUtils.getIcon(iconName);\n if (svgContent) {\n const iconSpan = button.querySelector('.icon');\n if (iconSpan) {\n iconSpan.innerHTML = svgContent;\n } else {\n button.innerHTML = `<span class=\"icon\">${svgContent}</span>`;\n }\n }\n }\n\n /**\n * Get all selected block elements\n */\n static getSelectedBlockElements(range) {\n const blocks = [];\n\n // Xác định block chứa điểm bắt đầu và kết thúc\n const startBlock = TextAlign.getBlockElement(range.startContainer);\n const endBlock = TextAlign.getBlockElement(range.endContainer);\n\n if (!startBlock || !endBlock) return blocks;\n\n // Nếu chỉ trong 1 block\n if (startBlock === endBlock) {\n blocks.push(startBlock);\n return blocks;\n }\n\n // Duyệt từ startBlock tới endBlock\n let currentBlock = startBlock;\n while (currentBlock) {\n // Chỉ thêm block nếu nó giao với range\n const blockRange = document.createRange();\n blockRange.selectNodeContents(currentBlock);\n\n if (range.compareBoundaryPoints(Range.END_TO_START, blockRange) < 0 &&\n range.compareBoundaryPoints(Range.START_TO_END, blockRange) > 0) {\n blocks.push(currentBlock);\n }\n\n if (currentBlock === endBlock) break;\n currentBlock = TextAlign.getNextBlockElement(currentBlock);\n }\n\n return blocks;\n}\n\n /**\n * Get the block element containing the given node\n */\n static getBlockElement(node) {\n if (!node) return null;\n \n let currentNode = node;\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.parentNode;\n }\n return null;\n }\n\n /**\n * Get next block element in document order\n */\n static getNextBlockElement(element) {\n let currentNode = element.nextSibling;\n \n while (currentNode) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.nextSibling;\n }\n \n return null;\n }\n\n /**\n * Apply alignment formatting with specified value\n * @param {string} value - Alignment value (left, center, right, justify)\n */\n apply(value = 'left') {\n TextAlign.applyAlignToCurrentSelection(value, this.editorId);\n }\n\n /**\n * Remove alignment formatting (reset to left)\n */\n remove() {\n TextAlign.applyAlignToCurrentSelection('left', this.editorId);\n }\n\n /**\n * Toggle alignment formatting - shows/hides alignment picker\n */\n toggle() {\n if (this.alignPicker.isVisible) {\n this.alignPicker.hide();\n } else {\n this.showAlignPicker();\n }\n }\n\n /**\n * Show alignment picker positioned relative to align button on toolbar\n */\n showAlignPicker() {\n // Find text-align button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let alignButton = null;\n \n if (toolbar) {\n alignButton = toolbar.getButton('text-align');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!alignButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n alignButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-align-btn');\n }\n }\n \n // Final fallback: find any text-align button in the current editor's wrapper\n if (!alignButton) {\n alignButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-align-btn');\n }\n \n if (!alignButton) {\n console.warn('Text-align button not found for editor:', this.editorId);\n return;\n }\n \n this.alignPicker.show(alignButton);\n }\n\n /**\n * Check if specific alignment is active in current selection\n * Always returns false because text-align button should not have active state\n * Instead, the button icon changes to reflect current alignment\n */\n isActive(alignment = null) {\n // Update button icon based on current alignment\n const currentAlignment = TextAlign.getCurrentAlignment();\n TextAlign.updateToolbarButtonIcon(currentAlignment, this.editorId);\n\n // Highlight when a non-default alignment (center / right / justify) is set.\n return !!currentAlignment && currentAlignment !== 'left' && currentAlignment !== 'start';\n }\n\n /**\n * Get current alignment of selection\n */\n static getCurrentAlignment() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return 'left';\n\n try {\n const range = selection.getRangeAt(0);\n // Lấy tất cả block trong vùng chọn\n const blocks = TextAlign.getSelectedBlockElements(range);\n\n // Nếu có nhiều block -> lấy block đầu tiên\n const firstBlock = blocks.length > 0 \n ? blocks[0] \n : TextAlign.getBlockElement(range.commonAncestorContainer);\n\n if (!firstBlock) return 'left';\n\n const textAlign = window.getComputedStyle(firstBlock).textAlign;\n return textAlign === 'left' || textAlign === 'start' || !textAlign ? 'left' : textAlign;\n } catch (error) {\n console.error('Error getting current alignment:', error);\n return 'left';\n }\n}\n\n}\n\nexport default TextAlign; ","import IconUtils from './icons.js';\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\n/**\n * List Picker Component - Popup for selecting list types\n */\nclass ListPicker {\n constructor(options = {}) {\n this.options = {\n listTypes: [\n { value: 'bullet', label: 'Bullet List', icon: 'list-bullet' },\n { value: 'ordered', label: 'Numbered List', icon: 'list-ordered' },\n { value: 'roman', label: 'Roman Numerals List', icon: 'list-roman' },\n { value: 'alpha', label: 'Alphabetical List', icon: 'list-alpha' }\n ],\n onListSelect: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.currentListType = null;\n this.clickOutsideHandler = null;\n \n this.createListPicker();\n }\n\n /**\n * Create list picker popup\n */\n createListPicker() {\n // Create popup\n this.popup = document.createElement('div');\n this.popup.className = 'list-picker-popup';\n \n // Create list type buttons\n this.createListTypeButtons();\n \n // Add popup to container\n appendPopup(this.popup);\n }\n\n /**\n * Create list type buttons\n */\n async createListTypeButtons() {\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'list-button-container';\n \n // Icons are now inline, no need to preload\n \n // Create buttons\n for (const listType of this.options.listTypes) {\n const listButton = document.createElement('button');\n listButton.type = 'button';\n listButton.className = 'list-button';\n listButton.dataset.listType = listType.value;\n listButton.title = listType.label;\n \n // Add icon\n const iconSvg = IconUtils.getIcon(listType.icon);\n if (iconSvg) {\n listButton.innerHTML = iconSvg;\n } else {\n listButton.textContent = listType.label.charAt(0);\n }\n \n listButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectListType(listType.value);\n });\n \n buttonContainer.appendChild(listButton);\n }\n \n this.popup.appendChild(buttonContainer);\n }\n\n /**\n * Setup click outside handler\n */\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n // Add slight delay to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n /**\n * Remove click outside handler\n */\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n /**\n * Show list picker popup\n * @param {HTMLElement} anchor - Element to position popup relative to\n */\n show(anchor) {\n if (!anchor) return;\n \n // Ensure popup is in DOM\n if (!document.body.contains(this.popup)) {\n appendPopup(this.popup);\n }\n \n // Update current list type state\n this.updateCurrentListType();\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup by adding visible class\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Setup click outside handler\n this.setupClickOutside();\n }\n\n /**\n * Hide list picker popup\n */\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n /**\n * Select list type and trigger callback\n * @param {string} listType - Selected list type\n */\n selectListType(listType) {\n this.currentListType = listType;\n \n if (this.options.onListSelect) {\n this.options.onListSelect(listType);\n }\n \n this.hide();\n }\n\n /**\n * Update current list type state based on selection\n */\n updateCurrentListType() {\n try {\n const listType = this.getCurrentListType();\n this.currentListType = listType;\n \n // Update button states\n this.updateButtonStates(listType);\n } catch (error) {\n console.warn('Error updating current list type:', error);\n }\n }\n\n /**\n * Update button states based on current list type\n * @param {string|null} currentListType - Current active list type\n */\n updateButtonStates(currentListType) {\n const buttons = this.popup.querySelectorAll('.list-button');\n buttons.forEach(button => {\n button.classList.remove('active');\n if (currentListType && button.dataset.listType === currentListType) {\n button.classList.add('active');\n }\n });\n }\n\n /**\n * Update toolbar button icon based on selection\n * @param {string} listType - Current list type\n */\n updateToolbarButtonIcon(listType) {\n const button = document.querySelector('.rich-editor-toolbar-btn.list-btn');\n if (!button) return;\n\n const iconMap = {\n 'bullet': 'list-bullet',\n 'ordered': 'list-ordered',\n 'roman': 'list-roman',\n 'alpha': 'list-alpha'\n };\n\n const titleMap = {\n 'bullet': 'Bullet List',\n 'ordered': 'Numbered List',\n 'roman': 'Roman Numerals List',\n 'alpha': 'Alphabetical List'\n };\n \n const iconName = iconMap[listType] || 'list-bullet';\n \n // Update button title\n button.title = titleMap[listType] || 'List';\n \n // Update icon\n const svgContent = IconUtils.getIcon(iconName);\n if (svgContent) {\n const iconSpan = button.querySelector('.icon');\n if (iconSpan) {\n iconSpan.innerHTML = svgContent;\n } else {\n button.innerHTML = `<span class=\"icon\">${svgContent}</span>`;\n }\n }\n }\n\n /**\n * Get parent list of an element\n */\n getParentList(element) {\n let current = element;\n while (current && current !== document.body) {\n if (current.tagName === 'UL' || current.tagName === 'OL') {\n return current;\n }\n current = current.parentElement;\n }\n return null;\n }\n\n /**\n * Get list type from list element\n */\n getListType(listElement) {\n if (listElement.tagName === 'OL') {\n const type = listElement.style.listStyleType;\n if (type === 'upper-roman') return 'roman';\n if (type === 'lower-alpha') return 'alpha';\n return 'ordered';\n }\n return 'bullet';\n }\n\n /**\n * Get block element containing the given node\n */\n getBlockElement(node) {\n if (!node) return null;\n \n let currentNode = node;\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI', 'UL', 'OL'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.parentNode;\n }\n return null;\n }\n\n /**\n * Get current list type\n * @returns {string|null}\n */\n getCurrentListType() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n const listElement = this.getParentList(range.commonAncestorContainer);\n \n return listElement ? this.getListType(listElement) : null;\n }\n\n /**\n * Destroy the list picker\n */\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n this.currentListType = null;\n }\n}\n\nexport default ListPicker; ","import { BlockFormat } from '../core/format.js';\nimport ListPicker from '../ui/list-picker.js';\nimport IconUtils from '../ui/icons.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * List Format - Handles list formatting (bullet, ordered, checklist)\n * Now supports multiple editor instances with separate popup instances\n */\nclass List extends BlockFormat {\n static formatName = 'list';\n static tagName = 'UL';\n static attribute = 'class';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for List format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a list picker instance\n let listPicker = currentEditor.getPopupInstance('list');\n \n if (!listPicker) {\n // Create new list picker instance for this editor\n listPicker = new ListPicker({\n onListSelect: (listType) => {\n List.applyListToCurrentSelection(listType, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('list', listPicker);\n }\n \n this.listPicker = listPicker;\n }\n\n /**\n * Create a new List format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {List} List format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new List();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create list element with specified type\n * @param {string} value - List type (bullet, ordered, checklist)\n * @returns {HTMLElement}\n */\n static create(value) {\n let node;\n \n switch(value) {\n case 'ordered':\n node = document.createElement('OL');\n node.style.listStyleType = 'decimal'; // 1, 2, 3...\n break;\n case 'roman':\n node = document.createElement('OL');\n node.style.listStyleType = 'upper-roman'; // I, II, III...\n break;\n case 'alpha':\n node = document.createElement('OL');\n node.style.listStyleType = 'lower-alpha'; // a, b, c...\n break;\n case 'bullet':\n default:\n node = document.createElement('UL');\n node.style.listStyleType = 'disc'; // bullet points\n break;\n }\n \n return node;\n }\n\n /**\n * Static method to apply list to current selection or cursor position\n * @param {string} listType - List type\n * @param {string} editorId - Editor instance ID\n */\n static applyListToCurrentSelection(listType, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for list application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n try {\n const range = selection.getRangeAt(0);\n const blockElements = List.getSelectedBlockElements(range);\n \n if (blockElements.length === 0) {\n // If no block elements found, create one\n List.createListFromSelection(listType);\n } else {\n // Apply list to existing blocks\n List.convertBlocksToList(blockElements, listType);\n\n }\n \n // Update toolbar button icon after applying list\n List.updateToolbarButtonIcon(listType, editorId);\n\n } catch (error) {\n console.error('Error applying list:', error);\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Create list from current selection\n */\n static createListFromSelection(listType) {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const selectedText = range.toString() || 'List item';\n \n // Create list element\n const listElement = List.create(listType);\n const listItem = document.createElement('LI');\n \n // Preserve HTML content if selection contains formatted text\n if (range.toString() === range.cloneContents().textContent) {\n // Plain text selection\n listItem.textContent = selectedText;\n } else {\n // HTML selection - preserve formatting\n const fragment = range.cloneContents();\n listItem.appendChild(fragment);\n }\n \n // Try to preserve style from existing block if cursor is inside one\n const existingBlock = List.getBlockElement(range.startContainer);\n if (existingBlock && existingBlock.style && existingBlock.style.cssText) {\n listItem.style.cssText = existingBlock.style.cssText;\n }\n \n listElement.appendChild(listItem);\n \n // Replace selection with list\n range.deleteContents();\n range.insertNode(listElement);\n \n // Position cursor in the list item\n const newRange = document.createRange();\n newRange.selectNodeContents(listItem);\n newRange.collapse(false);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n\n /**\n * Convert existing block elements to list\n */\n static convertBlocksToList(blocks, listType) {\n if (blocks.length === 0) return;\n\n // Check if blocks are already in a list\n const existingList = List.getParentList(blocks[0]);\n if (existingList) {\n // If already in a list, toggle or change list type\n List.toggleOrChangeListType(existingList, listType);\n return;\n }\n\n // Create new list\n const listElement = List.create(listType);\n const firstBlock = blocks[0];\n \n // Insert list before first block\n firstBlock.parentNode.insertBefore(listElement, firstBlock);\n \n let firstListItem = null;\n \n // Convert each block to list item\n blocks.forEach((block, index) => {\n const listItem = document.createElement('LI');\n \n // Preserve all HTML content including formatting (bold, italic, etc.)\n listItem.innerHTML = block.innerHTML || block.textContent || '';\n \n // Copy style attributes to preserve formatting like text-align\n if (block.style && block.style.cssText) {\n listItem.style.cssText = block.style.cssText;\n }\n \n listElement.appendChild(listItem);\n block.remove();\n \n // Lưu lại list item đầu tiên để đặt con trỏ\n if (index === 0) {\n firstListItem = listItem;\n }\n });\n \n // Đặt con trỏ vào list item đầu tiên\n if (firstListItem) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(firstListItem);\n range.collapse(false); // false = cuối nội dung để dễ tiếp tục gõ\n sel.removeAllRanges();\n sel.addRange(range);\n }\n }\n\n /**\n * Toggle or change list type\n */\n static toggleOrChangeListType(existingList, newListType) {\n const currentType = List.getListType(existingList);\n \n if (currentType === newListType) {\n // Same type - remove list formatting\n List.removeListFormatting(existingList);\n } else {\n // Different type - change list type\n List.changeListType(existingList, newListType);\n }\n }\n\n /**\n * Get list type from list element\n */\n static getListType(listElement) {\n if (listElement.tagName === 'OL') {\n const type = listElement.style.listStyleType;\n if (type === 'upper-roman') return 'roman';\n if (type === 'lower-alpha') return 'alpha';\n return 'ordered';\n }\n return 'bullet';\n }\n\n /**\n * Change list type\n */\n static changeListType(existingList, newListType) {\n const newList = List.create(newListType);\n \n // Copy all list items\n Array.from(existingList.children).forEach(item => {\n const newItem = document.createElement('LI');\n \n // Preserve all HTML content including formatting\n newItem.innerHTML = item.innerHTML || item.textContent || '';\n \n // Copy style attributes to preserve formatting like text-align\n if (item.style && item.style.cssText) {\n newItem.style.cssText = item.style.cssText;\n }\n \n newList.appendChild(newItem);\n });\n \n // Replace existing list\n existingList.parentNode.replaceChild(newList, existingList);\n \n // Đặt con trỏ vào list item đầu tiên\n if (newList.firstElementChild) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(newList.firstElementChild);\n range.collapse(false); // false = cuối nội dung để dễ tiếp tục gõ\n sel.removeAllRanges();\n sel.addRange(range);\n }\n }\n\n /**\n * Remove list formatting\n */\n static removeListFormatting(listElement) {\n const parent = listElement.parentNode;\n let firstParagraph = null;\n \n // Convert list items back to paragraphs\n Array.from(listElement.children).forEach((item, index) => {\n const p = document.createElement('P');\n \n // Preserve all HTML content including formatting\n p.innerHTML = item.innerHTML || item.textContent || '';\n \n // Copy style attributes to preserve formatting like text-align\n if (item.style && item.style.cssText) {\n p.style.cssText = item.style.cssText;\n }\n \n parent.insertBefore(p, listElement);\n \n // Lưu lại paragraph đầu tiên để đặt con trỏ\n if (index === 0) {\n firstParagraph = p;\n }\n });\n \n // Remove the list element\n listElement.remove();\n \n // Đặt con trỏ vào paragraph đầu tiên\n if (firstParagraph) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(firstParagraph);\n range.collapse(false); // false = cuối nội dung để dễ tiếp tục gõ\n sel.removeAllRanges();\n sel.addRange(range);\n }\n }\n\n /**\n * Get parent list of an element\n */\n static getParentList(element) {\n let current = element;\n while (current && current !== document.body) {\n if (current.tagName === 'UL' || current.tagName === 'OL') {\n return current;\n }\n current = current.parentElement;\n }\n return null;\n }\n\n /**\n * Get icon name for list type\n * @param {string} listType - List type\n * @returns {string} Icon name\n */\n static getIconNameForListType(listType) {\n const iconMap = {\n 'bullet': 'list-bullet',\n 'ordered': 'list-ordered',\n 'roman': 'list-roman',\n 'alpha': 'list-alpha'\n };\n return iconMap[listType] || 'list-bullet';\n }\n\n /**\n * Update toolbar button icon based on list type\n * @param {string} listType - Current list type\n * @param {string} editorId - Editor instance ID\n */\n static updateToolbarButtonIcon(listType, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let button = null;\n \n if (toolbar) {\n button = toolbar.getButton('list');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!button) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n button = toolbarContainer.querySelector('.rich-editor-toolbar-btn.list-btn');\n }\n }\n \n // Final fallback: find any list button in the current editor's wrapper\n if (!button) {\n button = editor.wrapper.querySelector('.rich-editor-toolbar-btn.list-btn');\n }\n \n if (!button) return;\n\n const iconName = List.getIconNameForListType(listType);\n const titleMap = {\n 'bullet': 'Bullet List',\n 'ordered': 'Numbered List',\n 'roman': 'Roman Numerals List',\n 'alpha': 'Alphabetical List'\n };\n \n // Update button title\n button.title = titleMap[listType] || 'List';\n \n // Update icon\n const svgContent = IconUtils.getIcon(iconName);\n if (svgContent) {\n const iconSpan = button.querySelector('.icon');\n if (iconSpan) {\n iconSpan.innerHTML = svgContent;\n } else {\n button.innerHTML = `<span class=\"icon\">${svgContent}</span>`;\n }\n }\n }\n\n /**\n * Get all selected block elements\n */\n static getSelectedBlockElements(range) {\n const blocks = [];\n const startContainer = range.startContainer;\n const endContainer = range.endContainer;\n \n // Get start block\n const startBlock = List.getBlockElement(startContainer);\n if (startBlock) blocks.push(startBlock);\n \n // If selection spans multiple blocks, get all blocks in between\n if (startContainer !== endContainer) {\n let currentNode = startBlock;\n while (currentNode && currentNode !== endContainer) {\n const nextBlock = List.getNextBlockElement(currentNode);\n if (nextBlock && !blocks.includes(nextBlock)) {\n blocks.push(nextBlock);\n currentNode = nextBlock;\n } else {\n break;\n }\n }\n \n // Get end block\n const endBlock = List.getBlockElement(endContainer);\n if (endBlock && !blocks.includes(endBlock)) {\n blocks.push(endBlock);\n }\n }\n \n return blocks;\n }\n\n /**\n * Get the block element containing the given node\n */\n static getBlockElement(node) {\n if (!node) return null;\n \n let currentNode = node;\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.parentNode;\n }\n return null;\n }\n\n /**\n * Get next block element in document order\n */\n static getNextBlockElement(element) {\n let currentNode = element.nextSibling;\n \n while (currentNode) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.nextSibling;\n }\n \n return null;\n }\n\n /**\n * Apply list formatting\n * @param {string} value - List type\n */\n apply(value = 'bullet') {\n List.applyListToCurrentSelection(value, this.editorId);\n }\n\n /**\n * Remove list formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const listElement = List.getParentList(range.commonAncestorContainer);\n \n if (listElement) {\n List.removeListFormatting(listElement);\n }\n }\n\n /**\n * Toggle list formatting - shows/hides list picker\n */\n toggle() {\n if (this.listPicker.isVisible) {\n this.listPicker.hide();\n } else {\n this.showListPicker();\n }\n }\n\n /**\n * Show list picker popup positioned relative to list button on toolbar\n */\n showListPicker() {\n // Find list button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let listButton = null;\n \n if (toolbar) {\n listButton = toolbar.getButton('list');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!listButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n listButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.list-btn');\n }\n }\n \n // Final fallback: find any list button in the current editor's wrapper\n if (!listButton) {\n listButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.list-btn');\n }\n \n if (!listButton) {\n console.warn('List button not found for editor:', this.editorId);\n return;\n }\n \n this.listPicker.show(listButton);\n }\n\n /**\n * Check if list formatting is active\n * Always returns false because list button should not have active state\n * Instead, the button icon changes to reflect current list type\n */\n isActive(listType = null) {\n // Update button icon based on current list type\n const currentListType = List.getCurrentListType();\n if (currentListType) {\n List.updateToolbarButtonIcon(currentListType, this.editorId);\n } else {\n // Reset to default bullet list icon\n List.updateToolbarButtonIcon('bullet', this.editorId);\n }\n \n // Highlight the button when the caret is inside a list.\n return !!currentListType;\n }\n\n /**\n * Get current list type\n * @returns {string|null}\n */\n static getCurrentListType() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n const listElement = List.getParentList(range.commonAncestorContainer);\n \n return listElement ? List.getListType(listElement) : null;\n }\n\n /**\n * Get current list type (instance method)\n * @returns {string|null}\n */\n getCurrentListType() {\n return List.getCurrentListType();\n }\n}\n\nexport default List; ","import { BlockFormat } from '../core/format.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Indent Format - Handles text indentation (increase/decrease)\n */\nclass Indent extends BlockFormat {\n static formatName = 'indent';\n static tagName = 'DIV';\n static attribute = 'style';\n\n constructor() {\n super();\n }\n\n /**\n * Create element with indentation\n * @param {string} value - Indent level (e.g., '20px', '40px')\n * @returns {HTMLElement}\n */\n static create(value) {\n const node = document.createElement('DIV');\n if (value) {\n node.style.paddingLeft = value;\n }\n return node;\n }\n\n /**\n * Apply indent to current selection\n * @param {string} direction - 'increase' or 'decrease'\n */\n static applyIndentToCurrentSelection(direction) {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n try {\n const range = selection.getRangeAt(0);\n const blockElements = Indent.getSelectedBlockElements(range);\n \n if (blockElements.length === 0) {\n // If no block elements found, create one and apply indent\n const newBlock = document.createElement('DIV');\n newBlock.style.paddingLeft = direction === 'increase' ? '20px' : '0px';\n \n // Get selected text or use default\n const selectedText = range.toString() || '';\n if (selectedText) {\n newBlock.textContent = selectedText;\n range.deleteContents();\n range.insertNode(newBlock);\n } else {\n // Insert at cursor position\n range.insertNode(newBlock);\n newBlock.innerHTML = '<br>'; // Make it editable\n }\n \n // Position cursor in the new block\n const newRange = document.createRange();\n newRange.selectNodeContents(newBlock);\n newRange.collapse(false);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n // Apply indent to existing blocks\n blockElements.forEach(block => {\n Indent.applyIndentToBlock(block, direction);\n });\n }\n } catch (error) {\n console.error('Error applying indent:', error);\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n const currentEditor = Editor.getCurrentInstance();\n if (currentEditor && typeof currentEditor.onContentChange === 'function') {\n currentEditor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply indent to a specific block element\n * @param {HTMLElement} block - Block element to indent\n * @param {string} direction - 'increase' or 'decrease'\n */\n static applyIndentToBlock(block, direction) {\n if (!block || !block.style) return;\n\n const currentIndent = parseInt(block.style.paddingLeft) || 0;\n let newIndent;\n\n if (direction === 'increase') {\n newIndent = currentIndent + 20; // Increase by 20px\n } else {\n newIndent = Math.max(0, currentIndent - 20); // Decrease by 20px, minimum 0\n }\n\n if (newIndent === 0) {\n block.style.paddingLeft = '';\n } else {\n block.style.paddingLeft = newIndent + 'px';\n }\n }\n\n /**\n * Get all selected block elements\n */\n static getSelectedBlockElements(range) {\n const blocks = [];\n const startContainer = range.startContainer;\n const endContainer = range.endContainer;\n \n // Get start block\n const startBlock = Indent.getBlockElement(startContainer);\n if (startBlock) blocks.push(startBlock);\n \n // If selection spans multiple blocks, get all blocks in between\n if (startContainer !== endContainer) {\n let currentNode = startBlock;\n while (currentNode && currentNode !== endContainer) {\n const nextBlock = Indent.getNextBlockElement(currentNode);\n if (nextBlock && !blocks.includes(nextBlock)) {\n blocks.push(nextBlock);\n currentNode = nextBlock;\n } else {\n break;\n }\n }\n \n // Get end block\n const endBlock = Indent.getBlockElement(endContainer);\n if (endBlock && !blocks.includes(endBlock)) {\n blocks.push(endBlock);\n }\n }\n \n return blocks;\n }\n\n /**\n * Get the block element containing the given node\n */\n static getBlockElement(node) {\n if (!node) return null;\n \n let currentNode = node;\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.parentNode;\n }\n return null;\n }\n\n /**\n * Get next block element in document order\n */\n static getNextBlockElement(element) {\n let currentNode = element.nextSibling;\n \n while (currentNode) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const tagName = currentNode.tagName;\n if (['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI'].includes(tagName)) {\n return currentNode;\n }\n }\n currentNode = currentNode.nextSibling;\n }\n \n return null;\n }\n\n /**\n * Apply indent formatting\n * @param {string} value - Direction ('increase' or 'decrease')\n */\n apply(value = 'increase') {\n Indent.applyIndentToCurrentSelection(value);\n }\n\n /**\n * Remove indent formatting (reset to 0)\n */\n remove() {\n Indent.applyIndentToCurrentSelection('remove');\n }\n\n /**\n * Check if indent formatting is active\n * @returns {boolean}\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n const blockElement = Indent.getBlockElement(range.commonAncestorContainer);\n \n if (!blockElement) return false;\n \n const paddingLeft = parseInt(blockElement.style.paddingLeft) || 0;\n return paddingLeft > 0;\n }\n\n /**\n * Get current indent level\n * @returns {number}\n */\n static getCurrentIndentLevel() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return 0;\n\n const range = selection.getRangeAt(0);\n const blockElement = Indent.getBlockElement(range.commonAncestorContainer);\n \n if (!blockElement) return 0;\n \n return parseInt(blockElement.style.paddingLeft) || 0;\n }\n\n /**\n * Get current indent level (instance method)\n * @returns {number}\n */\n getCurrentIndentLevel() {\n return Indent.getCurrentIndentLevel();\n }\n}\n\n/**\n * Indent Increase Format - Handles increasing indentation\n */\nclass IndentIncrease extends Indent {\n static formatName = 'indent-increase';\n\n /**\n * Apply increase indent formatting\n */\n apply() {\n Indent.applyIndentToCurrentSelection('increase');\n }\n\n /**\n * Toggle increase indent - always increases\n */\n toggle() {\n this.apply();\n }\n\n /**\n * Never active - this is an action button\n */\n isActive() {\n return false;\n }\n}\n\n/**\n * Indent Decrease Format - Handles decreasing indentation\n */\nclass IndentDecrease extends Indent {\n static formatName = 'indent-decrease';\n\n /**\n * Apply decrease indent formatting\n */\n apply() {\n Indent.applyIndentToCurrentSelection('decrease');\n }\n\n /**\n * Toggle decrease indent - always decreases\n */\n toggle() {\n this.apply();\n }\n\n /**\n * Never active - this is an action button\n */\n isActive() {\n return false;\n }\n}\n\nexport { Indent as default, IndentIncrease, IndentDecrease }; ","/**\n * Emoji Picker Component - Popup for selecting emojis\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass EmojiPicker {\n constructor(options = {}) {\n this.options = {\n emojis: [\n // Smileys & People\n '😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆', '😉', '😊',\n '😋', '😎', '😍', '🥰', '😘', '😗', '😙', '😚', '🙂', '🤗',\n '😳', '🥺', '😦', '😧', '😨', '😰', '😥', '😢', '😭', '😱',\n '🤬', '😈', '👿', '💀', '☠️', '💩', '🤡', '👹', '👺', '👻',\n ],\n onEmojiSelect: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.clickOutsideHandler = null;\n \n this.createEmojiPicker();\n }\n\n /**\n * Detect operating system\n * @returns {string} 'mac' or 'windows'\n */\n detectOS() {\n const platform = navigator.platform.toLowerCase();\n if (platform.includes('mac')) {\n return 'mac';\n } else if (platform.includes('win')) {\n return 'windows';\n }\n // Default to windows for other platforms\n return 'windows';\n }\n\n /**\n * Get emoji shortcut message based on OS\n * @returns {string} HTML string for the shortcut message\n */\n getEmojiShortcutMessage() {\n const os = this.detectOS();\n \n if (os === 'mac') {\n return `<div style=\"color: rgb(113, 120, 124); font-style: normal; font-weight: 400; line-height: normal; text-align: center;\">Get more emojis with <span style=\"border-radius: 2.2px; background: #EEE; padding: 2px 4px;\">⌘</span> <span style=\"color: #000;\">+</span> <span style=\"border-radius: 2.2px; background: #EEE; padding: 2px 4px;\">CTRL</span> <span style=\"color: #000;\">+</span> <span style=\"border-radius: 2.2px; background: #EEE; padding: 2px 4px;\">SPACE</span></div>`;\n } else {\n return `<div style=\"color: rgb(113, 120, 124); font-style: normal; font-weight: 400; line-height: normal; text-align: center;\">Get more emojis with <span style=\"border-radius: 2.2px; background: #EEE; padding: 2px 4px;\">WIN</span> <span style=\"color: #000;\">+</span> <span style=\"border-radius: 2.2px; background: #EEE; padding: 2px 4px;\">.</span></div>`;\n }\n }\n\n /**\n * Create emoji picker popup\n */\n createEmojiPicker() {\n // Create popup\n this.popup = document.createElement('div');\n this.popup.className = 'emoji-picker-popup';\n \n // Create emoji grid\n this.createEmojiGrid();\n const emojiTextMessage = document.createElement('div');\n emojiTextMessage.className = 'emoji-text-message';\n \n emojiTextMessage.innerHTML = this.getEmojiShortcutMessage();\n this.popup.appendChild(emojiTextMessage);\n\n // Add popup to container\n appendPopup(this.popup);\n }\n\n /**\n * Create emoji grid\n */\n createEmojiGrid() {\n const emojiGrid = document.createElement('div');\n emojiGrid.className = 'emoji-grid';\n \n // Create emoji buttons\n this.options.emojis.forEach(emoji => {\n const emojiButton = document.createElement('button');\n emojiButton.type = 'button';\n emojiButton.className = 'emoji-button';\n emojiButton.textContent = emoji;\n emojiButton.title = emoji;\n \n emojiButton.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.selectEmoji(emoji);\n });\n \n emojiGrid.appendChild(emojiButton);\n });\n \n this.popup.appendChild(emojiGrid);\n }\n\n /**\n * Setup click outside handler\n */\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n // Add slight delay to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n /**\n * Remove click outside handler\n */\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n /**\n * Show emoji picker popup\n * @param {HTMLElement} anchor - Element to position popup relative to\n */\n show(anchor) {\n if (!anchor) return;\n \n // Ensure popup is in DOM\n if (!document.body.contains(this.popup)) {\n appendPopup(this.popup);\n }\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n // Show popup by adding visible class\n this.popup.classList.add('visible');\n this.isVisible = true;\n \n // Setup click outside handler\n this.setupClickOutside();\n }\n\n /**\n * Hide emoji picker popup\n */\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n /**\n * Select emoji and trigger callback\n * @param {string} emoji - Selected emoji\n */\n selectEmoji(emoji) {\n if (this.options.onEmojiSelect) {\n this.options.onEmojiSelect(emoji);\n }\n \n this.hide();\n }\n\n /**\n * Destroy the emoji picker\n */\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n }\n}\n\nexport default EmojiPicker; ","import { InlineFormat } from '../core/format.js';\nimport EmojiPicker from '../ui/emoji-picker.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Emoji Format - Handles emoji insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Emoji extends InlineFormat {\n static formatName = 'emoji';\n static tagName = 'SPAN';\n static className = 'emoji';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Emoji format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has an emoji picker instance\n let emojiPicker = currentEditor.getPopupInstance('emoji');\n \n if (!emojiPicker) {\n // Create new emoji picker instance for this editor\n emojiPicker = new EmojiPicker({\n onEmojiSelect: (emoji) => {\n Emoji.insertEmojiAtCurrentPosition(emoji, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('emoji', emojiPicker);\n }\n \n this.emojiPicker = emojiPicker;\n }\n\n /**\n * Create a new Emoji format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Emoji} Emoji format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Emoji();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create emoji element\n * @param {string} value - Emoji character\n * @returns {HTMLElement}\n */\n static create(value) {\n const span = document.createElement('SPAN');\n span.className = 'emoji';\n span.textContent = value;\n span.setAttribute('data-emoji', value);\n return span;\n }\n\n /**\n * Insert emoji at current cursor position\n * @param {string} emoji - Emoji character to insert\n * @param {string} editorId - Editor instance ID\n */\n static insertEmojiAtCurrentPosition(emoji, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for emoji insertion');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n try {\n const range = selection.getRangeAt(0);\n \n // Check if cursor is inside an existing emoji span\n let currentNode = range.startContainer;\n let emojiParent = null;\n \n // If cursor is in a text node, check its parent\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentNode;\n }\n \n // Find if we're inside an emoji span\n while (currentNode && currentNode !== editor.element) {\n if (currentNode.classList && currentNode.classList.contains('emoji')) {\n emojiParent = currentNode;\n break;\n }\n currentNode = currentNode.parentNode;\n }\n \n // If cursor is inside an emoji span, move it outside\n if (emojiParent) {\n // Move cursor after the emoji span\n range.setStartAfter(emojiParent);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n \n // Create emoji element\n const emojiElement = Emoji.create(emoji);\n \n // Insert emoji at cursor position\n range.deleteContents();\n range.insertNode(emojiElement);\n \n // Create a zero-width space character after the emoji\n const zeroWidthSpace = document.createTextNode('\\u200B'); // Zero-width space\n \n // Insert zero-width space after emoji\n range.setStartAfter(emojiElement);\n range.insertNode(zeroWidthSpace);\n \n // Position cursor after the zero-width space\n range.setStartAfter(zeroWidthSpace);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n \n } catch (error) {\n console.error('Error inserting emoji:', error);\n }\n }\n\n /**\n * Apply emoji formatting - shows emoji picker\n */\n apply(value) {\n if (value) {\n Emoji.insertEmojiAtCurrentPosition(value, this.editorId);\n } else {\n this.showEmojiPicker();\n }\n }\n\n /**\n * Remove emoji formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const emojiElement = this.getEmojiElement(range);\n \n if (emojiElement) {\n // Replace emoji element with its text content\n const textNode = document.createTextNode(emojiElement.textContent);\n emojiElement.parentNode.replaceChild(textNode, emojiElement);\n }\n }\n\n /**\n * Toggle emoji formatting - shows emoji picker\n */\n toggle() {\n if (this.emojiPicker.isVisible) {\n this.emojiPicker.hide();\n } else {\n this.showEmojiPicker();\n }\n }\n\n /**\n * Show emoji picker popup\n */\n showEmojiPicker() {\n // Find emoji button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let emojiButton = null;\n \n if (toolbar) {\n emojiButton = toolbar.getButton('emoji');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!emojiButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n emojiButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.emoji-btn');\n }\n }\n \n // Final fallback: find any emoji button in the current editor's wrapper\n if (!emojiButton) {\n emojiButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.emoji-btn');\n }\n \n if (!emojiButton) {\n console.warn('Emoji button not found for editor:', this.editorId);\n return;\n }\n \n this.emojiPicker.show(emojiButton);\n }\n\n /**\n * Check if emoji formatting is active\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n const emojiElement = this.getEmojiElement(range);\n \n return emojiElement !== null;\n }\n\n /**\n * Get emoji element from selection\n * @param {Range} range - Selection range\n * @returns {HTMLElement|null}\n */\n getEmojiElement(range) {\n let node = range.commonAncestorContainer;\n \n // If it's a text node, get its parent\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n \n // Check if current node is an emoji\n if (node.classList && node.classList.contains('emoji')) {\n return node;\n }\n \n // Check if selection contains an emoji\n const emojiInSelection = range.cloneContents().querySelector('.emoji');\n if (emojiInSelection) {\n return emojiInSelection;\n }\n \n return null;\n }\n}\n\nexport default Emoji; ","import { InlineFormat } from '../core/format.js';\nimport Editor from '../core/editor.js';\nimport { isSafeUrl } from '../utils/sanitize.js';\n\n/**\n * Image Format - Handles image insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Image extends InlineFormat {\n static formatName = 'image';\n static tagName = 'IMG';\n static className = 'inserted-image';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Image format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n }\n\n /**\n * Create a new Image format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Image} Image format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Image();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create image element\n * @param {string} src - Image source URL\n * @param {string} alt - Alt text\n * @returns {HTMLElement}\n */\n static create(src, alt = '') {\n // Allow http(s)/relative URLs and raster data: image URIs; reject the rest.\n if (!isSafeUrl(src, { allowDataImage: true })) {\n console.warn('Blocked unsafe image URL:', src);\n return null;\n }\n const img = document.createElement('IMG');\n img.src = src;\n img.alt = alt || 'Inserted image';\n img.className = 'inserted-image';\n img.style.maxWidth = '100%';\n img.style.height = 'auto';\n img.setAttribute('contenteditable', 'false');\n return img;\n }\n\n /**\n * Insert image at current cursor position\n * @param {string} src - Image source URL\n * @param {string} alt - Alt text\n * @param {string} editorId - Editor instance ID\n */\n static insertImageAtCurrentPosition(src, alt = '', editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for image insertion');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection) return;\n // No caret (or caret outside this editor) → append at the end of the editor.\n if (!selection.rangeCount || !editor.editor.contains(selection.anchorNode)) {\n const r = document.createRange();\n r.selectNodeContents(editor.editor);\n r.collapse(false);\n selection.removeAllRanges();\n selection.addRange(r);\n }\n\n try {\n const range = selection.getRangeAt(0);\n // Create image element\n const imageElement = Image.create(src, alt);\n // Abort if the URL was rejected as unsafe\n if (!imageElement) return;\n // Insert image at cursor position\n range.deleteContents();\n range.insertNode(imageElement);\n // Add a space after the image for easier editing\n const spaceNode = document.createTextNode(' ');\n range.setStartAfter(imageElement);\n range.insertNode(spaceNode);\n // Position cursor after the space\n range.setStartAfter(spaceNode);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n } catch (error) {\n console.error('Error inserting image:', error);\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n const currentEditor = Editor.getCurrentInstance();\n if (currentEditor && typeof currentEditor.onContentChange === 'function') {\n currentEditor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Apply image formatting — insert a known src, or open the file picker.\n */\n apply(src, alt) {\n if (src) {\n Image.insertImageAtCurrentPosition(src, alt, this.editorId);\n } else {\n this.openFilePicker();\n }\n }\n\n /**\n * Open the native file browser, then insert the chosen image straight into\n * the editor (visible immediately). The selection is captured before the\n * dialog steals focus and restored before insertion.\n */\n openFilePicker() {\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n\n const selection = window.getSelection();\n const savedRange = selection && selection.rangeCount\n ? selection.getRangeAt(0).cloneRange()\n : null;\n\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = (editor.options.image && editor.options.image.accept) || 'image/*';\n input.style.display = 'none';\n input.addEventListener('change', () => {\n const file = input.files && input.files[0];\n if (file) {\n // Restore the caret captured before the file dialog stole focus.\n editor.focus();\n const sel = window.getSelection();\n if (savedRange) {\n sel.removeAllRanges();\n sel.addRange(savedRange);\n } else if (!sel.rangeCount || !editor.editor.contains(sel.anchorNode)) {\n const r = document.createRange();\n r.selectNodeContents(editor.editor);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n }\n // Single insertion path → honours the image.upload hook + validation.\n editor.insertImageFile(file);\n }\n input.remove();\n });\n document.body.appendChild(input);\n input.click();\n }\n\n /**\n * Remove image formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const imageElement = this.getImageElement(range);\n \n if (imageElement) {\n imageElement.remove();\n }\n }\n\n /**\n * Toggle image — opens the native file picker.\n */\n toggle() {\n this.openFilePicker();\n }\n\n /**\n * Check if image formatting is active\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n const imageElement = this.getImageElement(range);\n \n return imageElement !== null;\n }\n\n /**\n * Get image element from selection\n * @param {Range} range - Selection range\n * @returns {HTMLElement|null}\n */\n getImageElement(range) {\n let node = range.commonAncestorContainer;\n \n // If it's a text node, get its parent\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n \n // Check if current node is an image\n if (node.tagName === 'IMG' && node.classList && node.classList.contains('inserted-image')) {\n return node;\n }\n \n // Check if selection contains an image\n const imageInSelection = range.cloneContents().querySelector('.inserted-image');\n if (imageInSelection) {\n return imageInSelection;\n }\n \n return null;\n }\n\n /**\n * Handle file upload\n * @param {File} file - Image file\n * @returns {Promise<string>} - Promise that resolves to image URL\n */\n static async handleFileUpload(file) {\n return new Promise((resolve, reject) => {\n if (!file || !file.type.startsWith('image/')) {\n reject(new Error('Please select a valid image file'));\n return;\n }\n\n const reader = new FileReader();\n reader.onload = (e) => {\n resolve(e.target.result);\n };\n reader.onerror = () => {\n reject(new Error('Failed to read file'));\n };\n reader.readAsDataURL(file);\n });\n }\n\n /**\n * Validate image URL\n * @param {string} url - Image URL\n * @returns {Promise<boolean>} - Promise that resolves to validation result\n */\n static validateImageUrl(url) {\n return new Promise((resolve) => {\n // Check if it's a valid image URL format\n const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];\n const hasValidExtension = imageExtensions.some(ext => \n url.toLowerCase().includes(`.${ext}`)\n );\n \n // Check if it's a data URL\n if (url.startsWith('data:image/')) {\n resolve(true);\n return;\n }\n \n // Check if it's a valid HTTP(S) URL\n if (!/^https?:\\/\\//.test(url)) {\n resolve(false);\n return;\n }\n \n // If it has a valid extension, assume it's valid\n if (hasValidExtension) {\n resolve(true);\n return;\n }\n \n // Try to load the image (fallback)\n const img = new Image();\n img.onload = () => {\n resolve(true);\n };\n img.onerror = () => {\n // If loading fails, but URL looks like an image, still allow it\n // This handles cases where CORS blocks loading but the URL is valid\n if (url.includes('imgur.com') || url.includes('drive.google.com') || hasValidExtension) {\n resolve(true);\n } else {\n resolve(false);\n }\n };\n \n // Set timeout to avoid hanging\n setTimeout(() => {\n // If no response after 5 seconds, still allow if URL looks valid\n if (hasValidExtension || url.includes('imgur.com') || url.includes('drive.google.com')) {\n resolve(true);\n } else {\n resolve(false);\n }\n }, 5000);\n \n img.src = url;\n });\n }\n}\n\nexport default Image; ","/**\n * Video Popup Component - Popup for inserting videos\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass VideoPopup {\n constructor(options = {}) {\n this.options = {\n onVideoInsert: null,\n editor: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.clickOutsideHandler = null;\n this.selectedVideoSrc = null;\n this.savedSelection = null; // Save editor selection\n \n this.createVideoPopup();\n }\n\n /**\n * Create video popup\n */\n createVideoPopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'video-popup';\n \n const content = document.createElement('div');\n content.className = 'video-popup-content';\n \n // Title\n const title = document.createElement('h3');\n title.textContent = 'Insert video';\n title.className = 'yjd-input-title';\n content.appendChild(title);\n \n // Container\n const uploadContainer = document.createElement('div');\n uploadContainer.className = 'video-input-container';\n\n const textLabel = document.createElement('p');\n textLabel.textContent = 'Your video url';\n textLabel.className = 'yjd-input-label';\n\n const inputgroup1 = document.createElement('div');\n inputgroup1.className = 'yjd-input-upload-group';\n this.inputGroup = inputgroup1; // Store reference\n\n // input url\n this.urlInput = document.createElement('input');\n this.urlInput.type = 'url';\n this.urlInput.className = 'yjd-input';\n this.urlInput.placeholder = 'Please enter your video URL';\n this.urlInput.addEventListener('input', () => {\n this.updateInsertButton();\n // Show preview if URL is valid\n const url = this.urlInput.value.trim();\n if (url && this.isValidVideoUrl(url)) {\n this.showPreview(url);\n } else {\n this.removePreview();\n }\n if(this.urlInput.value.trim()){\n this.customButton.style.display = 'none';\n }else{\n this.customButton.style.display = 'block';\n }\n });\n\n // Hidden file input\n this.fileInput = document.createElement('input');\n this.fileInput.type = 'file';\n this.fileInput.accept = 'video/*';\n this.fileInput.className = 'image-input-hidden'; // ẩn bằng CSS\n this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));\n\n // Custom button\n const customButton = document.createElement('button');\n customButton.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" x2=\"12\" y1=\"3\" y2=\"15\"/></svg>`;\n customButton.className = 'yjd-custom-upload-button';\n this.customButton = customButton;\n customButton.addEventListener('click', () => this.fileInput.click());\n\n // Create preview container\n this.createPreviewContainer();\n\n // Append elements\n inputgroup1.appendChild(this.urlInput);\n inputgroup1.appendChild(this.fileInput);\n inputgroup1.appendChild(customButton);\n uploadContainer.appendChild(textLabel);\n uploadContainer.appendChild(inputgroup1);\n uploadContainer.appendChild(this.previewContainer);\n content.appendChild(uploadContainer);\n \n // Buttons\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'yjd-button-container';\n \n const cancelButton = document.createElement('button');\n cancelButton.type = 'button';\n cancelButton.className = 'image-button yjd-button-cancel';\n cancelButton.textContent = 'Cancel';\n cancelButton.addEventListener('click', () => {\n this.hide();\n // Maintain editor focus after popup close\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n this.insertButton = document.createElement('button');\n this.insertButton.type = 'button';\n this.insertButton.className = 'image-button yjd-button-confirm button-disable';\n this.insertButton.textContent = 'Add video';\n this.insertButton.disabled = true;\n this.insertButton.addEventListener('click', () => {\n this.insertVideo();\n // Maintain editor focus after insert\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n buttonContainer.appendChild(cancelButton);\n buttonContainer.appendChild(this.insertButton);\n content.appendChild(buttonContainer);\n \n this.popup.appendChild(content);\n appendPopup(this.popup);\n \n // Prevent focus loss when clicking on popup\n if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {\n this.options.editor.preventFocusLoss(this.popup);\n }\n }\n\n async handleFileSelect(e) {\n const file = e.target.files[0];\n if (!file) return;\n \n try {\n const { default: Video } = await import('../formats/video.js');\n this.selectedVideoSrc = await Video.handleFileUpload(file);\n this.urlInput.value = '';\n this.showPreview(this.selectedVideoSrc);\n this.updateInsertButton();\n } catch (error) {\n alert(error.message);\n }\n }\n\n updateInsertButton() {\n const hasVideo = this.selectedVideoSrc || this.urlInput.value.trim();\n this.insertButton.disabled = !hasVideo;\n this.insertButton.classList.toggle('button-disable', !hasVideo);\n }\n\n /**\n * Show video preview\n */\n showPreview(videoSrc) {\n if (!videoSrc) return;\n \n this.videoPreview.src = videoSrc;\n this.previewContainer.style.display = 'block';\n this.selectedVideoSrc = videoSrc;\n \n // Hide input group\n this.toggleInputGroup(false);\n \n // Recalculate position after preview is shown to ensure buttons remain visible\n this.recalculatePosition();\n }\n\n /**\n * Remove video preview and show input again\n */\n removePreview() {\n this.selectedVideoSrc = null;\n this.previewContainer.style.display = 'none';\n this.videoPreview.src = '';\n \n // Show input group and reset file input\n this.toggleInputGroup(true);\n if (this.fileInput) {\n this.fileInput.value = '';\n }\n \n this.updateInsertButton();\n \n // Recalculate position after preview is removed\n this.recalculatePosition();\n }\n\n /**\n * Toggle input group visibility\n */\n toggleInputGroup(show) {\n if (!this.inputGroup) return;\n \n if (show) {\n this.inputGroup.style.display = 'flex';\n this.inputGroup.style.visibility = 'visible';\n if (this.customButton) {\n this.customButton.style.pointerEvents = 'auto';\n }\n } else {\n this.inputGroup.style.display = 'none';\n this.inputGroup.style.visibility = 'hidden';\n }\n }\n\n /**\n * Create preview container with video and remove button\n */\n createPreviewContainer() {\n this.previewContainer = document.createElement('div');\n this.previewContainer.className = 'video-preview-container';\n this.previewContainer.style.cssText = 'display: none; position: relative;';\n \n // Video preview\n this.videoPreview = document.createElement('video');\n this.videoPreview.className = 'video-preview';\n this.videoPreview.style.cssText = 'max-width: 100%; max-height: 200px; border-radius: 8px; object-fit: contain;';\n this.videoPreview.controls = true;\n this.videoPreview.muted = true;\n \n // Remove button\n this.removeButton = document.createElement('button');\n this.removeButton.className = 'video-remove-button';\n this.removeButton.innerHTML = '×';\n this.removeButton.style.cssText = `\n position: absolute; top: 5px; right: 5px; background: rgba(0,0,0,0.7);\n color: white; border: none; border-radius: 50%; width: 24px; height: 24px;\n cursor: pointer; font-size: 16px; font-weight: bold;\n `;\n this.removeButton.addEventListener('click', () => this.removePreview());\n \n this.previewContainer.appendChild(this.videoPreview);\n this.previewContainer.appendChild(this.removeButton);\n }\n\n /**\n * Check if URL is a valid video URL\n */\n isValidVideoUrl(url) {\n try {\n const urlObj = new URL(url);\n const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv'];\n const videoHosts = ['youtube.com', 'youtu.be', 'vimeo.com', 'dailymotion.com'];\n \n const pathname = urlObj.pathname.toLowerCase();\n const hasVideoExtension = videoExtensions.some(ext => pathname.endsWith(ext));\n const isFromVideoHost = videoHosts.some(host => urlObj.hostname.includes(host));\n \n return hasVideoExtension || isFromVideoHost;\n } catch {\n return false;\n }\n }\n\n async insertVideo() {\n let src = this.selectedVideoSrc || this.urlInput.value.trim();\n \n if (!src) return;\n \n // Always validate URL (both file upload and URL input)\n try {\n const { default: Video } = await import('../formats/video.js');\n const isValid = await Video.validateVideoUrl(src);\n if (!isValid) {\n alert('Invalid video URL. Please check the URL and try again.');\n return;\n }\n } catch (error) {\n alert('Error validating video URL.');\n return;\n }\n \n // Restore editor selection before inserting\n this.restoreSelection();\n \n if (this.options.onVideoInsert) {\n this.options.onVideoInsert(src);\n }\n \n this.hide();\n this.reset();\n }\n\n reset() {\n this.fileInput.value = '';\n this.urlInput.value = '';\n this.selectedVideoSrc = null;\n \n // Hide preview and show input\n this.previewContainer.style.display = 'none';\n this.videoPreview.src = '';\n this.toggleInputGroup(true);\n \n this.updateInsertButton();\n this.customButton.style.display = 'block';\n }\n\n /**\n * Save current editor selection\n */\n saveSelection() {\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n this.savedSelection = selection.getRangeAt(0).cloneRange();\n }\n }\n\n /**\n * Restore editor selection\n */\n restoreSelection() {\n if (this.savedSelection) {\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(this.savedSelection);\n }\n }\n\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n show(anchor) {\n if (!anchor) return;\n \n // Save current editor selection before showing popup\n this.saveSelection();\n \n // Reset state when showing popup\n this.reset();\n \n // Store anchor for recalculation\n this.currentAnchor = anchor;\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n this.popup.classList.add('visible');\n this.isVisible = true;\n \n this.setupClickOutside();\n }\n\n /**\n * Recalculate popup position to ensure it stays within viewport\n */\n recalculatePosition() {\n if (!this.currentAnchor || !this.isVisible) return;\n \n // Small delay to ensure DOM updates are complete\n setTimeout(() => {\n const position = calculatePopupPosition(this.currentAnchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n }, 10);\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n // Clear saved selection to avoid memory leaks\n this.savedSelection = null;\n }\n\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n }\n}\n\nexport default VideoPopup; ","import { InlineFormat } from '../core/format.js';\nimport VideoPopup from '../ui/video-popup.js';\nimport Editor from '../core/editor.js';\nimport { isSafeUrl } from '../utils/sanitize.js';\n\n/**\n * Video Format - Handles video insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Video extends InlineFormat {\n static formatName = 'video';\n static tagName = 'VIDEO';\n static className = 'inserted-video';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Video format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a video popup instance\n let videoPopup = currentEditor.getPopupInstance('video');\n \n if (!videoPopup) {\n // Create new video popup instance for this editor\n videoPopup = new VideoPopup({\n onVideoInsert: (src) => {\n Video.insertVideoAtCurrentPosition(src, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('video', videoPopup);\n }\n \n this.videoPopup = videoPopup;\n }\n\n /**\n * Create a new Video format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Video} Video format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Video();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create video element\n * @param {string} src - Video source URL\n * @returns {HTMLElement}\n */\n static create(src) {\n // Check if it's a YouTube URL\n if (Video.isYouTubeUrl(src)) {\n return Video.createYouTubeEmbed(src);\n }\n \n // Reject unsafe URL schemes for direct (non-YouTube) video sources.\n if (!isSafeUrl(src)) {\n console.warn('Blocked unsafe video URL:', src);\n return null;\n }\n\n // Create regular video element for direct video URLs\n const video = document.createElement('VIDEO');\n video.src = src;\n video.className = 'inserted-video';\n video.controls = true;\n video.style.maxWidth = '100%';\n video.style.height = 'auto';\n video.setAttribute('contenteditable', 'false');\n return video;\n }\n\n /**\n * Create YouTube embedded iframe\n * @param {string} url - YouTube URL\n * @returns {HTMLElement}\n */\n static createYouTubeEmbed(url) {\n const videoId = Video.getYouTubeVideoId(url);\n if (!videoId) {\n throw new Error('Invalid YouTube URL');\n }\n \n const iframe = document.createElement('IFRAME');\n iframe.src = `https://www.youtube.com/embed/${videoId}`;\n iframe.className = 'inserted-video youtube-video';\n iframe.width = '560';\n iframe.height = '315';\n iframe.style.maxWidth = '100%';\n iframe.style.width = '560px'; // Set explicit width\n iframe.style.height = '315px'; // Set explicit height\n iframe.style.position = 'relative'; // Add position relative\n iframe.style.display = 'block'; // Make it block level\n iframe.setAttribute('frameborder', '0');\n iframe.setAttribute('allowfullscreen', '');\n iframe.setAttribute('contenteditable', 'false');\n \n return iframe;\n }\n\n /**\n * Insert video at current cursor position\n * @param {string} src - Video source URL\n * @param {string} editorId - Editor instance ID\n */\n static insertVideoAtCurrentPosition(src, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for video insertion');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) {\n return;\n }\n\n try {\n const range = selection.getRangeAt(0);\n // Create video element\n const videoElement = Video.create(src);\n // Abort if the URL was rejected as unsafe\n if (!videoElement) return;\n // Insert video at cursor position\n range.deleteContents();\n range.insertNode(videoElement);\n // Add a space after the video for easier editing\n const spaceNode = document.createTextNode(' ');\n range.setStartAfter(videoElement);\n range.insertNode(spaceNode);\n // Position cursor after the space\n range.setStartAfter(spaceNode);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n } catch (error) {\n console.error('Error inserting video:', error);\n }\n }\n\n /**\n * Apply video formatting - shows video popup\n */\n apply(src) {\n if (src) {\n Video.insertVideoAtCurrentPosition(src, this.editorId);\n } else {\n this.showVideoPopup();\n }\n }\n\n /**\n * Remove video formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const videoElement = this.getVideoElement(range);\n \n if (videoElement) {\n videoElement.remove();\n }\n }\n\n /**\n * Toggle video formatting - shows video popup\n */\n toggle() {\n if (this.videoPopup.isVisible) {\n this.videoPopup.hide();\n } else {\n this.showVideoPopup();\n }\n }\n\n /**\n * Show video popup\n */\n showVideoPopup() {\n // Find video button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let videoButton = null;\n \n if (toolbar) {\n videoButton = toolbar.getButton('video');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!videoButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n videoButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.video-btn');\n }\n }\n \n // Final fallback: find any video button in the current editor's wrapper\n if (!videoButton) {\n videoButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.video-btn');\n }\n \n if (!videoButton) {\n console.warn('Video button not found for editor:', this.editorId);\n return;\n }\n \n this.videoPopup.show(videoButton);\n }\n\n /**\n * Check if video formatting is active\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n const videoElement = this.getVideoElement(range);\n \n return videoElement !== null;\n }\n\n /**\n * Get video element from selection\n * @param {Range} range - Selection range\n * @returns {HTMLElement|null}\n */\n getVideoElement(range) {\n let node = range.commonAncestorContainer;\n \n // If it's a text node, get its parent\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n \n // Check if current node is a video or iframe\n if ((node.tagName === 'VIDEO' || node.tagName === 'IFRAME') && \n node.classList && node.classList.contains('inserted-video')) {\n return node;\n }\n \n // Check if selection contains a video or iframe\n const videoInSelection = range.cloneContents().querySelector('.inserted-video');\n if (videoInSelection) {\n return videoInSelection;\n }\n \n return null;\n }\n\n /**\n * Handle file upload\n * @param {File} file - Video file\n * @returns {Promise<string>} - Promise that resolves to video URL\n */\n static async handleFileUpload(file) {\n return new Promise((resolve, reject) => {\n if (!file || !file.type.startsWith('video/')) {\n reject(new Error('Please select a valid video file'));\n return;\n }\n\n const reader = new FileReader();\n reader.onload = (e) => {\n resolve(e.target.result);\n };\n reader.onerror = () => {\n reject(new Error('Failed to read file'));\n };\n reader.readAsDataURL(file);\n });\n }\n\n /**\n * Validate video URL\n * @param {string} url - Video URL\n * @returns {Promise<boolean>} - Promise that resolves to validation result\n */\n static validateVideoUrl(url) {\n return new Promise((resolve) => {\n // Check if it's a YouTube URL\n if (Video.isYouTubeUrl(url)) {\n resolve(true);\n return;\n }\n \n // Check if it's a valid video URL format\n const videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];\n const hasValidExtension = videoExtensions.some(ext => \n url.toLowerCase().includes(`.${ext}`)\n );\n \n if (hasValidExtension) {\n resolve(true);\n return;\n }\n \n // Try to load as video element (for direct video URLs)\n const video = document.createElement('video');\n video.onloadedmetadata = () => {\n resolve(true);\n };\n video.onerror = () => {\n resolve(false);\n };\n \n // Set timeout to avoid hanging\n setTimeout(() => {\n resolve(false);\n }, 5000);\n \n video.src = url;\n });\n }\n\n /**\n * Check if URL is a YouTube URL\n * @param {string} url - URL to check\n * @returns {boolean} - Whether it's a YouTube URL\n */\n static isYouTubeUrl(url) {\n const youtubeRegex = /(?:https?:\\/\\/)?(?:www\\.)?(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/|youtube\\.com\\/embed\\/)([a-zA-Z0-9_-]{11})/;\n return youtubeRegex.test(url);\n }\n\n /**\n * Extract YouTube video ID from URL\n * @param {string} url - YouTube URL\n * @returns {string|null} - Video ID or null if not found\n */\n static getYouTubeVideoId(url) {\n const youtubeRegex = /(?:https?:\\/\\/)?(?:www\\.)?(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/|youtube\\.com\\/embed\\/)([a-zA-Z0-9_-]{11})/;\n const match = url.match(youtubeRegex);\n return match ? match[1] : null;\n }\n}\n\nexport default Video; ","/**\n * Tag Popup Component - Popup for inserting custom tags\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass TagPopup {\n constructor(options = {}) {\n this.options = {\n onTagInsert: null,\n editor: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.clickOutsideHandler = null;\n this.selectedTagType = 'mention';\n \n this.createTagPopup();\n }\n\n createTagPopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'tag-popup';\n \n const content = document.createElement('div');\n content.className = 'tag-popup-content';\n \n // Title\n const title = document.createElement('h3');\n title.textContent = 'Insert tags';\n title.className = 'yjd-input-title';\n content.appendChild(title);\n \n // Tag type selector\n const group1 = document.createElement('div');\n group1.className = 'yjd-input-group';\n \n const typeLabel = document.createElement('label');\n typeLabel.textContent = 'Type';\n typeLabel.className = 'yjd-input-label';\n \n this.typeSelect = document.createElement('select');\n this.typeSelect.className = 'yjd-select-input';\n this.typeSelect.innerHTML = `\n <option value=\"mention\">Mention</option>\n <option value=\"hashtag\">Hashtag</option>\n <option value=\"custom\">Custom</option>\n `;\n this.typeSelect.addEventListener('change', () => this.updateSuggestions());\n \n group1.appendChild(typeLabel);\n group1.appendChild(this.typeSelect);\n content.appendChild(group1);\n \n // Content input\n const group2 = document.createElement('div');\n group2.className = 'yjd-input-group';\n \n const contentLabel = document.createElement('label');\n contentLabel.textContent = 'Content';\n contentLabel.className = 'yjd-input-label';\n \n this.contentInput = document.createElement('input');\n this.contentInput.type = 'text';\n this.contentInput.className = 'yjd-input';\n this.contentInput.placeholder = 'Please enter tag content';\n this.contentInput.addEventListener('input', () => this.updateInsertButton());\n this.contentInput.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n this.insertTag();\n }\n });\n \n group2.appendChild(contentLabel);\n group2.appendChild(this.contentInput);\n content.appendChild(group2);\n \n // Suggestions\n const group3 = document.createElement('div');\n group3.className = 'yjd-input-group';\n this.suggestionsContainer = document.createElement('div');\n this.suggestionsContainer.className = 'tag-suggestions-container';\n \n const suggestionsLabel = document.createElement('label');\n suggestionsLabel.textContent = 'Suggestions';\n suggestionsLabel.className = 'yjd-input-label';\n \n this.suggestionsList = document.createElement('div');\n this.suggestionsList.className = 'yjd-suggestions-list';\n \n this.suggestionsContainer.appendChild(this.suggestionsList);\n group3.appendChild(suggestionsLabel);\n group3.appendChild(this.suggestionsContainer);\n content.appendChild(group3);\n \n // Buttons\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'yjd-button-container';\n \n const cancelButton = document.createElement('button');\n cancelButton.type = 'button';\n cancelButton.className = 'yjd-button-cancel';\n cancelButton.textContent = 'Cancel';\n cancelButton.addEventListener('click', () => this.hide());\n \n this.insertButton = document.createElement('button');\n this.insertButton.type = 'button';\n this.insertButton.className = 'yjd-button-confirm';\n this.insertButton.textContent = 'Insert Tag';\n this.insertButton.disabled = true;\n this.insertButton.addEventListener('click', () => this.insertTag());\n \n buttonContainer.appendChild(cancelButton);\n buttonContainer.appendChild(this.insertButton);\n content.appendChild(buttonContainer);\n \n this.popup.appendChild(content);\n appendPopup(this.popup);\n \n // Prevent focus loss when clicking on popup\n if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {\n this.options.editor.preventFocusLoss(this.popup);\n }\n \n this.updateSuggestions();\n }\n\n updateSuggestions() {\n this.selectedTagType = this.typeSelect.value;\n this.suggestionsList.innerHTML = '';\n \n const suggestions = this.getSuggestions(this.selectedTagType);\n \n suggestions.forEach(suggestion => {\n const suggestionButton = document.createElement('button');\n suggestionButton.type = 'button';\n suggestionButton.className = 'yjd-suggestion-button';\n suggestionButton.textContent = suggestion;\n \n suggestionButton.addEventListener('click', () => {\n this.contentInput.value = suggestion;\n this.updateInsertButton();\n this.contentInput.focus();\n });\n \n this.suggestionsList.appendChild(suggestionButton);\n });\n }\n\n getSuggestions(tagType) {\n const suggestions = {\n mention: ['john', 'admin', 'team', 'support'],\n hashtag: ['urgent', 'done', 'important'],\n custom: ['warning', 'info', 'success']\n };\n \n return suggestions[tagType] || [];\n }\n\n updateInsertButton() {\n const hasContent = this.contentInput.value.trim();\n this.insertButton.disabled = !hasContent;\n }\n\n insertTag() {\n const content = this.contentInput.value.trim();\n \n if (!content) return;\n \n if (this.options.onTagInsert) {\n this.options.onTagInsert(this.selectedTagType, content);\n }\n \n this.hide();\n this.reset();\n }\n\n reset() {\n this.contentInput.value = '';\n this.typeSelect.value = 'mention';\n this.selectedTagType = 'mention';\n this.updateInsertButton();\n this.updateSuggestions();\n }\n\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n show(anchor) {\n if (!anchor) return;\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n this.popup.classList.add('visible');\n this.isVisible = true;\n \n this.setupClickOutside();\n \n setTimeout(() => {\n this.contentInput.focus();\n }, 100);\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n }\n}\n\nexport default TagPopup; ","import { InlineFormat } from '../core/format.js';\nimport TagPopup from '../ui/tag-popup.js';\nimport Editor from '../core/editor.js';\n\n/**\n * Tag Format - Handles custom tag insertion\n * Now supports multiple editor instances with separate popup instances\n */\nclass Tag extends InlineFormat {\n static formatName = 'tag';\n static tagName = 'SPAN';\n static className = 'custom-tag';\n static savedRanges = new Map(); // Map to store saved ranges for each editor\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Tag format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a tag popup instance\n let tagPopup = currentEditor.getPopupInstance('tag');\n \n if (!tagPopup) {\n // Create new tag popup instance for this editor\n tagPopup = new TagPopup({\n onTagInsert: (tagType, tagContent) => {\n Tag.insertTagAtCurrentPosition(tagType, tagContent, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('tag', tagPopup);\n }\n \n this.tagPopup = tagPopup;\n }\n\n /**\n * Create a new Tag format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Tag} Tag format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Tag();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Create tag element\n * @param {string} tagType - Type of tag (@, #, custom)\n * @param {string} content - Tag content\n * @returns {HTMLElement}\n */\n static create(tagType, content) {\n const span = document.createElement('SPAN');\n span.className = `custom-tag tag-${tagType}`;\n \n let displayText = content;\n if (tagType === 'mention') {\n displayText = `@${content}`;\n } else if (tagType === 'hashtag') {\n displayText = `#${content}`;\n } else if (tagType === 'custom') {\n displayText = `<${content}>`;\n }\n \n span.textContent = displayText;\n span.setAttribute('data-tag-type', tagType);\n span.setAttribute('data-tag-content', content);\n span.setAttribute('contenteditable', 'false');\n \n return span;\n }\n\n /**\n * Insert tag at current cursor position\n * @param {string} tagType - Type of tag\n * @param {string} content - Tag content\n * @param {string} editorId - Editor instance ID\n */\n static insertTagAtCurrentPosition(tagType, content, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for tag insertion');\n return;\n }\n \n // Use saved range if available, otherwise get current selection\n const selection = window.getSelection();\n if (!selection) return;\n\n try {\n // Restore saved range if exists for this editor\n const savedRange = Tag.savedRanges.get(editorId);\n if (savedRange) {\n selection.removeAllRanges();\n selection.addRange(savedRange);\n Tag.savedRanges.delete(editorId);\n } else if (!selection.rangeCount) {\n return;\n }\n\n const range = selection.getRangeAt(0);\n \n // Create tag element\n const tagElement = Tag.create(tagType, content);\n \n // Insert tag at cursor position\n range.deleteContents();\n range.insertNode(tagElement);\n \n // Add a space after the tag for easier editing\n const spaceNode = document.createTextNode(' ');\n range.setStartAfter(tagElement);\n range.insertNode(spaceNode);\n \n // Position cursor after the space\n range.setStartAfter(spaceNode);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Focus back on editor\n if (editor && editor.element) {\n editor.element.focus();\n }\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n \n } catch (error) {\n console.error('Error inserting tag:', error);\n }\n }\n\n /**\n * Apply tag formatting - shows tag popup\n */\n apply(tagType, content) {\n if (tagType && content) {\n Tag.insertTagAtCurrentPosition(tagType, content, this.editorId);\n } else {\n // Save current selection before showing popup\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n Tag.savedRanges.set(this.editorId, selection.getRangeAt(0).cloneRange());\n }\n this.showTagPopup();\n }\n }\n\n /**\n * Remove tag formatting\n */\n remove() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n const range = selection.getRangeAt(0);\n const tagElement = this.getTagElement(range);\n \n if (tagElement) {\n // Replace tag element with its text content\n const textNode = document.createTextNode(tagElement.textContent);\n tagElement.parentNode.replaceChild(textNode, tagElement);\n }\n }\n\n /**\n * Toggle tag formatting - shows tag popup\n */\n toggle() {\n if (this.tagPopup.isVisible) {\n this.tagPopup.hide();\n } else {\n // Save current selection before showing popup\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n Tag.savedRanges.set(this.editorId, selection.getRangeAt(0).cloneRange());\n }\n this.showTagPopup();\n }\n }\n\n /**\n * Show tag popup\n */\n showTagPopup() {\n // Find tag button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let tagButton = null;\n \n if (toolbar) {\n tagButton = toolbar.getButton('tag');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!tagButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n tagButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.tag-btn');\n }\n }\n \n // Final fallback: find any tag button in the current editor's wrapper\n if (!tagButton) {\n tagButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.tag-btn');\n }\n \n if (!tagButton) {\n console.warn('Tag button not found for editor:', this.editorId);\n return;\n }\n \n this.tagPopup.show(tagButton);\n }\n\n /**\n * Check if tag formatting is active\n */\n isActive() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return false;\n\n const range = selection.getRangeAt(0);\n return this.getTagElement(range) !== null;\n }\n\n /**\n * Get tag element from selection\n * @param {Range} range - Selection range\n * @returns {HTMLElement|null}\n */\n getTagElement(range) {\n let node = range.commonAncestorContainer;\n \n // If it's a text node, get its parent\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n \n // Check if current node is a tag\n if (node.classList && node.classList.contains('custom-tag')) {\n return node;\n }\n \n // Check if selection contains a tag\n const tagInSelection = range.cloneContents().querySelector('.custom-tag');\n return tagInSelection || null;\n }\n\n /**\n * Get predefined tag suggestions\n * @param {string} tagType - Type of tag\n * @returns {Array} - Array of suggestions\n */\n static getSuggestions(tagType) {\n const suggestions = {\n mention: ['john', 'sarah', 'admin', 'team', 'support'],\n hashtag: ['urgent', 'todo', 'done', 'review', 'important'],\n custom: ['note', 'warning', 'tip', 'info', 'success']\n };\n \n return suggestions[tagType] || [];\n }\n}\n\nexport default Tag; ","import { InlineFormat } from '../core/format.js';\nimport CustomSelect from '../ui/customselect.js';\nimport { saveBeforeFormat } from '../utils/history-helper.js';\nimport Editor from '../core/editor.js';\nimport { execFormat, queryFormatValue } from '../utils/exec-command.js';\n\n/**\n * Text Size Format - Handles font size formatting with 7 levels via execCommand\n * Now supports multiple editor instances with separate popup instances\n */\nclass TextSize extends InlineFormat {\n static formatName = 'textSize';\n static tagName = 'SPAN';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for TextSize format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has a text size select instance\n let customSelect = currentEditor.getPopupInstance('text-size');\n \n if (!customSelect) {\n // Create new custom select instance for this editor\n const sizeMap = TextSize.getSizeMap();\n const items = Object.values(sizeMap).map(sizeData => ({\n value: sizeData.size,\n label: sizeData.element,\n title: sizeData.title\n }));\n\n customSelect = new CustomSelect({\n items: items,\n displayProperty: 'label',\n valueProperty: 'value',\n className: 'text-size-select',\n onItemSelect: (value) => {\n TextSize.applyTextSizeToCurrentSelection(value, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('text-size', customSelect);\n }\n \n this.customSelect = customSelect;\n }\n\n /**\n * Create a new TextSize format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {TextSize} TextSize format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new TextSize();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * 7-level text size map aligned with execCommand('fontSize', 1..7)\n */\n static getSizeMap() {\n return {\n '1': { size: '1', element: '<span >XX-Small</span>', title: 'XX-Small' },\n '2': { size: '2', element: '<span >X-Small</span>', title: 'X-Small' },\n '3': { size: '3', element: '<span >Small</span>', title: 'Small' },\n '4': { size: '4', element: '<span >Medium</span>', title: 'Medium' },\n '5': { size: '5', element: '<span >Large</span>', title: 'Large' },\n '6': { size: '6', element: '<span >X-Large</span>', title: 'X-Large' },\n '7': { size: '7', element: '<span >XX-Large</span>', title: 'XX-Large' },\n };\n }\n\n static getSizeDisplayName(size) {\n const sizeMap = this.getSizeMap();\n return sizeMap[size]?.title || 'Medium';\n }\n\n /**\n * Update button text based on current text size\n */\n updateButtonText() {\n const currentSize = this.getCurrentSize();\n const displayName = TextSize.getSizeDisplayName(currentSize || '4');\n\n // Find text-size button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let textSizeButton = null;\n \n if (toolbar) {\n textSizeButton = toolbar.getButton('text-size');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!textSizeButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n textSizeButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n }\n \n // Final fallback: find any text-size button in the current editor's wrapper\n if (!textSizeButton) {\n textSizeButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n \n if (textSizeButton && textSizeButton.updateText) {\n textSizeButton.updateText(displayName);\n } else if (textSizeButton) {\n textSizeButton.textContent = displayName;\n }\n }\n\n /**\n * Static method to update button text for any editor\n * @param {string} editorId - Editor instance ID\n */\n static updateButtonTextStatic(editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) return;\n \n const currentSize = TextSize.getCurrentSizeStatic();\n const displayName = TextSize.getSizeDisplayName(currentSize || '4');\n\n const toolbar = editor.getModule('toolbar');\n let textSizeButton = null;\n \n if (toolbar) {\n textSizeButton = toolbar.getButton('text-size');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!textSizeButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n textSizeButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n }\n \n // Final fallback: find any text-size button in the current editor's wrapper\n if (!textSizeButton) {\n textSizeButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n \n if (textSizeButton && textSizeButton.updateText) {\n textSizeButton.updateText(displayName);\n } else if (textSizeButton) {\n textSizeButton.textContent = displayName;\n }\n }\n\n static create(size = '4') {\n const node = document.createElement('span');\n // Fallback creation with an approximate CSS size\n node.style.fontSize = TextSize.sizeToCss(size);\n return node;\n }\n\n /**\n * Apply text size to current selection\n * @param {string} size - Text size value\n * @param {string} editorId - Editor instance ID\n */\n static applyTextSizeToCurrentSelection(size, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for text size application');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n // Save state before applying format\n saveBeforeFormat();\n\n const sizeFormat = TextSize.createForEditor(editorId);\n if (sizeFormat) {\n sizeFormat.apply(size);\n sizeFormat.updateButtonText();\n }\n \n // Trigger content change after applying format\n setTimeout(() => {\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n }, 0);\n }\n\n /**\n * Map execCommand size (1..7) to CSS font-size for fallback/labels\n */\n static sizeToCss(size) {\n const map = {\n '1': '10px',\n '2': '12px',\n '3': '14px',\n '4': '16px',\n '5': '20px',\n '6': '28px',\n '7': '36px',\n };\n return map[String(size)] || '16px';\n }\n\n /**\n * Apply text size using execCommand; works with selection and collapsed caret\n */\n apply(size = '4') {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n saveBeforeFormat();\n\n const range = selection.getRangeAt(0);\n\n if (!range.collapsed) {\n // Bạn chưa nói đến xử lý khi bôi đen, nên mình bỏ qua\n execFormat('fontSize', String(size));\n\n // Lấy node bao quanh selection hiện tại\n const sel = window.getSelection();\n if (sel.rangeCount > 0) {\n const container = sel.getRangeAt(0).commonAncestorContainer;\n // Nếu container là text node → normalize ở parent\n if (container.nodeType === Node.TEXT_NODE) {\n container.parentNode.normalize();\n } else {\n container.normalize();\n }\n } \n \n return;\n }\n\n let node = range.startContainer;\n let offset = range.startOffset;\n\n // Nếu caret đang trong text node → lấy cha\n if (node.nodeType === Node.TEXT_NODE) {\n node = node.parentNode;\n }\n\n // Kiểm tra nếu đang ở trong một <font>\n const currentFont = node.closest && node.closest('font');\n\n // ========================\n // Trường hợp 1: caret trong <font> rỗng (chỉ có \\u200B)\n // ========================\n if (currentFont && currentFont.textContent === \"\\u200B\") {\n currentFont.setAttribute('size', String(size));\n return;\n }\n\n // ========================\n // Trường hợp 2: caret trong <font> có ký tự thực\n // ========================\n if (currentFont && currentFont.firstChild && currentFont.firstChild.nodeType === Node.TEXT_NODE) {\n const textNode = currentFont.firstChild;\n const caretPos = range.startOffset; // vị trí caret trong text node\n\n // Loại bỏ ký tự ẩn trong tính toán\n \n const textBefore = textNode.data.slice(0, caretPos);\n const textAfter = textNode.data.slice(caretPos);\n\n const parent = currentFont.parentNode;\n\n if (caretPos === 0) {\n // Đang ở ĐẦU thẻ font → chèn font mới trước\n const newFont = document.createElement('font');\n newFont.setAttribute('size', String(size));\n newFont.appendChild(document.createTextNode(\"\\u200B\"));\n parent.insertBefore(newFont, currentFont);\n\n moveCaretInside(newFont);\n\n } else if (caretPos === textNode.data.length) {\n // Đang ở CUỐI thẻ font → chèn font mới sau\n const newFont = document.createElement('font');\n newFont.setAttribute('size', String(size));\n newFont.appendChild(document.createTextNode(\"\\u200B\"));\n parent.insertBefore(newFont, currentFont.nextSibling);\n\n moveCaretInside(newFont);\n\n } else {\n\n \n const font1 = document.createElement('font');\n font1.setAttribute('size', currentFont.getAttribute('size'));\n font1.appendChild(document.createTextNode(textBefore));\n\n const font2 = document.createElement('font');\n font2.setAttribute('size', String(size));\n font2.appendChild(document.createTextNode(\"\\u200B\"));\n\n const font3 = document.createElement('font');\n font3.setAttribute('size', currentFont.getAttribute('size'));\n font3.appendChild(document.createTextNode(textAfter));\n\n parent.insertBefore(font1, currentFont);\n parent.insertBefore(font2, currentFont);\n parent.insertBefore(font3, currentFont);\n\n parent.removeChild(currentFont);\n\n moveCaretInside(font2);\n }\n return;\n }\n\n // ========================\n // Trường hợp 3: không ở trong <font> nào → tạo mới\n // ========================\n const newFont = document.createElement('font');\n newFont.setAttribute('size', String(size));\n const zwsp = document.createTextNode(\"\\u200B\");\n newFont.appendChild(zwsp);\n\n range.insertNode(newFont);\n moveCaretInside(newFont);\n\n // Hàm phụ để đưa caret vào sau ký tự ẩn\n function moveCaretInside(fontEl) {\n const sel = window.getSelection();\n const range = document.createRange();\n const textNode = fontEl.firstChild;\n range.setStart(textNode, textNode.length);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n\n \n }\n\n async toggle() {\n if (this.customSelect.isVisible) {\n this.customSelect.hide();\n } else {\n await this.showSizePicker();\n }\n }\n\n async showSizePicker() {\n // Find text-size button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let textSizeButton = null;\n \n if (toolbar) {\n textSizeButton = toolbar.getButton('text-size');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!textSizeButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n textSizeButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n }\n \n // Final fallback: find any text-size button in the current editor's wrapper\n if (!textSizeButton) {\n textSizeButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.text-size-btn');\n }\n \n if (!textSizeButton) {\n console.warn('Text-size button not found for editor:', this.editorId);\n return;\n }\n\n const currentSize = this.getCurrentSize();\n if (currentSize) {\n this.customSelect.setCurrentValue(currentSize);\n }\n\n await this.customSelect.show(textSizeButton);\n }\n\n isActive(size = null) {\n this.updateButtonText();\n return false;\n }\n\n /**\n * Get current text size near caret/selection, return one of '1'..'7'\n */\n getCurrentSize() {\n return TextSize.getCurrentSizeStatic();\n }\n\n /**\n * Static method to get current text size\n * @returns {string} Current text size\n */\n static getCurrentSizeStatic() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return '4';\n\n try {\n // Try to use queryCommandValue when available (returns 1..7 in many browsers)\n const val = queryFormatValue('fontSize');\n const num = parseInt(val, 10);\n if (!isNaN(num) && num >= 1 && num <= 7) {\n return String(num);\n }\n } catch (_) {}\n\n const range = selection.getRangeAt(0);\n let currentNode = range.startContainer;\n if (currentNode.nodeType === Node.TEXT_NODE) {\n currentNode = currentNode.parentElement;\n }\n\n while (currentNode && currentNode !== document.body) {\n if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const element = currentNode;\n const inline = element.style?.fontSize;\n if (inline) return this.normalizeCssSizeToExecSize(inline);\n\n const computed = window.getComputedStyle(element).fontSize;\n if (computed) return this.normalizeCssSizeToExecSize(computed);\n }\n currentNode = currentNode.parentElement;\n }\n\n return '4';\n }\n\n /**\n * Normalize CSS px value to closest execCommand size 1..7\n */\n normalizeCssSizeToExecSize(cssSize) {\n const px = parseFloat(cssSize);\n if (isNaN(px)) return '4';\n const steps = [10, 12, 14, 16, 20, 28, 36];\n let closestIndex = 3; // default to '4' (16px)\n let minDiff = Infinity;\n for (let i = 0; i < steps.length; i++) {\n const diff = Math.abs(px - steps[i]);\n if (diff < minDiff) {\n minDiff = diff;\n closestIndex = i;\n }\n }\n return String(closestIndex + 1);\n }\n}\n\nexport default TextSize;\n\n\n","/**\n * Import Popup Component - Popup for importing various file types\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass ImportPopup {\n constructor(options = {}) {\n this.options = {\n onImport: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.clickOutsideHandler = null;\n this.selectedFile = null;\n this.fileType = null;\n \n this.createImportPopup();\n }\n\n createImportPopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'import-popup';\n \n const content = document.createElement('div');\n content.className = 'import-popup-content';\n \n // Title\n const title = document.createElement('h3');\n title.textContent = 'Import File';\n title.className = 'import-popup-title';\n content.appendChild(title);\n \n // File type selector\n const typeContainer = document.createElement('div');\n typeContainer.className = 'import-type-container';\n \n const typeLabel = document.createElement('label');\n typeLabel.textContent = 'File Type:';\n typeLabel.className = 'import-input-label';\n \n this.typeSelect = document.createElement('select');\n this.typeSelect.className = 'import-type-select';\n this.typeSelect.innerHTML = `\n <option value=\"\">Select file type...</option>\n <option value=\"html\">HTML (.html, .htm)</option>\n <option value=\"excel\">Excel/CSV (.csv, .xlsx, .xls)</option>\n <option value=\"pdf\">PDF (.pdf)</option>\n <option value=\"word\">Word (.doc, .docx)</option>\n `;\n this.typeSelect.addEventListener('change', () => this.updateFileInput());\n \n typeContainer.appendChild(typeLabel);\n typeContainer.appendChild(this.typeSelect);\n content.appendChild(typeContainer);\n \n // File input\n this.fileInput = document.createElement('input');\n this.fileInput.type = 'file';\n this.fileInput.className = 'import-file-input';\n this.fileInput.disabled = true;\n this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));\n \n content.appendChild(this.fileInput);\n \n // File info\n this.fileInfo = document.createElement('div');\n this.fileInfo.className = 'import-file-info';\n this.fileInfo.style.display = 'none';\n content.appendChild(this.fileInfo);\n \n // Buttons\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'import-button-container';\n \n const cancelButton = document.createElement('button');\n cancelButton.type = 'button';\n cancelButton.className = 'import-button cancel-button';\n cancelButton.textContent = 'Cancel';\n cancelButton.addEventListener('click', () => this.hide());\n \n this.importButton = document.createElement('button');\n this.importButton.type = 'button';\n this.importButton.className = 'import-button import-button-main';\n this.importButton.textContent = 'Import';\n this.importButton.disabled = true;\n this.importButton.addEventListener('click', () => this.processImport());\n \n buttonContainer.appendChild(cancelButton);\n buttonContainer.appendChild(this.importButton);\n content.appendChild(buttonContainer);\n \n this.popup.appendChild(content);\n appendPopup(this.popup);\n }\n\n updateFileInput() {\n const selectedType = this.typeSelect.value;\n \n if (selectedType) {\n this.fileType = selectedType;\n this.fileInput.disabled = false;\n \n const acceptTypes = this.getAcceptTypes(selectedType);\n this.fileInput.accept = acceptTypes;\n } else {\n this.fileType = null;\n this.fileInput.disabled = true;\n this.fileInput.accept = '';\n }\n \n this.updateImportButton();\n }\n\n getAcceptTypes(fileType) {\n const types = {\n html: '.html,.htm,text/html',\n excel: '.csv,.xlsx,.xls,text/csv',\n pdf: '.pdf,application/pdf',\n word: '.doc,.docx'\n };\n \n return types[fileType] || '';\n }\n\n handleFileSelect(e) {\n const file = e.target.files[0];\n if (file) {\n this.setSelectedFile(file);\n }\n }\n\n setSelectedFile(file) {\n this.selectedFile = file;\n \n this.fileInfo.style.display = 'block';\n this.fileInfo.innerHTML = `\n <div><strong>Name:</strong> ${file.name}</div>\n <div><strong>Size:</strong> ${this.formatFileSize(file.size)}</div>\n <div><strong>Type:</strong> ${file.type || 'Unknown'}</div>\n `;\n \n this.updateImportButton();\n }\n\n formatFileSize(bytes) {\n if (bytes === 0) return '0 Bytes';\n \n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n }\n\n updateImportButton() {\n this.importButton.disabled = !this.selectedFile || !this.fileType;\n }\n\n async processImport() {\n if (!this.selectedFile || !this.fileType) return;\n \n try {\n let content;\n \n if (this.fileType === 'html') {\n content = await this.readAsText(this.selectedFile);\n } else if (this.fileType === 'excel') {\n if (this.selectedFile.name.toLowerCase().endsWith('.csv')) {\n const csvContent = await this.readAsText(this.selectedFile);\n content = this.parseCSV(csvContent);\n } else {\n alert('Excel files (.xlsx/.xls) require additional libraries. Please use CSV format.');\n return;\n }\n } else if (this.fileType === 'pdf') {\n alert('PDF import requires additional libraries. Feature coming soon.');\n return;\n } else if (this.fileType === 'word') {\n alert('Word document import requires additional libraries. Feature coming soon.');\n return;\n }\n \n if (this.options.onImport) {\n this.options.onImport(content, this.fileType);\n }\n \n this.hide();\n this.reset();\n \n } catch (error) {\n console.error('Import error:', error);\n alert('Error importing file: ' + error.message);\n }\n }\n\n parseCSV(csvContent) {\n const lines = csvContent.split('\\n');\n const result = [];\n \n lines.forEach(line => {\n if (line.trim()) {\n const cells = line.split(',').map(cell => cell.trim().replace(/^[\"']|[\"']$/g, ''));\n result.push(cells);\n }\n });\n \n return result;\n }\n\n readAsText(file) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = (e) => resolve(e.target.result);\n reader.onerror = () => reject(new Error('Failed to read file'));\n reader.readAsText(file);\n });\n }\n\n reset() {\n this.selectedFile = null;\n this.fileType = null;\n this.typeSelect.value = '';\n this.fileInput.value = '';\n this.fileInput.disabled = true;\n this.fileInfo.style.display = 'none';\n this.updateImportButton();\n }\n\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n show(anchor) {\n if (!anchor) return;\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n this.popup.classList.add('visible');\n this.isVisible = true;\n \n this.setupClickOutside();\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n }\n\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n }\n}\n\nexport default ImportPopup; ","import { InlineFormat } from '../core/format.js';\nimport ImportPopup from '../ui/import-popup.js';\nimport Editor from '../core/editor.js';\nimport { sanitizeHtml } from '../utils/sanitize.js';\n\n/**\n * Import Format - Handles importing various file types\n * Now supports multiple editor instances with separate popup instances\n */\nclass Import extends InlineFormat {\n static formatName = 'import';\n static tagName = 'DIV';\n static className = 'imported-content';\n\n constructor() {\n super();\n \n // Get current editor instance\n const currentEditor = Editor.getCurrentInstance();\n if (!currentEditor) {\n console.warn('No editor instance found for Import format');\n return;\n }\n \n this.editorId = currentEditor.instanceId;\n \n // Check if this editor already has an import popup instance\n let importPopup = currentEditor.getPopupInstance('import');\n \n if (!importPopup) {\n // Create new import popup instance for this editor\n importPopup = new ImportPopup({\n onImport: (content, fileType) => {\n Import.insertImportedContent(content, fileType, this.editorId);\n },\n editor: currentEditor,\n editorId: this.editorId\n });\n \n // Store popup instance in editor\n currentEditor.setPopupInstance('import', importPopup);\n }\n \n this.importPopup = importPopup;\n }\n\n /**\n * Create a new Import format instance for a specific editor\n * @param {string} editorId - Editor instance ID\n * @returns {Import} Import format instance\n */\n static createForEditor(editorId) {\n const editor = Editor.getInstanceById(editorId);\n if (!editor) {\n console.warn('No editor instance found for ID:', editorId);\n return null;\n }\n \n // Temporarily set as current instance\n const originalCurrent = Editor.currentInstance;\n Editor.currentInstance = editor;\n \n // Create format instance\n const format = new Import();\n \n // Restore original current instance\n Editor.currentInstance = originalCurrent;\n \n return format;\n }\n\n /**\n * Insert imported content at current cursor position\n * @param {string} content - Imported content\n * @param {string} fileType - Type of imported file\n * @param {string} editorId - Editor instance ID\n */\n static insertImportedContent(content, fileType, editorId = null) {\n // Get the correct editor instance\n let editor = null;\n if (editorId) {\n editor = Editor.getInstanceById(editorId);\n } else {\n editor = Editor.getCurrentInstance();\n }\n \n if (!editor) {\n console.warn('No editor instance found for content insertion');\n return;\n }\n \n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return;\n\n try {\n const range = selection.getRangeAt(0);\n \n // Create content element based on file type\n let contentElement;\n \n if (fileType === 'html') {\n contentElement = Import.processHtmlContent(content);\n } else if (fileType === 'excel') {\n contentElement = Import.processExcelContent(content);\n } else if (fileType === 'pdf' || fileType === 'word') {\n contentElement = Import.processTextContent(content);\n } else {\n contentElement = Import.processTextContent(content);\n }\n \n // Insert content at cursor position\n range.deleteContents();\n range.insertNode(contentElement);\n \n // Position cursor after the content\n range.setStartAfter(contentElement);\n range.collapse(true);\n selection.removeAllRanges();\n selection.addRange(range);\n \n // Trigger content change event\n if (editor && typeof editor.onContentChange === 'function') {\n editor.onContentChange();\n }\n \n } catch (error) {\n console.error('Error inserting imported content:', error);\n }\n }\n\n /**\n * Process HTML content\n * @param {string} htmlContent - HTML content to process\n * @returns {HTMLElement}\n */\n static processHtmlContent(htmlContent) {\n const container = document.createElement('div');\n container.className = 'imported-content html-content';\n\n // Sanitize untrusted HTML (DOMParser-based, inert) before inserting it.\n // sanitizeHtml strips scripts, event handlers and unsafe URLs.\n container.innerHTML = sanitizeHtml(htmlContent);\n\n // Additionally enforce the import tag/attribute whitelist.\n Import.cleanHtmlContent(container);\n\n return container;\n }\n\n /**\n * Process Excel content (CSV-like data)\n * @param {Array} data - Excel data as array of arrays\n * @returns {HTMLElement}\n */\n static processExcelContent(data) {\n const container = document.createElement('div');\n container.className = 'imported-content excel-content';\n \n if (!Array.isArray(data) || data.length === 0) {\n container.textContent = 'No data to import';\n return container;\n }\n \n // Create table\n const table = document.createElement('table');\n table.className = 'imported-table';\n \n // Add header row if available\n if (data.length > 0) {\n const thead = document.createElement('thead');\n const headerRow = document.createElement('tr');\n \n data[0].forEach(cellData => {\n const th = document.createElement('th');\n th.textContent = cellData || '';\n headerRow.appendChild(th);\n });\n \n thead.appendChild(headerRow);\n table.appendChild(thead);\n }\n \n // Add data rows\n if (data.length > 1) {\n const tbody = document.createElement('tbody');\n \n for (let i = 1; i < data.length; i++) {\n const row = document.createElement('tr');\n \n data[i].forEach(cellData => {\n const td = document.createElement('td');\n td.textContent = cellData || '';\n row.appendChild(td);\n });\n \n tbody.appendChild(row);\n }\n \n table.appendChild(tbody);\n }\n \n container.appendChild(table);\n return container;\n }\n\n /**\n * Process plain text content (PDF, Word)\n * @param {string} textContent - Text content to process\n * @returns {HTMLElement}\n */\n static processTextContent(textContent) {\n const container = document.createElement('div');\n container.className = 'imported-content text-content';\n \n // Split into paragraphs and process\n const paragraphs = textContent.split(/\\n\\s*\\n/);\n \n paragraphs.forEach(paragraph => {\n if (paragraph.trim()) {\n const p = document.createElement('p');\n p.textContent = paragraph.trim();\n container.appendChild(p);\n }\n });\n \n return container;\n }\n\n /**\n * Clean HTML content by removing dangerous elements and attributes\n * @param {HTMLElement} element - Element to clean\n */\n static cleanHtmlContent(element) {\n const allowedTags = ['p', 'div', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', \n 'strong', 'b', 'em', 'i', 'u', 'ul', 'ol', 'li', 'br', \n 'table', 'thead', 'tbody', 'tr', 'th', 'td'];\n \n const allowedAttrs = ['class', 'style'];\n \n const walker = document.createTreeWalker(\n element,\n NodeFilter.SHOW_ELEMENT,\n null,\n false\n );\n \n const elementsToRemove = [];\n \n while (walker.nextNode()) {\n const node = walker.currentNode;\n \n // Remove dangerous tags\n if (!allowedTags.includes(node.tagName.toLowerCase())) {\n elementsToRemove.push(node);\n continue;\n }\n \n // Clean attributes\n const attrs = Array.from(node.attributes);\n attrs.forEach(attr => {\n if (!allowedAttrs.includes(attr.name.toLowerCase())) {\n node.removeAttribute(attr.name);\n }\n });\n }\n \n // Remove dangerous elements\n elementsToRemove.forEach(el => {\n if (el.parentNode) {\n el.parentNode.removeChild(el);\n }\n });\n }\n\n /**\n * Parse CSV content\n * @param {string} csvContent - CSV content\n * @returns {Array} - Array of arrays representing CSV data\n */\n static parseCSV(csvContent) {\n const lines = csvContent.split('\\n');\n const result = [];\n \n lines.forEach(line => {\n if (line.trim()) {\n // Simple CSV parsing (doesn't handle quoted values with commas)\n const cells = line.split(',').map(cell => cell.trim().replace(/^[\"']|[\"']$/g, ''));\n result.push(cells);\n }\n });\n \n return result;\n }\n\n /**\n * Apply import formatting - shows import popup\n */\n apply() {\n this.showImportPopup();\n }\n\n /**\n * Toggle import formatting - shows import popup\n */\n toggle() {\n if (this.importPopup.isVisible) {\n this.importPopup.hide();\n } else {\n this.showImportPopup();\n }\n }\n\n /**\n * Show import popup\n */\n showImportPopup() {\n // Find import button in the current editor's toolbar\n const editor = Editor.getInstanceById(this.editorId);\n if (!editor) return;\n \n const toolbar = editor.getModule('toolbar');\n let importButton = null;\n \n if (toolbar) {\n importButton = toolbar.getButton('import');\n }\n \n // Fallback: find button by class in the current editor's toolbar\n if (!importButton) {\n const toolbarContainer = toolbar?.getContainer();\n if (toolbarContainer) {\n importButton = toolbarContainer.querySelector('.rich-editor-toolbar-btn.import-btn');\n }\n }\n \n // Final fallback: find any import button in the current editor's wrapper\n if (!importButton) {\n importButton = editor.wrapper.querySelector('.rich-editor-toolbar-btn.import-btn');\n }\n \n if (!importButton) {\n console.warn('Import button not found for editor:', this.editorId);\n return;\n }\n \n this.importPopup.show(importButton);\n }\n\n /**\n * Check if import formatting is active\n */\n isActive() {\n return false; // Import doesn't have an \"active\" state\n }\n\n /**\n * Get supported file types\n */\n static getSupportedTypes() {\n return {\n html: {\n extensions: ['.html', '.htm'],\n mimeTypes: ['text/html'],\n name: 'HTML Files'\n },\n excel: {\n extensions: ['.csv', '.xlsx', '.xls'],\n mimeTypes: ['text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],\n name: 'Excel Files'\n },\n pdf: {\n extensions: ['.pdf'],\n mimeTypes: ['application/pdf'],\n name: 'PDF Files'\n },\n word: {\n extensions: ['.doc', '.docx'],\n mimeTypes: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],\n name: 'Word Documents'\n }\n };\n }\n}\n\nexport default Import; ","import IconUtils from './icons.js';\n\n/**\n * Create Custom Button - Simple utility to create styled button\n * @param {string} text - Button text content\n * @param {Object} options - Button options\n * @param {string} options.width - Button width (e.g., '120px', 'auto')\n * @returns {HTMLElement} Button element\n */\nfunction createCustomButton(text = 'Button', options = {}) {\n const { width = 'auto', icon = null } = options;\n\n // Create button\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'custom-select-button';\n\n // Optional leading icon so the control is recognisable at a glance.\n if (icon) {\n const leadIcon = IconUtils.createIconElement(icon);\n leadIcon.className = 'select-lead-icon';\n button.appendChild(leadIcon);\n }\n\n // Create text span\n const textSpan = document.createElement('span');\n textSpan.textContent = text;\n textSpan.className = 'button-text';\n\n // Create dropdown icon\n const dropdownIcon = IconUtils.createIconElement('dropdown');\n dropdownIcon.className = 'dropdown-icon';\n\n // Add text and icon to button\n button.appendChild(textSpan);\n button.appendChild(dropdownIcon);\n \n // Apply styles\n button.style.width = width;\n button.style.padding = '0px 5px 0px 8px';\n button.style.setProperty('height', '32px', 'important');\n button.style.setProperty('borderRadius', '6px', 'important');\n button.style.setProperty('alignItems', 'center', 'important');\n button.style.fontSize = '14px';\n button.style.fontWeight = '400';\n button.style.color = '#374151';\n button.style.background = '#FFFFFF';\n button.style.cursor = 'pointer';\n button.style.border = '1px solid #d1d5db';\n button.style.display = 'flex';\n button.style.justifyContent = 'space-between';\n button.style.alignItems = 'center';\n \n // Style the text span to take available space\n textSpan.style.flex = '1';\n textSpan.style.textAlign = 'left';\n \n // Style the dropdown icon\n \n // Add method to update button text\n button.updateText = function(newText) {\n textSpan.textContent = newText;\n };\n \n return button;\n}\n\nexport default createCustomButton; ","import Module from '../core/module.js';\nimport ColorPicker from '../ui/color-picker.js';\nimport IconUtils from '../ui/icons.js';\nimport createCustomButton from '../ui/select-button.js';\n\n/**\n * Toolbar Module - Pure UI component with dual toolbar support\n * Only handles toolbar creation and event emission\n * No business logic or state management\n */\nclass Toolbar extends Module {\n static DEFAULTS = {\n container: null,\n toolbar1: [\n // Most-used inline formatting leads; the block-style (Paragraph) picker\n // sits after it rather than first.\n { group: 'text-format', items: ['bold', 'italic', 'underline', 'strike'] },\n { group: 'paragraph', items: ['heading'] },\n { group: 'colors', items: ['color', 'background'] },\n { group: 'link', items: ['link'] },\n { group: 'paragraph-ops', items: ['list', 'indent-increase', 'indent-decrease', 'text-align'] },\n { group: 'insert', items: ['image', 'table'] },\n // Undo/redo live on the right and stay hidden until there's history.\n { group: 'history', items: ['undo', 'redo'] },\n { group: 'more', items: ['more'] }\n ],\n toolbar2: [\n { group: 'font', items: ['font-family', 'text-size', 'line-height'] },\n { group: 'script', items: ['subscript', 'superscript', 'capitalization'] },\n { group: 'media', items: ['emoji', 'video', 'tag', 'horizontal-rule'] },\n { group: 'tools', items: ['clear-format', 'text-direction', 'find', 'code-view'] }\n ]\n };\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.buttons = new Map();\n this.toolbar2Visible = false;\n this.events = new Map(); // Add event system\n \n \n // Handle toolbar configuration\n if (Array.isArray(options.toolbar)) {\n // If toolbar array is provided, use only those items - COMPLETELY OVERRIDE DEFAULTS\n this.options = {\n container: null,\n toolbar1: [\n { group: 'text-format', items: options.toolbar }\n ],\n toolbar2: []\n };\n } else if (options.toolbar === 'full') {\n // Explicit full preset == the defaults.\n this.options = { ...Toolbar.DEFAULTS, ...options };\n } else if (options.toolbar === 'compact') {\n // One tidy row of the essentials — good for comment boxes.\n this.options = {\n container: null,\n toolbar1: [\n { group: 'text-format', items: ['bold', 'italic', 'underline'] },\n { group: 'link', items: ['link'] },\n { group: 'paragraph-ops', items: ['list'] },\n { group: 'insert', items: ['image', 'emoji'] },\n { group: 'more', items: ['more'] }\n ],\n toolbar2: []\n };\n } else if (options.toolbar && typeof options.toolbar === 'object' && Array.isArray(options.toolbar.exclude)) {\n // Start from the defaults and drop the named items (and any group left empty).\n const drop = new Set(options.toolbar.exclude);\n const prune = (rows) => (rows || [])\n .map(g => ({ ...g, items: g.items.filter(it => !drop.has(it)) }))\n .filter(g => g.items.length && !(g.items.length === 1 && g.items[0] === 'more' && false));\n this.options = {\n container: null,\n toolbar1: prune(Toolbar.DEFAULTS.toolbar1),\n toolbar2: prune(Toolbar.DEFAULTS.toolbar2)\n };\n } else if (options.toolbar1 || options.toolbar2) {\n // If specific toolbar1/toolbar2 config is provided, use it - COMPLETELY OVERRIDE DEFAULTS\n this.options = {\n container: null,\n toolbar1: options.toolbar1 || [],\n toolbar2: options.toolbar2 || []\n };\n } else {\n // Use full default configuration\n this.options = { ...Toolbar.DEFAULTS, ...options };\n }\n \n \n this.init();\n this.preloadIcons();\n }\n\n init() {\n this.container = this.createToolbarContainer();\n }\n\n /**\n * Preload icons for better performance\n */\n async preloadIcons() {\n // Icons are now inline, no need to preload\n // This method is kept for backward compatibility\n }\n\n /**\n * Create main toolbar container with both toolbars\n */\n createToolbarContainer() {\n const container = document.createElement('div');\n container.className = 'rich-editor-toolbar-container';\n container.setAttribute('role', 'toolbar');\n container.setAttribute('aria-label', 'Text formatting');\n\n // Prevent toolbar from taking focus away from editor\n this.editor.preventFocusLoss(container);\n\n // Keep the editor's text selection when a toolbar button is pressed (mouse\n // OR touch). Without this, tapping e.g. Bold on mobile can clear the\n // selection before the click handler runs, so the format applies to nothing.\n // preventing pointerdown's default stops the focus/selection change while\n // the click still fires normally.\n container.addEventListener('pointerdown', (e) => {\n if (e.target.closest('button')) e.preventDefault();\n });\n\n // Primary (always-visible) row and overflow (\"more\") row\n this.toolbar1 = document.createElement('div');\n this.toolbar1.className = 'rich-editor-toolbar-1';\n this.toolbar2 = document.createElement('div');\n this.toolbar2.className = 'rich-editor-toolbar-2';\n this.toolbar2.style.display = 'none';\n\n // Build every group (toolbar1 first = higher priority, then toolbar2) into\n // the primary row. reflow() later moves whatever doesn't fit into the\n // overflow row, so the toolbar adapts to any width instead of wrapping.\n this.flowGroups = [];\n const merged = [...(this.options.toolbar1 || []), ...(this.options.toolbar2 || [])];\n merged.forEach(group => {\n if (!group || !group.group || !Array.isArray(group.items)) return;\n // The \"more\" toggle is managed separately (added at the end).\n if (group.items.length === 1 && group.items[0] === 'more') return;\n const groupContainer = document.createElement('div');\n groupContainer.className = `toolbar-group toolbar-group-${group.group}`;\n group.items.forEach(item => {\n if (typeof item === 'string') this.addButton(groupContainer, item);\n });\n this.toolbar1.appendChild(groupContainer);\n this.flowGroups.push(groupContainer);\n });\n\n // The \"more\" button lives at the end of the primary row; shown only when\n // there is overflow.\n this.addMoreButton(this.toolbar1);\n this.moreBtn = this.buttons.get('more');\n if (this.moreBtn) this.moreBtn.classList.add('more-btn');\n\n container.appendChild(this.toolbar1);\n container.appendChild(this.toolbar2);\n\n // Responsive reflow: re-distribute groups whenever the toolbar resizes.\n if (typeof ResizeObserver !== 'undefined') {\n this._ro = new ResizeObserver(() => this._scheduleReflow());\n this._ro.observe(container);\n }\n requestAnimationFrame(() => this.reflow());\n\n // Keyboard navigation (arrow keys move between buttons; roving tabindex).\n this._setupKeyboardNav(container);\n\n return container;\n }\n\n /**\n * All currently focusable (visible, enabled) toolbar buttons in order.\n */\n _focusableButtons() {\n return Array.from(\n this.container.querySelectorAll('.rich-editor-toolbar-btn, .custom-select-button')\n ).filter(b => !b.disabled && b.offsetParent !== null);\n }\n\n /**\n * Roving tabindex: only one button is in the tab order at a time.\n */\n _updateRoving() {\n const btns = this._focusableButtons();\n btns.forEach((b, i) => { b.tabIndex = i === 0 ? 0 : -1; });\n }\n\n /**\n * Arrow-key navigation across the toolbar (ARIA toolbar pattern).\n */\n _setupKeyboardNav(container) {\n container.addEventListener('keydown', (e) => {\n if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) return;\n const btns = this._focusableButtons();\n if (!btns.length) return;\n const cur = btns.indexOf(document.activeElement);\n let next;\n if (e.key === 'Home') next = 0;\n else if (e.key === 'End') next = btns.length - 1;\n else if (cur === -1) next = 0;\n else next = e.key === 'ArrowRight'\n ? (cur + 1) % btns.length\n : (cur - 1 + btns.length) % btns.length;\n e.preventDefault();\n btns.forEach(b => { b.tabIndex = -1; });\n btns[next].tabIndex = 0;\n btns[next].focus();\n });\n }\n\n /**\n * Debounce reflow to one run per animation frame.\n */\n _scheduleReflow() {\n if (this._reflowQueued) return;\n this._reflowQueued = true;\n requestAnimationFrame(() => {\n this._reflowQueued = false;\n this.reflow();\n });\n }\n\n /**\n * Distribute groups between the primary row and the overflow (\"more\") row so\n * the primary row always fits on a single line at the current width.\n */\n reflow() {\n if (!this.toolbar1 || !this.flowGroups || !this.moreBtn) return;\n const GAP = 12; // matches .toolbar-group spacing\n\n // Pull every group back into the primary row (in priority order) to measure.\n this.flowGroups.forEach(g => this.toolbar1.insertBefore(g, this.moreBtn));\n\n // On small screens, skip the \"More\" split entirely — keep every tool in one\n // horizontally-scrollable row (how Google Docs / Notion handle mobile)\n // instead of wrapping into a cramped multi-row panel.\n if (typeof window !== 'undefined' && window.matchMedia &&\n window.matchMedia('(max-width: 640px)').matches) {\n this.moreBtn.style.display = 'none';\n this.toolbar2.style.display = 'none';\n this.toolbar2Visible = false;\n this._syncMoreButton();\n this._updateRoving();\n return;\n }\n\n const cs = getComputedStyle(this.toolbar1);\n const avail = this.toolbar1.clientWidth -\n (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0);\n if (avail <= 0) return; // not laid out yet; will reflow on resize\n\n let total = 0;\n this.flowGroups.forEach((g, i) => { total += g.offsetWidth + (i > 0 ? GAP : 0); });\n\n if (total <= avail) {\n // Everything fits — no overflow needed.\n this.moreBtn.style.display = 'none';\n this.toolbar2.style.display = 'none';\n this.toolbar2Visible = false;\n this._syncMoreButton();\n this._updateRoving();\n return;\n }\n\n // Overflow needed — keep groups that fit (reserving room for \"more\").\n const budget = avail - ((this.moreBtn.offsetWidth || 32) + GAP);\n let used = 0;\n let cut = this.flowGroups.length;\n for (let i = 0; i < this.flowGroups.length; i++) {\n const w = this.flowGroups[i].offsetWidth + (i > 0 ? GAP : 0);\n if (used + w > budget) { cut = i; break; }\n used += w;\n }\n if (cut < 1) cut = 1; // always keep at least one group visible\n\n for (let i = cut; i < this.flowGroups.length; i++) {\n this.toolbar2.appendChild(this.flowGroups[i]);\n }\n\n this.moreBtn.style.display = '';\n this.toolbar2.style.display = this.toolbar2Visible ? 'flex' : 'none';\n this._syncMoreButton();\n this._updateRoving();\n }\n\n /**\n * Sync the \"more\" button visual state with toolbar2 visibility.\n */\n _syncMoreButton() {\n const m = this.moreBtn;\n if (!m) return;\n m.setAttribute('aria-expanded', this.toolbar2Visible ? 'true' : 'false');\n if (this.toolbar2Visible) {\n m.classList.add('active');\n m.title = 'Hide more options';\n } else {\n m.classList.remove('active');\n m.title = 'More options';\n }\n }\n\n /**\n * Create toolbar element\n */\n createToolbar(className, toolbarItems) {\n const toolbar = document.createElement('div');\n toolbar.className = className;\n\n // Create button groups based on toolbar config\n if (Array.isArray(toolbarItems)) {\n toolbarItems.forEach(group => {\n if (group && group.group && Array.isArray(group.items)) {\n // Create group container\n const groupContainer = document.createElement('div');\n groupContainer.className = `toolbar-group toolbar-group-${group.group}`;\n \n // Add buttons to group\n group.items.forEach(item => {\n if (typeof item === 'string') {\n this.addButton(groupContainer, item);\n }\n });\n \n toolbar.appendChild(groupContainer);\n }\n });\n }\n\n return toolbar;\n }\n\n /**\n * Add button to toolbar\n */\n addButton(container, format) {\n // Special handling for more button\n if (format === 'more') {\n return this.addMoreButton(container);\n }\n\n // Custom buttons with dropdowns\n const customButtons = {\n 'heading': { text: 'Paragraph', width: '124px', title: 'Paragraph style', icon: 'heading' },\n 'font-family': { text: 'Font Family', width: '156px', title: 'Font', icon: 'font-family' },\n 'line-height': { text: 'Line Height', width: '116px', title: 'Line spacing', icon: 'line-height' },\n 'capitalization': { text: 'Capitalization', width: '146px', title: 'Letter case', icon: 'capitalization' },\n 'text-size': { text: 'Text Size', width: '116px', title: 'Font size', icon: 'text-size' }\n };\n\n if (customButtons[format]) {\n const config = customButtons[format];\n const customButton = createCustomButton(config.text, { width: config.width, icon: config.icon });\n customButton.dataset.command = format;\n customButton.classList.add('rich-editor-toolbar-btn', `${format}-btn`);\n customButton.title = config.title;\n customButton.setAttribute('aria-label', config.title);\n customButton.setAttribute('aria-haspopup', 'true');\n \n customButton.addEventListener('click', (e) => {\n e.preventDefault();\n this.emit('toolbar-click', { command: format, button: customButton });\n // Maintain editor focus after button click\n setTimeout(() => {\n this.editor.focus();\n }, 0);\n });\n\n this.buttons.set(format, customButton);\n container.appendChild(customButton);\n return customButton;\n }\n\n // Icon buttons with popups\n const iconButtons = {\n 'text-align': { icon: 'align-left', title: 'Align Left' },\n 'list': { icon: 'list', title: 'List' }\n };\n\n if (iconButtons[format]) {\n const config = iconButtons[format];\n const button = document.createElement('button');\n button.type = 'button';\n button.className = `rich-editor-toolbar-btn ${format}-btn`;\n button.dataset.command = format;\n button.title = config.title;\n button.setAttribute('aria-label', config.title);\n\n const svgContent = IconUtils.getIcon(config.icon);\n if (svgContent) {\n button.innerHTML = `<span class=\"icon\">${svgContent}</span>`;\n } else {\n button.textContent = format === 'text-align' ? '≡' : '•';\n }\n \n button.addEventListener('click', (e) => {\n e.preventDefault();\n this.emit('toolbar-click', { command: format, button: button });\n // Maintain editor focus after button click\n setTimeout(() => {\n this.editor.focus();\n }, 0);\n });\n\n this.buttons.set(format, button);\n container.appendChild(button);\n return button;\n }\n\n // Regular icon buttons\n const button = document.createElement('button');\n button.type = 'button';\n button.className = `rich-editor-toolbar-btn ${format}-btn`;\n button.dataset.command = format;\n \n // Add icon\n const iconElement = IconUtils.createIconElement(format, {\n width: '16px',\n height: '16px'\n });\n button.appendChild(iconElement);\n \n // Set title based on format\n const titles = {\n 'bold': 'Bold (Ctrl+B)',\n 'italic': 'Italic (Ctrl+I)',\n 'underline': 'Underline (Ctrl+U)',\n 'strike': 'Strikethrough',\n 'subscript': 'Subscript',\n 'superscript': 'Superscript',\n 'color': 'Text Color',\n 'background': 'Background Color',\n 'link': 'Insert/Edit Link',\n 'table': 'Insert Table',\n 'undo': 'Undo (Ctrl+Z)',\n 'redo': 'Redo (Ctrl+Y)',\n 'indent-increase': 'Increase Indent',\n 'indent-decrease': 'Decrease Indent',\n 'emoji': 'Insert Emoji',\n 'image': 'Insert Image',\n 'file': 'Attach File',\n 'video': 'Insert Video',\n 'tag': 'Insert Tag',\n 'horizontal-rule': 'Insert Horizontal Rule',\n 'clear-format': 'Clear Formatting',\n 'text-direction': 'Toggle Text Direction (LTR/RTL)',\n 'find': 'Find & Replace (Ctrl+F)',\n\n 'import': 'Import Files',\n 'code-view': 'Switch to HTML Editor',\n\n };\n \n button.title = titles[format] || format;\n button.setAttribute('aria-label', button.title);\n\n // Colour buttons get a swatch bar that reflects the colour at the caret.\n if (format === 'color' || format === 'background') {\n const swatch = document.createElement('span');\n swatch.className = 'rte-swatch';\n button.appendChild(swatch);\n }\n\n // Add fallback for code-view\n if (format === 'code-view') {\n setTimeout(() => {\n if (!iconElement.innerHTML.trim()) {\n iconElement.innerHTML = '&lt;/&gt;';\n iconElement.style.fontSize = '12px';\n iconElement.style.fontWeight = 'bold';\n }\n }, 1000);\n }\n \n button.addEventListener('click', (e) => {\n e.preventDefault();\n this.emit('toolbar-click', { command: format, button });\n // Maintain editor focus after button click\n setTimeout(() => {\n this.editor.focus();\n }, 0);\n });\n\n this.buttons.set(format, button);\n container.appendChild(button);\n return button;\n }\n\n /**\n * Add more button to toggle toolbar 2\n */\n addMoreButton(container) {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'rich-editor-toolbar-btn more-btn';\n button.dataset.command = 'more';\n \n const iconElement = IconUtils.createIconElement('more', {\n width: '16px',\n height: '16px'\n });\n button.appendChild(iconElement);\n button.title = 'More Options';\n button.setAttribute('aria-label', 'More Options');\n button.setAttribute('aria-expanded', 'false');\n\n button.addEventListener('click', (e) => {\n e.preventDefault();\n this.toggleToolbar2();\n // Maintain editor focus after button click\n setTimeout(() => {\n this.editor.focus();\n }, 0);\n });\n\n this.buttons.set('more', button);\n container.appendChild(button);\n return button;\n }\n\n /**\n * Toggle toolbar 2 visibility\n */\n toggleToolbar2() {\n // Nothing to toggle when there's no overflow.\n if (this.moreBtn && this.moreBtn.style.display === 'none') return;\n\n this.toolbar2Visible = !this.toolbar2Visible;\n this.toolbar2.style.display = this.toolbar2Visible ? 'flex' : 'none';\n this._syncMoreButton();\n this._updateRoving();\n }\n\n /**\n * Get toolbar container element\n */\n getContainer() {\n return this.container;\n }\n\n /**\n * Get button by command\n */\n getButton(command) {\n return this.buttons.get(command);\n }\n\n /**\n * Set button active state\n */\n setButtonActive(command, isActive) {\n const button = this.buttons.get(command);\n if (button && button.classList) {\n if (isActive) {\n button.classList.add('active');\n } else {\n button.classList.remove('active');\n }\n button.setAttribute('aria-pressed', isActive ? 'true' : 'false');\n }\n }\n\n /**\n * Set button disabled state\n */\n setButtonDisabled(command, isDisabled) {\n const button = this.buttons.get(command);\n if (button) {\n button.disabled = isDisabled;\n button.style.opacity = isDisabled ? '0.5' : '1';\n button.style.cursor = isDisabled ? 'not-allowed' : 'pointer';\n }\n }\n\n /**\n * Set button title\n */\n setButtonTitle(command, title) {\n const button = this.buttons.get(command);\n if (button) {\n button.title = title;\n }\n }\n\n /**\n * Check if toolbar 2 is visible\n */\n isToolbar2Visible() {\n return this.toolbar2Visible;\n }\n\n /**\n * Event system methods\n */\n on(event, callback) {\n if (!this.events.has(event)) {\n this.events.set(event, []);\n }\n this.events.get(event).push(callback);\n }\n\n emit(event, data) {\n const callbacks = this.events.get(event);\n if (callbacks) {\n callbacks.forEach(callback => {\n try {\n callback(data);\n } catch (error) {\n console.error(`Error in toolbar event ${event}:`, error);\n }\n });\n }\n }\n\n /**\n * Destroy toolbar\n */\n destroy() {\n if (this._ro) {\n this._ro.disconnect();\n this._ro = null;\n }\n if (this.container && this.container.parentNode) {\n this.container.parentNode.removeChild(this.container);\n }\n this.buttons.clear();\n this.events.clear();\n }\n}\n\nexport default Toolbar; ","import Module from '../core/module.js';\n\n/**\n * History Module - Handles undo/redo functionality\n * Extracted from FormatManager.js and ToolbarManager.js logic\n */\nclass History extends Module {\n static DEFAULTS = {\n delay: 1000, // Delay between history saves\n maxStack: 100, // Maximum number of undo states\n userOnly: false // Only save user-initiated changes\n };\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.stack = [];\n this.index = -1;\n this.lastSave = 0;\n this.savedSelection = null;\n \n this.init();\n }\n\n init() {\n this.setupEventListeners();\n this.saveState(); // Save initial state\n }\n\n /**\n * Setup event listeners for automatic history saving\n */\n setupEventListeners() {\n // Keep references so the listeners can be removed in destroy().\n this._onInput = () => {\n this.handleInput();\n };\n\n this._onKeydownSave = (e) => {\n // Save state before destructive operations\n if (e.key === 'Enter' || e.key === 'Backspace' || e.key === 'Delete') {\n this.saveState();\n }\n };\n\n this._onToolbarClick = (e) => {\n if (e.target.closest('.rich-editor-toolbar-btn')) {\n // Save state before applying format\n setTimeout(() => {\n this.saveState();\n }, 0);\n }\n };\n\n this._onUndoRedo = (e) => {\n if ((e.ctrlKey || e.metaKey) && !e.shiftKey && e.key === 'z') {\n e.preventDefault();\n this.undo();\n } else if (((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z') ||\n ((e.ctrlKey || e.metaKey) && e.key === 'y')) {\n e.preventDefault();\n this.redo();\n }\n };\n\n // Save state on input with debouncing\n this.editor.editor.addEventListener('input', this._onInput);\n // Save state on specific commands\n this.editor.editor.addEventListener('keydown', this._onKeydownSave);\n // Listen for toolbar clicks to save state before formatting\n this.editor.wrapper.addEventListener('click', this._onToolbarClick);\n // Handle undo/redo shortcuts - only when editor is focused\n this.editor.editor.addEventListener('keydown', this._onUndoRedo);\n\n // Listen for DOM changes to catch all formatting operations\n this.setupMutationObserver();\n }\n\n /**\n * Setup mutation observer to watch for DOM changes\n */\n setupMutationObserver() {\n this.mutationObserver = new MutationObserver((mutations) => {\n let shouldSave = false;\n \n for (const mutation of mutations) {\n // Check if the mutation is relevant (not just attribute changes on non-content elements)\n if (mutation.type === 'childList' || \n (mutation.type === 'attributes' && \n (mutation.target.nodeType === Node.TEXT_NODE || \n ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'PRE', 'UL', 'OL', 'LI', 'SPAN', 'STRONG', 'EM', 'U', 'S', 'SUB', 'SUP', 'A', 'IMG', 'VIDEO', 'TABLE', 'TR', 'TD', 'TH'].includes(mutation.target.tagName)))) {\n shouldSave = true;\n break;\n }\n }\n \n if (shouldSave) {\n // Debounce the save to avoid too many saves\n clearTimeout(this.mutationTimeout);\n this.mutationTimeout = setTimeout(() => {\n this.saveState();\n }, 100);\n }\n });\n\n // Start observing\n this.mutationObserver.observe(this.editor.editor, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class', 'href', 'src', 'alt', 'title']\n });\n }\n\n /**\n * Handle input event with debouncing\n */\n handleInput() {\n const now = Date.now();\n if (now - this.lastSave > this.options.delay) {\n this.saveState();\n this.lastSave = now;\n }\n }\n\n /**\n * Save current editor state\n */\n saveState() {\n const content = this.editor.getContent();\n const selection = this.saveSelection();\n \n // Don't save if content hasn't changed\n if (this.stack.length > 0 && this.stack[this.index]?.content === content) {\n return;\n }\n\n // Don't save if it's too soon after last save (debouncing)\n const now = Date.now();\n if (this.lastSave && now - this.lastSave < 50) {\n return;\n }\n\n // Remove any redo states if we're not at the end\n if (this.index < this.stack.length - 1) {\n this.stack.splice(this.index + 1);\n }\n\n // Add new state\n this.stack.push({\n content,\n selection,\n timestamp: now\n });\n\n // Limit stack size\n if (this.stack.length > this.options.maxStack) {\n this.stack.shift();\n } else {\n this.index++;\n }\n\n this.lastSave = now;\n }\n\n /**\n * Undo last change\n */\n undo() {\n if (!this.canUndo()) return false;\n\n this.index--;\n const state = this.stack[this.index];\n \n this.restoreState(state);\n this.onHistoryChange('undo');\n \n return true;\n }\n\n /**\n * Redo last undone change\n */\n redo() {\n if (!this.canRedo()) return false;\n\n this.index++;\n const state = this.stack[this.index];\n \n this.restoreState(state);\n this.onHistoryChange('redo');\n \n return true;\n }\n\n /**\n * Check if undo is possible\n */\n canUndo() {\n return this.index > 0;\n }\n\n /**\n * Check if redo is possible\n */\n canRedo() {\n return this.index < this.stack.length - 1;\n }\n\n /**\n * Restore editor state\n * @param {object} state - State to restore\n */\n restoreState(state) {\n if (!state) return;\n\n // Restore content\n this.editor.setContent(state.content);\n \n // Restore selection\n if (state.selection) {\n setTimeout(() => {\n this.restoreSelection(state.selection);\n }, 10);\n }\n }\n\n /**\n * Save current selection\n */\n saveSelection() {\n const selection = window.getSelection();\n if (!selection || !selection.rangeCount) return null;\n\n const range = selection.getRangeAt(0);\n const editorEl = this.editor.editor;\n \n // Calculate offset relative to editor\n const startOffset = this.getOffsetInEditor(range.startContainer, range.startOffset, editorEl);\n const endOffset = this.getOffsetInEditor(range.endContainer, range.endOffset, editorEl);\n \n return {\n startOffset,\n endOffset,\n collapsed: range.collapsed\n };\n }\n\n /**\n * Restore selection\n * @param {object} selectionState - Selection state to restore\n */\n restoreSelection(selectionState) {\n if (!selectionState) return;\n\n const editorEl = this.editor.editor;\n const range = document.createRange();\n const selection = window.getSelection();\n\n try {\n const startNode = this.getNodeAtOffset(editorEl, selectionState.startOffset);\n const endNode = this.getNodeAtOffset(editorEl, selectionState.endOffset);\n\n if (startNode && endNode) {\n range.setStart(startNode.node, startNode.offset);\n range.setEnd(endNode.node, endNode.offset);\n \n selection.removeAllRanges();\n selection.addRange(range);\n }\n } catch (error) {\n console.warn('Could not restore selection:', error);\n // Fallback: focus editor\n this.editor.focus();\n }\n }\n\n /**\n * Get offset of a position within editor\n * @param {Node} node - DOM node\n * @param {number} offset - Offset within node\n * @param {Element} root - Root element (editor)\n */\n getOffsetInEditor(node, offset, root) {\n let totalOffset = 0;\n const walker = document.createTreeWalker(\n root,\n NodeFilter.SHOW_TEXT,\n null,\n false\n );\n\n let currentNode;\n while (currentNode = walker.nextNode()) {\n if (currentNode === node) {\n return totalOffset + offset;\n }\n totalOffset += currentNode.textContent.length;\n }\n\n return totalOffset;\n }\n\n /**\n * Get node at specific offset within editor\n * @param {Element} root - Root element (editor)\n * @param {number} targetOffset - Target offset\n */\n getNodeAtOffset(root, targetOffset) {\n let currentOffset = 0;\n const walker = document.createTreeWalker(\n root,\n NodeFilter.SHOW_TEXT,\n null,\n false\n );\n\n let currentNode;\n while (currentNode = walker.nextNode()) {\n const nodeLength = currentNode.textContent.length;\n if (currentOffset + nodeLength >= targetOffset) {\n return {\n node: currentNode,\n offset: targetOffset - currentOffset\n };\n }\n currentOffset += nodeLength;\n }\n\n // Fallback: return last node\n return {\n node: root.lastChild || root,\n offset: 0\n };\n }\n\n /**\n * Clear history\n */\n clear() {\n this.stack = [];\n this.index = -1;\n this.saveState(); // Save current state as first entry\n }\n\n /**\n * Get current history state info\n */\n getState() {\n return {\n canUndo: this.canUndo(),\n canRedo: this.canRedo(),\n stackLength: this.stack.length,\n currentIndex: this.index\n };\n }\n\n /**\n * Called when history changes (undo/redo)\n * @param {string} action - 'undo' or 'redo'\n */\n onHistoryChange(action) {\n // Notify other modules about history change\n this.editor.modules.forEach(module => {\n if (module !== this && typeof module.onHistoryChange === 'function') {\n module.onHistoryChange(action, this.getState());\n }\n });\n\n // Trigger custom event\n const event = new CustomEvent('historychange', {\n detail: { action, state: this.getState() }\n });\n this.editor.editor.dispatchEvent(event);\n }\n\n /**\n * Force save current state (useful before major operations)\n */\n forceSave() {\n // Temporarily disable debouncing for force save\n const originalLastSave = this.lastSave;\n this.lastSave = 0;\n this.saveState();\n this.lastSave = originalLastSave;\n }\n\n /**\n * Save state before applying format (called by editor)\n */\n saveBeforeFormat() {\n this.forceSave();\n }\n\n /**\n * Destroy module\n */\n destroy() {\n // Remove event listeners\n if (this._onInput) {\n this.editor.editor.removeEventListener('input', this._onInput);\n this.editor.editor.removeEventListener('keydown', this._onKeydownSave);\n this.editor.editor.removeEventListener('keydown', this._onUndoRedo);\n this.editor.wrapper.removeEventListener('click', this._onToolbarClick);\n this._onInput = this._onKeydownSave = this._onUndoRedo = this._onToolbarClick = null;\n }\n\n // Disconnect mutation observer\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n this.mutationObserver = null;\n }\n\n // Clear timeout\n if (this.mutationTimeout) {\n clearTimeout(this.mutationTimeout);\n this.mutationTimeout = null;\n }\n\n this.stack = [];\n this.index = -1;\n this.savedSelection = null;\n }\n}\n\nexport default History; ","import Module from '../core/module.js';\nimport IconUtils from '../ui/icons.js';\nimport { execFormat, queryFormatState } from '../utils/exec-command.js';\n\n/**\n * Block Toolbar Module - Floating toolbar hiện lên khi select text hoặc ấn Enter\n */\nclass BlockToolbar extends Module {\n static DEFAULTS = {\n showOnSelection: true,\n showOnEnter: true,\n buttons: ['bold', 'italic', 'underline', 'strike', 'code']\n };\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.blockToolbar = null;\n this.isVisible = false;\n this.currentSelection = null; // Store current selection for scroll updates\n this.currentCursorPosition = null; // Store current cursor position for scroll updates\n this.originalTags = new Map(); // Store original tags before converting to code\n this.init();\n }\n\n init() {\n this.preloadIcons();\n this.createBlockToolbar();\n this.setupEventListeners();\n }\n\n async preloadIcons() {\n // Icons are now inline, no need to preload\n // This method is kept for backward compatibility\n }\n\n createBlockToolbar() {\n this.blockToolbar = document.createElement('div');\n this.blockToolbar.className = 'block-toolbar';\n \n // Create toolbar container\n const toolbarContainer = document.createElement('div');\n toolbarContainer.className = 'block-toolbar-container';\n \n // Button set is configurable via options.buttons (array of command names);\n // defaults to the inline formatting set.\n const meta = {\n bold: { icon: 'bold', title: 'Bold (Ctrl+B)' },\n italic: { icon: 'italic', title: 'Italic (Ctrl+I)' },\n underline: { icon: 'underline', title: 'Underline (Ctrl+U)' },\n strike: { icon: 'strike', title: 'Strikethrough' },\n code: { icon: 'code', title: 'Code' },\n 'font-family': { icon: 'font-family', title: 'Font Family' },\n link: { icon: 'link', title: 'Insert link' },\n color: { icon: 'color', title: 'Text color' },\n background: { icon: 'background', title: 'Background color' }\n };\n const names = Array.isArray(this.options.buttons) && this.options.buttons.length\n ? this.options.buttons\n : ['bold', 'italic', 'underline', 'strike', 'code', 'font-family'];\n const buttons = names.map(cmd => ({ cmd, icon: (meta[cmd] && meta[cmd].icon) || cmd, title: (meta[cmd] && meta[cmd].title) || cmd }));\n buttons.forEach(({ cmd, icon, title }) => {\n const button = document.createElement('button');\n button.className = 'block-toolbar-btn';\n button.title = title;\n button.dataset.command = cmd;\n const iconElement = IconUtils.createIconElement(icon, { width: '16px', height: '16px' });\n button.appendChild(iconElement);\n button.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.handleCommand(cmd, button);\n });\n toolbarContainer.appendChild(button);\n });\n \n // Create arrow element\n const arrow = document.createElement('div');\n arrow.className = 'block-toolbar-arrow';\n \n // Add container and arrow to toolbar\n this.blockToolbar.appendChild(toolbarContainer);\n this.blockToolbar.appendChild(arrow);\n \n this.editor.wrapper.appendChild(this.blockToolbar);\n }\n\n setupEventListeners() {\n // Keep references so listeners can be removed in destroy() (prevents leaks\n // on document/window when multiple editors are created/destroyed).\n this._onEditorMouseup = () => {\n setTimeout(() => this.handleSelectionChange(), 0);\n };\n\n this._onEditorKeydown = (e) => {\n // Only react to Enter; do NOT hide on every keystroke (that broke typing UX).\n if (e.key === 'Enter' && !e.shiftKey) {\n requestAnimationFrame(() => {\n setTimeout(() => this.showAtCursorAfterEnter(), 10);\n });\n }\n };\n\n this._onDocMousedown = (e) => {\n // Don't hide if clicking on font-family popup or its items\n if (e.target.closest('.font-family-select-popup') || e.target.closest('.custom-select-popup')) {\n return;\n }\n if (!e.target.closest('.block-toolbar') && !e.target.closest('.rich-editor-area')) {\n this.hide();\n }\n };\n\n this._onWindowScroll = () => {\n if (this.isVisible) this.updateToolbarPosition();\n };\n\n this._onEditorScroll = () => {\n if (this.isVisible) this.updateToolbarPosition();\n };\n\n this._onEditorKeyup = (e) => {\n // Shift + Enter hides the toolbar\n if (e.key === 'Enter' && e.shiftKey) {\n this.hide();\n return;\n }\n if (this.isVisible) {\n this.updateButtonStates();\n } else {\n this.handleSelectionChange();\n }\n };\n\n if (this.options.showOnSelection) {\n this.editor.editor.addEventListener('mouseup', this._onEditorMouseup);\n }\n if (this.options.showOnEnter) {\n this.editor.editor.addEventListener('keydown', this._onEditorKeydown);\n }\n document.addEventListener('mousedown', this._onDocMousedown);\n window.addEventListener('scroll', this._onWindowScroll);\n this.editor.editor.addEventListener('scroll', this._onEditorScroll);\n this.editor.editor.addEventListener('keyup', this._onEditorKeyup);\n }\n\n handleSelectionChange() {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return this.hide();\n const range = selection.getRangeAt(0);\n const isInEditableArea = this.editor.isSelectionInEditableArea ?\n this.editor.isSelectionInEditableArea(selection) :\n this.editor.editor.contains(range.commonAncestorContainer);\n if (!isInEditableArea) return this.hide();\n if (!range.collapsed && selection.toString().trim().length > 0) {\n this.showAtSelection(selection);\n } else {\n this.hide();\n }\n }\n\n showAtSelection(selection) {\n if (!selection || selection.rangeCount === 0) return;\n \n // Store current selection for scroll updates\n this.currentSelection = selection;\n this.currentCursorPosition = null;\n \n const range = selection.getRangeAt(0);\n const rect = range.getBoundingClientRect();\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n this.showAt(\n rect.left + rect.width / 2 - editorRect.left + scrollLeft,\n rect.top - editorRect.top + scrollTop - 10\n );\n }\n\n showAtCursorAfterEnter() {\n this.editor.focus();\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return;\n const range = selection.getRangeAt(0);\n const isInEditableArea = this.editor.isSelectionInEditableArea ?\n this.editor.isSelectionInEditableArea(selection) :\n this.editor.editor.contains(range.commonAncestorContainer);\n if (!isInEditableArea) return;\n \n this.ensureCursorAtEndOfLine(range);\n \n // Store current cursor position for scroll updates\n this.currentSelection = selection;\n this.currentCursorPosition = this.getCursorPositionAfterEnter();\n \n const rect = this.currentCursorPosition;\n if (!rect) return;\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n this.showAt(\n rect.left - editorRect.left + scrollLeft,\n rect.top - editorRect.top + scrollTop - 10\n );\n }\n\n ensureCursorAtEndOfLine(range) {\n if (!range.collapsed) return;\n const selection = window.getSelection();\n const currentNode = range.startContainer;\n if (currentNode.nodeType === Node.TEXT_NODE) {\n const textLength = currentNode.textContent.length;\n if (range.startOffset < textLength) {\n range.setStart(currentNode, textLength);\n range.setEnd(currentNode, textLength);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n } else if (currentNode.nodeType === Node.ELEMENT_NODE) {\n const walker = document.createTreeWalker(currentNode, NodeFilter.SHOW_TEXT, null, false);\n let lastTextNode = null, node;\n while (node = walker.nextNode()) lastTextNode = node;\n if (lastTextNode) {\n const textLength = lastTextNode.textContent.length;\n range.setStart(lastTextNode, textLength);\n range.setEnd(lastTextNode, textLength);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n }\n }\n\n getCursorPositionAfterEnter() {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return null;\n const range = selection.getRangeAt(0);\n const marker = document.createElement('span');\n marker.innerHTML = '&#8203;';\n marker.style.position = 'absolute';\n marker.style.visibility = 'hidden';\n marker.style.pointerEvents = 'none';\n range.insertNode(marker);\n const rect = marker.getBoundingClientRect();\n if (marker.parentNode) marker.parentNode.removeChild(marker);\n const newRange = document.createRange();\n newRange.setStart(range.startContainer, range.startOffset);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n return rect;\n }\n\n showAtCursor() {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return;\n const range = selection.getRangeAt(0);\n const isInEditableArea = this.editor.isSelectionInEditableArea ?\n this.editor.isSelectionInEditableArea(selection) :\n this.editor.editor.contains(range.commonAncestorContainer);\n if (!isInEditableArea) return;\n let rect;\n if (range.collapsed) {\n const span = document.createElement('span');\n span.innerHTML = '&#8203;';\n span.style.position = 'absolute';\n span.style.visibility = 'hidden';\n span.style.pointerEvents = 'none';\n range.insertNode(span);\n rect = span.getBoundingClientRect();\n if (span.parentNode) span.parentNode.removeChild(span);\n const newRange = document.createRange();\n newRange.setStart(range.startContainer, range.startOffset);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } else {\n rect = range.getBoundingClientRect();\n }\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n this.showAt(\n rect.left - editorRect.left + scrollLeft,\n rect.top - editorRect.top + scrollTop - 10\n );\n }\n\n showAt(x, y) {\n if (!this.blockToolbar) return;\n this.blockToolbar.classList.add('visible');\n this.isVisible = true;\n this.ensureToolbarInViewport(x,y);\n this.updateButtonStates();\n }\n\n ensureToolbarInViewport(x,y) {\n if (!this.blockToolbar) return;\n \n // Lấy thông tin về editor-area\n const editorArea = this.editor.editor;\n const editorRect = editorArea.getBoundingClientRect();\n const toolbarRect = this.blockToolbar.getBoundingClientRect();\n const toolbarContainer = this.editor.wrapper.querySelector('.rich-editor-toolbar-container');\n const toolbarRect2 = toolbarContainer ? toolbarContainer.getBoundingClientRect() : null;\n \n \n let left = x - this.blockToolbar.offsetWidth/2;\n let top = editorRect.y + y -(toolbarRect2.height) - editorArea.scrollTop - (editorRect.y + window.scrollY) +toolbarContainer.offsetHeight-49;\n\n let arrowLeft = '50%';\n let arrowDirection = 'down'; // mũi tên hướng xuống\n \n // Trường hợp 1: Vượt quá lề trái của editor\n if (left < 0) {\n left =(x - (this.blockToolbar.offsetWidth * (10/100)));\n if(left < 0) left = 0;\n arrowLeft = '10%'; // Mũi tên ở 10%\n }\n // Trường hợp 2: Vượt quá lề phải của editor\n if (left + this.blockToolbar.offsetWidth > (this.editor.wrapper.offsetWidth - 2)) {\n left = x - this.blockToolbar.offsetWidth*0.9;\n arrowLeft = '90%'; // Mũi tên ở 90%\n }\n \n // Trường hợp 3: Vượt quá lề trên của editor\n\n if (top < toolbarRect2.height) {\n top = editorRect.y + y -(toolbarRect2.height) - editorArea.scrollTop +100 - (editorRect.y + window.scrollY)+toolbarContainer.offsetHeight-49;\n arrowDirection = 'up'; // Mũi tên hướng lên\n if(top < toolbarRect2.height ){\n this.hide();\n return;\n }\n }\n if(top > editorRect.height){\n this.hide();\n return;\n }\n // Cập nhật vị trí mũi tên\n const arrow = this.blockToolbar.querySelector('.block-toolbar-arrow');\n if (arrow) {\n arrow.style.left = arrowLeft;\n \n if (arrowDirection === 'up') {\n // Mũi tên hướng lên\n arrow.style.bottom = 'auto';\n arrow.style.top = '-8px';\n arrow.style.borderTop = 'none';\n arrow.style.borderBottom = '8px solid #fff';\n arrow.style.borderLeft = '6px solid transparent';\n arrow.style.borderRight = '6px solid transparent';\n } else {\n // Mũi tên hướng xuống (mặc định)\n arrow.style.top = 'auto';\n arrow.style.bottom = '-8px';\n arrow.style.borderBottom = 'none';\n arrow.style.borderTop = '8px solid #fff';\n arrow.style.borderLeft = '6px solid transparent';\n arrow.style.borderRight = '6px solid transparent';\n }\n }\n // Áp dụng vị trí cuối cùng\n this.blockToolbar.style.left = left + 'px';\n this.blockToolbar.style.top = top + 'px';\n }\n\n /**\n * Update toolbar position based on current selection or cursor position\n */\n updateToolbarPosition() {\n if (!this.isVisible) return;\n\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) {\n this.hide();\n return;\n }\n\n const range = selection.getRangeAt(0);\n const isInEditableArea = this.editor.isSelectionInEditableArea ?\n this.editor.isSelectionInEditableArea(selection) :\n this.editor.editor.contains(range.commonAncestorContainer);\n \n if (!isInEditableArea) {\n this.hide();\n return;\n }\n\n let rect;\n \n if (range.collapsed) {\n // For cursor position, get current cursor rect\n const span = document.createElement('span');\n span.innerHTML = '&#8203;';\n span.style.position = 'absolute';\n span.style.visibility = 'hidden';\n span.style.pointerEvents = 'none';\n \n try {\n range.insertNode(span);\n rect = span.getBoundingClientRect();\n if (span.parentNode) span.parentNode.removeChild(span);\n \n // Restore range\n const newRange = document.createRange();\n newRange.setStart(range.startContainer, range.startOffset);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n } catch (e) {\n // If insertion fails, hide toolbar\n this.hide();\n return;\n }\n } else {\n // For selection, use selection rect\n rect = range.getBoundingClientRect();\n }\n\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n \n let x, y;\n if (range.collapsed) {\n x = rect.left - editorRect.left + scrollLeft;\n y = rect.top - editorRect.top + scrollTop - 10;\n } else {\n x = rect.left + rect.width / 2 - editorRect.left + scrollLeft;\n y = rect.top - editorRect.top + scrollTop - 10;\n }\n \n this.updateToolbarAt(x, y);\n \n // Update font-family popup position if it's visible\n const fontFamilyFormat = this.editor.registry.get('formats/font-family');\n if (fontFamilyFormat && fontFamilyFormat.selectInstance && fontFamilyFormat.selectInstance.isVisible) {\n fontFamilyFormat.selectInstance.updatePosition();\n }\n }\n\n /**\n * Update toolbar position at specific coordinates\n */\n updateToolbarAt(x, y) {\n if (!this.blockToolbar) return;\n \n this.ensureToolbarInViewport(x, y);\n this.updateButtonStates();\n }\n\n hide() {\n if (!this.blockToolbar || !this.isVisible) return;\n this.blockToolbar.classList.remove('visible');\n this.isVisible = false;\n // Clear stored positions\n this.currentSelection = null;\n this.currentCursorPosition = null;\n \n // Hide any open font-family popup when block toolbar is hidden\n const fontFamilyFormat = this.editor.registry.get('formats/font-family');\n if (fontFamilyFormat && fontFamilyFormat.selectInstance) {\n fontFamilyFormat.selectInstance.hide();\n }\n }\n\n handleCommand(command, button) {\n const selection = window.getSelection();\n const isInEditableArea = this.editor.isSelectionInEditableArea ?\n this.editor.isSelectionInEditableArea(selection) : true;\n if (!isInEditableArea) {\n this.hide();\n return;\n }\n \n // Special handling for font-family command\n if (command === 'font-family') {\n const fontFamilyFormat = this.editor.registry.get('formats/font-family');\n if (fontFamilyFormat) {\n const format = new fontFamilyFormat();\n format.toggle(button); // Pass the button as anchor\n this.updateButtonState(command, button);\n this.editor.focus();\n return;\n }\n }\n \n // Special handling for code command to use PRE tag from heading format\n if (command === 'code') {\n const headingFormat = this.editor.registry.get('formats/heading');\n if (headingFormat) {\n const heading = new headingFormat();\n const currentTag = heading.getCurrentTag();\n \n // If current tag is PRE, convert back to original tag or P\n // If current tag is not PRE, convert to PRE (code format)\n if (currentTag === 'PRE') {\n // Get the selection to find the block element\n const selection = window.getSelection();\n if (selection && selection.rangeCount) {\n const range = selection.getRangeAt(0);\n const block = this.getBlockElement(range.startContainer);\n \n if (block) {\n // Get original tag for this block, default to P\n const originalTag = this.originalTags.get(block) || 'P';\n heading.apply(originalTag); \n this.originalTags.delete(block); // Clean up\n } else {\n heading.apply('P');\n }\n } else {\n heading.apply('P');\n }\n } else {\n // Store original tag before converting to PRE\n const selection = window.getSelection();\n if (selection && selection.rangeCount) {\n const range = selection.getRangeAt(0);\n const block = this.getBlockElement(range.startContainer);\n \n if (block) {\n this.originalTags.set(block, currentTag || 'P');\n }\n }\n \n heading.apply('PRE');\n }\n \n this.updateButtonState(command, button);\n this.editor.focus();\n return;\n }\n }\n\n const formatClass = this.editor.registry.get(`formats/${command}`);\n if (formatClass) {\n const format = new formatClass();\n if (typeof format.toggle === 'function') format.toggle();\n else if (typeof format.apply === 'function') format.apply();\n } else {\n execFormat(command);\n }\n this.updateButtonState(command, button);\n this.editor.focus();\n }\n\n updateButtonStates() {\n if (!this.blockToolbar) return;\n const buttons = this.blockToolbar.querySelectorAll('.block-toolbar-btn');\n buttons.forEach(button => {\n const command = button.dataset.command;\n this.updateButtonState(command, button);\n });\n }\n\n updateButtonState(command, button) {\n if (!button) return;\n let isActive = false;\n if (command === 'font-family') {\n const fontFamilyFormat = this.editor.registry.get('formats/font-family');\n if (fontFamilyFormat) {\n const format = new fontFamilyFormat();\n isActive = format.isActive();\n }\n } else if (command === 'code') {\n // Check if current block is PRE tag\n const headingFormat = this.editor.registry.get('formats/heading');\n if (headingFormat) {\n const heading = new headingFormat();\n const currentTag = heading.getCurrentTag();\n isActive = currentTag === 'PRE';\n }\n } else if (command === 'strike') {\n const formatClass = this.editor.registry.get(`formats/${command}`);\n if (formatClass) {\n const format = new formatClass();\n isActive = format.isActive();\n }\n } else {\n isActive = queryFormatState(command);\n }\n if (isActive) button.classList.add('active');\n else button.classList.remove('active');\n }\n\n /**\n * Get block element from a node\n * @param {Node} node - Node to find block element for\n * @returns {Element|null} Block element or null\n */\n getBlockElement(node) {\n if (!node) return null;\n \n // If node is an element and is a block, return it\n if (node.nodeType === Node.ELEMENT_NODE) {\n const blockTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'PRE', 'BLOCKQUOTE', 'DIV'];\n if (blockTags.includes(node.tagName)) {\n return node;\n }\n }\n \n // Walk up the DOM tree to find block element\n let current = node;\n while (current && current !== this.editor.editor) {\n if (current.nodeType === Node.ELEMENT_NODE) {\n const blockTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'PRE', 'BLOCKQUOTE', 'DIV'];\n if (blockTags.includes(current.tagName)) {\n return current;\n }\n }\n current = current.parentNode;\n }\n \n return null;\n }\n\n destroy() {\n // Remove event listeners (document/window ones would otherwise leak)\n if (this._onDocMousedown) {\n document.removeEventListener('mousedown', this._onDocMousedown);\n window.removeEventListener('scroll', this._onWindowScroll);\n this.editor.editor.removeEventListener('mouseup', this._onEditorMouseup);\n this.editor.editor.removeEventListener('keydown', this._onEditorKeydown);\n this.editor.editor.removeEventListener('scroll', this._onEditorScroll);\n this.editor.editor.removeEventListener('keyup', this._onEditorKeyup);\n this._onDocMousedown = this._onWindowScroll = this._onEditorMouseup = null;\n this._onEditorKeydown = this._onEditorScroll = this._onEditorKeyup = null;\n }\n\n if (this.blockToolbar && this.blockToolbar.parentNode) {\n this.blockToolbar.parentNode.removeChild(this.blockToolbar);\n }\n this.blockToolbar = null;\n this.isVisible = false;\n this.originalTags.clear(); // Clean up stored tags\n }\n}\n\nexport default BlockToolbar; ","import Module from '../core/module.js';\nimport IconUtils from '../ui/icons.js';\n\n/**\n * Table Toolbar Module - Floating toolbar hiện lên khi click vào table\n */\nclass TableToolbar extends Module {\n static DEFAULTS = {\n fadeDelay: 3000, // Auto hide after 3 seconds of inactivity\n buttons: ['tableProfile', 'deleteTable', 'insertRowAbove', 'insertRowBelow', 'deleteRow', 'insertColRight', 'insertColLeft', 'deleteCol']\n };\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.tableToolbar = null;\n this.currentTable = null;\n this.currentCell = null;\n this.hideTimeout = null;\n this.isVisible = false;\n \n this.init();\n }\n\n async init() {\n await this.createTableToolbar();\n this.setupEventListeners();\n }\n\n\n\n /**\n * Tạo table toolbar element\n */\n async createTableToolbar() {\n this.tableToolbar = document.createElement('div');\n this.tableToolbar.className = 'table-toolbar';\n \n // Create toolbar container\n const toolbarContainer = document.createElement('div');\n toolbarContainer.className = 'table-toolbar-container';\n \n // Define button groups\n const buttonGroups = [\n {\n name: 'table-actions',\n buttons: [\n { cmd: 'tableProfile', icon: 'icon-table-profile', title: 'Table Profile' },\n { cmd: 'deleteTable', icon: 'icon-delete-table', title: 'Delete Table' }\n ]\n },\n {\n name: 'row-actions',\n buttons: [\n { cmd: 'insertRowAbove', icon: 'icon-add-row-above', title: 'Add Row Above' },\n { cmd: 'insertRowBelow', icon: 'icon-add-row-below', title: 'Add Row Below' },\n { cmd: 'deleteRow', icon: 'icon-delete-row', title: 'Delete Selected Row' }\n ]\n },\n {\n name: 'col-actions',\n buttons: [\n { cmd: 'insertColRight', icon: 'icon-add-col-right', title: 'Add Column Right' },\n { cmd: 'insertColLeft', icon: 'icon-add-col-left', title: 'Add Column Left' },\n { cmd: 'deleteCol', icon: 'icon-delete-col', title: 'Delete Selected Column' }\n ]\n }\n ];\n\n // Create groups\n for (const group of buttonGroups) {\n const groupDiv = document.createElement('div');\n groupDiv.className = `table-toolbar-group ${group.name}`;\n\n // Create buttons in this group\n for (const { cmd, icon, title } of group.buttons) {\n const button = document.createElement('button');\n button.className = 'table-toolbar-btn';\n button.title = title;\n button.dataset.command = cmd;\n\n // Load and set SVG icon using IconUtils\n const svgContent = IconUtils.getIcon(icon.replace('icon-', ''));\n if (svgContent) {\n button.innerHTML = svgContent;\n }\n\n button.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.handleCommand(cmd, button);\n });\n\n groupDiv.appendChild(button);\n }\n\n toolbarContainer.appendChild(groupDiv);\n }\n\n // Create arrow element\n const arrow = document.createElement('div');\n arrow.className = 'table-toolbar-arrow';\n \n // Add container and arrow to toolbar\n this.tableToolbar.appendChild(toolbarContainer);\n this.tableToolbar.appendChild(arrow);\n\n // Add to editor wrapper\n this.editor.wrapper.appendChild(this.tableToolbar);\n }\n\n /**\n * Setup event listeners\n */\n setupEventListeners() {\n // Keep references so listeners can be removed in destroy().\n this._onEditorClick = (e) => {\n const clickedCell = e.target.closest('td, th');\n const clickedTable = e.target.closest('table');\n\n if (clickedTable && clickedCell) {\n // Check if the clicked table is within the editable area\n const isInEditableArea = this.editor.isNodeInEditableArea ?\n this.editor.isNodeInEditableArea(clickedTable) : true;\n\n if (!isInEditableArea) {\n this.hide();\n return;\n }\n\n this.currentTable = clickedTable;\n this.currentCell = clickedCell;\n this.showAtTable(clickedTable);\n } else {\n this.hide();\n }\n };\n\n this._onDocMousedown = (e) => {\n if (!e.target.closest('.table-toolbar') && !e.target.closest('table')) {\n this.hide();\n }\n };\n\n this._onWindowScroll = () => {\n if (this.isVisible && this.currentTable) {\n this.updateToolbarPosition();\n }\n };\n\n this._onEditorScroll = () => {\n if (this.isVisible && this.currentTable) {\n this.updateToolbarPosition();\n }\n };\n\n // Listen for clicks on table cells\n this.editor.editor.addEventListener('click', this._onEditorClick);\n // Hide on outside click\n document.addEventListener('mousedown', this._onDocMousedown);\n // Track position on scroll instead of hiding\n window.addEventListener('scroll', this._onWindowScroll);\n this.editor.editor.addEventListener('scroll', this._onEditorScroll);\n\n // Update when selection changes within table\n this._onEditorKeyup = (e) => {\n if (this.isVisible && this.currentTable) {\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n const currentCell = selection.getRangeAt(0).startContainer;\n const tableCell = currentCell.nodeType === Node.TEXT_NODE \n ? currentCell.parentElement.closest('td, th')\n : currentCell.closest('td, th');\n \n if (tableCell && tableCell !== this.currentCell) {\n // Verify the table cell is still in editable area\n const isInEditableArea = this.editor.isNodeInEditableArea ? \n this.editor.isNodeInEditableArea(tableCell) : true;\n \n if (!isInEditableArea) {\n this.hide();\n return;\n }\n \n this.currentCell = tableCell;\n }\n }\n }\n };\n this.editor.editor.addEventListener('keyup', this._onEditorKeyup);\n }\n\n /**\n * Update toolbar position based on current table\n */\n updateToolbarPosition() {\n if (!this.isVisible || !this.currentTable) return;\n\n // Check if table is still in DOM and in editable area\n if (!document.body.contains(this.currentTable)) {\n this.hide();\n return;\n }\n\n const isInEditableArea = this.editor.isNodeInEditableArea ? \n this.editor.isNodeInEditableArea(this.currentTable) : true;\n \n if (!isInEditableArea) {\n this.hide();\n return;\n }\n\n // Update position based on current table position\n const rect = this.currentTable.getBoundingClientRect();\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n \n this.updateToolbarAt(\n rect.left - editorRect.left + scrollLeft,\n rect.top - editorRect.top + scrollTop,\n rect.width,\n rect.height\n );\n }\n\n /**\n * Update toolbar position at specific coordinates\n */\n updateToolbarAt(x, y, width, height) {\n if (!this.tableToolbar) return;\n \n this.ensureToolbarInViewport(x, y, width, height);\n }\n\n /**\n * Show toolbar at table position\n */\n showAtTable(table) {\n if (!table || !this.tableToolbar) return;\n\n const rect = table.getBoundingClientRect();\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop;\n const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;\n this.showAt(\n rect.left - editorRect.left + scrollLeft,\n rect.top - editorRect.top + scrollTop,\n rect.width,\n rect.height\n );\n }\n\n /**\n * Show toolbar at specific position\n */\n showAt(x, y, width, height) {\n if (!this.tableToolbar) return;\n\n this.tableToolbar.classList.add('visible');\n this.isVisible = true;\n\n this.ensureToolbarInViewport(x, y, width, height);\n this.clearHideTimeout();\n }\n\n /**\n * Ensure toolbar stays within viewport and editor bounds\n */\n ensureToolbarInViewport(x, y, width, height) {\n if (!this.tableToolbar) return;\n \n // Lấy thông tin về editor-area\n const editorArea = this.editor.editor;\n const editorRect = editorArea.getBoundingClientRect();\n const toolbarRect = this.tableToolbar.getBoundingClientRect();\n const toolbarContainer = this.editor.wrapper.querySelector('.rich-editor-toolbar-container');\n const toolbarRect2 = toolbarContainer ? toolbarContainer.getBoundingClientRect() : null;\n let left = x+width/2 - this.tableToolbar.offsetWidth/2;\n let top = y - 60- document.documentElement.scrollTop;\n let arrowLeft = '50%';\n let arrowDirection = 'down'; // mũi tên hướng xuống\n \n // Trường hợp 1: Vượt quá lề trái của editor\n if (left < 0) {\n left = (x - (this.tableToolbar.offsetWidth * (10/100)));\n if(left < 0) left = 0;\n arrowLeft = '10%'; // Mũi tên ở 10%\n }\n \n // Trường hợp 2: Vượt quá lề phải của editor\n if (left + this.tableToolbar.offsetWidth > (this.editor.wrapper.offsetWidth - 2)) {\n left = x - this.tableToolbar.offsetWidth*0.9;\n arrowLeft = '90%'; // Mũi tên ở 90%\n }\n \n // Trường hợp 3: Vượt quá lề trên của editor\n if (top < (toolbarRect2 ? toolbarRect2.height : 48)) {\n top = y + height +10 - document.documentElement.scrollTop;\n arrowDirection = 'up'; // Mũi tên hướng lên\n if(top < (toolbarRect2 ? toolbarRect2.height : 48)){\n this.hide();\n return;\n }\n }\n if(top > editorRect.height){\n this.hide();\n return;\n }\n // Cập nhật vị trí mũi tên (nếu có)\n const arrow = this.tableToolbar.querySelector('.table-toolbar-arrow');\n if (arrow) {\n arrow.style.left = arrowLeft;\n \n if (arrowDirection === 'up') {\n // Mũi tên hướng lên\n arrow.style.bottom = 'auto';\n arrow.style.top = '-8px';\n arrow.style.borderTop = 'none';\n arrow.style.borderBottom = '8px solid #fff';\n arrow.style.borderLeft = '6px solid transparent';\n arrow.style.borderRight = '6px solid transparent';\n } else {\n // Mũi tên hướng xuống (mặc định)\n arrow.style.top = 'auto';\n arrow.style.bottom = '-8px';\n arrow.style.borderBottom = 'none';\n arrow.style.borderTop = '8px solid #fff';\n arrow.style.borderLeft = '6px solid transparent';\n arrow.style.borderRight = '6px solid transparent';\n }\n }\n // Áp dụng vị trí cuối cùng\n this.tableToolbar.style.left = left + 'px';\n this.tableToolbar.style.top = top + 'px';\n }\n\n /**\n * Hide toolbar\n */\n hide() {\n if (!this.tableToolbar || !this.isVisible) return;\n\n this.tableToolbar.classList.remove('visible');\n this.isVisible = false;\n this.currentTable = null;\n this.currentCell = null;\n this.clearHideTimeout();\n \n }\n\n /**\n * Clear hide timeout\n */\n clearHideTimeout() {\n if (this.hideTimeout) {\n clearTimeout(this.hideTimeout);\n this.hideTimeout = null;\n }\n }\n\n /**\n * Handle command execution - only if table is in editable area\n */\n handleCommand(command, button) {\n\n if (!this.currentTable || !this.currentCell) {\n return;\n }\n\n // Double check that the table is still in editable area before executing command\n const isInEditableArea = this.editor.isNodeInEditableArea ? \n this.editor.isNodeInEditableArea(this.currentTable) : true;\n \n if (!isInEditableArea) {\n this.hide();\n return;\n }\n\n switch (command) {\n case 'tableProfile':\n this.showTableProfile();\n break;\n case 'deleteTable':\n this.deleteTable();\n break;\n case 'insertRowAbove':\n this.insertRowAbove();\n break;\n case 'insertRowBelow':\n this.insertRowBelow();\n break;\n case 'deleteRow':\n this.deleteRow();\n break;\n case 'insertColRight':\n this.insertColumnRight();\n break;\n case 'insertColLeft':\n this.insertColumnLeft();\n break;\n case 'deleteCol':\n this.deleteColumn();\n break;\n }\n\n this.editor.focus();\n }\n\n /**\n * Insert row above current cell\n */\n insertRowAbove() {\n const currentRow = this.currentCell.parentElement;\n const newRow = this.createNewRow(currentRow.cells.length);\n currentRow.parentElement.insertBefore(newRow, currentRow);\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n }\n\n /**\n * Insert row below current cell\n */\n insertRowBelow() {\n const currentRow = this.currentCell.parentElement;\n const newRow = this.createNewRow(currentRow.cells.length);\n \n if (currentRow.nextElementSibling) {\n currentRow.parentElement.insertBefore(newRow, currentRow.nextElementSibling);\n } else {\n currentRow.parentElement.appendChild(newRow);\n }\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n }\n\n /**\n * Insert column left of current cell\n */\n insertColumnLeft() {\n const cellIndex = Array.from(this.currentCell.parentElement.children).indexOf(this.currentCell);\n const tbody = this.currentTable.querySelector('tbody') || this.currentTable;\n const rows = tbody.querySelectorAll('tr');\n \n rows.forEach(row => {\n const newCell = this.createNewCell();\n const targetCell = row.children[cellIndex];\n if (targetCell) {\n row.insertBefore(newCell, targetCell);\n } else {\n row.appendChild(newCell);\n }\n });\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n }\n\n /**\n * Insert column right of current cell\n */\n insertColumnRight() {\n const cellIndex = Array.from(this.currentCell.parentElement.children).indexOf(this.currentCell);\n const tbody = this.currentTable.querySelector('tbody') || this.currentTable;\n const rows = tbody.querySelectorAll('tr');\n \n rows.forEach(row => {\n const newCell = this.createNewCell();\n const targetCell = row.children[cellIndex + 1];\n if (targetCell) {\n row.insertBefore(newCell, targetCell);\n } else {\n row.appendChild(newCell);\n }\n });\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n }\n\n /**\n * Delete current row\n */\n deleteRow() {\n const currentRow = this.currentCell.parentElement;\n const tbody = currentRow.parentElement;\n \n // Don't delete if it's the only row\n if (tbody.children.length <= 1) {\n return;\n }\n \n currentRow.remove();\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n \n this.hide();\n }\n\n /**\n * Delete current column\n */\n deleteColumn() {\n const cellIndex = Array.from(this.currentCell.parentElement.children).indexOf(this.currentCell);\n const tbody = this.currentTable.querySelector('tbody') || this.currentTable;\n const rows = tbody.querySelectorAll('tr');\n \n // Don't delete if it's the only column\n if (this.currentCell.parentElement.children.length <= 1) {\n console.warn('Cannot delete the only column in table');\n return;\n }\n \n rows.forEach(row => {\n if (row.children[cellIndex]) {\n row.children[cellIndex].remove();\n }\n });\n \n // Update resize handles if they exist\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.checkAndUpdateHandles();\n }\n \n this.hide();\n }\n\n /**\n * Show table profile/properties\n */\n showTableProfile() {\n if (!this.currentTable) return;\n \n const rows = this.currentTable.querySelectorAll('tr').length;\n const cols = this.currentTable.querySelector('tr') ? \n this.currentTable.querySelector('tr').querySelectorAll('td, th').length : 0;\n \n const tableInfo = {\n rows: rows,\n columns: cols,\n totalCells: rows * cols,\n tableWidth: this.currentTable.offsetWidth,\n tableHeight: this.currentTable.offsetHeight\n };\n \n // You can customize this to show a modal or popup with table information\n alert(`Table Profile:\\n` +\n `Rows: ${tableInfo.rows}\\n` +\n `Columns: ${tableInfo.columns}\\n` +\n `Total Cells: ${tableInfo.totalCells}\\n` +\n `Width: ${tableInfo.tableWidth}px\\n` +\n `Height: ${tableInfo.tableHeight}px`);\n \n }\n\n /**\n * Delete entire table\n */\n deleteTable() {\n // Hide resize handles before removing table\n if (this.editor.resizeHandles) {\n this.editor.resizeHandles.hideHandles();\n }\n \n this.currentTable.remove();\n this.hide();\n }\n\n /**\n * Create new row with specified number of cells\n */\n createNewRow(cellCount) {\n const row = document.createElement('tr');\n for (let i = 0; i < cellCount; i++) {\n row.appendChild(this.createNewCell());\n }\n return row;\n }\n\n /**\n * Create new cell\n */\n createNewCell() {\n const cell = document.createElement('td');\n cell.innerHTML = '&nbsp;';\n cell.style.minWidth = '50px';\n cell.style.minHeight = '24px';\n cell.style.padding = '4px 8px';\n cell.style.border = '1px solid #ddd';\n cell.style.verticalAlign = 'top';\n cell.contentEditable = 'true';\n return cell;\n }\n\n /**\n * Destroy module\n */\n destroy() {\n // Remove event listeners (document/window ones would otherwise leak)\n if (this._onDocMousedown) {\n document.removeEventListener('mousedown', this._onDocMousedown);\n window.removeEventListener('scroll', this._onWindowScroll);\n this.editor.editor.removeEventListener('click', this._onEditorClick);\n this.editor.editor.removeEventListener('scroll', this._onEditorScroll);\n this.editor.editor.removeEventListener('keyup', this._onEditorKeyup);\n this._onDocMousedown = this._onWindowScroll = this._onEditorClick = null;\n this._onEditorScroll = this._onEditorKeyup = null;\n }\n\n if (this.tableToolbar && this.tableToolbar.parentNode) {\n this.tableToolbar.parentNode.removeChild(this.tableToolbar);\n }\n\n this.clearHideTimeout();\n this.tableToolbar = null;\n this.currentTable = null;\n this.currentCell = null;\n this.isVisible = false;\n \n }\n}\n\nexport default TableToolbar; ","import Module from '../core/module.js';\nimport { sanitizeHtml } from '../utils/sanitize.js';\n\n/**\n * Code View Module - Toggles between normal editor view and HTML source code view\n */\nclass CodeView extends Module {\n constructor(editor, options = {}) {\n super(editor, options);\n \n this.isCodeView = false;\n this.originalContent = '';\n this.codeTextarea = null;\n this.disabledModules = new Set(); // Track disabled modules\n \n this.init();\n }\n\n init() {\n \n // Listen for code view toggle events\n this.editor.on('toolbar-click', (data) => {\n if (data.command === 'code-view') {\n this.toggleCodeView();\n }\n });\n }\n\n /**\n * Toggle between normal editor view and code view\n */\n toggleCodeView() {\n \n if (this.isCodeView) {\n this.showNormalView();\n } else {\n this.showCodeView();\n }\n \n this.updateToolbarButton();\n }\n\n /**\n * Show code view - display HTML source\n */\n showCodeView() {\n const editorArea = this.editor.editor;\n if (!editorArea) return;\n\n // Store original content\n this.originalContent = editorArea.innerHTML;\n \n // Create textarea for code editing\n this.codeTextarea = document.createElement('textarea');\n this.codeTextarea.className = 'code-view-textarea';\n this.codeTextarea.value = this.formatHTML(this.originalContent);\n \n // Replace editor content with textarea\n editorArea.style.display = 'none';\n editorArea.parentNode.insertBefore(this.codeTextarea, editorArea);\n \n // Add CSS class to wrapper for styling\n const wrapper = this.editor.wrapper;\n if (wrapper) {\n wrapper.classList.add('code-view-active');\n }\n \n // Focus on textarea\n this.codeTextarea.focus();\n \n // Set flag\n this.isCodeView = true;\n\n // Reflect the source content in the word/char counter\n if (typeof this.editor.updateStatusbar === 'function') {\n this.editor.updateStatusbar();\n }\n\n // Disable other features\n this.disableOtherFeatures();\n \n // Add event listener for real-time updates\n this.codeTextarea.addEventListener('input', () => {\n this.updateOriginalContent();\n });\n \n }\n\n /**\n * Show normal editor view - restore visual editor\n */\n showNormalView() {\n const editorArea = this.editor.editor;\n if (!editorArea || !this.codeTextarea) return;\n\n // Get updated content from textarea\n const updatedHTML = this.codeTextarea.value;\n \n // Remove textarea\n this.codeTextarea.parentNode.removeChild(this.codeTextarea);\n this.codeTextarea = null;\n \n // Remove CSS class from wrapper\n const wrapper = this.editor.wrapper;\n if (wrapper) {\n wrapper.classList.remove('code-view-active');\n }\n \n // Show editor area\n editorArea.style.display = '';\n\n // Update editor content (sanitize HTML typed in the source view to prevent XSS)\n editorArea.innerHTML = sanitizeHtml(updatedHTML);\n \n // Focus on editor\n editorArea.focus();\n \n // Set flag\n this.isCodeView = false;\n \n // Enable other features\n this.enableOtherFeatures();\n \n // Trigger content change event\n this.editor.onContentChange();\n \n }\n\n /**\n * Disable other features when in code view\n */\n disableOtherFeatures() {\n // Disable toolbar buttons (except code-view and theme)\n const toolbar = this.editor.getModule('toolbar');\n if (toolbar) {\n const allCommands = [\n 'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript',\n 'color', 'background', 'link', 'table', 'heading',\n 'font-family', 'line-height', 'capitalization', 'text-align', 'list',\n 'indent-increase', 'indent-decrease', 'text-size', 'emoji', 'image',\n 'video', 'tag', 'import', 'undo', 'redo',\n 'clear-format', 'horizontal-rule', 'text-direction', 'find', 'more'\n ];\n \n allCommands.forEach(command => {\n toolbar.setButtonDisabled(command, true);\n });\n \n // Keep code-view and theme enabled\n toolbar.setButtonDisabled('code-view', false);\n toolbar.setButtonDisabled('theme', false);\n }\n \n // Disable editor events\n this.disableEditorEvents();\n \n // Disable other modules\n this.disableOtherModules();\n \n // Hide any open popups\n this.hideAllPopups();\n }\n\n /**\n * Enable other features when returning to normal view\n */\n enableOtherFeatures() {\n // Enable toolbar buttons\n const toolbar = this.editor.getModule('toolbar');\n if (toolbar) {\n const allCommands = [\n 'bold', 'italic', 'underline', 'strike', 'subscript', 'superscript',\n 'color', 'background', 'link', 'table', 'heading',\n 'font-family', 'line-height', 'capitalization', 'text-align', 'list',\n 'indent-increase', 'indent-decrease', 'text-size', 'emoji', 'image',\n 'video', 'tag', 'import', 'undo', 'redo',\n 'clear-format', 'horizontal-rule', 'text-direction', 'find', 'more'\n ];\n \n allCommands.forEach(command => {\n toolbar.setButtonDisabled(command, false);\n });\n }\n \n // Enable editor events\n this.enableEditorEvents();\n \n // Enable other modules\n this.enableOtherModules();\n }\n\n /**\n * Disable editor events when in code view\n */\n disableEditorEvents() {\n const editorArea = this.editor.editor;\n if (!editorArea) return;\n \n // Make editor non-editable to disable all editing functionality\n editorArea.contentEditable = false;\n \n // Add a visual indicator that editor is disabled\n editorArea.style.opacity = '0.5';\n editorArea.style.pointerEvents = 'none';\n editorArea.style.cursor = 'not-allowed';\n \n // Add a title to indicate the editor is disabled\n editorArea.title = 'Editor is disabled in code view mode. Click \"Switch to Visual Editor\" to return to normal editing.';\n }\n\n /**\n * Enable editor events when returning to normal view\n */\n enableEditorEvents() {\n const editorArea = this.editor.editor;\n if (!editorArea) return;\n \n // Restore editor functionality\n editorArea.contentEditable = true;\n editorArea.style.opacity = '';\n editorArea.style.pointerEvents = '';\n editorArea.style.cursor = '';\n editorArea.title = '';\n }\n\n /**\n * Disable other modules when in code view\n */\n disableOtherModules() {\n const modulesToDisable = ['history', 'block-toolbar', 'table-toolbar', 'resize-handles'];\n \n modulesToDisable.forEach(moduleName => {\n const module = this.editor.getModule(moduleName);\n if (module) {\n // Try to disable module if it has disable method\n if (typeof module.disable === 'function') {\n module.disable();\n this.disabledModules.add(moduleName);\n }\n // For modules without disable method, we can hide their UI elements\n else if (module.getContainer && typeof module.getContainer === 'function') {\n const container = module.getContainer();\n if (container) {\n container.style.display = 'none';\n this.disabledModules.add(moduleName);\n }\n }\n }\n });\n }\n\n /**\n * Enable other modules when returning to normal view\n */\n enableOtherModules() {\n this.disabledModules.forEach(moduleName => {\n const module = this.editor.getModule(moduleName);\n if (module) {\n // Try to enable module if it has enable method\n if (typeof module.enable === 'function') {\n module.enable();\n }\n // For modules without enable method, show their UI elements\n else if (module.getContainer && typeof module.getContainer === 'function') {\n const container = module.getContainer();\n if (container) {\n container.style.display = '';\n }\n }\n }\n });\n \n this.disabledModules.clear();\n }\n\n /**\n * Update original content when user types in textarea\n */\n updateOriginalContent() {\n if (this.codeTextarea) {\n this.originalContent = this.codeTextarea.value;\n \n // Trigger content change event to call onChange callback\n // Get the HTML content from textarea\n const content = this.codeTextarea.value;\n \n // Call onChange callback if provided\n if (this.editor.options.onChange && typeof this.editor.options.onChange === 'function') {\n this.editor.options.onChange(content);\n }\n \n // Emit text-change event\n this.editor.emit('text-change', content);\n\n // Keep the word/char counter in sync with the edited source\n if (typeof this.editor.updateStatusbar === 'function') {\n this.editor.updateStatusbar();\n }\n }\n }\n\n /**\n * Format HTML for better readability\n */\n formatHTML(html) {\n let formatted = html;\n \n // Tách thẻ mở và đóng thành dòng riêng biệt\n formatted = formatted.replace(/></g, '>\\n<');\n \n // Tách nội dung giữa thẻ mở và đóng thành dòng riêng\n formatted = formatted.replace(/>([^<>\\s][^<]*)</g, '>\\n$1\\n<');\n \n const lines = formatted.split('\\n');\n const indentSize = 4;\n const formattedLines = [];\n const tagStack = []; // Stack để theo dõi thẻ mở\n\n for (let line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n // Check for closing tag - xử lý trước khi in\n if (/^<\\/(\\w+)/.test(trimmed)) {\n const closeTagMatch = trimmed.match(/^<\\/(\\w+)/);\n if (closeTagMatch) {\n const tagName = closeTagMatch[1];\n // Tìm và loại bỏ thẻ mở tương ứng từ stack\n for (let i = tagStack.length - 1; i >= 0; i--) {\n if (tagStack[i] === tagName) {\n tagStack.splice(i, 1);\n break;\n }\n }\n }\n }\n\n // Apply indentation based on current stack level\n let currentLevel = tagStack.length;\n \n // Nếu là nội dung text (không phải thẻ), nó nằm bên trong thẻ cha nên cần thụt lề thêm\n if (!trimmed.startsWith('<')) {\n currentLevel = tagStack.length;\n }\n \n formattedLines.push(' '.repeat(currentLevel * indentSize) + trimmed);\n\n // Check for opening tag (not self-closing) - xử lý sau khi in\n const openTagMatch = trimmed.match(/^<(\\w+)/);\n if (\n openTagMatch &&\n !trimmed.startsWith('</') &&\n !trimmed.endsWith('/>') &&\n !['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr'].includes(openTagMatch[1].toLowerCase())\n ) {\n const tagName = openTagMatch[1];\n tagStack.push(tagName);\n }\n }\n\n return formattedLines.join('\\n');\n }\n\n /**\n * Update toolbar button state\n */\n updateToolbarButton() {\n \n const toolbar = this.editor.getModule('toolbar');\n if (toolbar) {\n toolbar.setButtonActive('code-view', this.isCodeView);\n \n // Update button title\n const buttonTitle = this.isCodeView ? 'Switch to Visual Editor' : 'Switch to HTML Editor';\n toolbar.setButtonTitle('code-view', buttonTitle);\n \n } else {\n }\n }\n\n /**\n * Check if currently in code view\n */\n isInCodeView() {\n return this.isCodeView;\n }\n\n /**\n * Get current content (from textarea if in code view, otherwise from editor)\n */\n getCurrentContent() {\n if (this.isCodeView && this.codeTextarea) {\n return this.codeTextarea.value;\n }\n return this.editor.editor.innerHTML;\n }\n\n /**\n * Set content programmatically\n */\n setContent(html) {\n if (this.isCodeView && this.codeTextarea) {\n this.codeTextarea.value = this.formatHTML(html);\n this.updateOriginalContent();\n } else {\n this.editor.editor.innerHTML = html;\n }\n }\n\n /**\n * Hide all popups when entering code view\n */\n hideAllPopups() {\n // Remove all popup elements from the DOM\n const popups = document.querySelectorAll('.rich-editor-popup, .color-picker-popup, .emoji-picker-popup, .link-popup, .image-popup, .video-popup, .table-popup, .tag-popup, .import-popup');\n popups.forEach(popup => {\n if (popup.parentNode) {\n popup.parentNode.removeChild(popup);\n }\n });\n \n // Clear any popup instances from the editor\n if (this.editor.popupInstances) {\n this.editor.popupInstances.clear();\n }\n }\n\n /**\n * Clean up when module is destroyed\n */\n destroy() {\n if (this.isCodeView) {\n this.showNormalView();\n }\n \n if (this.codeTextarea && this.codeTextarea.parentNode) {\n this.codeTextarea.parentNode.removeChild(this.codeTextarea);\n }\n \n this.codeTextarea = null;\n this.originalContent = '';\n this.isCodeView = false;\n this.disabledModules.clear();\n }\n}\n\nexport default CodeView; ","import Module from '../core/module.js';\nimport IconUtils from '../ui/icons.js';\n\n/**\n * Find & Replace module.\n *\n * Opens with Ctrl/Cmd+F or the toolbar \"find\" button. Highlights matches\n * (wrapping them in <mark> within single text nodes), supports next/prev\n * navigation, replace-current and replace-all. Matches that span across\n * formatting boundaries (e.g. half inside a <b>) are not highlighted — a\n * documented limitation of the per-text-node approach.\n */\nexport default class FindReplace extends Module {\n constructor(editor, options = {}) {\n super(editor, options);\n this.hits = [];\n this.activeIndex = -1;\n this.caseSensitive = false;\n this.isOpen = false;\n this.buildPanel();\n this.bindEvents();\n }\n\n buildPanel() {\n const panel = document.createElement('div');\n panel.className = 'yjd-find-replace';\n\n const mkInput = (ph, cls) => {\n const i = document.createElement('input');\n i.type = 'text';\n i.placeholder = ph;\n i.className = `yjd-find-input ${cls}`;\n i.setAttribute('aria-label', ph);\n return i;\n };\n const mkBtn = (label, title, cls = '', icon = null) => {\n const b = document.createElement('button');\n b.type = 'button';\n if (icon) {\n const svg = IconUtils.getIcon(icon);\n b.innerHTML = svg ? `<span class=\"icon\">${svg}</span>` : label;\n } else {\n b.textContent = label;\n }\n b.title = title;\n b.setAttribute('aria-label', title);\n b.className = `yjd-find-btn ${cls}`.trim();\n return b;\n };\n\n // Two rows: find controls, then replace controls\n const findRow = document.createElement('div');\n findRow.className = 'yjd-find-row';\n const replaceRow = document.createElement('div');\n replaceRow.className = 'yjd-find-row';\n\n this.findInput = mkInput('Find', 'yjd-find-field');\n this.replaceInput = mkInput('Replace with', 'yjd-find-field');\n this.countEl = document.createElement('span');\n this.countEl.className = 'yjd-find-count';\n this.countEl.textContent = '0/0';\n\n this.prevBtn = mkBtn('', 'Previous match', 'yjd-find-icon', 'chevron-up');\n this.nextBtn = mkBtn('', 'Next match', 'yjd-find-icon', 'chevron-down');\n this.caseBtn = mkBtn('Aa', 'Match case', 'yjd-find-icon yjd-find-toggle');\n this.closeBtn = mkBtn('', 'Close (Esc)', 'yjd-find-icon yjd-find-close', 'close');\n this.replaceBtn = mkBtn('Replace', 'Replace current');\n this.replaceAllBtn = mkBtn('Replace all', 'Replace all matches');\n\n findRow.append(this.findInput, this.countEl, this.prevBtn, this.nextBtn, this.caseBtn, this.closeBtn);\n replaceRow.append(this.replaceInput, this.replaceBtn, this.replaceAllBtn);\n panel.append(findRow, replaceRow);\n\n this.panel = panel;\n this.editor.wrapper.appendChild(panel);\n }\n\n bindEvents() {\n // Open with Ctrl/Cmd+F from within the editor\n this._onKeydown = (e) => {\n if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'f') {\n e.preventDefault();\n this.open();\n }\n };\n this.editor.editor.addEventListener('keydown', this._onKeydown);\n\n // Open from the toolbar \"find\" button\n this._onToolbarClick = (data) => {\n if (data && data.command === 'find') this.open();\n };\n this.editor.on('toolbar-click', this._onToolbarClick);\n\n this.findInput.addEventListener('input', () => this.runSearch());\n this.findInput.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') { e.preventDefault(); this.navigate(e.shiftKey ? -1 : 1); }\n else if (e.key === 'Escape') { e.preventDefault(); this.close(); }\n });\n this.replaceInput.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') { e.preventDefault(); this.replaceCurrent(); }\n else if (e.key === 'Escape') { e.preventDefault(); this.close(); }\n });\n this.prevBtn.addEventListener('click', () => this.navigate(-1));\n this.nextBtn.addEventListener('click', () => this.navigate(1));\n this.replaceBtn.addEventListener('click', () => this.replaceCurrent());\n this.replaceAllBtn.addEventListener('click', () => this.replaceAll());\n this.caseBtn.addEventListener('click', () => {\n this.caseSensitive = !this.caseSensitive;\n this.caseBtn.classList.toggle('active', this.caseSensitive);\n this.caseBtn.setAttribute('aria-pressed', this.caseSensitive ? 'true' : 'false');\n this.runSearch();\n });\n this.closeBtn.addEventListener('click', () => this.close());\n }\n\n open() {\n this.isOpen = true;\n this.panel.classList.add('open');\n // Sit just below the toolbar so the panel never covers its buttons\n // (the toolbar height changes when \"More\" is expanded).\n const toolbar = this.editor.wrapper.querySelector('.rich-editor-toolbar-container');\n if (toolbar) this.panel.style.top = (toolbar.offsetHeight + 6) + 'px';\n // Prefill with the current selection (if any, single-line)\n const sel = window.getSelection();\n const selText = sel && !sel.isCollapsed ? sel.toString() : '';\n if (selText && !selText.includes('\\n')) this.findInput.value = selText;\n this.findInput.focus();\n this.findInput.select();\n this.runSearch();\n }\n\n close() {\n this.isOpen = false;\n this.panel.classList.remove('open');\n this.clearHighlights();\n this.hits = [];\n this.activeIndex = -1;\n this.editor.focus();\n }\n\n escapeRegex(s) {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n\n clearHighlights() {\n const marks = this.editor.editor.querySelectorAll('mark.yjd-find-hit');\n marks.forEach((m) => {\n const parent = m.parentNode;\n if (!parent) return;\n while (m.firstChild) parent.insertBefore(m.firstChild, m);\n parent.removeChild(m);\n parent.normalize();\n });\n }\n\n runSearch() {\n this.clearHighlights();\n this.hits = [];\n this.activeIndex = -1;\n\n const term = this.findInput.value;\n if (!term) { this.updateCount(); return; }\n\n let regex;\n try {\n regex = new RegExp(this.escapeRegex(term), this.caseSensitive ? 'g' : 'gi');\n } catch (e) {\n this.updateCount();\n return;\n }\n\n const root = this.editor.editor;\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);\n const textNodes = [];\n let node;\n while ((node = walker.nextNode())) {\n if (node.nodeValue && node.nodeValue.length) textNodes.push(node);\n }\n\n textNodes.forEach((textNode) => {\n const text = textNode.nodeValue;\n const ranges = [];\n regex.lastIndex = 0;\n let m;\n while ((m = regex.exec(text)) !== null) {\n if (m[0].length === 0) { regex.lastIndex++; continue; }\n ranges.push([m.index, m.index + m[0].length]);\n }\n // Wrap from last match to first so earlier offsets stay valid\n for (let i = ranges.length - 1; i >= 0; i--) {\n const r = document.createRange();\n r.setStart(textNode, ranges[i][0]);\n r.setEnd(textNode, ranges[i][1]);\n const mark = document.createElement('mark');\n mark.className = 'yjd-find-hit';\n try { r.surroundContents(mark); } catch (e) { /* skip un-wrappable */ }\n }\n });\n\n // Collect in document order\n this.hits = Array.from(root.querySelectorAll('mark.yjd-find-hit'));\n if (this.hits.length) {\n this.activeIndex = 0;\n this.highlightActive(true);\n }\n this.updateCount();\n }\n\n highlightActive(scroll) {\n this.hits.forEach((m, i) => {\n m.classList.toggle('active', i === this.activeIndex);\n });\n if (scroll && this.activeIndex >= 0 && this.hits[this.activeIndex]) {\n this.hits[this.activeIndex].scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n }\n\n navigate(dir) {\n if (!this.hits.length) return;\n this.activeIndex = (this.activeIndex + dir + this.hits.length) % this.hits.length;\n this.highlightActive(true);\n this.updateCount();\n }\n\n updateCount() {\n const total = this.hits.length;\n const cur = total ? this.activeIndex + 1 : 0;\n this.countEl.textContent = `${cur}/${total}`;\n }\n\n replaceCurrent() {\n if (this.activeIndex < 0 || !this.hits[this.activeIndex]) return;\n const history = this.editor.getModule('history');\n if (history && typeof history.saveBeforeFormat === 'function') history.saveBeforeFormat();\n\n const mark = this.hits[this.activeIndex];\n const at = this.activeIndex;\n const parent = mark.parentNode;\n parent.replaceChild(document.createTextNode(this.replaceInput.value), mark);\n parent.normalize();\n this.editor.onContentChange();\n\n this.runSearch();\n if (this.hits.length) {\n this.activeIndex = Math.min(at, this.hits.length - 1);\n this.highlightActive(true);\n this.updateCount();\n }\n }\n\n replaceAll() {\n if (!this.hits.length) return;\n const history = this.editor.getModule('history');\n if (history && typeof history.saveBeforeFormat === 'function') history.saveBeforeFormat();\n\n const repl = this.replaceInput.value;\n this.hits.forEach((mark) => {\n const parent = mark.parentNode;\n if (parent) parent.replaceChild(document.createTextNode(repl), mark);\n });\n this.editor.editor.normalize();\n this.editor.onContentChange();\n this.runSearch();\n }\n\n destroy() {\n this.editor.editor.removeEventListener('keydown', this._onKeydown);\n this.editor.off('toolbar-click', this._onToolbarClick);\n this.clearHighlights();\n if (this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);\n super.destroy();\n }\n}\n","import Module from '../core/module.js';\nimport IconUtils from '../ui/icons.js';\n\n/**\n * Slash command menu.\n *\n * Type \"/\" at the start of a block (or after whitespace) to open a quick menu\n * of block commands. Filter by typing, navigate with ↑/↓, choose with Enter,\n * dismiss with Esc. Selecting a command removes the typed \"/query\" and applies\n * the block transform.\n */\nexport default class SlashMenu extends Module {\n constructor(editor, options = {}) {\n super(editor, options);\n this.isOpen = false;\n this.activeIndex = 0;\n this.query = '';\n this.filtered = [];\n this.commands = this.buildCommands();\n this.buildMenu();\n this.bindEvents();\n }\n\n buildCommands() {\n const ed = this.editor;\n return [\n { id: 'h1', label: 'Heading 1', hint: 'Big section heading', icon: 'heading', run: () => ed.setBlockType('h1') },\n { id: 'h2', label: 'Heading 2', hint: 'Medium heading', icon: 'heading', run: () => ed.setBlockType('h2') },\n { id: 'h3', label: 'Heading 3', hint: 'Small heading', icon: 'heading', run: () => ed.setBlockType('h3') },\n { id: 'ul', label: 'Bullet list', hint: 'Unordered list', icon: 'list-bullet', run: () => ed.setBlockType('ul') },\n { id: 'ol', label: 'Numbered list', hint: 'Ordered list', icon: 'list-ordered', run: () => ed.setBlockType('ol') },\n { id: 'quote', label: 'Quote', hint: 'Blockquote', icon: 'code', run: () => ed.setBlockType('blockquote') },\n { id: 'code', label: 'Code block', hint: 'Preformatted code', icon: 'code-view', run: () => ed.setBlockType('pre') },\n { id: 'hr', label: 'Divider', hint: 'Horizontal rule', icon: 'horizontal-rule', run: () => ed.insertHorizontalRule() },\n { id: 'table', label: 'Table', hint: '3×3 table', icon: 'table', run: () => this.insertTable() },\n { id: 'p', label: 'Text', hint: 'Plain paragraph', icon: 'font-family', run: () => ed.setBlockType('p') }\n ];\n }\n\n insertTable() {\n const Table = this.editor.registry.get('formats/table');\n if (Table && typeof Table.createTableElement === 'function' && typeof this.editor.insertBlock === 'function') {\n this.editor.insertBlock(Table.createTableElement(3, 3));\n }\n }\n\n buildMenu() {\n const menu = document.createElement('div');\n menu.className = 'yjd-slash-menu';\n menu.setAttribute('role', 'listbox');\n menu.style.display = 'none';\n this.menu = menu;\n document.body.appendChild(menu);\n }\n\n bindEvents() {\n this._onInput = () => this.handleInput();\n this.editor.editor.addEventListener('input', this._onInput);\n\n // Keyboard interaction while open (capture so we beat other handlers).\n this._onKeydown = (e) => {\n if (!this.isOpen) return;\n const handled = ['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(e.key);\n if (handled) { e.preventDefault(); e.stopPropagation(); }\n if (e.key === 'ArrowDown') this.move(1);\n else if (e.key === 'ArrowUp') this.move(-1);\n else if (e.key === 'Enter') this.choose(this.activeIndex);\n else if (e.key === 'Escape') this.close();\n };\n this.editor.editor.addEventListener('keydown', this._onKeydown, true);\n\n this._onDocPointer = (e) => {\n if (this.isOpen && !this.menu.contains(e.target)) this.close();\n };\n document.addEventListener('pointerdown', this._onDocPointer, true);\n }\n\n handleInput() {\n const sel = window.getSelection();\n if (!sel || !sel.isCollapsed || !sel.rangeCount) return this.close();\n const range = sel.getRangeAt(0);\n const node = range.startContainer;\n if (node.nodeType !== Node.TEXT_NODE) return this.close();\n\n const before = node.textContent.slice(0, range.startOffset);\n const m = before.match(/(?:^|\\s)\\/([^\\s/]*)$/);\n if (!m) return this.close();\n\n this.query = m[1];\n this.slashNode = node;\n this.slashStart = range.startOffset - this.query.length - 1; // index of \"/\"\n const q = this.query.toLowerCase();\n this.filtered = this.commands.filter(c =>\n c.label.toLowerCase().includes(q) ||\n c.id.toLowerCase().includes(q) ||\n (c.hint || '').toLowerCase().includes(q));\n if (!this.filtered.length) return this.close();\n this.activeIndex = 0;\n this.render();\n this.open(range);\n }\n\n open(range) {\n this.isOpen = true;\n this.menu.style.display = 'block';\n // Position below the caret.\n const rect = range.getBoundingClientRect();\n const x = rect.left || (range.startContainer.parentElement || this.editor.editor).getBoundingClientRect().left;\n const y = rect.bottom || rect.top;\n this.menu.style.left = `${Math.round(x + window.scrollX)}px`;\n this.menu.style.top = `${Math.round(y + window.scrollY + 6)}px`;\n // Flip up if off the bottom.\n const mh = this.menu.offsetHeight;\n if (rect.bottom + mh + 8 > window.innerHeight) {\n this.menu.style.top = `${Math.round(rect.top + window.scrollY - mh - 6)}px`;\n }\n }\n\n close() {\n if (!this.isOpen) return;\n this.isOpen = false;\n this.menu.style.display = 'none';\n }\n\n move(delta) {\n this.activeIndex = (this.activeIndex + delta + this.filtered.length) % this.filtered.length;\n this.render();\n }\n\n render() {\n this.menu.innerHTML = '';\n this.filtered.forEach((cmd, i) => {\n const item = document.createElement('button');\n item.type = 'button';\n item.className = 'yjd-slash-item' + (i === this.activeIndex ? ' active' : '');\n item.setAttribute('role', 'option');\n item.setAttribute('aria-selected', i === this.activeIndex ? 'true' : 'false');\n\n const icon = document.createElement('span');\n icon.className = 'yjd-slash-icon';\n icon.innerHTML = IconUtils.getIcon(cmd.icon) || '';\n\n const text = document.createElement('span');\n text.className = 'yjd-slash-text';\n text.innerHTML = `<span class=\"yjd-slash-label\">${cmd.label}</span><span class=\"yjd-slash-hint\">${cmd.hint}</span>`;\n\n item.append(icon, text);\n // pointerdown (not click) so the editor selection isn't lost first.\n item.addEventListener('pointerdown', (e) => { e.preventDefault(); this.choose(i); });\n this.menu.appendChild(item);\n });\n }\n\n choose(index) {\n const cmd = this.filtered[index];\n if (!cmd) return this.close();\n\n // Remove the typed \"/query\" then run the command.\n try {\n const node = this.slashNode;\n const sel = window.getSelection();\n const del = document.createRange();\n del.setStart(node, this.slashStart);\n del.setEnd(node, this.slashStart + this.query.length + 1);\n del.deleteContents();\n const caret = document.createRange();\n caret.setStart(node, this.slashStart);\n caret.collapse(true);\n sel.removeAllRanges();\n sel.addRange(caret);\n } catch (e) { /* node moved; run anyway */ }\n\n this.close();\n this.editor.focus();\n cmd.run(this.editor);\n }\n\n destroy() {\n this.editor.editor.removeEventListener('input', this._onInput);\n this.editor.editor.removeEventListener('keydown', this._onKeydown, true);\n document.removeEventListener('pointerdown', this._onDocPointer, true);\n if (this.menu && this.menu.parentNode) this.menu.parentNode.removeChild(this.menu);\n super.destroy();\n }\n}\n","import Module from '../core/module.js';\n\n/**\n * @mention module — trigger-based autocomplete that inserts a token carrying an\n * id, so the serialized HTML/Markdown can tell the server who was tagged.\n *\n * new Editor(el, {\n * mention: {\n * trigger: '@',\n * source: async (query) => [{ id, name, avatar_url }],\n * renderItem: (item) => `<img src=\"${item.avatar_url}\"> ${item.name}`,\n * // optional extra triggers, e.g. '#' for task refs:\n * triggers: [{ char: '#', source: async (q) => [...] }]\n * }\n * })\n *\n * Token HTML: <span class=\"mention\" data-id=\"ID\" data-trigger=\"@\"\n * contenteditable=\"false\">@Name</span>\n * → getMarkdown() emits `@[Name](id)`. Fires editor.on('mention:select', item).\n */\nexport default class Mention extends Module {\n // --rte-* theme vars copied onto the portaled menu when it opens.\n static THEME_VARS = ['--rte-accent', '--rte-accent-ink', '--rte-accent-weak', '--rte-ink', '--rte-muted', '--rte-border', '--rte-bg', '--rte-radius-md', '--rte-shadow'];\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.isOpen = false;\n this.activeIndex = 0;\n this.items = [];\n this.sources = this._buildSources();\n this.buildMenu();\n this.bindEvents();\n }\n\n _buildSources() {\n const cfg = this.editor.options.mention || this.options || {};\n const map = {};\n const renderItem = cfg.renderItem;\n if (typeof cfg.source === 'function') {\n map[cfg.trigger || '@'] = { source: cfg.source, renderItem: cfg.renderItem || renderItem };\n }\n (cfg.triggers || []).forEach((t) => {\n if (t && t.char && typeof t.source === 'function') {\n map[t.char] = { source: t.source, renderItem: t.renderItem || renderItem };\n }\n });\n return map;\n }\n\n get enabled() { return Object.keys(this.sources).length > 0; }\n\n buildMenu() {\n const menu = document.createElement('div');\n menu.className = 'yjd-mention-menu';\n menu.setAttribute('role', 'listbox');\n menu.style.display = 'none';\n this.menu = menu;\n document.body.appendChild(menu);\n }\n\n bindEvents() {\n if (!this.enabled) return;\n this._onInput = () => this.handleInput();\n this.editor.editor.addEventListener('input', this._onInput);\n\n this._onKeydown = (e) => {\n if (!this.isOpen) return;\n // Stop here (capture phase) so an outer Enter-to-submit handler doesn't\n // also fire once choose() closes the menu.\n const handled = ['ArrowDown', 'ArrowUp', 'Enter', 'Tab', 'Escape'].includes(e.key);\n if (handled) { e.preventDefault(); e.stopPropagation(); }\n if (e.key === 'ArrowDown') this.move(1);\n else if (e.key === 'ArrowUp') this.move(-1);\n else if (e.key === 'Enter' || e.key === 'Tab') this.choose(this.activeIndex);\n else if (e.key === 'Escape') this.close();\n };\n this.editor.editor.addEventListener('keydown', this._onKeydown, true);\n\n this._onDocPointer = (e) => { if (this.isOpen && !this.menu.contains(e.target)) this.close(); };\n document.addEventListener('pointerdown', this._onDocPointer, true);\n }\n\n handleInput() {\n const sel = window.getSelection();\n if (!sel || !sel.isCollapsed || !sel.rangeCount) return this.close();\n const range = sel.getRangeAt(0);\n const node = range.startContainer;\n if (node.nodeType !== Node.TEXT_NODE) return this.close();\n\n const triggers = Object.keys(this.sources).map((c) => c.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')).join('');\n const before = node.textContent.slice(0, range.startOffset);\n const m = before.match(new RegExp(`(?:^|\\\\s)([${triggers}])([^\\\\s${triggers}]*)$`));\n if (!m) return this.close();\n\n this.char = m[1];\n this.query = m[2];\n this.node = node;\n this.start = range.startOffset - this.query.length - 1; // index of trigger char\n this._loadFor(range);\n }\n\n _loadFor(range) {\n const src = this.sources[this.char];\n if (!src) return this.close();\n clearTimeout(this._t);\n const q = this.query, char = this.char;\n this._t = setTimeout(() => {\n Promise.resolve(src.source(q)).then((items) => {\n // Ignore stale responses (user kept typing / switched trigger).\n if (this.char !== char || this.query !== q) return;\n this.items = Array.isArray(items) ? items : [];\n if (!this.items.length) return this.close();\n this.activeIndex = 0;\n this.render(src.renderItem);\n this.open(range);\n }).catch(() => this.close());\n }, 120);\n }\n\n open(range) {\n this.isOpen = true;\n this.menu.style.display = 'block';\n this._applyTheme();\n const rect = range.getBoundingClientRect();\n const x = rect.left || (range.startContainer.parentElement || this.editor.editor).getBoundingClientRect().left;\n const y = rect.bottom || rect.top;\n this.menu.style.left = `${Math.round(x + window.scrollX)}px`;\n this.menu.style.top = `${Math.round(y + window.scrollY + 6)}px`;\n const mh = this.menu.offsetHeight;\n if (rect.bottom + mh + 8 > window.innerHeight) {\n this.menu.style.top = `${Math.round(rect.top + window.scrollY - mh - 6)}px`;\n }\n }\n\n close() {\n if (!this.isOpen) return;\n this.isOpen = false;\n this.menu.style.display = 'none';\n }\n\n /**\n * The menu is portaled to <body>, so it can't inherit the editor's --rte-*\n * theme vars. Copy them across when opening so a themed editor themes its\n * mention menu too (no need to re-declare the vars on .yjd-mention-menu).\n */\n _applyTheme() {\n const root = this.editor.wrapper || this.editor.root;\n if (!root) return;\n const cs = getComputedStyle(root);\n Mention.THEME_VARS.forEach((v) => {\n const val = cs.getPropertyValue(v);\n if (val) this.menu.style.setProperty(v, val.trim());\n });\n }\n\n move(d) {\n this.activeIndex = (this.activeIndex + d + this.items.length) % this.items.length;\n [...this.menu.children].forEach((el, i) => {\n el.classList.toggle('active', i === this.activeIndex);\n el.setAttribute('aria-selected', i === this.activeIndex ? 'true' : 'false');\n });\n }\n\n render(renderItem) {\n this.menu.innerHTML = '';\n this.items.forEach((item, i) => {\n const el = document.createElement('button');\n el.type = 'button';\n el.className = 'yjd-mention-item' + (i === this.activeIndex ? ' active' : '');\n el.setAttribute('role', 'option');\n el.setAttribute('aria-selected', i === this.activeIndex ? 'true' : 'false');\n const label = item.name || item.label || item.id || '';\n // Default row: avatar (or an item.icon for special entries like \"@all\"),\n // then the name. Apps only need a custom renderItem for richer layouts.\n const media = item.avatar_url\n ? `<img class=\"yjd-mention-avatar\" src=\"${item.avatar_url}\" alt=\"\">`\n : (item.icon ? `<span class=\"yjd-mention-ico\">${item.icon}</span>` : '');\n el.innerHTML = typeof renderItem === 'function'\n ? renderItem(item)\n : `${media}<span class=\"yjd-mention-name\">${this.char}${label}</span>`;\n el.addEventListener('pointerdown', (e) => { e.preventDefault(); this.choose(i); });\n this.menu.appendChild(el);\n });\n }\n\n choose(index) {\n const item = this.items[index];\n if (!item) return this.close();\n const name = item.name || item.label || item.id || '';\n try {\n const node = this.node;\n const sel = window.getSelection();\n const del = document.createRange();\n del.setStart(node, this.start);\n del.setEnd(node, this.start + this.query.length + 1);\n del.deleteContents();\n\n const span = document.createElement('span');\n span.className = 'mention';\n span.setAttribute('data-id', String(item.id != null ? item.id : ''));\n span.setAttribute('data-trigger', this.char);\n span.setAttribute('contenteditable', 'false');\n span.textContent = this.char + name;\n del.insertNode(span);\n\n const space = document.createTextNode(' ');\n span.after(space);\n const caret = document.createRange();\n caret.setStart(space, 1);\n caret.collapse(true);\n sel.removeAllRanges();\n sel.addRange(caret);\n } catch (e) { /* node moved */ }\n\n this.close();\n this.editor.focus();\n if (typeof this.editor.onContentChange === 'function') this.editor.onContentChange();\n this.editor.emit('mention:select', item);\n }\n\n destroy() {\n if (this._onInput) this.editor.editor.removeEventListener('input', this._onInput);\n if (this._onKeydown) this.editor.editor.removeEventListener('keydown', this._onKeydown, true);\n if (this._onDocPointer) document.removeEventListener('pointerdown', this._onDocPointer, true);\n if (this.menu && this.menu.parentNode) this.menu.parentNode.removeChild(this.menu);\n super.destroy();\n }\n}\n","import Module from '../core/module.js';\n\n/**\n * Resize Handles Module - Adds resize functionality to images, videos, and tables\n * Creates 4 corner handles for dragging to resize elements\n */\nclass ResizeHandles extends Module {\n static DEFAULTS = {\n minWidth: 50,\n minHeight: 50,\n maxWidth: 800,\n maxHeight: 600,\n maintainAspectRatio: true, // For images and videos\n snapToGrid: false,\n gridSize: 10\n };\n\n constructor(editor, options = {}) {\n super(editor, options);\n this.activeElement = null;\n this.handles = [];\n this.isResizing = false;\n this.startX = 0;\n this.startY = 0;\n this.startWidth = 0;\n this.startHeight = 0;\n this.currentHandle = null;\n this.aspectRatio = 1;\n \n this.init();\n }\n\n init() {\n this.createHandles();\n this.setupEventListeners();\n }\n\n /**\n * Create resize handles container\n */\n createHandles() {\n this.handlesContainer = document.createElement('div');\n this.handlesContainer.className = 'resize-handles-container';\n this.handlesContainer.style.position = 'absolute';\n this.handlesContainer.style.pointerEvents = 'none';\n this.handlesContainer.style.zIndex = '997'; // Lower than all toolbar elements\n this.handlesContainer.style.display = 'none';\n\n // Create 4 corner handles\n const handlePositions = [\n { name: 'nw', cursor: 'nw-resize', position: { top: -4, left: -4 } },\n { name: 'ne', cursor: 'ne-resize', position: { top: -4, right: -4 } },\n { name: 'sw', cursor: 'sw-resize', position: { bottom: -4, left: -4 } },\n { name: 'se', cursor: 'se-resize', position: { bottom: -4, right: -4 } }\n ];\n\n handlePositions.forEach(config => {\n const handle = this.createHandle(config);\n this.handles.push(handle);\n this.handlesContainer.appendChild(handle);\n });\n\n // Add to editor wrapper but ensure it's behind toolbars\n this.editor.wrapper.appendChild(this.handlesContainer);\n }\n\n /**\n * Create individual resize handle\n */\n createHandle(config) {\n const handle = document.createElement('div');\n handle.className = `resize-handle resize-handle-${config.name}`;\n handle.style.position = 'absolute';\n handle.style.width = '8px';\n handle.style.height = '8px';\n handle.style.backgroundColor = '#3b82f6';\n handle.style.border = '1px solid #fff';\n handle.style.borderRadius = '50%';\n handle.style.cursor = config.cursor;\n handle.style.pointerEvents = 'auto';\n handle.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';\n handle.style.zIndex = '999'; // Lower than toolbars\n handle.dataset.handle = config.name;\n\n // Position handle\n Object.entries(config.position).forEach(([key, value]) => {\n handle.style[key] = value + 'px';\n });\n\n // Add event listeners\n handle.addEventListener('mousedown', (e) => this.handleMouseDown(e, config.name));\n \n return handle;\n }\n\n /**\n * Setup event listeners\n */\n setupEventListeners() {\n // Keep references so listeners can be removed in destroy() (the document/\n // window handlers would otherwise leak across editor create/destroy cycles).\n this._onEditorClick = (e) => this.handleElementClick(e);\n this._onDocClick = (e) => {\n if (!this.isClickOnResizableElement(e) && !this.isClickOnHandle(e)) {\n this.hideHandles();\n }\n };\n this._onDocMousemove = (e) => this.handleMouseMove(e);\n this._onDocMouseup = (e) => this.handleMouseUp(e);\n this._onWindowScroll = () => {\n if (this.activeElement) this.updateHandlePosition();\n };\n this._onEditorScroll = () => {\n if (this.activeElement) this.updateHandlePosition();\n };\n\n this.editor.editor.addEventListener('click', this._onEditorClick);\n document.addEventListener('click', this._onDocClick);\n document.addEventListener('mousemove', this._onDocMousemove);\n document.addEventListener('mouseup', this._onDocMouseup);\n window.addEventListener('scroll', this._onWindowScroll);\n this.editor.editor.addEventListener('scroll', this._onEditorScroll);\n\n // Listen for DOM changes to update handles\n this.setupMutationObserver();\n }\n\n /**\n * Handle click on resizable elements\n */\n handleElementClick(e) {\n const target = e.target;\n \n // Debug logging\n \n \n // Find the actual resizable element\n let resizableElement = this.findResizableElement(target);\n \n if (resizableElement) {\n e.preventDefault();\n e.stopPropagation();\n this.showHandles(resizableElement);\n }\n }\n\n /**\n * Find the actual resizable element from a clicked target\n */\n findResizableElement(target) {\n // If target is already resizable, return it\n if (this.isResizableElement(target)) {\n return target;\n }\n \n // If target is inside a table (td, th, tr, tbody), find the table\n if (target.tagName === 'TD' || target.tagName === 'TH' || \n target.tagName === 'TR' || target.tagName === 'TBODY') {\n let parent = target.parentElement;\n while (parent && parent.tagName !== 'TABLE') {\n parent = parent.parentElement;\n }\n if (parent && this.isResizableElement(parent)) {\n return parent;\n }\n }\n \n // Check if any parent is resizable\n let parent = target.parentElement;\n while (parent && parent !== this.editor.wrapper) {\n if (this.isResizableElement(parent)) {\n return parent;\n }\n parent = parent.parentElement;\n }\n \n return null;\n }\n\n /**\n * Check if element is resizable\n */\n isResizableElement(element) {\n // Debug logging\n\n \n const isImage = element.classList.contains('inserted-image');\n const isVideo = element.classList.contains('inserted-video');\n const isTable = element.classList.contains('rich-editor-table');\n \n\n \n return isImage || isVideo || isTable;\n }\n\n /**\n * Check if click is on resizable element\n */\n isClickOnResizableElement(e) {\n return this.isResizableElement(e.target);\n }\n\n /**\n * Check if click is on resize handle\n */\n isClickOnHandle(e) {\n return e.target.classList.contains('resize-handle');\n }\n\n /**\n * Show resize handles for element\n */\n showHandles(element) {\n \n \n this.activeElement = element;\n this.updateHandlePosition();\n this.handlesContainer.style.display = 'block';\n \n // Store aspect ratio for images and videos\n if (element.classList.contains('inserted-image') || element.classList.contains('inserted-video')) {\n this.aspectRatio = element.offsetWidth / element.offsetHeight;\n }\n \n // For tables, ensure they have proper positioning and setup size monitoring\n if (element.classList.contains('rich-editor-table')) {\n element.style.position = 'relative';\n element.style.display = 'table';\n \n // Store initial dimensions for comparison\n this.lastTableWidth = element.offsetWidth;\n this.lastTableHeight = element.offsetHeight;\n \n // Setup periodic size checking for tables\n this.setupTableSizeMonitoring(element);\n }\n \n\n }\n\n /**\n * Hide resize handles\n */\n hideHandles() {\n // Clear table size monitoring\n if (this.tableSizeInterval) {\n clearInterval(this.tableSizeInterval);\n this.tableSizeInterval = null;\n }\n \n this.activeElement = null;\n this.handlesContainer.style.display = 'none';\n }\n\n /**\n * Update handle position based on active element\n */\n updateHandlePosition() {\n if (!this.activeElement) return;\n\n // Check if element still exists in DOM\n if (!document.body.contains(this.activeElement)) {\n this.hideHandles();\n return;\n }\n\n const elementRect = this.activeElement.getBoundingClientRect();\n const editorRect = this.editor.wrapper.getBoundingClientRect();\n const scrollTop = this.editor.wrapper.scrollTop || 0;\n const scrollLeft = this.editor.wrapper.scrollLeft || 0;\n\n // Position handles container\n const top = elementRect.top - editorRect.top + scrollTop;\n const left = elementRect.left - editorRect.left + scrollLeft;\n const width = elementRect.width;\n const height = elementRect.height;\n const bottom = top + height;\n this.handlesContainer.style.top = top + 'px';\n this.handlesContainer.style.left = left + 'px';\n this.handlesContainer.style.width = width + 'px';\n this.handlesContainer.style.height = height + 'px';\n \n if(bottom < 0){\n this.hideHandles();\n return;\n }\n if(top > editorRect.height){\n this.hideHandles();\n return;\n }\n }\n\n /**\n * Handle mouse down on resize handle\n */\n handleMouseDown(e, handleName) {\n e.preventDefault();\n e.stopPropagation();\n\n this.isResizing = true;\n this.currentHandle = handleName;\n this.startX = e.clientX;\n this.startY = e.clientY;\n this.startWidth = this.activeElement.offsetWidth;\n this.startHeight = this.activeElement.offsetHeight;\n\n // Store initial position\n const elementRect = this.activeElement.getBoundingClientRect();\n this.startLeft = elementRect.left;\n this.startTop = elementRect.top;\n\n // Add resizing class for styling\n this.activeElement.classList.add('resizing');\n document.body.style.cursor = e.target.style.cursor;\n document.body.style.userSelect = 'none';\n }\n\n /**\n * Handle mouse move during resize\n */\n handleMouseMove(e) {\n if (!this.isResizing || !this.activeElement) return;\n\n const deltaX = e.clientX - this.startX;\n const deltaY = e.clientY - this.startY;\n\n let newWidth = this.startWidth;\n let newHeight = this.startHeight;\n\n // Calculate new dimensions based on handle position\n switch (this.currentHandle) {\n case 'nw':\n newWidth = this.startWidth - deltaX;\n newHeight = this.startHeight - deltaY;\n break;\n case 'ne':\n newWidth = this.startWidth + deltaX;\n newHeight = this.startHeight - deltaY;\n break;\n case 'sw':\n newWidth = this.startWidth - deltaX;\n newHeight = this.startHeight + deltaY;\n break;\n case 'se':\n newWidth = this.startWidth + deltaX;\n newHeight = this.startHeight + deltaY;\n break;\n }\n\n // Never let an element grow past the editor's content width (its inner\n // width minus padding) — otherwise tables/images overflow the editor.\n let maxW = this.options.maxWidth;\n const area = this.editor && this.editor.editor;\n if (area) {\n const cs = getComputedStyle(area);\n const avail = area.clientWidth\n - (parseFloat(cs.paddingLeft) || 0)\n - (parseFloat(cs.paddingRight) || 0);\n if (avail > 0) maxW = Math.min(maxW, avail);\n }\n\n // Apply constraints\n newWidth = Math.max(this.options.minWidth, Math.min(maxW, newWidth));\n newHeight = Math.max(this.options.minHeight, Math.min(this.options.maxHeight, newHeight));\n\n // Maintain aspect ratio for images and videos\n if ((this.activeElement.classList.contains('inserted-image') ||\n this.activeElement.classList.contains('inserted-video')) &&\n this.options.maintainAspectRatio) {\n\n const ratioByWidth = newWidth / this.aspectRatio;\n const ratioByHeight = newHeight * this.aspectRatio;\n\n if (Math.abs(newWidth - ratioByHeight) < Math.abs(newHeight - ratioByWidth)) {\n newWidth = ratioByHeight;\n } else {\n newHeight = ratioByWidth;\n }\n // Aspect-ratio math can push width back over the limit — re-clamp.\n if (newWidth > maxW) {\n newWidth = maxW;\n newHeight = newWidth / this.aspectRatio;\n }\n }\n\n // Snap to grid if enabled\n if (this.options.snapToGrid) {\n newWidth = Math.round(newWidth / this.options.gridSize) * this.options.gridSize;\n newHeight = Math.round(newHeight / this.options.gridSize) * this.options.gridSize;\n }\n\n // Apply new dimensions\n this.applyDimensions(newWidth, newHeight);\n this.updateHandlePosition();\n\n // Emit resize event\n this.emit('element-resize', {\n element: this.activeElement,\n width: newWidth,\n height: newHeight,\n handle: this.currentHandle\n });\n }\n\n /**\n * Handle mouse up - end resize\n */\n handleMouseUp(e) {\n if (!this.isResizing) return;\n\n this.isResizing = false;\n this.currentHandle = null;\n \n // Remove resizing class\n if (this.activeElement) {\n this.activeElement.classList.remove('resizing');\n }\n \n // Reset cursor\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n\n // Emit resize complete event\n this.emit('element-resize-complete', {\n element: this.activeElement,\n width: this.activeElement.offsetWidth,\n height: this.activeElement.offsetHeight\n });\n }\n\n /**\n * Apply dimensions to element\n */\n applyDimensions(width, height) {\n if (!this.activeElement) return;\n\n if (this.activeElement.classList.contains('rich-editor-table')) {\n // For tables, set both width and height\n this.activeElement.style.width = width + 'px';\n this.activeElement.style.minWidth = width + 'px';\n this.activeElement.style.height = height + 'px';\n this.activeElement.style.minHeight = height + 'px';\n \n // Calculate cell dimensions\n const rows = this.activeElement.querySelectorAll('tr');\n const cols = rows.length > 0 ? rows[0].querySelectorAll('td, th').length : 0;\n \n if (rows.length > 0 && cols > 0) {\n const cellWidth = Math.floor(width / cols);\n const cellHeight = Math.floor(height / rows.length);\n \n // Apply dimensions to all cells\n const cells = this.activeElement.querySelectorAll('td, th');\n cells.forEach(cell => {\n cell.style.minWidth = cellWidth + 'px';\n cell.style.minHeight = cellHeight + 'px';\n cell.style.height = cellHeight + 'px';\n });\n }\n } else {\n // For images and videos (including iframes)\n this.activeElement.style.width = width + 'px';\n this.activeElement.style.height = height + 'px';\n \n // If it's an iframe, update its attributes too\n if (this.activeElement.tagName === 'IFRAME') {\n this.activeElement.width = width;\n this.activeElement.height = height;\n }\n }\n }\n\n /**\n * Get current active element\n */\n getActiveElement() {\n return this.activeElement;\n }\n\n /**\n * Set active element programmatically\n */\n setActiveElement(element) {\n if (this.isResizableElement(element)) {\n this.showHandles(element);\n }\n }\n\n /**\n * Check and update handles if active element has changed\n */\n checkAndUpdateHandles() {\n if (this.activeElement) {\n // Check if element still exists and is still resizable\n if (!document.body.contains(this.activeElement) || !this.isResizableElement(this.activeElement)) {\n this.hideHandles();\n return;\n }\n \n // Update position if element still exists\n this.updateHandlePosition();\n \n // For tables, also check if size has changed due to content\n if (this.activeElement.classList.contains('rich-editor-table')) {\n this.checkTableSizeChange();\n }\n }\n }\n\n /**\n * Force refresh handles for current active element\n */\n refreshHandles() {\n if (this.activeElement && document.body.contains(this.activeElement)) {\n this.updateHandlePosition();\n }\n }\n\n /**\n * Setup periodic monitoring for table size changes\n */\n setupTableSizeMonitoring(tableElement) {\n // Clear any existing interval\n if (this.tableSizeInterval) {\n clearInterval(this.tableSizeInterval);\n }\n \n // Check table size every 100ms\n this.tableSizeInterval = setInterval(() => {\n if (this.activeElement && this.activeElement.classList.contains('rich-editor-table')) {\n this.checkTableSizeChange();\n } else {\n // Clear interval if no longer monitoring a table\n clearInterval(this.tableSizeInterval);\n this.tableSizeInterval = null;\n }\n }, 100);\n }\n\n /**\n * Check if table size has changed and update handles accordingly\n */\n checkTableSizeChange() {\n if (!this.activeElement || !this.activeElement.classList.contains('rich-editor-table')) {\n return;\n }\n \n const currentWidth = this.activeElement.offsetWidth;\n const currentHeight = this.activeElement.offsetHeight;\n \n // Check if dimensions have changed significantly (more than 1px to avoid floating point issues)\n if (Math.abs(currentWidth - this.lastTableWidth) > 1 || \n Math.abs(currentHeight - this.lastTableHeight) > 1) {\n \n // Update stored dimensions\n this.lastTableWidth = currentWidth;\n this.lastTableHeight = currentHeight;\n \n // Update handle positions\n this.updateHandlePosition();\n \n // Emit size change event\n this.emit('table-size-changed', {\n element: this.activeElement,\n width: currentWidth,\n height: currentHeight,\n previousWidth: this.lastTableWidth,\n previousHeight: this.lastTableHeight\n });\n }\n }\n\n /**\n * Setup mutation observer to watch for DOM changes\n */\n setupMutationObserver() {\n if (typeof MutationObserver !== 'undefined') {\n this.mutationObserver = new MutationObserver((mutations) => {\n let shouldUpdate = false;\n \n mutations.forEach((mutation) => {\n // Check if active element was removed or modified\n if (this.activeElement) {\n if (mutation.type === 'childList') {\n // Check if active element was removed\n if (mutation.removedNodes) {\n for (let node of mutation.removedNodes) {\n if (node === this.activeElement || node.contains(this.activeElement)) {\n shouldUpdate = true;\n break;\n }\n }\n }\n \n // Check if active element was modified\n if (mutation.target === this.activeElement || \n (mutation.target.nodeType === Node.ELEMENT_NODE && \n this.activeElement.contains(mutation.target))) {\n shouldUpdate = true;\n }\n }\n \n // Check for text content changes that might affect table size\n if (mutation.type === 'characterData' && this.activeElement.classList.contains('rich-editor-table')) {\n // Check if the text change is within the active table\n let target = mutation.target;\n while (target && target !== this.activeElement) {\n target = target.parentNode;\n }\n if (target === this.activeElement) {\n shouldUpdate = true;\n }\n }\n \n // Check for attribute changes that might affect size\n if (mutation.type === 'attributes' && this.activeElement.classList.contains('rich-editor-table')) {\n const attributeName = mutation.attributeName;\n // Monitor changes to style attributes that affect size\n if (attributeName === 'style' || attributeName === 'class') {\n shouldUpdate = true;\n }\n }\n }\n });\n \n if (shouldUpdate) {\n // Use setTimeout to ensure DOM is fully updated\n setTimeout(() => {\n this.checkAndUpdateHandles();\n }, 0);\n }\n });\n \n // Start observing the editor content with more comprehensive monitoring\n this.mutationObserver.observe(this.editor.editor, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class'],\n characterData: true, // Monitor text content changes\n characterDataOldValue: true\n });\n }\n }\n\n /**\n * Enable/disable aspect ratio maintenance\n */\n setMaintainAspectRatio(maintain) {\n this.options.maintainAspectRatio = maintain;\n }\n\n /**\n * Set resize constraints\n */\n setConstraints(minWidth, minHeight, maxWidth, maxHeight) {\n this.options.minWidth = minWidth || this.options.minWidth;\n this.options.minHeight = minHeight || this.options.minHeight;\n this.options.maxWidth = maxWidth || this.options.maxWidth;\n this.options.maxHeight = maxHeight || this.options.maxHeight;\n }\n\n /**\n * Destroy module\n */\n destroy() {\n this.hideHandles();\n\n // Remove global event listeners\n if (this._onDocClick) {\n this.editor.editor.removeEventListener('click', this._onEditorClick);\n document.removeEventListener('click', this._onDocClick);\n document.removeEventListener('mousemove', this._onDocMousemove);\n document.removeEventListener('mouseup', this._onDocMouseup);\n window.removeEventListener('scroll', this._onWindowScroll);\n this.editor.editor.removeEventListener('scroll', this._onEditorScroll);\n this._onEditorClick = this._onDocClick = this._onDocMousemove = null;\n this._onDocMouseup = this._onWindowScroll = this._onEditorScroll = null;\n }\n\n if (this.handlesContainer) {\n this.handlesContainer.remove();\n }\n\n // Clear table size monitoring\n if (this.tableSizeInterval) {\n clearInterval(this.tableSizeInterval);\n this.tableSizeInterval = null;\n }\n \n // Disconnect mutation observer\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n this.mutationObserver = null;\n }\n \n super.destroy();\n }\n}\n\nexport default ResizeHandles; ","/**\n * Image Popup Component - Popup for inserting images\n */\nimport { appendPopup, calculatePopupPosition, setPopupPosition } from '../utils/popup-helper.js';\n\nclass ImagePopup {\n constructor(options = {}) {\n this.options = {\n onImageInsert: null,\n editor: null,\n ...options\n };\n \n this.popup = null;\n this.isVisible = false;\n this.clickOutsideHandler = null;\n this.selectedImageSrc = null;\n this.savedSelection = null; // Save editor selection\n this.resizeHandler = null;\n \n this.createImagePopup();\n }\n\n /**\n * Create image popup\n */\n createImagePopup() {\n this.popup = document.createElement('div');\n this.popup.className = 'image-popup';\n \n const content = document.createElement('div');\n content.className = 'image-popup-content';\n \n // Title\n const title = document.createElement('h3');\n title.textContent = 'Insert image';\n title.className = 'yjd-input-title';\n content.appendChild(title);\n \n // Container\n const uploadContainer = document.createElement('div');\n uploadContainer.className = 'image-input-container';\n\n const textLabel = document.createElement('p');\n textLabel.textContent = 'Your image url';\n textLabel.className = 'yjd-input-label';\n\n const inputgroup1 = document.createElement('div');\n inputgroup1.className = 'yjd-input-upload-group';\n this.inputGroup = inputgroup1; // Store reference\n\n\n // input url\n this.urlInput = document.createElement('input');\n this.urlInput.type = 'url';\n this.urlInput.className = 'yjd-input';\n this.urlInput.placeholder = 'Please enter your image URL';\n \n\n // Hidden file input\n this.fileInput = document.createElement('input');\n this.fileInput.type = 'file';\n this.fileInput.accept = 'image/*';\n this.fileInput.className = 'image-input-hidden'; // ẩn bằng CSS\n this.fileInput.addEventListener('change', (e) => this.handleFileSelect(e));\n\n // Custom button\n const customButton = document.createElement('button');\n customButton.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" x2=\"12\" y1=\"3\" y2=\"15\"/></svg>`;\n customButton.className = 'yjd-custom-upload-button';\n this.customButton = customButton;\n customButton.addEventListener('click', () => this.fileInput.click());\n\n // Create preview container\n this.createPreviewContainer();\n\n // Append elements\n inputgroup1.appendChild(this.urlInput);\n inputgroup1.appendChild(this.fileInput);\n inputgroup1.appendChild(customButton);\n uploadContainer.appendChild(textLabel);\n uploadContainer.appendChild(inputgroup1);\n uploadContainer.appendChild(this.previewContainer);\n content.appendChild(uploadContainer);\n this.urlInput.addEventListener('input', () => {\n this.updateInsertButton();\n // Show preview if URL is valid\n const url = this.urlInput.value.trim();\n if (url && this.isValidImageUrl(url)) {\n this.showPreview(url);\n } else {\n this.removePreview();\n }\n if(this.urlInput.value.trim()){\n this.customButton.style.display = 'none';\n }else{\n this.customButton.style.display = 'flex';\n }\n });\n // Buttons\n const buttonContainer = document.createElement('div');\n buttonContainer.className = 'yjd-button-container';\n \n const cancelButton = document.createElement('button');\n cancelButton.type = 'button';\n cancelButton.className = 'image-button yjd-button-cancel';\n cancelButton.textContent = 'Cancel';\n cancelButton.addEventListener('click', () => {\n this.hide();\n // Maintain editor focus after popup close\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n this.insertButton = document.createElement('button');\n this.insertButton.type = 'button';\n this.insertButton.className = 'image-button yjd-button-confirm button-disable';\n this.insertButton.textContent = 'Add image';\n this.insertButton.disabled = true;\n this.insertButton.addEventListener('click', () => {\n this.insertImage();\n // Maintain editor focus after insert\n if (this.options.editor) {\n setTimeout(() => this.options.editor.focus(), 0);\n }\n });\n \n buttonContainer.appendChild(cancelButton);\n buttonContainer.appendChild(this.insertButton);\n content.appendChild(buttonContainer);\n \n this.popup.appendChild(content);\n appendPopup(this.popup);\n \n // Prevent focus loss when clicking on popup\n if (this.options.editor && typeof this.options.editor.preventFocusLoss === 'function') {\n this.options.editor.preventFocusLoss(this.popup);\n }\n }\n\n async handleFileSelect(e) {\n const file = e.target.files[0];\n if (!file) return;\n \n try {\n const { default: Image } = await import('../formats/image.js');\n this.selectedImageSrc = await Image.handleFileUpload(file);\n this.urlInput.value = '';\n this.showPreview(this.selectedImageSrc);\n this.updateInsertButton();\n } catch (error) {\n alert(error.message);\n }\n }\n\n updateInsertButton() {\n const hasImage = this.selectedImageSrc || this.urlInput.value.trim();\n this.insertButton.disabled = !hasImage;\n this.insertButton.classList.toggle('button-disable', !hasImage);\n }\n\n /**\n * Show image preview\n */\n showPreview(imageSrc) {\n if (!imageSrc) return;\n \n this.imagePreview.src = imageSrc;\n this.previewContainer.style.display = 'block';\n this.selectedImageSrc = imageSrc;\n \n // Hide input group\n this.toggleInputGroup(false);\n \n // Recalculate position after preview is shown to ensure buttons remain visible\n this.recalculatePosition();\n }\n\n /**\n * Remove image preview and show input again\n */\n removePreview() {\n this.selectedImageSrc = null;\n this.previewContainer.style.display = 'none';\n this.imagePreview.src = '';\n \n // Show input group and reset file input\n this.toggleInputGroup(true);\n if (this.fileInput) {\n this.fileInput.value = '';\n }\n \n this.updateInsertButton();\n \n // Recalculate position after preview is removed\n this.recalculatePosition();\n }\n\n /**\n * Toggle input group visibility\n */\n toggleInputGroup(show) {\n if (!this.inputGroup) return;\n \n if (show) {\n this.inputGroup.style.display = 'flex';\n this.inputGroup.style.visibility = 'visible';\n if (this.customButton) {\n this.customButton.style.pointerEvents = 'auto';\n }\n } else {\n this.inputGroup.style.display = 'none';\n this.inputGroup.style.visibility = 'hidden';\n }\n }\n\n /**\n * Create preview container with image and remove button\n */\n createPreviewContainer() {\n this.previewContainer = document.createElement('div');\n this.previewContainer.className = 'image-preview-container';\n this.previewContainer.style.cssText = 'display: none; position: relative;';\n \n // Image preview\n this.imagePreview = document.createElement('img');\n this.imagePreview.className = 'image-preview';\n this.imagePreview.style.cssText = 'max-width: 100%; max-height: 200px; border-radius: 8px; object-fit: contain;';\n \n // Remove button\n this.removeButton = document.createElement('button');\n this.removeButton.className = 'image-remove-button';\n this.removeButton.innerHTML = '×';\n this.removeButton.style.cssText = `\n position: absolute; top: 5px; right: 5px; background: rgba(0,0,0,0.7);\n color: white; border: none; border-radius: 50%; width: 24px; height: 24px;\n cursor: pointer; font-size: 16px; font-weight: bold;\n `;\n this.removeButton.addEventListener('click', () => this.removePreview());\n \n this.previewContainer.appendChild(this.imagePreview);\n this.previewContainer.appendChild(this.removeButton);\n }\n\n /**\n * Check if URL is a valid image URL\n */\n isValidImageUrl(url) {\n try {\n const urlObj = new URL(url);\n const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp'];\n const imageHosts = ['imgur.com', 'images.unsplash.com', 'picsum.photos', 'via.placeholder.com'];\n \n const pathname = urlObj.pathname.toLowerCase();\n const hasImageExtension = imageExtensions.some(ext => pathname.endsWith(ext));\n const isFromImageHost = imageHosts.some(host => urlObj.hostname.includes(host));\n \n return hasImageExtension || isFromImageHost;\n } catch {\n return false;\n }\n }\n\n async insertImage() {\n let src = this.selectedImageSrc || this.urlInput.value.trim();\n const alt = '';\n \n if (!src) return;\n \n // Always validate URL (both file upload and URL input)\n try {\n const { default: Image } = await import('../formats/image.js');\n const isValid = await Image.validateImageUrl(src);\n if (!isValid) {\n alert('Invalid image URL. Please check the URL and try again.');\n return;\n }\n } catch (error) {\n alert('Error validating image URL.');\n return;\n }\n \n // Restore editor selection before inserting\n this.restoreSelection();\n \n if (this.options.onImageInsert) {\n this.options.onImageInsert(src, alt);\n }\n \n this.hide();\n this.reset();\n }\n\n reset() {\n this.fileInput.value = '';\n this.urlInput.value = '';\n this.selectedImageSrc = null;\n \n // Hide preview and show input\n this.previewContainer.style.display = 'none';\n this.imagePreview.src = '';\n this.toggleInputGroup(true);\n \n this.updateInsertButton();\n this.customButton.style.display = 'block';\n }\n\n /**\n * Save current editor selection\n */\n saveSelection() {\n const selection = window.getSelection();\n if (selection && selection.rangeCount > 0) {\n this.savedSelection = selection.getRangeAt(0).cloneRange();\n }\n }\n\n /**\n * Restore editor selection\n */\n restoreSelection() {\n if (this.savedSelection) {\n const selection = window.getSelection();\n selection.removeAllRanges();\n selection.addRange(this.savedSelection);\n }\n }\n\n setupClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n }\n \n this.clickOutsideHandler = (e) => {\n if (!this.popup.contains(e.target)) {\n this.hide();\n }\n };\n \n setTimeout(() => {\n document.addEventListener('click', this.clickOutsideHandler);\n }, 100);\n }\n\n setupResizeHandler() {\n if (this.resizeHandler) {\n window.removeEventListener('resize', this.resizeHandler);\n }\n \n this.resizeHandler = () => {\n if (this.isVisible) {\n this.recalculatePosition();\n }\n };\n \n window.addEventListener('resize', this.resizeHandler);\n }\n\n removeResizeHandler() {\n if (this.resizeHandler) {\n window.removeEventListener('resize', this.resizeHandler);\n this.resizeHandler = null;\n }\n }\n\n removeClickOutside() {\n if (this.clickOutsideHandler) {\n document.removeEventListener('click', this.clickOutsideHandler);\n this.clickOutsideHandler = null;\n }\n }\n\n show(anchor) {\n if (!anchor) return;\n \n // Save current editor selection before showing popup\n this.saveSelection();\n \n // Reset state when showing popup\n this.reset();\n \n // Store anchor for recalculation\n this.currentAnchor = anchor;\n \n // Calculate and set popup position\n const position = calculatePopupPosition(anchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n \n this.popup.classList.add('visible');\n this.isVisible = true;\n \n this.setupClickOutside();\n }\n\n /**\n * Recalculate popup position to ensure it stays within viewport\n */\n recalculatePosition() {\n if (!this.currentAnchor || !this.isVisible) return;\n \n // Small delay to ensure DOM updates are complete\n setTimeout(() => {\n const position = calculatePopupPosition(this.currentAnchor, this.popup, {\n offsetY: 5,\n offsetX: 0\n });\n setPopupPosition(this.popup, position);\n }, 10);\n }\n\n hide() {\n this.popup.classList.remove('visible');\n this.isVisible = false;\n this.removeClickOutside();\n // Clear saved selection to avoid memory leaks\n this.savedSelection = null;\n }\n\n destroy() {\n this.removeClickOutside();\n \n if (this.popup && this.popup.parentNode) {\n this.popup.parentNode.removeChild(this.popup);\n }\n \n this.popup = null;\n this.isVisible = false;\n }\n}\n\nexport default ImagePopup; ","import Editor from './lib/core/editor.js';\nimport registry from './lib/core/registry.js';\nimport Module from './lib/core/module.js';\nimport { Format, InlineFormat, BlockFormat } from './lib/core/format.js';\nimport StylesLoader from './lib/styles-loader.js';\nimport { renderStatic } from './lib/static.js';\nimport { htmlToMarkdown, markdownToHtml, domToJson, jsonToHtml } from './lib/serialize.js';\n\n// Import formats\nimport Bold from './lib/formats/bold.js';\nimport Italic from './lib/formats/italic.js';\nimport Underline from './lib/formats/underline.js';\nimport Strike from './lib/formats/strike.js';\nimport Subscript from './lib/formats/subscript.js';\nimport Superscript from './lib/formats/superscript.js';\nimport Color from './lib/formats/color.js';\nimport Background from './lib/formats/background.js';\nimport Link from './lib/formats/link.js';\nimport Table from './lib/formats/table.js';\nimport Heading from './lib/formats/heading.js';\nimport FontFamily from './lib/formats/font-family.js';\nimport LineHeight from './lib/formats/line-height.js';\nimport Capitalization from './lib/formats/capitalization.js';\nimport TextAlign from './lib/formats/text-align.js';\nimport List from './lib/formats/list.js';\nimport Indent, { IndentIncrease, IndentDecrease } from './lib/formats/indent.js';\nimport Emoji from './lib/formats/emoji.js';\nimport Image from './lib/formats/image.js';\nimport Video from './lib/formats/video.js';\nimport Tag from './lib/formats/tag.js';\nimport TextSize from './lib/formats/text-size.js';\n\nimport Import from './lib/formats/import.js';\n\n// Import modules\nimport Toolbar from './lib/modules/toolbar.js';\nimport History from './lib/modules/history.js';\nimport BlockToolbar from './lib/modules/block-toolbar.js';\nimport TableToolbar from './lib/modules/table-toolbar.js';\nimport CodeView from './lib/modules/code-view.js';\nimport FindReplace from './lib/modules/find-replace.js';\nimport SlashMenu from './lib/modules/slash-menu.js';\nimport Mention from './lib/modules/mention.js';\n\nimport ResizeHandles from './lib/modules/resize-handles.js';\n\n// Import UI components\nimport ColorPicker from './lib/ui/color-picker.js';\nimport IconUtils from './lib/ui/icons.js';\nimport LinkPopup from './lib/ui/link-popup.js';\nimport TablePopup from './lib/ui/table-popup.js';\nimport TextAlignPicker from './lib/ui/text-align-picker.js';\nimport ListPicker from './lib/ui/list-picker.js';\nimport EmojiPicker from './lib/ui/emoji-picker.js';\nimport ImagePopup from './lib/ui/image-popup.js';\nimport VideoPopup from './lib/ui/video-popup.js';\nimport TagPopup from './lib/ui/tag-popup.js';\n\nimport createCustomButton from './lib/ui/select-button.js';\n\n\n\n// Register default formats\nregistry.register('formats/bold', Bold, true);\nregistry.register('formats/italic', Italic, true);\nregistry.register('formats/underline', Underline, true);\nregistry.register('formats/strike', Strike, true);\nregistry.register('formats/subscript', Subscript, true);\nregistry.register('formats/superscript', Superscript, true);\nregistry.register('formats/color', Color, true);\nregistry.register('formats/background', Background, true);\nregistry.register('formats/link', Link, true);\nregistry.register('formats/table', Table, true);\nregistry.register('formats/heading', Heading, true);\nregistry.register('formats/font-family', FontFamily, true);\nregistry.register('formats/line-height', LineHeight, true);\nregistry.register('formats/capitalization', Capitalization, true);\nregistry.register('formats/text-align', TextAlign, true);\nregistry.register('formats/list', List, true);\nregistry.register('formats/indent', Indent, true);\nregistry.register('formats/indent-increase', IndentIncrease, true);\nregistry.register('formats/indent-decrease', IndentDecrease, true);\nregistry.register('formats/emoji', Emoji, true);\nregistry.register('formats/image', Image, true);\nregistry.register('formats/video', Video, true);\nregistry.register('formats/tag', Tag, true);\nregistry.register('formats/text-size', TextSize, true);\n\nregistry.register('formats/import', Import, true);\n\n// Register default modules\nregistry.register('modules/toolbar', Toolbar, true);\nregistry.register('modules/history', History, true);\nregistry.register('modules/block-toolbar', BlockToolbar, true);\nregistry.register('modules/table-toolbar', TableToolbar, true);\nregistry.register('modules/code-view', CodeView, true);\nregistry.register('modules/find-replace', FindReplace, true);\nregistry.register('modules/slash-menu', SlashMenu, true);\nregistry.register('modules/mention', Mention, true);\n\nregistry.register('modules/resize-handles', ResizeHandles, true);\n\n// Register UI components\nregistry.register('ui/color-picker', ColorPicker, true);\nregistry.register('ui/text-align-picker', TextAlignPicker, true);\nregistry.register('ui/list-picker', ListPicker, true);\nregistry.register('ui/emoji-picker', EmojiPicker, true);\nregistry.register('ui/image-popup', ImagePopup, true);\nregistry.register('ui/video-popup', VideoPopup, true);\nregistry.register('ui/tag-popup', TagPopup, true);\n\nregistry.register('ui/custom-button', createCustomButton, true);\n\n\n\n// Load CSS styles\nStylesLoader.loadStyles().catch(error => {\n console.warn('Could not load Rich Editor styles:', error);\n});\n\n// Main Editor class with registration system\nclass RichEditor extends Editor {\n /**\n * Register a module, format, or theme\n * @param {string|object} path - Registration path\n * @param {*} definition - Class definition\n * @param {boolean} suppressWarning - Suppress overwrite warnings\n */\n static register(path, definition, suppressWarning = false) {\n registry.register(path, definition, suppressWarning);\n }\n\n /**\n * Get registered item\n * @param {string} path - Registration path\n */\n static get(path) {\n return registry.get(path);\n }\n\n /**\n * Create new editor instance\n * @param {string|Element} selector - DOM selector or element\n * @param {object} options - Editor options\n */\n static create(selector, options = {}) {\n return new RichEditor(selector, options);\n }\n\n /**\n * Progressive-enhance a <textarea>: hide it, mount an editor in its place,\n * and keep the textarea's value in sync so existing form submits keep working.\n *\n * const ed = RichEditor.fromTextarea(document.querySelector('#body'), {\n * // any editor option; `format` chooses how the textarea is read/written:\n * format: 'html' | 'markdown', // default 'html'\n * });\n *\n * The textarea's current value seeds the editor (parsed as HTML or Markdown\n * per `format`). Binding is TWO-WAY: editor edits update textarea.value (and\n * fire native input/change events), and writing `textarea.value = …` from app\n * code (e.g. resetting a form) updates the editor. The returned editor also\n * carries a small controller:\n *\n * const ed = RichEditor.fromTextarea('#body', { format: 'markdown' });\n * ed.setValue(md); // load new content into the editor\n * ed.getValue(); // current content (html or markdown per `format`)\n * ed.destroy(); // remove the editor, restore the textarea + last value\n *\n * @param {HTMLTextAreaElement|string} textarea Element or selector.\n * @param {object} [options] Editor options + optional `format`.\n * @returns {RichEditor}\n */\n static fromTextarea(textarea, options = {}) {\n const ta = typeof textarea === 'string' ? document.querySelector(textarea) : textarea;\n if (!ta) throw new Error('RichEditor.fromTextarea: textarea not found');\n\n const format = options.format === 'markdown' ? 'markdown' : 'html';\n const read = (ed) => (format === 'markdown' ? ed.getMarkdown() : ed.getContent());\n const write = (ed, v) => (format === 'markdown' ? ed.setMarkdown(v || '') : ed.setHTML(v || ''));\n\n // Mount point right after the textarea; hide the original.\n const mount = document.createElement('div');\n ta.after(mount);\n ta.style.display = 'none';\n ta.setAttribute('aria-hidden', 'true');\n\n // Take over textarea.value so app writes flow into the editor and reads\n // reflect it. Keep the native descriptor to restore on destroy + to set the\n // real value (for form submission) without re-triggering our setter.\n const nativeDesc = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');\n let raw = ta.value || '';\n let syncing = false; // guards editor→textarea writes from re-entering setValue\n\n const initial = raw;\n const editor = new RichEditor(mount, {\n width: '100%',\n ...options,\n content: options.content != null ? options.content\n : (format === 'markdown' ? markdownToHtml(initial) : initial),\n });\n\n Object.defineProperty(ta, 'value', {\n configurable: true,\n get() { return raw; },\n set(v) {\n raw = v == null ? '' : String(v);\n nativeDesc.set.call(ta, raw); // keep the real textarea value for submits\n if (!syncing) write(editor, raw); // app write → update editor\n },\n });\n\n // editor edit → push to textarea + fire native events for app bindings.\n const onChange = () => {\n const next = read(editor);\n if (raw === next) return;\n raw = next;\n syncing = true;\n nativeDesc.set.call(ta, next);\n ta.dispatchEvent(new Event('input', { bubbles: true }));\n ta.dispatchEvent(new Event('change', { bubbles: true }));\n syncing = false;\n };\n editor.on('change', onChange);\n onChange(); // normalise textarea to the editor's serialization up front.\n\n // Controller surface on the editor instance.\n editor.textarea = ta;\n editor.getValue = () => read(editor);\n editor.setValue = (v) => { write(editor, v); };\n const baseDestroy = editor.destroy.bind(editor);\n editor.destroy = () => {\n const last = read(editor);\n editor.off('change', onChange);\n baseDestroy();\n mount.remove();\n delete ta.value; // restore the prototype's value accessor\n nativeDesc.set.call(ta, last);\n ta.style.display = '';\n ta.removeAttribute('aria-hidden');\n };\n return editor;\n }\n}\n\n// Export classes for extension. `yjd` is the brand-aligned name; `RichEditor`\n// is kept as an alias for backward compatibility.\nexport {\n RichEditor as default,\n RichEditor,\n RichEditor as yjd,\n Editor,\n Module,\n Format,\n InlineFormat,\n BlockFormat,\n registry\n};\n\n// Export formats\nexport {\n Bold,\n Italic,\n Underline,\n Strike,\n Subscript,\n Superscript,\n Color,\n Background,\n Link,\n Table,\n Heading,\n FontFamily,\n LineHeight,\n Capitalization,\n TextAlign,\n List,\n Indent,\n IndentIncrease,\n IndentDecrease,\n Emoji,\n Image,\n Video,\n Tag,\n TextSize,\n\n Import\n};\n\n// Export modules\nexport {\n Toolbar,\n History,\n BlockToolbar,\n TableToolbar,\n CodeView,\n FindReplace,\n SlashMenu,\n Mention,\n\n ResizeHandles\n};\n\n// Export UI components\nexport {\n ColorPicker,\n IconUtils,\n LinkPopup,\n TablePopup,\n TextAlignPicker,\n ListPicker,\n EmojiPicker,\n ImagePopup,\n VideoPopup,\n TagPopup,\n\n createCustomButton\n};\n\n// Static rendering + serialization helpers\nexport {\n renderStatic,\n htmlToMarkdown,\n markdownToHtml,\n domToJson,\n jsonToHtml\n};\n\n\n\n\n/**\n * Utility function to create editor instance\n * @param {string|Element} selector - DOM selector or element\n * @param {object} options - Editor options\n */\nexport function createEditor(selector, options = {}) {\n return new RichEditor(selector, options);\n} ","import cssText from './styles.css.js';\n\n/**\n * CSS Loader - Load và inject CSS styles vào DOM\n * Thay thế cho việc sử dụng inline styles\n *\n * CSS được import trực tiếp dưới dạng chuỗi (sinh từ styles.css), nên hoạt động\n * cả với native ESM trong trình duyệt lẫn khi đóng gói bằng Rollup/CDN — không\n * còn phụ thuộc vào fetch runtime (vốn làm hỏng việc dùng qua npm/CDN).\n */\nclass StylesLoader {\n static loaded = false;\n static styleElement = null;\n\n /**\n * Load CSS (inject inlined stylesheet vào <head>)\n * Trả về Promise để giữ tương thích với code gọi cũ (.catch/.then).\n */\n static loadStyles() {\n if (this.loaded) return Promise.resolve();\n\n try {\n this.styleElement = document.createElement('style');\n this.styleElement.id = 'rich-editor-styles';\n this.styleElement.textContent = cssText;\n document.head.appendChild(this.styleElement);\n this.loaded = true;\n } catch (error) {\n // Fallback: load minimal styles\n this.loadFallbackStyles();\n }\n\n return Promise.resolve();\n }\n\n /**\n * Load minimal fallback styles nếu không thể load từ file\n */\n static loadFallbackStyles() {\n const fallbackCSS = `\n .yjd-rich-editor { \n position: relative; \n background: #fff; \n border: 1px solid #ddd; \n border-radius: 4px; \n display: flex; \n flex-direction: column; \n font-family: system-ui, sans-serif; \n }\n .yjd-rich-editor .rich-editor-area { \n flex: 1; \n padding: 20px; \n outline: none; \n min-height: 100px; \n }\n .yjd-rich-editor .rich-editor-toolbar { \n display: flex; \n gap: 4px; \n padding: 8px; \n border-bottom: 1px solid #ddd; \n background: #f9f9f9; \n }\n .yjd-rich-editor .rich-editor-toolbar-btn { \n padding: 4px 8px; \n border: 1px solid #ccc; \n border-radius: 3px; \n background: #fff; \n cursor: pointer; \n }\n .yjd-rich-editor .table-grid-selector { \n position: absolute; \n background: white; \n border: 1px solid #ccc; \n border-radius: 4px; \n padding: 10px; \n box-shadow: 0 2px 8px rgba(0,0,0,0.15); \n z-index: 1000; \n display: none; \n }\n .yjd-rich-editor .table-grid-cell { \n width: 20px; \n height: 20px; \n border: 1px solid #ddd; \n cursor: pointer; \n background: white; \n }\n `;\n\n this.styleElement = document.createElement('style');\n this.styleElement.id = 'rich-editor-styles-fallback';\n this.styleElement.textContent = fallbackCSS;\n document.head.appendChild(this.styleElement);\n \n this.loaded = true;\n }\n\n /**\n * Unload styles\n */\n static unloadStyles() {\n if (this.styleElement && this.styleElement.parentNode) {\n this.styleElement.parentNode.removeChild(this.styleElement);\n this.styleElement = null;\n this.loaded = false;\n }\n }\n\n /**\n * Check if styles are loaded\n */\n static isLoaded() {\n return this.loaded;\n }\n\n /**\n * Reload styles\n */\n static async reloadStyles() {\n this.unloadStyles();\n await this.loadStyles();\n }\n\n /**\n * Add custom CSS\n */\n static addCustomCSS(css, id = 'rich-editor-custom') {\n // Remove existing custom styles\n const existing = document.getElementById(id);\n if (existing) {\n existing.remove();\n }\n\n // Add new custom styles\n const style = document.createElement('style');\n style.id = id;\n style.textContent = css;\n document.head.appendChild(style);\n \n }\n}\n\nexport default StylesLoader; ","// AUTO-GENERATED from lib/styles.css by scripts/generate-css.js — do not edit directly.\nexport default \"@keyframes rte-pop-in{0%{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}@keyframes yjd-slash-in{0%{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}@keyframes yjd-spin{to{transform:rotate(360deg)}}.yjd-rich-editor,.yjd-rich-editor .rich-editor-area{background:#fff;position:relative;box-sizing:border-box}.yjd-rich-editor{border:1px solid #d1d5db;border-radius:8px;overflow:hidden;margin:0 auto;display:flex;flex-direction:column}@layer base{.yjd-rich-editor .toolbar-container ::file-selector-button,.yjd-rich-editor .toolbar-container button,.yjd-rich-editor .toolbar-container input,.yjd-rich-editor .toolbar-container optgroup,.yjd-rich-editor .toolbar-container select,.yjd-rich-editor .toolbar-container textarea{font:500 14px/1.4\\\"Inter\\\",Arial,sans-serif!important;letter-spacing:normal!important;font-feature-settings:normal!important;font-variation-settings:normal!important}.yjd-rich-editor .emoji-picker-popup .emoji-button,.yjd-rich-editor .emoji-picker-popup button.emoji-button{font-size:20px!important}}@layer base{.yjd-rich-editor .rich-editor-popup-container ::file-selector-button,.yjd-rich-editor .rich-editor-popup-container button,.yjd-rich-editor .rich-editor-popup-container input,.yjd-rich-editor .rich-editor-popup-container optgroup,.yjd-rich-editor .rich-editor-popup-container select,.yjd-rich-editor .rich-editor-popup-container textarea{font:500 14px/1.4\\\"Inter\\\",Arial,sans-serif!important;letter-spacing:normal!important;font-feature-settings:normal!important;font-variation-settings:normal!important}.yjd-rich-editor .emoji-picker-popup .emoji-button,.yjd-rich-editor .emoji-picker-popup button.emoji-button{font-size:20px!important}}.yjd-rich-editor .rich-editor-area{flex:1 1 auto;min-height:40px;outline:0;overflow-y:auto;border:0;text-align:left;font-family:inherit;color:#2c3e50}.yjd-rich-editor .rich-editor-area[data-placeholder]:before{content:attr(data-placeholder);color:#9ca3af;pointer-events:none;position:absolute;top:0;left:0;right:0;padding:inherit;box-sizing:border-box;font:inherit;font-style:italic;line-height:inherit;display:none;z-index:1}.yjd-rich-editor .custom-select-item-button.current .item-checkmark,.yjd-rich-editor .rich-editor-area div,.yjd-rich-editor .rich-editor-area[data-placeholder].placeholder-visible:before{display:block}.yjd-rich-editor .rich-editor-area b,.yjd-rich-editor .rich-editor-area strong{font-weight:700}.yjd-rich-editor .rich-editor-area em,.yjd-rich-editor .rich-editor-area i{font-style:italic}.yjd-rich-editor .rich-editor-area u{text-decoration:underline}.yjd-rich-editor .rich-editor-area a{color:#2563eb;text-decoration:underline;cursor:pointer;color:var(--rte-accent-ink);text-decoration-color:var(--rte-accent-ring);text-underline-offset:2px}.yjd-content p,.yjd-rich-editor .rich-editor-area p{margin:0 0 1em}.yjd-content h1,.yjd-rich-editor .rich-editor-area h1{font-size:2em;font-weight:700;margin:.67em 0}.yjd-content h2,.yjd-rich-editor .rich-editor-area h2{font-size:1.5em;font-weight:700;margin:.75em 0}.yjd-content h3,.yjd-rich-editor .rich-editor-area h3{font-size:1.25em;font-weight:700;margin:.83em 0}.yjd-content h4,.yjd-rich-editor .rich-editor-area h4{font-size:1.1em;font-weight:700;margin:1em 0}.yjd-content h5,.yjd-rich-editor .rich-editor-area h5{font-size:1em;font-weight:700;margin:1.25em 0}.yjd-content h6,.yjd-rich-editor .rich-editor-area h6{font-size:.875em;font-weight:700;margin:1.5em 0;color:#555}.yjd-content ol,.yjd-content ul{margin:0 0 1em 2em;padding:0}.yjd-rich-editor .rich-editor-area ol,.yjd-rich-editor .rich-editor-area ul{padding:0}.yjd-rich-editor .rich-editor-area li{margin:.25em 0 4px;line-height:1.6}.yjd-rich-editor .rich-editor-area code,.yjd-rich-editor .rich-editor-area pre{font-family:Consolas,Menlo,Monaco,\\\"Courier New\\\",monospace;background:#f1f2f3;overflow-x:auto}.yjd-rich-editor .rich-editor-area pre{border-radius:4px;display:block;margin:1em 0;background-color:#f1f2f3;width:96%;white-space:pre;word-wrap:normal;background:#20242f;color:#ececf5;border-radius:var(--rte-radius-md);padding:14px 16px;border:0}.yjd-rich-editor .rich-editor-area code{display:inline;background:var(--rte-chrome-2);border-radius:6px;padding:2px 6px;font-size:.9em}.yjd-content blockquote{margin:1em 0;padding:4px 12px}.yjd-content blockquote,.yjd-rich-editor .rich-editor-area blockquote{border-left:4px solid #d1d5db;color:#555;font-style:italic;background:#f9fafb}.yjd-rich-editor .rich-editor-area sub{font-size:.75em;vertical-align:sub;line-height:1}.yjd-rich-editor .rich-editor-area sup{font-size:.75em;vertical-align:super;line-height:1}.yjd-rich-editor .rich-editor-area:empty::before{content:\\\"\\\";display:block}.yjd-rich-editor .rich-editor-statusbar{position:relative;z-index:1000;border-top:1px solid #d1d5db;background:#f9fafb;font-size:13px;color:#6b7280;font-family:inherit;min-height:32px;box-sizing:border-box}.yjd-rich-editor .rich-editor-toolbar-container{border-bottom:1px solid #d1d5db;position:relative;z-index:1000}.yjd-rich-editor .rich-editor-toolbar-1,.yjd-rich-editor .rich-editor-toolbar-2{gap:20px;background:#fcfcfc;min-height:48px;box-sizing:border-box;width:100%;z-index:1000;position:relative}.yjd-rich-editor .rich-editor-toolbar-2{display:none;overflow:hidden}.yjd-rich-editor .rich-editor-toolbar-2.visible{display:flex}.yjd-rich-editor .rich-editor-toolbar-btn{width:22px;border-radius:4px;font-size:14px;font-weight:500}.yjd-rich-editor .custom-select-button span{justify-content:left!important}.yjd-rich-editor .custom-select-button:focus{outline:0;border-color:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.1)}.yjd-rich-editor .rich-editor-toolbar-btn span{display:flex!important;align-items:center;justify-content:center}.yjd-rich-editor .rich-editor-toolbar-btn:hover{background:#eee!important;cursor:pointer}.yjd-rich-editor .block-toolbar-btn{border-radius:4px;padding:4px;cursor:pointer;font-size:14px;font-weight:500;align-items:center;justify-content:center;display:inline-flex}.yjd-rich-editor .block-toolbar-btn:hover{background:#eee}.yjd-rich-editor .block-toolbar-btn.active{background:#ccc;color:#136fdf!important}.yjd-rich-editor .block-toolbar-btn.active svg,.yjd-rich-editor .block-toolbar-btn.active svg path{fill:#136fdf!important}.yjd-rich-editor .block-toolbar-container{display:flex;align-items:center;background:#fff;border-radius:6.9px;box-shadow:0 4px 24px rgba(0,0,0,.13)}.yjd-rich-editor .block-toolbar-arrow{position:absolute;bottom:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;filter:drop-shadow(0 2px 4px rgba(0,0,0,.1))}.yjd-rich-editor .table-toolbar-container{display:flex;align-items:center;background:#fff;border-radius:6px;box-shadow:0 4px 24px rgba(0,0,0,.13)}.yjd-rich-editor .table-toolbar-arrow{position:absolute;bottom:-6px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;filter:drop-shadow(0 2px 4px rgba(0,0,0,.1))}.yjd-rich-editor .block-toolbar{display:none;position:absolute;z-index:1001;background:0 0;border:0;border-radius:6.9px;box-sizing:border-box;padding:0;gap:0;flex-direction:column;align-items:center}.yjd-rich-editor .block-toolbar.visible{display:flex}.yjd-rich-editor .color-picker-popup{position:absolute;background:#fff;display:none;border-radius:6.9px;padding:8px;z-index:1000;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .capitalization-select-popup.visible,.yjd-rich-editor .color-picker-popup.visible,.yjd-rich-editor .custom-select-popup.visible,.yjd-rich-editor .font-family-select-popup.visible,.yjd-rich-editor .heading-select-popup.visible,.yjd-rich-editor .line-height-select-popup.visible,.yjd-rich-editor .text-size-select-popup.visible{display:block!important}.yjd-rich-editor .capitalization-select-popup,.yjd-rich-editor .custom-select-popup,.yjd-rich-editor .font-family-select-popup,.yjd-rich-editor .heading-select-popup,.yjd-rich-editor .line-height-select-popup,.yjd-rich-editor .text-size-select-popup{position:absolute;background:#fff;display:none;border:1px solid #d1d5db;border-radius:6.9px;z-index:1002;max-height:200px;overflow-y:auto;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .image-popup,.yjd-rich-editor .link-popup,.yjd-rich-editor .tag-popup,.yjd-rich-editor .video-popup{width:299px}.yjd-rich-editor .item-list{display:flex;flex-direction:column;gap:2px}.yjd-rich-editor .heading-select-popup{width:190px;max-width:100vw}.yjd-rich-editor .capitalization-select-popup{width:150px;max-width:100vw}.yjd-rich-editor .font-family-select-popup{width:190px;max-width:100vw}.yjd-rich-editor .line-height-select-popup{width:80px;max-width:100vw}.yjd-rich-editor .text-size-select-popup{width:150px;max-width:100vw}.yjd-rich-editor .custom-select-item-button{width:100%;border:0;background:0 0;border-radius:6px;cursor:pointer;text-align:left;transition:all .2s ease;font-size:14px;line-height:1.4;color:#111827;display:flex;align-items:center;justify-content:space-between;pointer-events:auto}.yjd-rich-editor .custom-select-item-button:hover{background:#eee}.yjd-rich-editor .custom-select-item-button.current{background:#eee!important}.yjd-rich-editor .custom-select-item-button .item-checkmark{display:none}.yjd-rich-editor .item-text{flex:1}.yjd-rich-editor .dropdown-icon{display:inline-flex!important;align-items:center;justify-content:center;width:16px;height:16px;flex:0 0 auto}.yjd-rich-editor .dropdown-icon svg{width:16px;height:16px;display:block;stroke:currentColor;fill:none}.yjd-rich-editor .color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:6px 4px;margin-bottom:12px}.yjd-rich-editor .color-button{width:24px;height:24px;border:0;border-radius:50%;cursor:pointer;transition:all .2s ease;padding:0;margin:0;pointer-events:auto}.yjd-rich-editor .color-button:hover{transform:scale(1.1);border-color:#374151;box-shadow:0 2px 4px rgba(0,0,0,.1)}.yjd-rich-editor .custom-color-container{position:relative;padding-top:8px;display:flex;align-items:center;gap:8px;z-index:0}.yjd-rich-editor .custom-color-container::before{content:\\\"\\\";position:absolute;top:0;left:-8px;width:calc(100% + 16px);height:0;border-top:1px solid #e5e7eb;z-index:-1}.yjd-rich-editor .custom-color-label{font-size:12px;color:#6b7280;margin:0}.yjd-rich-editor .yjd-input-label,.yjd-rich-editor .yjd-input-title{margin:0;color:#252424;font-size:16px;font-style:normal;line-height:normal}.yjd-rich-editor .yjd-input-label{font-size:14px;font-weight:700}.yjd-rich-editor .yjd-input-group{display:flex;flex-direction:column;gap:8px}.yjd-rich-editor .yjd-input{flex:1 1 auto;min-width:0;max-width:100%;max-height:37px;border-radius:10px;border:1px solid #e1e1e1;color:#252424!important}.yjd-rich-editor .yjd-input:focus{border-color:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.1)}.yjd-rich-editor .yjd-button-container{display:flex;justify-content:flex-end;gap:4px}.yjd-rich-editor .yjd-button-cancel,.yjd-rich-editor .yjd-button-confirm{border-radius:6px;align-items:center;box-shadow:0 1px 3.3px 0 rgba(0,0,0,.04);font-style:normal;line-height:normal;cursor:pointer}.yjd-rich-editor .yjd-button-confirm{background:#181616}.yjd-rich-editor .yjd-button-cancel{border:1px solid #e0e0e0;background:#fff;color:#2a2727}.yjd-rich-editor .button-disable{cursor:not-allowed}.yjd-rich-editor .link-popup{position:absolute;background:#fff;font-family:\\\"Lato\\\",sans-serif;display:none;border:0;border-radius:10px;z-index:1000;margin:0;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .link-popup.visible{display:block!important}.yjd-rich-editor .link-popup-content{display:flex;flex-direction:column;gap:16px;padding:16px}.yjd-rich-editor .yjd-input-upload-group{display:flex;align-items:center;gap:4px}.yjd-rich-editor .yjd-custom-upload-button{display:flex!important;align-items:center;justify-content:center;padding:0;border:1px solid #e1e1e1;background:#fff;width:34px;height:34px;border-radius:10px;cursor:pointer}.yjd-rich-editor .yjd-suggestions-list{display:flex;flex-wrap:wrap;gap:8px;flex:1 1 auto;min-width:0;max-width:100%}.yjd-rich-editor .yjd-suggestion-button{border:0;display:flex;padding:4px 10px;justify-content:center;align-items:center;border-radius:30px;background:#f5f5f5;cursor:pointer;font-size:14px;color:#252424!important}.yjd-rich-editor .yjd-select-input{border-radius:10px;border:1px solid #e1e1e1;max-height:37px;align-items:center;color:#252424!important}.yjd-rich-editor .yjd-select-input:focus{border-color:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.1)}.yjd-rich-editor .table-popup{position:absolute;background:#fff;font-family:\\\"Lato\\\",sans-serif;display:none;border:1px solid #d1d5db;border-radius:6.9px;z-index:1000;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .table-popup.visible{display:block!important}.yjd-rich-editor .table-popup-content{padding:8px;display:flex;flex-direction:column;align-items:center;gap:12px;position:relative}.yjd-rich-editor .table-popup-content>*{position:relative;width:100%}.yjd-rich-editor .table-popup-content>:not(:last-child)::after{content:\\\"\\\";position:absolute;bottom:-8px;left:-8px;right:-8px;height:1px;background-color:#e5e7eb}.yjd-rich-editor .table-grid-selector{display:grid;grid-template-columns:repeat(8,1fr);gap:1px;border-bottom:1px solid #eee}.yjd-rich-editor .table-size-display{display:flex;align-items:center;justify-content:center;font-family:\\\"Lato\\\",sans-serif;color:#252424!important;font-size:12px;font-style:normal;font-weight:400;line-height:normal}.yjd-rich-editor .table-grid-cell{width:20px;height:20px;background:#fff;border:1px solid #eee;cursor:pointer;transition:background-color .1s ease;border-radius:2px}.yjd-rich-editor .table-grid-cell.highlighted,.yjd-rich-editor .table-grid-cell:hover{background:#eee}.yjd-rich-editor .rich-editor-table{border-collapse:collapse;width:100%;max-width:100%;box-sizing:border-box;margin:16px 0;font-size:14px;line-height:1.5;position:relative;display:table;table-layout:fixed}.yjd-rich-editor .rich-editor-table td{box-sizing:border-box;border:1px solid #d1d5db;padding:8px 12px;min-height:28px;vertical-align:top;background:#fff;overflow-wrap:break-word}.yjd-rich-editor .rich-editor-table td:focus{outline-offset:-2px;background:#fefefe}.yjd-rich-editor .table-toolbar{position:absolute;display:none;z-index:1002;background:0 0;border:0;border-radius:6.9px;opacity:0;pointer-events:none}.yjd-rich-editor .table-toolbar-group{display:flex;gap:8px;align-items:center}.yjd-rich-editor .table-toolbar.visible{display:block;opacity:1;pointer-events:auto;transform:translateY(0)}.yjd-rich-editor .table-toolbar-btn{padding:4px;display:inline-flex;align-items:center;justify-content:center;border-radius:4px;font-size:12px;font-weight:500;cursor:pointer;transition:all .15s ease;white-space:nowrap}.yjd-rich-editor .table-toolbar-btn:hover{background:#f3f4f6;color:#111827}.yjd-rich-editor .table-toolbar-btn:active{background:#e5e7eb;border-color:#9ca3af;color:#136fdf!important}@media (max-width:768px){.yjd-rich-editor .table-toolbar-container{flex-wrap:wrap;max-width:200px;gap:2px}.yjd-rich-editor .table-toolbar-btn{height:24px;min-width:24px;font-size:10px;padding:2px 4px}}.yjd-rich-editor .text-align-picker-popup{position:absolute;background:#fff;display:none;border:1px solid #d1d5db;border-radius:6.9px;z-index:1000;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .text-align-picker-popup.visible{display:block!important}.yjd-rich-editor .align-button-container{display:flex}.yjd-rich-editor .align-button .icon-wrapper{display:flex;margin-left:8px;align-items:center;justify-content:center}.yjd-rich-editor .align-button .label-text{color:#252424;font-size:12px;font-style:normal;font-weight:400;line-height:normal}.yjd-rich-editor .align-button{border-radius:3.456px;background:#fff;border:0;align-items:center;display:flex;cursor:pointer;pointer-events:auto}.yjd-rich-editor .align-button:hover{background:#f3f4f6}.yjd-rich-editor .align-button.active{background:#e5e7eb}.yjd-rich-editor .align-button.active svg{color:#1f2937}.yjd-rich-editor .list-picker-popup{position:absolute;background:#fff;display:none;border-radius:6.9px;box-shadow:0 4px 16px rgba(0,0,0,.1);z-index:1000;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .list-picker-popup.visible{display:block!important}.yjd-rich-editor .list-button-container{display:grid;grid-template-columns:repeat(3,1fr)}.yjd-rich-editor .list-button{border-radius:3.46px;display:flex}.yjd-rich-editor .list-button:hover{background:#f3f4f6}.yjd-rich-editor .list-button.active{background:#eee}.yjd-rich-editor .list-button svg{color:#374151}.yjd-rich-editor .list-button.active svg{color:#fff}.yjd-rich-editor .rich-editor-area ul.checklist{list-style:none;padding-left:0}.yjd-rich-editor .rich-editor-area ul.checklist li{position:relative;padding-left:25px;margin-bottom:4px}.yjd-rich-editor .rich-editor-area ul.checklist li input[type=checkbox]{position:absolute;left:0;top:2px;margin:0;cursor:pointer}.yjd-rich-editor .rich-editor-area ol,.yjd-rich-editor .rich-editor-area ul{margin:12px 0;padding-left:20px}.yjd-rich-editor .rich-editor-area .indented,.yjd-rich-editor .rich-editor-area [style*=padding-left]{transition:padding-left .2s ease}.yjd-rich-editor .indent-decrease-btn svg,.yjd-rich-editor .indent-increase-btn svg{width:16px;height:16px;fill:#454545}.yjd-rich-editor .indent-decrease-btn:hover svg,.yjd-rich-editor .indent-increase-btn:hover svg{fill:#454545}.yjd-rich-editor .indent-decrease-btn:disabled,.yjd-rich-editor .indent-increase-btn:disabled{opacity:.5;cursor:not-allowed}.yjd-rich-editor .indent-decrease-btn:disabled svg,.yjd-rich-editor .indent-increase-btn:disabled svg{fill:#9ca3af}.yjd-rich-editor .emoji-picker-popup{position:absolute;z-index:10000;background:#fff;border-radius:8px;padding:8px;display:none;overflow-y:auto;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .emoji-picker-popup.visible{display:block}.yjd-rich-editor .emoji-grid{display:grid;grid-template-columns:repeat(10,1fr);gap:4px;max-height:240px;overflow-y:auto}.yjd-rich-editor .emoji-text-message{margin-top:4px;color:#71787c;text-align:center;font-family:\\\"Segoe UI\\\",Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:normal}.yjd-rich-editor .emoji-button{width:28px;height:28px;border:0;background:0 0;padding:0;border-radius:4px;cursor:pointer;font-size:20px!important;display:flex;align-items:center;justify-content:center;transition:background-color .2s ease;pointer-events:auto}.yjd-rich-editor .emoji-button:hover{background-color:#f3f4f6}.yjd-rich-editor .emoji{font-size:1.1em;line-height:1}.yjd-rich-editor .image-popup{position:absolute;z-index:10000;background:#fff;font-family:\\\"Lato\\\",sans-serif;border-radius:10px;display:none;margin:0;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .image-popup.visible,.yjd-rich-editor .tag-popup.visible,.yjd-rich-editor .video-popup.visible{display:block}.yjd-rich-editor .image-popup-content{display:flex;flex-direction:column;gap:16px;padding:16px}.yjd-rich-editor .image-input-container{gap:8px;display:flex;flex-direction:column}.yjd-rich-editor .image-input-hidden{display:none}.yjd-rich-editor .video-popup{position:absolute;z-index:10000;background:#fff;font-family:\\\"Lato\\\",sans-serif;border-radius:10px;display:none;margin:0;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .video-popup-content{display:flex;flex-direction:column;gap:16px;padding:16px}.yjd-rich-editor .video-input-container{gap:8px;display:flex;flex-direction:column}.yjd-rich-editor .inserted-image,.yjd-rich-editor .inserted-video{max-width:100%;height:auto;margin:4px 0;border-radius:4px;display:block}.yjd-rich-editor .inserted-video{background:#000}.yjd-rich-editor .tag-popup{position:absolute;z-index:10000;background:#fff;font-family:\\\"Lato\\\",sans-serif;border-radius:10px;display:none;margin:0;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tag-popup{max-height:80vh;overflow-y:auto}.tag-suggestions-container{max-height:200px;overflow-y:auto}.yjd-rich-editor .tag-popup-content{display:flex;flex-direction:column;gap:16px;padding:16px}.yjd-rich-editor .tag-type-container{gap:8px;display:flex;flex-direction:column}.yjd-rich-editor .custom-tag{display:inline-block;padding:2px 6px;border-radius:12px;font-size:.85em;font-weight:500;cursor:pointer;margin:0 2px}.yjd-rich-editor .custom-tag.tag-mention{background:#dbeafe;color:#1d4ed8}.yjd-rich-editor .custom-tag.tag-hashtag{background:#dcfce7;color:#166534}.yjd-rich-editor .custom-tag.tag-custom{background:#fef3c7;color:#92400e}.yjd-rich-editor .custom-tag:hover{opacity:.8}.yjd-rich-editor .import-popup{position:absolute;z-index:10000;background:#fff;border:1px solid #d1d5db;border-radius:8px;display:none;width:400px;max-width:90vw;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.yjd-rich-editor .import-popup.visible{display:block}.yjd-rich-editor .import-popup-content{padding:20px}.yjd-rich-editor .import-popup-title{margin:0 0 20px;font-size:18px;font-weight:600;color:#1f2937}.yjd-rich-editor .import-type-container{margin-bottom:20px}.yjd-rich-editor .import-input-label{display:block;margin-bottom:8px;font-size:14px;font-weight:500;color:#374151}.yjd-rich-editor .import-type-select{border:1px solid #d1d5db;border-radius:6px;font-size:14px;background:#fff;color:#374151}.yjd-rich-editor .import-type-select:focus{border-color:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.1)}.yjd-rich-editor .import-file-input{width:100%;padding:10px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:14px;background:#fff;color:#374151;margin-bottom:16px;cursor:pointer;box-sizing:border-box}.yjd-rich-editor .import-file-input:disabled{background-color:#f3f4f6;cursor:not-allowed;opacity:.5}.yjd-rich-editor .import-file-input:focus{outline:0;border-color:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.1)}.yjd-rich-editor .import-file-info{padding:12px;background-color:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;margin-bottom:16px;font-size:14px;color:#374151}.yjd-rich-editor .import-file-info div{margin-bottom:4px}.yjd-rich-editor .import-file-info div:last-child{margin-bottom:0}.yjd-rich-editor .import-button-container{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.yjd-rich-editor .import-button{padding:10px 20px;border:1px solid #d1d5db;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s}.yjd-rich-editor .import-button:hover{background-color:#f3f4f6}.yjd-rich-editor .import-button.import-button-main{background-color:#3b82f6;color:#fff;border-color:#3b82f6}.yjd-rich-editor .import-button.import-button-main:hover{background-color:#2563eb}.yjd-rich-editor .import-button.import-button-main:disabled{background-color:#9ca3af;border-color:#9ca3af;cursor:not-allowed}.yjd-rich-editor .import-button.cancel-button{background-color:#fff;color:#6b7280}.yjd-rich-editor .import-button.cancel-button:hover,.yjd-rich-editor .imported-table tr:hover{background-color:#f9fafb}.yjd-rich-editor .imported-content{margin:16px 0;padding:16px;border:1px dashed #d1d5db;border-radius:6px;background-color:#f9fafb}.yjd-rich-editor .imported-content.html-content{border-color:#3b82f6;background-color:#eff6ff}.yjd-rich-editor .imported-content.excel-content{border-color:#10b981;background-color:#f0fdf4}.yjd-rich-editor .imported-content.text-content{border-color:#f59e0b;background-color:#fffbeb}.yjd-rich-editor .imported-table{width:100%;border-collapse:collapse;font-size:14px;background:#fff;border-radius:4px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.1)}.yjd-rich-editor .imported-table td,.yjd-rich-editor .imported-table th{padding:12px;text-align:left;border-bottom:1px solid #e5e7eb}.yjd-rich-editor .imported-table th{background-color:#f3f4f6;font-weight:600;color:#374151}.yjd-rich-editor .imported-content p{margin:8px 0;line-height:1.6;color:#374151}.yjd-rich-editor .imported-content p:first-child{margin-top:0}.yjd-rich-editor .imported-content p:last-child{margin-bottom:0}.yjd-rich-editor .code-view-textarea{width:100%;height:500px;font-family:\\\"Consolas\\\",\\\"Monaco\\\",\\\"Courier New\\\",monospace;font-size:14px;line-height:1.5;padding:16px;background-color:#f8fafc;color:#374151;resize:vertical;border:0;outline:0;white-space:pre;word-wrap:break-word;tab-size:2;box-sizing:border-box}@media (max-width:768px){.yjd-rich-editor .code-view-textarea{font-size:13px;padding:12px}.yjd-rich-editor.code-view-active::after{top:5px;right:5px;font-size:11px;padding:3px 6px}}.yjd-rich-editor .resize-handles-container{position:absolute;pointer-events:none;z-index:997;display:none}.yjd-rich-editor .resize-handles-container.active{display:block}.yjd-rich-editor .resize-handle{position:absolute;width:8px;height:8px;background-color:#3b82f6;pointer-events:auto;box-shadow:0 2px 4px rgba(0,0,0,.2);transition:all .2s ease;z-index:999}.yjd-rich-editor .resize-handle:hover{background-color:#2563eb;transform:scale(1.2);box-shadow:0 2px 6px rgba(0,0,0,.3)}.yjd-rich-editor .resize-handle-nw{top:-4px;left:-4px;cursor:nw-resize}.yjd-rich-editor .resize-handle-ne{top:-4px;right:-4px;cursor:ne-resize}.yjd-rich-editor .resize-handle-sw{bottom:-4px;left:-4px;cursor:sw-resize}.yjd-rich-editor .resize-handle-se{bottom:-4px;right:-4px;cursor:se-resize}.yjd-rich-editor .inserted-image.resizing,.yjd-rich-editor .inserted-video.resizing,.yjd-rich-editor .rich-editor-table.resizing{outline:2px solid #3b82f6;outline-offset:2px;opacity:.8}.yjd-rich-editor .inserted-image.resizing,.yjd-rich-editor .inserted-video.resizing{-webkit-user-select:none;user-select:none;pointer-events:none}.yjd-rich-editor .rich-editor-table.resizing{-webkit-user-select:none;user-select:none}.yjd-rich-editor .rich-editor-table.resizing td{pointer-events:none}.yjd-rich-editor .inserted-image:hover,.yjd-rich-editor .inserted-video:hover,.yjd-rich-editor .rich-editor-table:hover{outline:1px solid #3b82f6;outline-offset:1px;cursor:pointer}@media (max-width:768px){.yjd-rich-editor .resize-handle{width:12px;height:12px}.yjd-rich-editor .resize-handle-nw{top:-6px;left:-6px}.yjd-rich-editor .resize-handle-ne{top:-6px;right:-6px}.yjd-rich-editor .resize-handle-sw{bottom:-6px;left:-6px}.yjd-rich-editor .resize-handle-se{bottom:-6px;right:-6px}}@media (max-width:849px){.yjd-rich-editor{margin:0;width:100%!important;max-width:100%!important}.yjd-rich-editor .rich-editor-area{padding:12px;min-height:150px;font-size:14px;line-height:1.4}.yjd-rich-editor .rich-editor-statusbar{padding:6px 8px;font-size:11px;flex-wrap:wrap;gap:8px}.yjd-rich-editor .editor-dropdown,.yjd-rich-editor .editor-popup{position:fixed!important;top:50%!important;left:50%!important;transform:translate(-50%,-50%)!important;width:90%!important;max-width:320px!important;max-height:80vh!important;overflow-y:auto;z-index:10000}.yjd-rich-editor .editor-popup::before{content:\\\"\\\";position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);z-index:-1}.yjd-rich-editor .color-picker{width:100%!important;max-width:280px!important}.yjd-rich-editor .color-picker-grid{grid-template-columns:repeat(6,1fr)!important;gap:8px!important}.yjd-rich-editor .color-picker-item{width:32px!important;height:32px!important}.yjd-rich-editor .table-popup{max-width:300px!important}.yjd-rich-editor .table-size-grid{grid-template-columns:repeat(8,1fr)!important;gap:2px!important}.yjd-rich-editor .table-size-cell{width:24px!important;height:24px!important}.yjd-rich-editor .list-picker{width:100%!important;max-width:250px!important}.yjd-rich-editor .list-picker-btn{padding:12px!important;font-size:14px!important}.yjd-rich-editor .text-align-picker{width:100%!important;max-width:200px!important}.yjd-rich-editor .image-popup,.yjd-rich-editor .import-popup,.yjd-rich-editor .link-popup,.yjd-rich-editor .video-popup{width:90%!important;max-width:350px!important}.yjd-rich-editor .media-upload-area{min-height:100px!important;padding:20px!important}.yjd-rich-editor .tag-popup{width:90%!important;max-width:320px!important}.yjd-rich-editor .emoji-picker{width:100%!important;max-width:300px!important;max-height:350px!important}.yjd-rich-editor .emoji-grid{grid-template-columns:repeat(8,1fr)!important;gap:4px!important}.yjd-rich-editor .emoji-item{width:32px!important;height:32px!important;font-size:18px!important}.yjd-rich-editor .code-view-textarea{font-size:12px!important;padding:8px!important;line-height:1.3!important}.yjd-rich-editor .rich-editor-area h1{font-size:1.5em!important;margin:.5em 0!important}.yjd-rich-editor .rich-editor-area h2{font-size:1.3em!important;margin:.5em 0!important}.yjd-rich-editor .rich-editor-area h3{font-size:1.1em!important;margin:.5em 0!important}.yjd-rich-editor .rich-editor-area p{margin:.8em 0!important}.yjd-rich-editor .rich-editor-area ol,.yjd-rich-editor .rich-editor-area ul{padding-left:20px!important;margin:.8em 0!important}.yjd-rich-editor .rich-editor-area blockquote{padding-left:12px!important;margin:.8em 0!important;font-size:14px!important}.yjd-rich-editor .rich-editor-area table{font-size:13px!important;overflow-x:auto!important;display:block!important;white-space:nowrap!important}.yjd-rich-editor .rich-editor-area td,.yjd-rich-editor .rich-editor-area th{padding:6px 8px!important;min-width:60px!important}}@media (max-width:290px){.yjd-rich-editor .emoji-picker{max-width:calc(100vw - 20px)!important}.yjd-rich-editor .emoji-grid{grid-template-columns:repeat(auto-fit,minmax(30px,1fr))!important}}@media print{.yjd-rich-editor .rich-editor-statusbar,.yjd-rich-editor .rich-editor-toolbar-1,.yjd-rich-editor .rich-editor-toolbar-2{display:none!important}.yjd-rich-editor{border:0!important;box-shadow:none!important}.yjd-rich-editor .rich-editor-area{padding:0!important;background:#fff!important;color:#000!important}}.yjd-rich-editor .rich-editor-popup-container{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1005;overflow:visible;max-height:90%;overflow-y:auto}.yjd-rich-editor .capitalization-select-popup button,.yjd-rich-editor .color-picker-popup button,.yjd-rich-editor .custom-color-input,.yjd-rich-editor .custom-select-popup button,.yjd-rich-editor .custom-tag,.yjd-rich-editor .emoji-picker-popup button,.yjd-rich-editor .font-family-select-popup button,.yjd-rich-editor .heading-select-popup button,.yjd-rich-editor .image-input-hidden,.yjd-rich-editor .image-popup button,.yjd-rich-editor .image-popup input,.yjd-rich-editor .image-popup select,.yjd-rich-editor .import-csv-input,.yjd-rich-editor .import-file-input,.yjd-rich-editor .import-html-input,.yjd-rich-editor .import-input,.yjd-rich-editor .import-json-input,.yjd-rich-editor .import-markdown-input,.yjd-rich-editor .import-popup button,.yjd-rich-editor .import-popup input,.yjd-rich-editor .import-popup select,.yjd-rich-editor .import-text-input,.yjd-rich-editor .import-toml-input,.yjd-rich-editor .import-type-select,.yjd-rich-editor .import-url-input,.yjd-rich-editor .import-xml-input,.yjd-rich-editor .import-yaml-input,.yjd-rich-editor .line-height-select-popup button,.yjd-rich-editor .link-popup button,.yjd-rich-editor .link-popup input,.yjd-rich-editor .link-popup select,.yjd-rich-editor .list-button,.yjd-rich-editor .list-picker-popup button,.yjd-rich-editor .table-popup button,.yjd-rich-editor .table-popup input,.yjd-rich-editor .table-toolbar-btn,.yjd-rich-editor .tag-popup button,.yjd-rich-editor .tag-popup input,.yjd-rich-editor .tag-popup select,.yjd-rich-editor .tag-type-select,.yjd-rich-editor .tag-value-input,.yjd-rich-editor .text-align-picker-popup button,.yjd-rich-editor .text-size-select-popup button,.yjd-rich-editor .video-input-hidden,.yjd-rich-editor .video-popup button,.yjd-rich-editor .video-popup input,.yjd-rich-editor .video-popup select,.yjd-rich-editor .yjd-button-cancel,.yjd-rich-editor .yjd-button-confirm,.yjd-rich-editor .yjd-custom-upload-button,.yjd-rich-editor .yjd-input,.yjd-rich-editor .yjd-select-input,.yjd-rich-editor .yjd-suggestion-button{pointer-events:auto!important}.yjd-rich-editor .rich-editor-popup-container>*{pointer-events:auto}.yjd-rich-editor .rich-editor-popup-container .color-picker-popup,.yjd-rich-editor .rich-editor-popup-container .custom-select-popup,.yjd-rich-editor .rich-editor-popup-container .emoji-picker-popup,.yjd-rich-editor .rich-editor-popup-container .image-popup,.yjd-rich-editor .rich-editor-popup-container .import-popup,.yjd-rich-editor .rich-editor-popup-container .link-popup,.yjd-rich-editor .rich-editor-popup-container .list-picker-popup,.yjd-rich-editor .rich-editor-popup-container .table-popup,.yjd-rich-editor .rich-editor-popup-container .tag-popup,.yjd-rich-editor .rich-editor-popup-container .text-align-picker-popup,.yjd-rich-editor .rich-editor-popup-container .video-popup{position:absolute!important;z-index:1001!important}.yjd-rich-editor{--rte-bg:#ffffff;--rte-chrome:#fbfbfd;--rte-chrome-2:#f2f2f7;--rte-border:#e9e9f1;--rte-border-strong:#dadae6;--rte-ink:#20242f;--rte-muted:#767c8e;--rte-accent:#6d5efc;--rte-accent-ink:#5a48ee;--rte-accent-weak:#efedff;--rte-accent-ring:rgba(109, 94, 252, 0.28);--rte-danger:#e5484d;--rte-radius:14px;--rte-radius-md:11px;--rte-radius-sm:8px;--rte-shadow-sm:0 1px 2px rgba(20, 24, 46, 0.06);--rte-shadow:0 12px 32px -8px rgba(20, 24, 46, 0.20), 0 4px 10px -4px rgba(20, 24, 46, 0.10);--rte-t:140ms cubic-bezier(0.4, 0, 0.2, 1);border:1px solid var(--rte-border)!important;border-radius:var(--rte-radius)!important;background:var(--rte-bg);color:var(--rte-ink);box-shadow:var(--rte-shadow-sm);font-family:system-ui,-apple-system,BlinkMacSystemFont,\\\"Segoe UI\\\",Roboto,sans-serif}.yjd-rich-editor .rich-editor-popup-container button,.yjd-rich-editor .rich-editor-popup-container input,.yjd-rich-editor .rich-editor-popup-container select,.yjd-rich-editor .rich-editor-popup-container textarea,.yjd-rich-editor .toolbar-container button,.yjd-rich-editor .toolbar-container input,.yjd-rich-editor .toolbar-container select,.yjd-rich-editor .toolbar-container textarea{font-family:system-ui,-apple-system,BlinkMacSystemFont,\\\"Segoe UI\\\",Roboto,sans-serif!important}.yjd-rich-editor .rich-editor-toolbar-container{background:var(--rte-chrome);border-bottom:1px solid var(--rte-border)}.yjd-rich-editor .rich-editor-toolbar-1,.yjd-rich-editor .rich-editor-toolbar-2{display:flex;align-items:center;column-gap:0;row-gap:4px;padding:8px 12px}.yjd-rich-editor .rich-editor-toolbar-2{justify-content:flex-start}.yjd-rich-editor .rich-editor-toolbar-2.visible{border-top:1px solid var(--rte-border)}.yjd-rich-editor .toolbar-group{display:flex;align-items:center;gap:2px;border-right:none;margin:0;padding:0}.yjd-rich-editor .toolbar-group+.toolbar-group{margin-left:12px}.yjd-rich-editor .rich-editor-toolbar-btn{color:var(--rte-ink)}.yjd-rich-editor .custom-select-button,.yjd-rich-editor .rich-editor-toolbar-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-width:32px;height:32px;padding:0 8px;border:1px solid transparent;border-radius:var(--rte-radius-sm);background:0 0;cursor:pointer;transition:background var(--rte-t),color var(--rte-t),box-shadow var(--rte-t),border-color var(--rte-t)}.yjd-rich-editor .rich-editor-toolbar-btn:hover{background:var(--rte-chrome-2)}.yjd-rich-editor .custom-select-button:hover{background:var(--rte-chrome-2);color:var(--rte-ink)}.yjd-rich-editor .rich-editor-toolbar-btn.active{background:var(--rte-accent-weak);border-color:transparent}.yjd-rich-editor .rich-editor-toolbar-btn:disabled,.yjd-rich-editor .rich-editor-toolbar-btn[disabled]{opacity:.38!important;cursor:not-allowed!important}.yjd-rich-editor .custom-select-button:focus-visible,.yjd-rich-editor .rich-editor-toolbar-btn:focus-visible{outline:0;border-color:var(--rte-accent);box-shadow:0 0 0 3px var(--rte-accent-ring)}.yjd-rich-editor .rich-editor-statusbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:7px 14px;background:var(--rte-chrome);border-top:1px solid var(--rte-border)}.yjd-rich-editor .rich-editor-breadcrumb,.yjd-rich-editor .wordcount{color:var(--rte-muted);font-size:12px;letter-spacing:.01em}.yjd-rich-editor .rich-editor-area{color:var(--rte-ink);font-size:16px;line-height:1.75;padding:22px 24px}.yjd-rich-editor .rich-editor-area[data-placeholder].placeholder-visible:before{color:var(--rte-muted);opacity:.55}.yjd-rich-editor .rich-editor-area blockquote{border-left:3px solid var(--rte-accent);background:var(--rte-accent-weak);border-radius:0 var(--rte-radius-sm) var(--rte-radius-sm)0;padding:8px 16px;color:var(--rte-ink);margin:12px 0}.yjd-rich-editor .rich-editor-area pre code{background:0 0;color:inherit;padding:0}.yjd-rich-editor .capitalization-select-popup,.yjd-rich-editor .color-picker-popup,.yjd-rich-editor .custom-select-popup,.yjd-rich-editor .emoji-picker-popup,.yjd-rich-editor .font-family-select-popup,.yjd-rich-editor .heading-select-popup,.yjd-rich-editor .image-popup,.yjd-rich-editor .import-popup,.yjd-rich-editor .line-height-select-popup,.yjd-rich-editor .link-popup,.yjd-rich-editor .table-popup,.yjd-rich-editor .tag-popup,.yjd-rich-editor .text-align-picker-popup,.yjd-rich-editor .text-size-select-popup,.yjd-rich-editor .video-popup{background:var(--rte-bg);border:1px solid var(--rte-border);box-shadow:var(--rte-shadow)}.yjd-rich-editor .custom-select-item-button{color:var(--rte-ink);transition:background var(--rte-t),color var(--rte-t)}.yjd-rich-editor .custom-select-item-button:hover{background:var(--rte-chrome-2)}.yjd-rich-editor .custom-select-item-button.current .item-checkmark{color:var(--rte-accent-ink)}.yjd-rich-editor .yjd-input{transition:border-color var(--rte-t),box-shadow var(--rte-t)}.yjd-rich-editor .yjd-input:focus{outline:0}.yjd-rich-editor .yjd-input-title{color:var(--rte-ink);font-weight:600}.yjd-rich-editor .yjd-input-label{color:var(--rte-muted)}.yjd-rich-editor .yjd-button-confirm{border:0}.yjd-rich-editor .yjd-button-cancel{background:var(--rte-chrome-2)}.yjd-rich-editor .yjd-button-cancel:hover{background:#e9e9f2}.yjd-rich-editor .yjd-custom-upload-button{border-radius:var(--rte-radius-sm);border:1px dashed var(--rte-border-strong);color:var(--rte-accent-ink);background:var(--rte-accent-weak);transition:background var(--rte-t),border-color var(--rte-t)}.yjd-rich-editor .yjd-custom-upload-button:hover{border-color:var(--rte-accent)}.yjd-rich-editor .block-toolbar-container,.yjd-rich-editor .table-toolbar-container{background:var(--rte-bg);border:1px solid var(--rte-border);border-radius:var(--rte-radius-md);box-shadow:var(--rte-shadow);padding:4px;gap:2px}.yjd-rich-editor .block-toolbar-btn,.yjd-rich-editor .table-toolbar-btn{border:1px solid transparent;border-radius:var(--rte-radius-sm);background:0 0;color:var(--rte-ink);transition:background var(--rte-t),color var(--rte-t)}.yjd-rich-editor .block-toolbar-btn:hover,.yjd-rich-editor .table-toolbar-btn:hover{background:var(--rte-chrome-2)}.yjd-rich-editor .block-toolbar-btn.active{background:var(--rte-accent-weak);color:var(--rte-accent-ink)}.yjd-rich-editor .block-toolbar-btn.active .icon svg,.yjd-rich-editor .block-toolbar-btn.active .icon svg path{fill:var(--rte-accent-ink)}.yjd-rich-editor .rich-editor-table td{border:1px solid var(--rte-border-strong)!important}.yjd-rich-editor .rich-editor-table td:focus{outline:0;box-shadow:inset 0 0 0 2px var(--rte-accent);background:var(--rte-accent-weak)}.yjd-rich-editor .table-grid-cell.highlighted{background:var(--rte-accent);border-color:var(--rte-accent)}.yjd-rich-editor .resize-handle{background:var(--rte-accent)!important;border:2px solid #fff!important;border-radius:50%!important;box-shadow:var(--rte-shadow-sm)!important}.yjd-rich-editor .yjd-find-replace{position:absolute;top:10px;right:10px;z-index:1100;display:none;flex-direction:column;gap:8px;padding:10px;background:var(--rte-bg);border:1px solid var(--rte-border);border-radius:var(--rte-radius-md);box-shadow:var(--rte-shadow);font:13px/1.4 system-ui,-apple-system,sans-serif;max-width:calc(100% - 20px)}.yjd-rich-editor .yjd-find-replace.open{display:flex}.yjd-rich-editor .yjd-find-row{display:flex;align-items:center;gap:6px}.yjd-rich-editor .yjd-find-input{flex:1 1 auto;min-width:150px;transition:border-color var(--rte-t),box-shadow var(--rte-t)}.yjd-rich-editor .yjd-find-input:focus{outline:0}.yjd-rich-editor .yjd-find-count{min-width:46px;text-align:center;color:var(--rte-muted);font-variant-numeric:tabular-nums}.yjd-rich-editor .yjd-find-btn{border:1px solid var(--rte-border);background:var(--rte-chrome);color:var(--rte-ink);cursor:pointer}.yjd-rich-editor .yjd-find-btn:hover{background:var(--rte-chrome-2)}.yjd-rich-editor .yjd-find-btn:focus-visible{outline:0;border-color:var(--rte-accent);box-shadow:0 0 0 3px var(--rte-accent-ring)}.yjd-rich-editor .yjd-find-icon{width:30px;padding:0;font-size:15px;line-height:1}.yjd-rich-editor .yjd-find-toggle.active{background:var(--rte-accent-weak);color:var(--rte-accent-ink);border-color:transparent}.yjd-rich-editor .yjd-find-replace .yjd-find-btn.yjd-find-close:hover{background:#fdecec;color:var(--rte-danger);border-color:#f6caca}.yjd-rich-editor .yjd-find-replace-input{flex:1 1 auto}.yjd-rich-editor .yjd-find-row .yjd-find-btn:not(.yjd-find-icon){flex:0 0 auto}.yjd-rich-editor mark.yjd-find-hit{background:#fff1b8;color:inherit;border-radius:3px}.yjd-rich-editor mark.yjd-find-hit.active{background:var(--rte-accent);color:#fff}@media (prefers-reduced-motion:reduce){.yjd-rich-editor *{transition-duration:.01ms!important;animation-duration:.01ms!important}}.yjd-rich-editor .block-toolbar-btn svg [fill=\\\"#454545\\\"],.yjd-rich-editor .icon svg [fill=\\\"#010101\\\"],.yjd-rich-editor .icon svg [fill=\\\"#231F20\\\"],.yjd-rich-editor .icon svg [fill=\\\"#454545\\\"],.yjd-rich-editor .icon svg[fill=\\\"#454545\\\"],.yjd-rich-editor .table-toolbar-btn svg [fill=\\\"#454545\\\"]{fill:currentColor}.yjd-rich-editor .block-toolbar-btn .icon,.yjd-rich-editor .rich-editor-toolbar-btn .icon,.yjd-rich-editor .table-toolbar-btn .icon{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;color:inherit}.yjd-rich-editor .block-toolbar-btn .icon svg,.yjd-rich-editor .rich-editor-toolbar-btn .icon svg,.yjd-rich-editor .table-toolbar-btn .icon svg{width:auto!important;height:16px!important;max-width:18px;display:block}.yjd-rich-editor .rich-editor-toolbar-btn .icon svg,.yjd-rich-editor .rich-editor-toolbar-btn .icon svg path{fill:currentColor}.yjd-rich-editor .rich-editor-toolbar-btn{color:#4b5060}.yjd-rich-editor .rich-editor-toolbar-btn:hover{color:var(--rte-ink)}.yjd-rich-editor .rich-editor-toolbar-btn.active,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg [fill]{color:var(--rte-accent-ink)!important;fill:var(--rte-accent-ink)!important}.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg path{fill:var(--rte-accent-ink)!important}.yjd-rich-editor .custom-select-button,.yjd-rich-editor .rich-editor-toolbar-btn{box-shadow:none}.yjd-rich-editor .custom-select-button{border-color:var(--rte-border);background:var(--rte-bg);color:var(--rte-ink);font-weight:500}.yjd-rich-editor .custom-select-button:hover{border-color:var(--rte-border-strong);background:var(--rte-bg)}.yjd-rich-editor .dropdown-icon{color:var(--rte-muted)}.yjd-rich-editor .capitalization-select-popup,.yjd-rich-editor .color-picker-popup,.yjd-rich-editor .custom-select-popup,.yjd-rich-editor .font-family-select-popup,.yjd-rich-editor .heading-select-popup,.yjd-rich-editor .image-popup,.yjd-rich-editor .import-popup,.yjd-rich-editor .line-height-select-popup,.yjd-rich-editor .link-popup,.yjd-rich-editor .table-popup,.yjd-rich-editor .tag-popup,.yjd-rich-editor .text-size-select-popup,.yjd-rich-editor .video-popup{border-color:var(--rte-border);border-radius:var(--rte-radius-md);box-shadow:0 10px 30px -10px rgba(20,24,46,.18),0 2px 6px -2px rgba(20,24,46,.08)}.yjd-rich-editor .emoji-picker-popup,.yjd-rich-editor .text-align-picker-popup{border-color:var(--rte-border);box-shadow:0 10px 30px -10px rgba(20,24,46,.18),0 2px 6px -2px rgba(20,24,46,.08)}.yjd-rich-editor .emoji-picker-popup{border-radius:var(--rte-radius-md)}.yjd-rich-editor .capitalization-select-popup,.yjd-rich-editor .custom-select-popup,.yjd-rich-editor .font-family-select-popup,.yjd-rich-editor .heading-select-popup,.yjd-rich-editor .line-height-select-popup,.yjd-rich-editor .text-size-select-popup{padding:6px}.yjd-rich-editor .capitalization-select-popup.visible,.yjd-rich-editor .color-picker-popup.visible,.yjd-rich-editor .custom-select-popup.visible,.yjd-rich-editor .font-family-select-popup.visible,.yjd-rich-editor .heading-select-popup.visible,.yjd-rich-editor .image-popup.visible,.yjd-rich-editor .line-height-select-popup.visible,.yjd-rich-editor .link-popup.visible,.yjd-rich-editor .table-popup.visible,.yjd-rich-editor .tag-popup.visible,.yjd-rich-editor .text-align-picker-popup.visible,.yjd-rich-editor .text-size-select-popup.visible,.yjd-rich-editor .video-popup.visible,.yjd-rich-editor .yjd-find-replace.open{animation:rte-pop-in 90ms ease-out}.yjd-rich-editor .yjd-input{height:36px;border:1px solid var(--rte-border);border-radius:var(--rte-radius-sm);background:var(--rte-bg);color:var(--rte-ink)}.yjd-rich-editor .yjd-select-input{height:36px;background:var(--rte-bg);font-size:14px}.yjd-rich-editor .yjd-find-input{height:36px;border:1px solid var(--rte-border);border-radius:var(--rte-radius-sm);background:var(--rte-bg);color:var(--rte-ink)}.yjd-rich-editor .yjd-find-input::placeholder,.yjd-rich-editor .yjd-input::placeholder,.yjd-rich-editor .yjd-select-input::placeholder{color:var(--rte-muted);opacity:.7}.yjd-rich-editor .yjd-find-input:focus,.yjd-rich-editor .yjd-input:focus{border-color:var(--rte-accent);box-shadow:0 0 0 3px var(--rte-accent-ring)}.yjd-rich-editor .yjd-button-cancel,.yjd-rich-editor .yjd-button-confirm,.yjd-rich-editor .yjd-find-btn{height:36px;padding:0 16px;font-size:14px;font-weight:500;border-radius:var(--rte-radius-sm);box-shadow:none;transition:background var(--rte-t),color var(--rte-t),border-color var(--rte-t)}.yjd-rich-editor .yjd-button-confirm{background:var(--rte-accent);border:1px solid var(--rte-accent);color:#fff}.yjd-rich-editor .yjd-button-confirm:hover{background:var(--rte-accent-ink);border-color:var(--rte-accent-ink)}.yjd-rich-editor .yjd-button-cancel{background:0 0;border:1px solid var(--rte-border);color:var(--rte-muted)}.yjd-rich-editor .yjd-button-cancel:hover{background:var(--rte-chrome-2);color:var(--rte-ink)}.yjd-rich-editor .yjd-find-btn:not(.yjd-find-icon){background:0 0;border:1px solid var(--rte-border);color:var(--rte-ink)}.yjd-rich-editor .yjd-find-btn:not(.yjd-find-icon):hover{background:var(--rte-chrome-2)}.yjd-rich-editor .yjd-find-btn.yjd-find-icon{height:32px;width:32px;color:var(--rte-muted);background:0 0;border:1px solid transparent}.yjd-rich-editor .yjd-find-btn.yjd-find-icon:hover{background:var(--rte-chrome-2);color:var(--rte-ink)}@media (prefers-reduced-motion:reduce){.yjd-rich-editor .color-picker-popup.visible,.yjd-rich-editor .yjd-find-replace.open,.yjd-rich-editor [class$=-popup].visible,.yjd-rich-editor [class*=select-popup].visible{animation:none!important}}.yjd-rich-editor .icon svg{display:block;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;overflow:visible}.yjd-rich-editor .icon svg line,.yjd-rich-editor .icon svg path,.yjd-rich-editor .icon svg polyline,.yjd-rich-editor .icon svg rect{fill:none!important;stroke:currentColor}.yjd-rich-editor .icon svg [fill=currentColor],.yjd-rich-editor .list-button svg [fill=currentColor],.yjd-rich-editor .table-toolbar-btn svg [fill=currentColor]{fill:currentColor!important;stroke:none!important}.yjd-rich-editor .rich-editor-toolbar-btn .icon svg{color:#565b6b}.yjd-rich-editor .rich-editor-toolbar-btn:hover .icon svg{color:var(--rte-ink)}.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg line,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg path,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg polyline,.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg rect{color:var(--rte-accent-ink)!important;stroke:var(--rte-accent-ink)!important;fill:none!important}.yjd-rich-editor .rich-editor-toolbar-btn.active .icon svg [fill=currentColor]{fill:var(--rte-accent-ink)!important;stroke:none!important}.yjd-rich-editor .block-toolbar-btn.active .icon svg,.yjd-rich-editor .block-toolbar-btn.active .icon svg path{stroke:var(--rte-accent-ink)!important;fill:none!important}.yjd-rich-editor .list-picker-popup{background:var(--rte-bg);border:1px solid var(--rte-border);border-radius:var(--rte-radius-md);box-shadow:var(--rte-shadow);padding:6px}.yjd-rich-editor .list-picker-popup.visible{animation:rte-pop-in 90ms ease-out}.yjd-rich-editor .list-button-container{display:flex;align-items:center;gap:2px}.yjd-rich-editor .list-button{display:inline-flex;align-items:center;justify-content:center;width:34px;height:32px;padding:0;border:1px solid transparent;border-radius:var(--rte-radius-sm);background:0 0;color:#565b6b;cursor:pointer;transition:background var(--rte-t),color var(--rte-t)}.yjd-rich-editor .list-button:hover{background:var(--rte-chrome-2);color:var(--rte-ink)}.yjd-rich-editor .list-button.active{background:var(--rte-accent-weak);color:var(--rte-accent-ink)}.yjd-rich-editor .icon svg,.yjd-rich-editor .list-button svg,.yjd-rich-editor .table-toolbar-btn svg{width:auto!important;height:16px!important;max-width:20px;fill:none!important}.yjd-rich-editor .list-button svg{stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.yjd-rich-editor .table-toolbar-btn svg{display:block}.yjd-rich-editor .table-toolbar-btn{color:#565b6b}.yjd-rich-editor .table-toolbar-btn:hover{color:var(--rte-ink)}.yjd-rich-editor .align-button{border-radius:var(--rte-radius-sm);background:0 0;color:var(--rte-ink);transition:background var(--rte-t),color var(--rte-t)}.yjd-rich-editor .align-button:hover{background:var(--rte-chrome-2);border-color:transparent}.yjd-rich-editor .align-button.active{background:var(--rte-accent-weak);color:var(--rte-accent-ink);border-color:transparent}.yjd-rich-editor .align-button svg,.yjd-rich-editor .table-toolbar-btn svg,.yjd-slash-icon svg{stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.yjd-rich-editor .align-button svg{width:auto!important;height:16px!important;color:inherit;fill:none!important}.yjd-rich-editor .align-button.active svg{color:var(--rte-accent-ink)}.yjd-rich-editor .yjd-custom-upload-button svg{width:17px;height:17px;fill:none;stroke:var(--rte-accent-ink)}.yjd-rich-editor .text-align-picker-popup{min-width:168px}.yjd-rich-editor .align-button,.yjd-rich-editor .align-button .label-text{white-space:nowrap}.yjd-rich-editor .rich-editor-toolbar-1{flex-wrap:nowrap!important;overflow:hidden}.yjd-rich-editor .rich-editor-toolbar-2{border-top:1px solid var(--rte-border)}.yjd-rich-editor .custom-select-button,.yjd-rich-editor .rich-editor-toolbar-btn,.yjd-rich-editor .toolbar-group{flex:0 0 auto}.yjd-rich-editor .block-toolbar-arrow,.yjd-rich-editor .table-toolbar-arrow{display:none!important}.yjd-slash-menu{position:absolute;z-index:2000;min-width:264px;max-width:320px;max-height:320px;overflow-y:auto;padding:6px;background:#fff;border:1px solid #e9e9f1;border-radius:12px;box-shadow:0 12px 32px -8px rgba(20,24,46,.2),0 4px 10px -4px rgba(20,24,46,.1);font:14px/1.4 system-ui,-apple-system,BlinkMacSystemFont,\\\"Segoe UI\\\",Roboto,sans-serif;animation:yjd-slash-in 90ms ease-out}.yjd-slash-item{display:flex;align-items:center;gap:10px;width:100%;padding:8px 10px;border:0;background:0 0;border-radius:8px;cursor:pointer;text-align:left;color:#20242f}.yjd-slash-item.active,.yjd-slash-item:hover{background:#efedff;color:#5a48ee}.yjd-slash-icon{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;flex:0 0 auto;border-radius:7px;background:#f2f2f7;color:inherit}.yjd-slash-item.active .yjd-slash-icon{background:#fff}.yjd-slash-icon svg{width:auto;height:16px;fill:none}.yjd-slash-icon svg [fill=currentColor]{fill:currentColor;stroke:none}.yjd-slash-text{display:flex;flex-direction:column;line-height:1.25;min-width:0}.yjd-slash-label{font-weight:500}.yjd-slash-hint{font-size:12px;color:#767c8e}.yjd-slash-item.active .yjd-slash-hint{color:#8b7ff0}@media (prefers-reduced-motion:reduce){.yjd-slash-menu{animation:none}}.yjd-rich-editor .rich-editor-toolbar-btn.background-btn,.yjd-rich-editor .rich-editor-toolbar-btn.color-btn{position:relative}.yjd-rich-editor .rich-editor-toolbar-btn .rte-swatch{position:absolute;left:50%;bottom:5px;transform:translateX(-50%);width:16px;height:3px;border-radius:2px;background:var(--rte-ink, #20242f);pointer-events:none;box-shadow:0 0 0 1px rgba(0,0,0,.06)}.yjd-rich-editor .rich-editor-toolbar-btn.background-btn .rte-swatch{background:0 0;box-shadow:inset 0 0 0 1px var(--rte-border, #e9e9f1)}.yjd-rich-editor .rich-editor-toolbar-btn.background-btn.has-color .rte-swatch{box-shadow:0 0 0 1px rgba(0,0,0,.06)}.yjd-rich-editor .rich-editor-toolbar-btn.background-btn .icon,.yjd-rich-editor .rich-editor-toolbar-btn.color-btn .icon{transform:translateY(-1px)}.yjd-rich-editor .link-popup.link-popup--inline{width:auto;min-width:320px;max-width:min(92vw,420px);padding:10px}.yjd-rich-editor .link-popup--inline .link-popup-content{display:flex;flex-direction:column;gap:8px}.yjd-rich-editor .link-popup--inline .link-popup-row{display:flex;align-items:center;gap:8px}.yjd-rich-editor .link-popup--inline .link-popup-row .yjd-input{flex:1;min-width:0;margin:0}.yjd-rich-editor .link-popup--inline .link-popup-apply{flex:0 0 auto;white-space:nowrap;padding:8px 16px;margin:0}.yjd-rich-editor .custom-select-item-button{min-height:34px;padding:6px 10px!important;gap:10px;border-radius:var(--rte-radius-sm)!important}.yjd-rich-editor .custom-select-item-button.current,.yjd-rich-editor .custom-select-item-button:hover{background:var(--rte-accent-weak)!important;color:var(--rte-accent-ink)!important}.yjd-rich-editor .custom-select-item-button.current{font-weight:600}.yjd-rich-editor .font-family-select-popup .custom-select-item-button,.yjd-rich-editor .heading-select-popup .custom-select-item-button,.yjd-rich-editor .text-size-select-popup .custom-select-item-button{line-height:1.15}.yjd-rich-editor .import-type-select,.yjd-rich-editor .tag-type-select,.yjd-rich-editor .yjd-select-input,.yjd-rich-editor select{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;box-sizing:border-box;padding:10px 36px 10px 12px;font:500 14px/1.4 system-ui,-apple-system,\\\"Segoe UI\\\",Roboto,sans-serif;color:var(--rte-ink);background-color:var(--rte-bg);background-image:url(\\\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='16'%20height='16'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23767c8e'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='m6%209%206%206%206-6'/%3E%3C/svg%3E\\\");background-repeat:no-repeat;background-position:right 12px center;background-size:16px;border:1px solid var(--rte-border-strong);border-radius:var(--rte-radius-sm);cursor:pointer;transition:border-color var(--rte-t),box-shadow var(--rte-t)}.yjd-rich-editor .import-type-select:hover,.yjd-rich-editor .tag-type-select:hover,.yjd-rich-editor .yjd-select-input:hover,.yjd-rich-editor select:hover{border-color:var(--rte-accent)}.yjd-rich-editor .import-type-select:focus,.yjd-rich-editor .tag-type-select:focus,.yjd-rich-editor .yjd-select-input:focus,.yjd-rich-editor select:focus{outline:0;border-color:var(--rte-accent);box-shadow:0 0 0 3px var(--rte-accent-ring)}.yjd-rich-editor .yjd-find-input,.yjd-rich-editor .yjd-input{box-sizing:border-box;padding:10px 12px;font-size:14px;line-height:1.4}.yjd-rich-editor .table-grid-cell:hover{background:var(--rte-accent-weak)!important;border-color:var(--rte-accent)!important}.yjd-rich-editor .rich-editor-toolbar-1{justify-content:flex-start!important}.yjd-rich-editor .rich-editor-toolbar-1 .more-btn{margin-left:auto!important}@media (max-width:640px){.yjd-rich-editor .rich-editor-toolbar-1{flex-wrap:nowrap!important;overflow-x:auto!important;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none;justify-content:flex-start!important;padding:6px 8px;scroll-padding:8px}.yjd-rich-editor .rich-editor-toolbar-1::-webkit-scrollbar{display:none}.yjd-rich-editor .rich-editor-toolbar-1 .more-btn,.yjd-rich-editor .rich-editor-toolbar-2{display:none!important}.yjd-rich-editor .toolbar-group{flex:0 0 auto}.yjd-rich-editor .toolbar-group+.toolbar-group{margin-left:8px}.yjd-rich-editor .custom-select-button,.yjd-rich-editor .rich-editor-toolbar-btn{min-width:32px;height:32px;padding:0 8px;flex:0 0 auto}.yjd-rich-editor .custom-select-button{max-width:none}}.yjd-rich-editor .rich-editor-toolbar-btn.rte-hidden{display:none!important}.yjd-rich-editor .rich-editor-area>:first-child{margin-top:0!important}.yjd-rich-editor .rich-editor-area>:last-child{margin-bottom:0!important}.yjd-rich-editor .align-button-container{flex-direction:row!important;width:auto!important;height:auto!important;gap:4px}.yjd-rich-editor .align-button{width:34px!important;min-height:34px!important;height:34px!important;padding:0!important;gap:0!important;justify-content:center!important}.yjd-rich-editor .text-align-picker-popup{width:auto!important}.yjd-rich-editor .rich-editor-toolbar-2{flex-wrap:nowrap!important;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none}.yjd-rich-editor .rich-editor-toolbar-2::-webkit-scrollbar{display:none}.yjd-rich-editor .rich-editor-toolbar-2 .toolbar-group{flex:0 0 auto}.yjd-rich-editor .custom-select-header{padding:8px 12px 6px;font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--rte-muted);border-bottom:1px solid var(--rte-border);margin-bottom:4px;white-space:nowrap}.yjd-rich-editor .custom-select-button .select-lead-icon{width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--rte-muted);margin-right:4px}.yjd-rich-editor .custom-select-button .select-lead-icon svg{width:16px;height:16px;display:block;stroke:currentColor;fill:none}.yjd-rich-editor .custom-select-button{white-space:nowrap;overflow:hidden}.yjd-rich-editor .custom-select-button .button-text{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.yjd-rich-editor .custom-select-button .dropdown-icon,.yjd-rich-editor .custom-select-button .select-lead-icon{flex:0 0 auto}.yjd-rich-editor .text-align-picker-popup{padding:6px!important;border-radius:var(--rte-radius-md)!important}.yjd-rich-editor .align-button-container{padding:0!important}.yjd-rich-editor .rich-editor-breadcrumb,.yjd-rich-editor .wordcount{color:#5b6373}.yjd-rich-editor .yjd-find-btn{display:inline-flex;align-items:center;justify-content:center}.yjd-rich-editor .yjd-find-icon .icon,.yjd-rich-editor .yjd-find-icon .icon svg{width:16px;height:16px;display:block}.yjd-mention-ico svg,.yjd-rich-editor .yjd-find-icon .icon svg{stroke:currentColor;fill:none}.mention{display:inline;padding:0 2px;border-radius:4px;background:#efedff;color:#5a48ee;font-weight:500;text-decoration:none;white-space:nowrap}.mention[data-trigger=\\\"#\\\"]{background:#e8f3ff;color:#1f6feb}.yjd-mention-menu{position:absolute;z-index:2000;min-width:220px;max-width:320px;max-height:280px;overflow-y:auto;padding:6px;background:var(--rte-bg, #ffffff);border:1px solid var(--rte-border, #e9e9f1);border-radius:var(--rte-radius-md, 12px);box-shadow:var(--rte-shadow, 0 12px 32px -8px rgba(20, 24, 46, 0.20), 0 4px 10px -4px rgba(20, 24, 46, 0.10));font:14px/1.4 system-ui,-apple-system,BlinkMacSystemFont,\\\"Segoe UI\\\",Roboto,sans-serif;animation:yjd-slash-in 90ms ease-out}.yjd-mention-item{display:flex;align-items:center;gap:10px;width:100%;padding:7px 10px;border:0;background:0 0;border-radius:8px;cursor:pointer;text-align:left;color:var(--rte-ink, #20242f)}.yjd-mention-item.active,.yjd-mention-item:hover{background:var(--rte-accent-weak, #efedff);color:var(--rte-accent-ink, #5a48ee)}.yjd-mention-avatar,.yjd-mention-ico{width:26px;height:26px;flex:0 0 auto;border-radius:50%}.yjd-mention-avatar{object-fit:cover;background:#f2f2f7}.yjd-mention-ico{display:inline-flex;align-items:center;justify-content:center;background:var(--rte-accent-weak, #efedff);color:var(--rte-accent-ink, #5a48ee)}.yjd-mention-ico svg{width:16px;height:16px}.yjd-mention-name{font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media (prefers-reduced-motion:reduce){.yjd-mention-menu{animation:none}}.yjd-content{font-family:-apple-system,BlinkMacSystemFont,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif;font-size:16px;line-height:1.6;color:#20242f;word-wrap:break-word}.yjd-content>:first-child{margin-top:0}.yjd-content>:last-child{margin-bottom:0}.yjd-content li{margin:.25em 0}.yjd-content a{color:#2563eb;text-decoration:underline}.yjd-content code,.yjd-content pre{font-family:Consolas,Menlo,Monaco,\\\"Courier New\\\",monospace;background:#f1f2f3;padding:2px 6px;border-radius:4px}.yjd-content pre{padding:12px 14px;border-radius:6px;margin:1em 0;overflow-x:auto;white-space:pre}.yjd-content pre code{background:0 0;padding:0}.yjd-content img{max-width:100%;height:auto;border-radius:4px}.yjd-content hr{border:0;border-top:1px solid #d1d5db;margin:1.5em 0}.yjd-content table{border-collapse:collapse;width:100%;margin:1em 0}.yjd-content table td,.yjd-content table th{border:1px solid #d1d5db;padding:8px 10px;text-align:left}.yjd-file-chip{display:inline-flex;align-items:center;gap:7px;max-width:100%;vertical-align:baseline;padding:3px 10px 3px 8px;margin:0 1px;border:1px solid #e0e0ec;border-radius:8px;background:#f7f7fb;color:#20242f;text-decoration:none;font-size:.9em;line-height:1.5;white-space:nowrap}.yjd-file-chip:hover{border-color:#6d5efc;background:#efedff}.yjd-file-chip[data-state=uploading]{opacity:.6}.yjd-file-ico{display:inline-flex;flex:0 0 auto;color:#5a48ee}.yjd-file-ico svg{width:15px;height:15px;stroke:currentColor;fill:none}.yjd-file-name{font-weight:500;overflow:hidden;text-overflow:ellipsis;max-width:22em}.yjd-file-size{color:#767c8e;font-variant-numeric:tabular-nums}.yjd-spinner{display:inline-block;width:15px;height:15px;flex:0 0 auto;border:2px solid var(--rte-accent-weak, #d9d4ff);border-top-color:var(--rte-accent, #6d5efc);border-radius:50%;animation:yjd-spin .7s linear infinite;vertical-align:middle}@media (prefers-reduced-motion:reduce){.yjd-spinner{animation-duration:2s}}.yjd-upload{display:inline-flex;align-items:center;gap:9px;vertical-align:middle;max-width:100%;padding:10px 14px;margin:2px 0;border:1px dashed var(--rte-border-strong, #c9c5ec);border-radius:10px;background:var(--rte-accent-weak, #f3f1ff);color:var(--rte-muted, #5b6373);font-size:.9em;user-select:none}.yjd-upload-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:18em}.rich-editor-area.yjd-drag-over{outline:2px dashed var(--rte-accent, #6d5efc);outline-offset:-4px;background:var(--rte-accent-weak, #f6f5ff)}\";\n"],"names":["registry","constructor","this","modules","Map","formats","themes","ui","register","path","def","suppressWarning","Object","entries","forEach","key","value","type","name","split","get","set","has","undefined","getAll","unregister","delete","clear","getAllItems","items","Array","from","keys","Module","static","editor","options","DEFAULTS","events","on","event","handler","push","off","handlers","index","indexOf","splice","emit","data","error","destroy","onContentChange","onSelectionChange","range","execFormat","command","document","execCommand","e","setStyleWithCSS","enabled","queryFormatState","queryCommandState","SAFE_URL_SCHEMES","TRUSTED_IFRAME_PREFIXES","isSafeUrl","url","allowDataImage","trimmed","trim","stripped","replace","schemeMatch","match","scheme","toLowerCase","test","includes","FORBIDDEN_TAGS","Set","cleanElement","el","attrs","attributes","attr","startsWith","removeAttribute","tagName","sanitizeHtml","html","doc","DOMParser","parseFromString","elements","body","querySelectorAll","tag","remove","src","getAttribute","some","prefix","innerHTML","htmlToMarkdown","root","createElement","blocksToMd","parent","depth","out","childNodes","node","nodeType","t","textContent","repeat","inline","c","map","l","join","listToMd","rows","length","cells","r","children","head","slice","tableToMd","imgToMd","nodeBlock","ordered","i","li","marker","pad","text","nested","ch","inlineNode","classList","contains","id","nameEl","querySelector","size","markdownToHtml","md","lines","isList","line","h","inlineMd","code","escapeHtml","q","parseTable","next","parseList","para","indentOf","start","baseIndent","ind","m","row","s","_","a","href","String","domToJson","content","nodeToJson","filter","Boolean","obj","kids","jsonToHtml","json","isArray","jsonNodeToHtml","n","k","v","inner","S","Icons","bold","italic","underline","strike","subscript","superscript","list","image","video","file","table","color","background","undo","redo","link","emoji","import","find","close","check","dropdown","more","theme","heading","capitalization","IconUtils","getIcon","iconName","createIconElement","iconElement","className","style","display","alignItems","justifyContent","width","height","verticalAlign","hasIcon","getIconNames","Editor","placeholder","maxWidth","maxHeight","features","wordCount","breadcrumb","selector","toolbarBtns","statusbarEls","dropdownMenus","popupInstances","currentInstance","instanceId","generateInstanceId","instances","init","Date","now","Math","random","toString","substr","createStructure","loadModules","loadFormats","setupEventListeners","updateStatusbar","wrapper","cssSize","minHeight","position","contentEditable","setAttribute","ariaLabel","direction","getDefaultContent","appendChild","popupContainer","top","left","pointerEvents","zIndex","createStatusbar","updatePlaceholderVisibility","isHtmlContent","pattern","wrapTextInParagraph","saved","_getAutosaved","statusbar","wordcount","modulesToLoad","toolbar","toolbar1","toolbar2","mention","moduleName","ModuleClass","moduleInstance","getContainer","toolbarContainer","insertBefore","handleToolbarClick","formatsToLoad","formatName","FormatClass","_markActive","addEventListener","_onDocSelectionChange","activeElement","_scheduleSelectionUpdate","ensureEditorHasContent","ctrlKey","metaKey","altKey","b","u","shiftKey","preventDefault","toggleFormat","setTimeout","handlePaste","dataTransfer","types","add","relatedTarget","dt","files","imageFile","f","placeCaretAtPoint","clientX","clientY","insertImageFile","attachment","insertFileAttachment","maxLength","inputType","incoming","_remainingChars","markdown","handleMarkdownShortcut","submit","onEnter","submitCfg","isComposing","isMenuOpen","getContent","updateHistoryButtons","focus","_emitChange","module","onChange","_scheduleAutosave","maxContentSize","over","_overflowed","max","isEditorEmpty","selInEditor","isSelectionInEditableArea","window","getSelection","paragraph","setCursorToElement","_clearStickyInlineFormats","updateToolbarButtonStates","cmd","ensureParagraphForEditing","lastChild","element","createRange","selection","firstChild","Node","TEXT_NODE","setStart","collapse","removeAllRanges","addRange","_selUpdateQueued","requestAnimationFrame","rangeCount","getRangeAt","isInEditableArea","collapsed","_lastRange","cloneRange","updateToolbarAccessibility","startContainer","endContainer","startInEditor","isNodeInEditableArea","endInEditor","currentNode","parentNode","getModule","setButtonDisabled","sel","anchorNode","parentElement","tagInfo","classes","unshift","codeView","isInCodeView","tmp","getCurrentContent","words","chars","label","setContent","processedContent","getHTML","setHTML","getJSON","setJSON","getMarkdown","setMarkdown","getText","isEmpty","insertText","insertHTML","clipboard","clipboardData","imageItem","it","kind","getAsFile","getData","remaining","pasteAsPlainText","Infinity","selLen","isCollapsed","cfg","accept","endsWith","reason","maxSize","makeImg","extra","ImageClass","create","img","upload","reader","FileReader","onload","ev","target","result","outerHTML","readAsDataURL","placeholderId","round","performance","_upCounter","escName","phHTML","Promise","resolve","then","replaceWith","catch","err","formatBytes","bytes","toFixed","openFileAttachmentPicker","savedRange","input","selectNodeContents","click","ico","esc","makeChip","meta","state","icoHTML","insertChipHTML","onerror","_fileCounter","ph","opacity","res","icoEl","sizeEl","x","y","caretRangeFromPoint","caretPositionFromPoint","pos","offsetNode","offset","block","pre","setEnd","startOffset","blockTag","listType","history","saveBeforeFormat","deleteContents","caret","setDirection","dir","d","setButtonActive","getDirection","toggleDirection","_autosaveCfg","_autosaveMemo","autosave","debounce","localStorage","getItem","clearTimeout","_autosaveTimer","setItem","clearAutosave","removeItem","isOpen","offsetParent","getComputedStyle","clearFormatting","historyModule","setBlockType","BLOCK","insertHorizontalRule","_isBlockEmpty","insertBlock","blockEl","topBlock","wasEmpty","nextSibling","p","setReadOnly","readOnly","_readOnly","toggle","buttons","isReadOnly","getFormat","definition","button","registryKey","_getFormatInstance","_fmtCache","inst","createForEditor","original","formatInstance","isActive","updateButtonText","TextSizeClass","updateButtonTextStatic","updateColorSwatches","getButton","canUndo","canRedo","setState","btn","disabled","apply","swatch","removeProperty","ColorClass","getCurrentColor","BgClass","preventFocusLoss","allowedSelector","closest","getCurrentInstance","maintainFocus","callback","editorInstance","getPopupContainer","getPopupInstance","popupType","setPopupInstance","popupInstance","getPopupInstanceById","editorId","getInstanceById","getAllInstances","destroyPopupInstances","removeEventListener","removeChild","Format","domNode","getOffsetWithin","container","walker","createTreeWalker","NodeFilter","SHOW_TEXT","nextNode","InlineFormat","super","formatNode","createTextNode","insertNode","newRange","contents","extractContents","removeAtCursor","removeFromSelection","findFormatNode","absoluteOffset","beforeText","afterText","fragment","createDocumentFragment","beforeNode","cloneNode","zwspNode","afterNode","setStartAfter","ELEMENT_NODE","findFormatNodesInRange","nodes","commonAncestorContainer","SHOW_ELEMENT","acceptNode","intersectsNode","FILTER_ACCEPT","FILTER_SKIP","altTags","alternativeTagNames","BlockFormat","blocks","getBlockElements","createBlockAtCursor","convertBlock","removeBlockFormat","blockNode","existingBlock","getBlockElement","cssText","newBlock","shouldPreserveClass","replaceChild","startBlock","endBlock","nextElementSibling","endOffset","current","isBlockElement","getNextBlockElement","self","toUpperCase","hasValue","renderStatic","safe","Bold","Italic","Underline","Strike","Subscript","removeSuperscriptBeforeApply","Superscript","removeSubscriptBeforeApply","appendPopup","popup","getPopupDimensions","rect","getBoundingClientRect","offsetWidth","offsetHeight","computedStyle","visibility","originalDisplay","originalVisibility","originalPosition","originalTop","originalLeft","originalZIndex","tempRect","computedWidth","parseInt","computedHeight","calculatePopupPosition","anchor","offsetX","offsetY","preferTop","preferLeft","anchorRect","wrapperRect","popupWidth","popupHeight","topPosition","bottom","scrollY","scrollX","innerWidth","innerHeight","setPopupPosition","ColorPicker","colors","customColorEnabled","onColorSelect","isVisible","currentColor","clickOutsideHandler","createColorPicker","createColorGrid","createCustomColorInput","grid","colorButton","backgroundColor","dataset","title","stopPropagation","selectColor","customContainer","noColorButton","noColorIcon","whiteButton","border","blackButton","customColorButton","font","customInput","setupClickOutside","hide","removeClickOutside","show","Color","currentEditor","colorPicker","applyColorToCurrentSelection","savedRanges","showColorPicker","Background","applyBackgroundToCurrentSelection","originalCurrent","format","backgroundButton","LinkPopup","onLinkSelect","urlInput","textInput","createPopup","textGroup","applyBtn","onclick","handleOk","_refocusEditor","onKey","onkeydown","raw","existingLink","selectedText","hasSelection","closeOnClickOutside","Link","linkPopup","linkData","insertLink","linkElement","rel","showPopup","getCurrentLink","TablePopup","maxRows","maxCols","onTableSelect","selectedRows","selectedCols","sizeDisplay","createSizeDisplay","createGridSelector","col","cell","highlightGrid","selectSize","handleInsert","cols","updateSizeDisplay","cellRow","cellCol","Table","tablePopup","tableData","insertTable","tableElement","createTableElement","firstCell","cellSpacing","cellPadding","tbody","minWidth","padding","borderCollapse","margin","tableButton","getCurrentTable","richEditor","resizeHandles","hideHandles","CustomSelect","onItemSelect","displayProperty","valueProperty","currentValue","initialized","createSelect","header","createItemList","checkIconSvg","item","itemButton","getItemValue","itemText","getItemDisplay","checkmark","selectItem","updateItems","existingList","setupScrollHandler","scrollHandler","updatePosition","removeScrollHandler","ed","editorEl","_savedRange","checkInit","highlightCurrentItem","currentAnchor","setCurrentValue","getCurrentValue","Heading","customSelect","tagMap","getTagMap","values","tagData","applyTagToCurrentSelection","H1","H2","H3","H4","H5","H6","P","PRE","BLOCKQUOTE","getTagDisplayName","currentTag","getCurrentTag","displayName","headingButton","updateText","headingFormat","blocksArray","findBlockIndex","idx","charOffsetFromBlockStart","startBlockIndex","endBlockIndex","startCharOffset","endCharOffset","newBlocks","resolvePositionByCharOffset","charOffset","len","nodeValue","min","setCursorAtStartOfBlock","firstTextNode","showTagPicker","FontFamily","fontMap","getFontMap","fontData","applyFontFamilyToCurrentSelection","setupSelectionListener","updateTimeout","debouncedUpdate","isSameNode","selectionListener","Arial","Helvetica","Georgia","Verdana","Impact","getFontDisplayName","currentFont","getCurrentFont","fontFamilyButton","fontFamily","fontFamilyFormat","moveCaretInside","textNode","fontNormalized","styleFont","isCaretInsideFontSpan","currentSpan","caretPos","textBefore","textAfter","newSpan","span1","span2","span3","anchorButton","showFontPicker","setCurrentFont","LineHeight","heightMap","getHeightMap","heightData","applyLineHeightToCurrentSelection","getHeightDisplayName","num","parseFloat","isNaN","currentHeight","getCurrentHeight","lineHeightButton","lineHeight","lineHeightFormat","blockParent","blockElements","getBlockElementsInRange","heightSpan","blockTags","cloneContents","actualNode","showHeightPicker","normalizeHeightValue","fontSize","lineHeightPx","relative","numValue","normalized","Capitalization","capMap","getCapitalizationMap","capData","applyCapitalizationToCurrentSelection","capitalize","uppercase","lowercase","getCapitalizationDisplayName","currentCap","getCurrentCapitalization","capitalizationButton","fontVariant","textTransform","capFormat","hasCapitalizationStyling","computed","isCapitalizationElement","findAncestorCapitalizationElement","setElementCapitalizationStyle","frag","span","transformText","char","toRemove","childElementCount","removeEmptyElements","removeExistingCapitalization","elementsToProcess","maybeAdd","addAncestors","startNode","unwrapElement","showCapitalizationPicker","setCurrentCapitalization","currentCapitalization","toggleUppercase","newStyle","toggleLowercase","toggleCapitalize","toggleSmallCaps","TextAlignPicker","alignments","icon","onAlignSelect","currentAlignment","createAlignPicker","createAlignmentButtons","buttonContainer","alignment","alignButton","iconSvg","charAt","selectAlignment","updateCurrentAlignment","getSelectedBlockElements","firstBlock","textAlign","updateToolbarButtonIcon","default","TextAlign","alignPicker","applyAlignToCurrentSelection","originalRange","caretContainer","caretOffset","applyAlignmentToBlock","newCaretRange","getIconNameForAlignment","center","right","justify","svgContent","iconSpan","currentBlock","blockRange","compareBoundaryPoints","Range","END_TO_START","START_TO_END","showAlignPicker","getCurrentAlignment","ListPicker","listTypes","onListSelect","currentListType","createListPicker","createListTypeButtons","listButton","selectListType","updateCurrentListType","getCurrentListType","updateButtonStates","bullet","roman","alpha","getParentList","getListType","listElement","listStyleType","List","listPicker","applyListToCurrentSelection","createListFromSelection","convertBlocksToList","listItem","toggleOrChangeListType","firstListItem","newListType","removeListFormatting","changeListType","newList","newItem","firstElementChild","firstParagraph","getIconNameForListType","nextBlock","showListPicker","Indent","paddingLeft","applyIndentToCurrentSelection","applyIndentToBlock","currentIndent","newIndent","blockElement","getCurrentIndentLevel","IndentIncrease","IndentDecrease","EmojiPicker","emojis","onEmojiSelect","createEmojiPicker","detectOS","platform","navigator","getEmojiShortcutMessage","createEmojiGrid","emojiTextMessage","emojiGrid","emojiButton","selectEmoji","Emoji","emojiPicker","insertEmojiAtCurrentPosition","emojiParent","emojiElement","zeroWidthSpace","showEmojiPicker","getEmojiElement","emojiInSelection","Image","alt","insertImageAtCurrentPosition","imageElement","spaceNode","openFilePicker","getImageElement","imageInSelection","handleFileUpload","reject","Error","validateImageUrl","hasValidExtension","ext","VideoPopup","onVideoInsert","selectedVideoSrc","savedSelection","createVideoPopup","uploadContainer","textLabel","inputgroup1","inputGroup","updateInsertButton","isValidVideoUrl","showPreview","removePreview","customButton","fileInput","handleFileSelect","createPreviewContainer","previewContainer","cancelButton","insertButton","insertVideo","Video","alert","message","hasVideo","videoSrc","videoPreview","toggleInputGroup","recalculatePosition","controls","muted","removeButton","urlObj","URL","videoExtensions","videoHosts","pathname","hasVideoExtension","isFromVideoHost","host","hostname","validateVideoUrl","restoreSelection","reset","saveSelection","videoPopup","insertVideoAtCurrentPosition","isYouTubeUrl","createYouTubeEmbed","videoId","getYouTubeVideoId","iframe","videoElement","showVideoPopup","getVideoElement","videoButton","videoInSelection","onloadedmetadata","TagPopup","onTagInsert","selectedTagType","createTagPopup","group1","typeLabel","typeSelect","updateSuggestions","group2","contentLabel","contentInput","insertTag","group3","suggestionsContainer","suggestionsLabel","suggestionsList","getSuggestions","suggestion","suggestionButton","tagType","hashtag","custom","hasContent","Tag","tagPopup","tagContent","insertTagAtCurrentPosition","displayText","tagElement","showTagPopup","getTagElement","tagButton","TextSize","sizeMap","getSizeMap","sizeData","applyTextSizeToCurrentSelection","getSizeDisplayName","currentSize","getCurrentSize","textSizeButton","getCurrentSizeStatic","sizeToCss","sizeFormat","normalize","newFont","font1","font2","font3","zwsp","fontEl","showSizePicker","val","queryCommandValue","queryFormatValue","normalizeCssSizeToExecSize","px","steps","closestIndex","minDiff","diff","abs","ImportPopup","onImport","selectedFile","fileType","createImportPopup","typeContainer","updateFileInput","fileInfo","importButton","processImport","selectedType","acceptTypes","getAcceptTypes","updateImportButton","excel","pdf","word","setSelectedFile","formatFileSize","floor","log","pow","readAsText","csvContent","parseCSV","Import","importPopup","insertImportedContent","contentElement","processHtmlContent","processExcelContent","processTextContent","htmlContent","cleanHtmlContent","thead","headerRow","cellData","th","td","allowedTags","allowedAttrs","elementsToRemove","showImportPopup","getSupportedTypes","extensions","mimeTypes","createCustomButton","leadIcon","textSpan","dropdownIcon","setProperty","fontWeight","cursor","flex","newText","Toolbar","group","toolbar2Visible","exclude","drop","prune","g","preloadIcons","createToolbarContainer","flowGroups","groupContainer","addButton","addMoreButton","moreBtn","ResizeObserver","_ro","_scheduleReflow","observe","reflow","_setupKeyboardNav","_focusableButtons","_updateRoving","tabIndex","btns","cur","_reflowQueued","matchMedia","matches","_syncMoreButton","cs","avail","clientWidth","paddingRight","total","budget","used","cut","w","createToolbar","toolbarItems","customButtons","config","iconButtons","toggleToolbar2","isDisabled","setButtonTitle","isToolbar2Visible","callbacks","disconnect","History","delay","maxStack","userOnly","stack","lastSave","saveState","_onInput","handleInput","_onKeydownSave","_onToolbarClick","_onUndoRedo","setupMutationObserver","mutationObserver","MutationObserver","mutations","shouldSave","mutation","mutationTimeout","childList","subtree","attributeFilter","timestamp","shift","restoreState","onHistoryChange","getOffsetInEditor","selectionState","getNodeAtOffset","endNode","totalOffset","targetOffset","currentOffset","nodeLength","getState","stackLength","currentIndex","action","CustomEvent","detail","dispatchEvent","forceSave","originalLastSave","BlockToolbar","showOnSelection","showOnEnter","blockToolbar","currentSelection","currentCursorPosition","originalTags","createBlockToolbar","handleCommand","arrow","_onEditorMouseup","handleSelectionChange","_onEditorKeydown","showAtCursorAfterEnter","_onDocMousedown","_onWindowScroll","updateToolbarPosition","_onEditorScroll","_onEditorKeyup","showAtSelection","editorRect","scrollTop","pageYOffset","documentElement","scrollLeft","pageXOffset","showAt","ensureCursorAtEndOfLine","getCursorPositionAfterEnter","textLength","lastTextNode","showAtCursor","ensureToolbarInViewport","editorArea","toolbarRect2","arrowLeft","arrowDirection","borderTop","borderBottom","borderLeft","borderRight","updateToolbarAt","selectInstance","updateButtonState","originalTag","formatClass","TableToolbar","fadeDelay","tableToolbar","currentTable","currentCell","hideTimeout","createTableToolbar","buttonGroups","groupDiv","_onEditorClick","clickedCell","clickedTable","showAtTable","tableCell","clearHideTimeout","showTableProfile","deleteTable","insertRowAbove","insertRowBelow","deleteRow","insertColumnRight","insertColumnLeft","deleteColumn","currentRow","newRow","createNewRow","checkAndUpdateHandles","cellIndex","newCell","createNewCell","targetCell","tableInfo","cellCount","CodeView","isCodeView","originalContent","codeTextarea","disabledModules","toggleCodeView","showNormalView","showCodeView","updateToolbarButton","formatHTML","disableOtherFeatures","updateOriginalContent","updatedHTML","enableOtherFeatures","disableEditorEvents","disableOtherModules","hideAllPopups","enableEditorEvents","enableOtherModules","disable","enable","formatted","formattedLines","tagStack","closeTagMatch","currentLevel","openTagMatch","buttonTitle","FindReplace","hits","activeIndex","caseSensitive","buildPanel","bindEvents","panel","mkInput","cls","mkBtn","svg","findRow","replaceRow","findInput","replaceInput","countEl","prevBtn","nextBtn","caseBtn","closeBtn","replaceBtn","replaceAllBtn","append","_onKeydown","open","runSearch","navigate","replaceCurrent","replaceAll","selText","select","clearHighlights","escapeRegex","term","updateCount","regex","RegExp","textNodes","ranges","lastIndex","exec","mark","surroundContents","highlightActive","scroll","scrollIntoView","at","repl","SlashMenu","query","filtered","commands","buildCommands","buildMenu","hint","run","menu","move","choose","_onDocPointer","slashNode","slashStart","render","mh","delta","del","Mention","sources","_buildSources","renderItem","source","trigger","triggers","_loadFor","_t","_applyTheme","THEME_VARS","getPropertyValue","media","avatar_url","space","after","ResizeHandles","maintainAspectRatio","snapToGrid","gridSize","handles","isResizing","startX","startY","startWidth","startHeight","currentHandle","aspectRatio","createHandles","handlesContainer","handle","createHandle","borderRadius","boxShadow","handleMouseDown","handleElementClick","_onDocClick","isClickOnResizableElement","isClickOnHandle","_onDocMousemove","handleMouseMove","_onDocMouseup","handleMouseUp","updateHandlePosition","resizableElement","findResizableElement","showHandles","isResizableElement","isImage","isVideo","isTable","lastTableWidth","lastTableHeight","setupTableSizeMonitoring","tableSizeInterval","clearInterval","elementRect","handleName","startLeft","startTop","userSelect","deltaX","deltaY","newWidth","newHeight","maxW","area","ratioByWidth","ratioByHeight","applyDimensions","cellWidth","cellHeight","getActiveElement","setActiveElement","checkTableSizeChange","refreshHandles","setInterval","currentWidth","previousWidth","previousHeight","shouldUpdate","removedNodes","attributeName","characterData","characterDataOldValue","setMaintainAspectRatio","maintain","setConstraints","ImagePopup","onImageInsert","selectedImageSrc","resizeHandler","createImagePopup","isValidImageUrl","insertImage","hasImage","imageSrc","imagePreview","imageExtensions","imageHosts","hasImageExtension","isFromImageHost","setupResizeHandler","removeResizeHandler","loadStyles","loaded","styleElement","loadFallbackStyles","unloadStyles","isLoaded","reloadStyles","addCustomCSS","css","existing","getElementById","RichEditor","fromTextarea","textarea","ta","read","write","mount","nativeDesc","getOwnPropertyDescriptor","HTMLTextAreaElement","prototype","syncing","initial","defineProperty","configurable","call","Event","bubbles","getValue","setValue","baseDestroy","bind","last","createEditor"],"mappings":"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"}