@atri-editor/core 0.4.0 → 0.5.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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/utils/table.ts","../src/extensions/table.ts","../src/media/insert-position.ts","../src/extensions/AtriImage.ts","../src/media/file-policy.ts","../src/utils/url.ts","../src/extensions/attachment-attrs.ts","../src/extensions/attachment-markdown.ts","../src/extensions/Attachment.ts","../src/extensions/AttachmentLink.ts","../src/utils/dom.ts","../src/extensions/MediaFiles.ts","../src/extensions/media.ts","../src/utils/escape.ts","../src/extensions/markdown-inline-html.ts","../src/extensions/formatting.ts","../src/media/Uploader.ts","../src/utils/safe-call.ts","../src/media/MediaRuntime.ts","../src/core/bubble-toolbar.ts","../src/core/CoreEditor.ts","../src/utils/markdown.ts","../src/core/MarkdownService.ts","../src/core/ThemeManager.ts","../src/core/I18nManager.ts","../src/core/ExtensionManager.ts","../src/core/floating-panel.ts","../src/media/InsertPanel.ts","../src/core/StyleOptionPanel.ts","../src/core/style-presets.ts","../src/core/TableGridPanel.ts","../src/core/ToolbarMenuPanel.ts","../src/utils/formatting.ts","../src/core/icons.ts","../src/core/ToolbarManager.ts","../src/core/LinkPanel.ts","../src/core/TableMenu.ts","../src/media/MediaStatusStrip.ts","../src/ai/AIInterceptor.ts","../src/utils/selection.ts","../src/ai/AIService.ts","../src/ai/AICommandMenu.ts","../src/AtriEditor.ts","../src/AtriEditorElement.ts"],"sourcesContent":["/**\n * 表格能力探测:工具栏按钮与门面 insertTable 共用的 gate\n *\n * 内部使用,不进 utils barrel、不随包出口——检测口径(schema spec 上的 tableRole)\n * 是 extension-table 的实现细节,公共面只承诺\"能不能插\"这件事本身\n */\nimport type { Editor } from '@tiptap/core';\n\n/**\n * 内置表格是否真的赢下了 schema\n *\n * 不能扫 extensionManager 里的名字:tiptap 对重名扩展只告警不去重,顶掉场景下\n * 内置 Table 仍在列表里、赢的却是用户的 schema 节点,而它没有 tableRole。\n * prosemirror-tables 的列宽插件启动时就按 tableRole 找 table 节点,找不到直接抛错,\n * 所以这个判据同时决定\"按钮摆不摆\"和\"这套插件注不注册\"\n */\nexport function isBuiltinTableActive(editor: Editor): boolean {\n // tableRole 由 extension-table 的 extendNodeSchema 写进 node spec,prosemirror 的类型里没有它\n const spec = editor.schema.nodes.table?.spec as { tableRole?: string } | undefined;\n return spec?.tableRole === 'table';\n}\n\n/**\n * 内置表格是否真的可用:table:false 不注册、用户用同名扩展顶掉,两种情况都该判 false\n *\n * 命令存在性 + schema 角色两条一起看,判定的才是\"这一下真能插出来\"——\n * 内置 insertTable 命令按 spec.tableRole 找型,找不到就在点击当场抛错\n */\nexport function canInsertTable(editor: Editor): boolean {\n if (typeof editor.commands.insertTable !== 'function') return false;\n return isBuiltinTableActive(editor);\n}\n","/**\n * 表格扩展装配\n *\n * 增量只有一处:列宽拖拽默认打开。单元格对齐不必动 schema —— extension-table 的单元格\n * 自带 align 属性(解析 style=\"text-align: …\",Markdown 的 pipe table 分隔行也对到它,\n * 双向都通),缺的只是按得动的按钮,那是操作菜单的事。\n */\nimport type { EditorOptions } from '@tiptap/core';\nimport { Table, TableCell, TableHeader, TableRow } from '@tiptap/extension-table';\nimport type { AtriTableConfig } from '../types';\nimport { isBuiltinTableActive } from '../utils/table';\n\nconst DEFAULT_TABLE_CONFIG: Required<AtriTableConfig> = {\n resizable: true,\n menu: true,\n};\n\n/**\n * 补上默认值,供扩展装配与操作菜单共用:两处必须看到同一个配置,\n * 否则会出现\"菜单浮得出来,节点却没注册\"这种按了没反应的组合\n *\n * 只归一化 true / undefined / 对象三种写法,false(不注册表格)由调用方先行拦掉\n */\nexport function resolveTableConfig(table?: boolean | AtriTableConfig): Required<AtriTableConfig> {\n return { ...DEFAULT_TABLE_CONFIG, ...(typeof table === 'object' ? table : undefined) };\n}\n\n/**\n * 列宽拖拽由 prosemirror-tables 的列宽插件实现,它启动时按 spec.tableRole 找 table 节点,\n * 而用户用同名扩展顶掉内置 table 时赢的那个节点没有 tableRole——插件照常注册就会直接抛。\n * 插件建立时 schema 已经就绪,所以在此探测而不是在配置里假设\n */\nconst AtriTable = Table.extend({\n addProseMirrorPlugins() {\n if (!isBuiltinTableActive(this.editor)) return [];\n return this.parent?.() ?? [];\n },\n});\n\n/**\n * 构造表格扩展\n *\n * 四件套在这里集中,调用方不必知道 resizable 落在那一个扩展上\n */\nexport function createTableExtensions(\n table?: AtriTableConfig\n): NonNullable<EditorOptions['extensions']> {\n const config = resolveTableConfig(table);\n\n return [AtriTable.configure({ resizable: config.resizable }), TableRow, TableHeader, TableCell];\n}\n","/**\n * 媒体节点的插入落点\n */\nimport { NodeSelection, type Selection } from '@tiptap/pm/state';\n\n/**\n * 插完一个 atom 媒体节点,它会处于选中态;插入命令替换的是整个选区,\n * 于是空文档里连插两张图只剩一张。选中的正是媒体节点时,把落点挪到它后面。\n */\nexport function mediaInsertTarget(selection: Selection): number | { from: number; to: number } {\n if (\n selection instanceof NodeSelection &&\n (selection.node.type.name === 'image' ||\n selection.node.type.name === 'attachment' ||\n selection.node.type.name === 'attachmentLink')\n ) {\n return selection.to;\n }\n\n return { from: selection.from, to: selection.to };\n}\n","/**\n * AtriImage - 图片节点\n *\n * 只往 stock Image 上加\"上传中\"这类临时态:命令、输入规则、markdown 双向 hook\n * 与缩放的 ResizableNodeView 全部沿用原实现,不去 fork 它的 NodeView。\n *\n * 图片没有 progress 属性——逐文件百分比画在附件卡片上,图片的整体进度由\n * MediaRuntime 的队列快照(subscribe)表达,状态条一处说清一批文件。\n */\nimport Image from '@tiptap/extension-image';\nimport { mediaInsertTarget } from '../media/insert-position';\n\nexport const AtriImage = Image.extend({\n addAttributes() {\n return {\n ...this.parent?.(),\n // 只写不读:保存下来的 HTML 里不该留着一个永远不会完成的\"上传中\"\n status: {\n default: null,\n parseHTML: () => null,\n renderHTML: (attributes) =>\n attributes.status ? { 'data-atri-upload-status': attributes.status } : {},\n },\n uploadId: {\n default: null,\n parseHTML: () => null,\n rendered: false,\n },\n };\n },\n\n addCommands() {\n return {\n ...this.parent?.(),\n // stock 版走 insertContent,替换的是整个选区;而插入一张图之后选区正好盖住这张图,\n // 于是空文档里连插两张只剩一张。落点交给 mediaInsertTarget 判断\n setImage:\n (options) =>\n ({ state, commands }) =>\n commands.insertContentAt(mediaInsertTarget(state.selection), {\n type: this.name,\n attrs: options,\n }),\n };\n },\n});\n","/**\n * 文件策略:大小展示与类型白名单\n */\n\nconst UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const;\nconst UNIT_FACTOR = 1024;\n\n/**\n * 字节数 → 人类可读标签,1024 进制\n * B 之外的单位保留一位小数,整数值去掉多余的 .0\n */\nexport function formatFileSize(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes < UNIT_FACTOR) return `${Math.round(bytes)} B`;\n\n let value = bytes;\n let unit = 0;\n while (value >= UNIT_FACTOR && unit < UNITS.length - 1) {\n value /= UNIT_FACTOR;\n unit += 1;\n }\n\n const label = Number.isInteger(value) ? String(value) : value.toFixed(1);\n return `${label} ${UNITS[unit]}`;\n}\n\n/**\n * 人类可读标签 → 字节数\n * 与 formatFileSize 成对使用;标签只有一位小数精度,因此还原值是近似值\n */\nexport function parseFileSize(label?: string | null): number | undefined {\n if (!label) return undefined;\n const match = /^\\s*([\\d.]+)\\s*(B|KB|MB|GB|TB)\\s*$/i.exec(label);\n if (!match) return undefined;\n\n const value = Number(match[1]);\n if (!Number.isFinite(value)) return undefined;\n\n const unit = UNITS.indexOf(match[2].toUpperCase() as (typeof UNITS)[number]);\n return Math.round(value * UNIT_FACTOR ** unit);\n}\n\n/** 单个文件默认上限:10MB */\nexport const DEFAULT_MAX_FILE_SIZE = 10 * UNIT_FACTOR ** 2;\n\n/** 一次投放/选择的默认文件数上限 */\nexport const DEFAULT_MAX_FILES = 10;\n\n/** accept 支持的三种写法:`.pdf`、`image/*`、`image/png` */\nexport function matchesAccept(file: File, accept?: string | string[]): boolean {\n if (!accept) return true;\n\n const patterns = (Array.isArray(accept) ? accept : accept.split(','))\n .map((pattern) => pattern.trim().toLowerCase())\n .filter(Boolean);\n\n if (!patterns.length) return true;\n\n const name = file.name.toLowerCase();\n const type = (file.type || '').toLowerCase();\n\n return patterns.some((pattern) => {\n if (pattern.startsWith('.')) return name.endsWith(pattern);\n if (pattern.endsWith('/*')) return type.startsWith(pattern.slice(0, -1));\n return type === pattern;\n });\n}\n\n/** 把配置的 accept 归一成 <input accept> 需要的逗号串:选择器与校验得是同一份白名单 */\nexport function acceptAttribute(accept?: string | string[]): string | undefined {\n if (!accept) return undefined;\n const patterns = (Array.isArray(accept) ? accept : accept.split(','))\n .map((pattern) => pattern.trim())\n .filter(Boolean);\n\n return patterns.length ? patterns.join(',') : undefined;\n}\n\nexport type FileValidation = { ok: true } | { ok: false; reason: 'too-large' | 'unsupported-type' };\n\nexport function validateFile(\n file: File,\n options: { accept?: string | string[]; maxFileSize?: number }\n): FileValidation {\n const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;\n\n if (file.size > maxFileSize) return { ok: false, reason: 'too-large' };\n if (!matchesAccept(file, options.accept)) return { ok: false, reason: 'unsupported-type' };\n\n return { ok: true };\n}\n\n/** 拖放与粘贴进来的文件按 MIME 分流到图片还是附件 */\nexport function isImageFile(file: File): boolean {\n return (file.type || '').toLowerCase().startsWith('image/');\n}\n","/**\n * URL 协议白名单\n *\n * 库里有五条来源会把 URL 写进可点击的 DOM 属性:宿主调 insertImage/insertAttachment、\n * 上传服务的响应体、外部粘贴的 HTML、setJSON 回灌、以及节点属性的 setNodeMarkup。\n * 逐条在入口校验拦不住全部——只有渲染层是唯一的收口点,所以校验放在 renderHTML。\n */\n\n/** 可点击地址:data: 会让 javascript: / data:text/html 点击即导航执行,故不在列 */\nconst HREF_SCHEMES = ['http', 'https', 'mailto', 'tel', 'ftp', 'ftps'] as const;\n\n/** 媒体资源地址:blob: 是本地预览、data: 是上传失败后的内联退路,两者都必须留 */\nconst MEDIA_SCHEMES = ['http', 'https', 'data', 'blob'] as const;\n\n/**\n * 浏览器解析 URL 前会做的清理:任意位置的 tab/CR/LF 被删掉,首尾的 C0 控制符与空格被剥掉。\n * 不先做这一步,`java\\tscript:` 与 `\\x01javascript:` 这类写法就会绕过前缀匹配。\n * 用 charCodeAt 而不是正则字符类,是为了不在库源码里压制 no-control-regex。\n */\nfunction stripUrlNoise(value: string): string {\n const collapsed = value.replace(/[\\t\\n\\r]/g, '');\n\n let start = 0;\n let end = collapsed.length;\n while (start < end && isStripped(collapsed.charCodeAt(start))) start += 1;\n while (end > start && isStripped(collapsed.charCodeAt(end - 1))) end -= 1;\n\n return collapsed.slice(start, end);\n}\n\nfunction isStripped(code: number): boolean {\n return code <= 0x20 || code === 0x7f;\n}\n\n/** scheme 必须是字母开头,后跟字母数字或 + - . */\nconst SCHEME_PREFIX = /^([a-z][a-z0-9+.-]*):/i;\n\n/**\n * 校验 URL 的协议是否安全,返回清洗后的地址;不接受时返回 null。\n *\n * 没有 scheme 的相对路径(/files/a.pdf、a.pdf、#anchor、?q=1)一律放行:\n * 附件与图片的常见部署形态就是同源相对路径,按协议拦会把正常用法打断。\n */\nexport function safeHref(value: unknown, schemes: readonly string[] = HREF_SCHEMES): string | null {\n if (typeof value !== 'string') return null;\n\n const url = stripUrlNoise(value);\n if (!url) return null;\n\n const scheme = SCHEME_PREFIX.exec(url)?.[1];\n if (!scheme) return url;\n\n return schemes.includes(scheme.toLowerCase()) ? url : null;\n}\n\n/**\n * 校验图片 / 附件的资源地址:比 href 多允许 data: 与 blob:,\n * 因为「本地预览 → 上传完成后换成服务端地址」这条链路全程依赖它们。\n */\nexport function safeMediaSrc(value: unknown): string | null {\n return safeHref(value, MEDIA_SCHEMES);\n}\n","/**\n * 附件两种形态(块级卡片 attachment / 行内链接 attachmentLink)共享的节点属性\n *\n * PM 的 inline/group 是 schema 级声明,一个类型当不了两种形态,只能拆成两个节点;\n * 但属性集必须完全一致,否则切换形态会丢字段,上传管线也得按类型开分支\n */\nimport type { Attribute, CommandProps } from '@tiptap/core';\nimport { NodeSelection, type Transaction } from '@tiptap/pm/state';\nimport type { InsertAttachmentOptions } from '../types';\nimport { mediaInsertTarget } from '../media/insert-position';\nimport { safeHref, safeMediaSrc } from '../utils/url';\n\n/** 两种形态的节点名,判一处用 */\nexport function isAttachmentForm(name: string): boolean {\n return name === 'attachment' || name === 'attachmentLink';\n}\n\n/** 把选区放到离 from 最近的、指定形态的附件节点上——只认\"刚插的那种\",\n * 否则句中建卡片时旧链接会和新卡片打平距离,选错对象 */\nexport function selectNearestForm(tr: Transaction, from: number, typeName: string): void {\n let best = -1;\n let bestDist = Number.POSITIVE_INFINITY;\n tr.doc.descendants((node, pos) => {\n if (node.type.name !== typeName) return;\n const dist = Math.abs(pos - from);\n if (dist < bestDist) {\n bestDist = dist;\n best = pos;\n }\n });\n if (best < 0) return;\n try {\n tr.setSelection(NodeSelection.create(tr.doc, best));\n } catch {\n // 选不上就算了,节点已经进文档\n }\n}\n\n/**\n * 插入附件节点并把选区落在它身上\n *\n * tiptap 的 insertContentAt 收尾只给 TextSelection,而这两种 atom 没有 NodeView\n * 不会自己选中——切形态、删除都以\"选中的附件\"为作用对象,插完就该是选中的\n */\nexport function insertAttachmentContent(\n props: CommandProps,\n typeName: string,\n options: InsertAttachmentOptions\n): boolean {\n const { state, commands, tr, dispatch } = props;\n const target = mediaInsertTarget(state.selection);\n const anchor = typeof target === 'number' ? target : target.to;\n\n // 判据与渲染层一致用 safeHref:附件的 src 最终就是要进 href 的那个值。\n // 用更宽的 safeMediaSrc 会放行 blob:/data:,命令返回 true 而节点渲染出来没有链接,\n // 正是\"成功了但什么都没发生\"。上传管线不经这条命令(直接 setNodeMarkup 写 src),\n // 所以本地预览地址照常工作\n if (safeHref(options.src) === null) {\n console.warn(\n `[Atri Editor] insertAttachment rejected: \"${options.src}\" is not a clickable address.`\n );\n return false;\n }\n\n if (\n !commands.insertContentAt(target, {\n type: typeName,\n attrs: {\n src: options.src,\n name: options.name ?? null,\n size: options.size ?? null,\n mime: options.mime ?? null,\n },\n })\n ) {\n return false;\n }\n\n if (dispatch) selectNearestForm(tr, anchor, typeName);\n return true;\n}\n\n/** 属性值一律走 data-* 或文本节点,不进 innerHTML:文件名可能来自上传方,不可信 */\nexport function dataAttr(\n name: string,\n value: string | number | null | undefined\n): Record<string, string> {\n return value === null || value === undefined || value === '' ? {} : { [name]: String(value) };\n}\n\n/** 百分比同源写两份:data-* 是\"知道进度\"的开关,内联自定义属性给样式表取值 */\nexport function progressAttrs(progress: number | null | undefined): Record<string, string> {\n if (typeof progress !== 'number' || !Number.isFinite(progress)) return {};\n const percent = Math.min(100, Math.max(0, Math.round(progress)));\n return {\n 'data-atri-upload-progress': String(percent),\n style: `--atri-upload-progress: ${percent}%`,\n };\n}\n\n/** 展示名缺省时从地址尾巴推导 */\nexport function attachmentLabel(name: unknown, src: unknown): string {\n return (\n (name as string) ||\n String(src || '')\n .split('/')\n .pop() ||\n ''\n );\n}\n\nexport function attachmentAttributes(): Record<string, Attribute> {\n return {\n src: {\n default: null,\n // 粘贴进来的 data-src 也洗一遍:不洗的话它绕开 AttachmentLink 那处 getAttrs,\n // 直接落进节点属性,再由渲染层的 safeHref 挡掉——属性里仍留着地址,\n // 而 setNodeMarkup / JSON 回灌都不经过渲染层\n parseHTML: (element) => safeMediaSrc(element.getAttribute('data-src')),\n renderHTML: (attributes) => dataAttr('data-src', attributes.src),\n },\n name: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-name'),\n renderHTML: (attributes) => dataAttr('data-name', attributes.name),\n },\n size: {\n default: null,\n parseHTML: (element) => {\n const raw = element.getAttribute('data-size');\n const parsed = raw === null ? Number.NaN : Number(raw);\n return Number.isFinite(parsed) ? parsed : null;\n },\n renderHTML: (attributes) => dataAttr('data-size', attributes.size),\n },\n mime: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-mime'),\n renderHTML: (attributes) => dataAttr('data-mime', attributes.mime),\n },\n // 下面三项是上传过程中的瞬时态:写得出、读不回,\n // 免得保存下来的 HTML 里带着一个永远不会完成的\"上传中\"\n status: {\n default: null,\n parseHTML: () => null,\n renderHTML: (attributes) => dataAttr('data-atri-upload-status', attributes.status),\n },\n // 行内链接没有卡片那条进度条,百分比只落 data 属性;\n // 照常定义是为了让运行时的补丁不分形态,切换后下一个进度 tick 也接得上\n progress: {\n default: null,\n parseHTML: () => null,\n renderHTML: (attributes) => progressAttrs(attributes.progress),\n },\n uploadId: {\n default: null,\n parseHTML: () => null,\n rendered: false,\n },\n };\n}\n","/**\n * 附件两类节点(卡片 file / 行内链接 filelink)的 Markdown 三件套工厂。\n *\n * tokenize/parse/render 的结构完全相同,只差 token 名、语法层级与落点节点名,\n * 收拢成一份实现,避免两处手抄漂移。\n */\nimport type {\n JSONContent,\n MarkdownParseHelpers,\n MarkdownParseResult,\n MarkdownRendererHelpers,\n MarkdownToken,\n MarkdownTokenizer,\n RenderContext,\n} from '@tiptap/core';\nimport { formatFileSize, parseFileSize } from '../media/file-policy';\n\nexport interface AttachmentMarkdownSpec {\n markdownTokenName: string;\n markdownTokenizer: MarkdownTokenizer;\n parseMarkdown: (token: MarkdownToken, helpers: MarkdownParseHelpers) => MarkdownParseResult;\n renderMarkdown: (\n node: JSONContent,\n helpers: MarkdownRendererHelpers,\n ctx: RenderContext\n ) => string;\n}\n\nexport function makeAttachmentMarkdown(options: {\n /** token 名,语法前缀按 `!<name>[` 拼出:file / filelink */\n name: string;\n /** 语法层级:卡片是块级(要求行尾收束),行内链接是 inline */\n level: 'block' | 'inline';\n /** Markdown 解析落点节点名:attachment / attachmentLink */\n nodeName: string;\n}): AttachmentMarkdownSpec {\n const { name, level, nodeName } = options;\n // 语法指令:!file / !filelink,方括号是名称组的开括号,不并入指令\n const directive = `!${name}`;\n // 块级语法要求行尾收束,行内语法不限制\n const trailing = level === 'block' ? '(?:\\\\n|$)' : '';\n const pattern = new RegExp(\n `^${directive.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\[([^\\\\]]*)\\\\]\\\\(\\\\s*([^\\\\s)]+)(?:\\\\s+\"([^\"]*)\")?\\\\s*\\\\)${trailing}`\n );\n\n return {\n markdownTokenName: name,\n\n markdownTokenizer: {\n name,\n level,\n start: (src) => src.indexOf(`${directive}[`),\n tokenize: (src): MarkdownToken | undefined => {\n const match = pattern.exec(src);\n if (!match) return undefined;\n\n return {\n type: name,\n raw: match[0],\n text: match[1] ?? '',\n href: match[2] ?? '',\n title: match[3],\n };\n },\n },\n\n parseMarkdown: (token, helpers) =>\n helpers.createNode(\n nodeName,\n {\n src: token.href,\n name: token.text || null,\n size: parseFileSize(token.title) ?? null,\n mime: null,\n },\n []\n ),\n\n renderMarkdown: (node) => {\n const src = node.attrs?.src ?? '';\n const label = node.attrs?.name ?? '';\n const size = node.attrs?.size;\n\n return size\n ? `${directive}[${label}](${src} \"${formatFileSize(size)}\")`\n : `${directive}[${label}](${src})`;\n },\n };\n}\n","/**\n * Attachment - 文件附件节点\n *\n * 不能走 ExtensionManager.registerNodeView:那条路合成的节点声明不了 addCommands 与\n * markdown 三件套(只有 renderMarkdown,导出得回来不去),附件要能往返。\n */\nimport { Node, mergeAttributes } from '@tiptap/core';\nimport type { DOMOutputSpec, Node as PmNode, NodeType } from '@tiptap/pm/model';\nimport {\n NodeSelection,\n Plugin,\n PluginKey,\n type EditorState,\n type Transaction,\n} from '@tiptap/pm/state';\nimport type { AtriAttachmentDisplay, InsertAttachmentOptions } from '../types';\nimport { formatFileSize } from '../media/file-policy';\nimport { safeHref } from '../utils/url';\nimport {\n attachmentAttributes,\n attachmentLabel,\n dataAttr,\n insertAttachmentContent,\n isAttachmentForm,\n} from './attachment-attrs';\nimport { makeAttachmentMarkdown } from './attachment-markdown';\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n attachment: {\n /** 在选区处插入一个附件卡片 */\n setAttachment: (options: InsertAttachmentOptions) => ReturnType;\n /** 把选中的附件换成指定显示形态(卡片 / 行内链接) */\n setAttachmentDisplay: (options: { display: AtriAttachmentDisplay }) => ReturnType;\n /** 在卡片与行内链接之间切换选中的附件 */\n toggleAttachmentDisplay: () => ReturnType;\n };\n }\n}\n\nconst FORM_TYPES: Record<AtriAttachmentDisplay, 'attachment' | 'attachmentLink'> = {\n card: 'attachment',\n link: 'attachmentLink',\n};\n\n/** 选区指在哪枚附件节点上:NodeSelection、光标紧贴其前/其后都算 */\nfunction locateAttachment(state: EditorState): { node: PmNode; pos: number } | null {\n const isForm = (type: NodeType) => isAttachmentForm(type.name);\n\n const { selection } = state;\n if (selection instanceof NodeSelection) {\n return isForm(selection.node.type) ? { node: selection.node, pos: selection.from } : null;\n }\n if (!('$from' in selection)) return null;\n\n const after = selection.$from.nodeAfter;\n if (after && isForm(after.type)) return { node: after, pos: selection.from };\n const before = selection.$from.nodeBefore;\n if (before && isForm(before.type)) return { node: before, pos: selection.from - before.nodeSize };\n return null;\n}\n\nfunction switchAttachmentDisplay(\n state: EditorState,\n dispatch: ((tr: Transaction) => void) | undefined,\n display: AtriAttachmentDisplay\n): boolean {\n const located = locateAttachment(state);\n if (!located) return false;\n\n const wantName = FORM_TYPES[display];\n if (located.node.type.name === wantName) return false;\n\n const target = state.schema.nodes[wantName];\n if (!target) return false;\n if (!dispatch) return true;\n\n // 不能 setNodeMarkup:block↔inline 换的是父结构(卡片进段落要 wrap,\n // 句中链接转卡片要劈段),replaceRangeWith 会按 schema 把这两件事都做对\n const next = target.create(located.node.attrs);\n const tr = state.tr.replaceRangeWith(located.pos, located.pos + located.node.nodeSize, next);\n\n // 换形态动了结构,映射后的位置不保证还指着节点本身;对不上就按属性找回来,\n // 让选区跟着落到新形态上,连着切换或继续输入都不用再点一次\n let newPos = tr.mapping.map(located.pos, 1);\n if (tr.doc.nodeAt(newPos)?.type.name !== wantName) {\n newPos = -1;\n tr.doc.descendants((n, pos) => {\n if (\n newPos < 0 &&\n n.type.name === wantName &&\n n.attrs.src === located.node.attrs.src &&\n n.attrs.uploadId === located.node.attrs.uploadId\n ) {\n newPos = pos;\n }\n });\n }\n if (newPos >= 0 && target.spec.selectable !== false) {\n const nodeSelection = NodeSelection.create(tr.doc, newPos);\n tr.setSelection(nodeSelection);\n }\n\n dispatch(tr);\n return true;\n}\n\nexport const Attachment = Node.create({\n name: 'attachment',\n\n group: 'block',\n\n atom: true,\n\n selectable: true,\n\n draggable: true,\n\n addAttributes() {\n return attachmentAttributes();\n },\n\n parseHTML() {\n return [{ tag: 'div[data-atri-attachment]' }];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n const { src, name, size, status } = node.attrs;\n const label = attachmentLabel(name, src);\n\n const children: DOMOutputSpec[] = [\n ['span', { class: 'atri-attachment-icon', 'aria-hidden': 'true' }],\n ];\n\n const body: DOMOutputSpec[] = [\n // src 有五条来路(宿主 insertAttachment、上传响应、外部粘贴、setNodeMarkup、\n // setJSON 回灌),逐条在入口校验拦不全,渲染层是唯一都经过的地方。\n // 不过 dataAttr 的\"空值即省略键\"是关键:传 null 会被 mergeAttributes 留下,\n // 序列化出来就成了 href=\"null\",反而多一枚假链接\n [\n 'a',\n {\n class: 'atri-attachment-name',\n ...dataAttr('href', safeHref(src)),\n rel: 'noopener noreferrer',\n },\n label,\n ],\n ];\n if (size) {\n body.push(['span', { class: 'atri-attachment-size' }, formatFileSize(size)]);\n }\n // 名字与大小各占一行:单行挤不下长文件名,省略号又藏掉了最关键的信息\n children.push(['div', { class: 'atri-attachment-body' }, ...body]);\n\n // 卡片上没有一处硬编码文案:状态只靠进度条与配色表达,\n // \"上传中 2/3\"、\"重试\"这类带文字的信息在本地化的状态条里\n if (status) {\n children.push([\n 'span',\n { class: 'atri-attachment-progress' },\n ['span', { class: 'atri-attachment-progress-bar' }],\n ]);\n }\n\n return [\n 'div',\n mergeAttributes(HTMLAttributes, { 'data-atri-attachment': '', class: 'atri-attachment' }),\n ...children,\n ];\n },\n\n addCommands() {\n return {\n setAttachment: (options) => (props) => insertAttachmentContent(props, this.name, options),\n\n setAttachmentDisplay:\n (options) =>\n ({ state, dispatch }) =>\n switchAttachmentDisplay(state, dispatch, options.display),\n\n toggleAttachmentDisplay:\n () =>\n ({ state, dispatch }) => {\n const located = locateAttachment(state);\n if (!located) return false;\n return switchAttachmentDisplay(\n state,\n dispatch,\n located.node.type.name === 'attachment' ? 'link' : 'card'\n );\n },\n };\n },\n\n addProseMirrorPlugins() {\n const findForm = (event: Event) => {\n const target = event.target instanceof Element ? event.target : null;\n const dom = target?.closest('[data-atri-attachment], [data-atri-attachment-link]');\n if (!dom) return null;\n\n const view = this.editor.view;\n const pos = view.posAtDOM(dom as HTMLElement, 0);\n const node = view.state.doc.nodeAt(pos);\n return node && isAttachmentForm(node.type.name) ? { dom, pos, node } : null;\n };\n\n // 一次物理点击 = mousedown 紧跟 click,判定只能在 mousedown 里做、交给 click 兑现:\n // click 时刻的选区分不清\"这次选中的\"与\"上次就选中的\"\n let pendingClick: { pos: number; navigate: boolean } | null = null;\n\n return [\n new Plugin({\n key: new PluginKey('atriAttachmentSelection'),\n props: {\n handleDOMEvents: {\n // 这两种 atom 没有 NodeView,鼠标点了不会自己变成 NodeSelection——\n // 而切形态、删除都以\"选中的附件\"为作用对象;点了却切不动比多一次点击更糟\n mousedown: (view, event) => {\n const hit = findForm(event);\n if (!hit) {\n pendingClick = null;\n return false;\n }\n\n const sel = view.state.selection;\n if (sel instanceof NodeSelection && sel.from === hit.pos) {\n // 已选中:这一笔是\"打开文件\",两个事件都原样放行——\n // 连 mousedown 的默认行为一起拦掉,浏览器就不跳了\n pendingClick = { pos: hit.pos, navigate: true };\n return false;\n }\n\n event.preventDefault();\n pendingClick = { pos: hit.pos, navigate: false };\n view.dispatch(\n view.state.tr.setSelection(NodeSelection.create(view.state.doc, hit.pos))\n );\n return true;\n },\n // 附件里的 <a>(卡片的名字链接、行内链接本体):第一次点击只选中附件,\n // 已选中再点才真的打开文件——否则\"点一下选中去切换\"与\"点开文件\"互相打架。\n // 判据取 mousedown 记下的那一笔而不是 click 时刻的选区:\n // 放行的 mousedown 会被 PM 改写成 TextSelection,选区就当不得证据\n click: (_view, event) => {\n const hit = findForm(event);\n const pending = pendingClick;\n pendingClick = null;\n if (!hit || !pending || pending.pos !== hit.pos) return false;\n\n const inAnchor = event.target instanceof Element && !!event.target.closest('a[href]');\n if (pending.navigate && (inAnchor || hit.node.type.name === 'attachmentLink')) {\n return false;\n }\n\n event.preventDefault();\n return true;\n },\n },\n },\n }),\n ];\n },\n\n ...makeAttachmentMarkdown({\n name: 'file',\n level: 'block',\n nodeName: 'attachment',\n }),\n});\n","/**\n * AttachmentLink - 行内链接形态的文件附件\n *\n * 与 attachment(块级卡片)是两个节点类型:PM 的 inline/group 在 schema 里静态声明,\n * 同一类型当不了两种形态。属性集、瞬时态补丁与 Markdown 语法家族都走共享模块,\n * 切换命令(Attachment.ts 的 setAttachmentDisplay)只在两者间换节点。\n */\nimport { Node, mergeAttributes } from '@tiptap/core';\nimport type { InsertAttachmentOptions } from '../types';\nimport { safeHref } from '../utils/url';\nimport {\n attachmentAttributes,\n attachmentLabel,\n dataAttr,\n insertAttachmentContent,\n} from './attachment-attrs';\nimport { makeAttachmentMarkdown } from './attachment-markdown';\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n attachmentLink: {\n /** 在选区处插入一个行内附件链接 */\n setAttachmentLink: (options: InsertAttachmentOptions) => ReturnType;\n };\n }\n}\n\nexport const AttachmentLink = Node.create({\n name: 'attachmentLink',\n\n inline: true,\n\n group: 'inline',\n\n atom: true,\n\n selectable: true,\n\n draggable: true,\n\n addAttributes() {\n return attachmentAttributes();\n },\n\n parseHTML() {\n // priority 不能省:StarterKit 默认带 Link 扩展,它的 a[href] 规则会把这枚\n // <a> 抢成\"链接 mark 包文字\",节点就静默退化成普通超链接\n return [\n {\n tag: 'a[data-atri-attachment-link]',\n priority: 100,\n // 手写或从别处粘来的 <a> 只带 href,没有 data-src:认下来,别留一枚空链接。\n // data-src 那条来路在 attachmentAttributes().src.parseHTML 里已经洗过\n getAttrs: (element) =>\n element.getAttribute('data-src') ? {} : { src: safeHref(element.getAttribute('href')) },\n },\n ];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n const { src, name } = node.attrs;\n\n return [\n 'a',\n mergeAttributes(HTMLAttributes, {\n 'data-atri-attachment-link': '',\n class: 'atri-attachment-link',\n // 协议不合法就整枚 href 都不写,退化成一枚没有地址的文字,\n // 而不是留着 href=\"javascript:…\" 等用户点\n ...dataAttr('href', safeHref(src)),\n // download 让同源地址点击即下载(跨域浏览器会退化成导航);\n // 值为 null 时 PM 序列化会跳过,没名字就只剩普通链接\n download: name,\n rel: 'noopener noreferrer',\n }),\n ['span', { class: 'atri-attachment-icon', 'aria-hidden': 'true' }],\n attachmentLabel(name, src),\n ];\n },\n\n addCommands() {\n return {\n setAttachmentLink: (options) => (props) => insertAttachmentContent(props, this.name, options),\n };\n },\n\n ...makeAttachmentMarkdown({\n name: 'filelink',\n level: 'inline',\n nodeName: 'attachmentLink',\n }),\n});\n","/**\n * DOM 工具函数\n */\n\n/**\n * 解析元素选择器,返回 HTMLElement\n */\nexport function resolveElement(element: string | HTMLElement): HTMLElement {\n if (typeof element === 'string') {\n const el = document.querySelector(element);\n if (!el) {\n throw new Error(`Element not found: ${element}`);\n }\n return el as HTMLElement;\n }\n return element;\n}\n\n/**\n * 创建带 class 的 div 元素\n */\nexport function createContainer(className?: string): HTMLDivElement {\n const div = document.createElement('div');\n if (className) {\n div.className = className;\n }\n return div;\n}\n\n/**\n * 从拖放/剪贴板事件里取文件列表\n * 剪贴板里的截图与文件都在 files 上,text 为空不代表没有可投的东西\n */\nexport function filesOf(transfer: DataTransfer | null | undefined): File[] {\n if (!transfer) return [];\n return Array.from(transfer.files);\n}\n","/**\n * MediaFiles - 拖放与剪贴板投放本地文件\n *\n * 只做事件到 MediaRuntime 的转接:校验、限流、插入与回写都在运行时里,\n * 面板选文件走的是同一条路。\n */\nimport { Extension } from '@tiptap/core';\nimport { Plugin } from '@tiptap/pm/state';\nimport type { EditorView } from '@tiptap/pm/view';\nimport type { MediaRuntime } from '../media/MediaRuntime';\nimport { filesOf } from '../utils/dom';\n\nexport interface MediaFilesOptions {\n runtime: MediaRuntime | null;\n}\n\n/**\n * 投放点:坐标落在哪个块就插到哪儿,而不是永远跟着当前选区走。\n * 只算位置、不动选区——TextSelection.near 在找不到文本位置时会退化成\n * AllSelection,那次插入就会把整篇文档替换掉。\n */\nfunction dropPos(view: EditorView, event: DragEvent): number | undefined {\n const result = view.posAtCoords({ left: event.clientX, top: event.clientY });\n if (!result) return undefined;\n\n // inside 是坐标所在块的 content 起点,比外层边界位置更贴近用户看到的那个位置\n return result.inside > -1 ? result.inside : result.pos;\n}\n\nexport const MediaFiles = Extension.create<MediaFilesOptions>({\n name: 'mediaFiles',\n\n addOptions() {\n return { runtime: null };\n },\n\n addProseMirrorPlugins() {\n const runtime = this.options.runtime;\n if (!runtime) return [];\n\n return [\n new Plugin({\n props: {\n /** 先于 PM 的剪贴板解析:带文件的粘贴(截图)交给上传管线,纯文本照常走默认链路 */\n handlePaste: (view, event) => {\n const files = filesOf(event.clipboardData);\n if (!files.length) return false;\n\n event.preventDefault();\n if (view.editable) void runtime.handleFiles(files);\n return true;\n },\n\n handleDrop: (view, event) => {\n const dragEvent = event as DragEvent;\n const files = filesOf(dragEvent.dataTransfer);\n if (!files.length) return false;\n\n // 不管能不能插,都要挡下浏览器默认的文件导航\n dragEvent.preventDefault();\n if (!view.editable) return true;\n\n void runtime.handleFiles(files, { pos: dropPos(view, dragEvent) });\n return true;\n },\n },\n }),\n ];\n },\n});\n","/**\n * 媒体扩展装配\n * 图片与附件是一等 Tiptap 扩展:需要 addCommands / addProseMirrorPlugins / markdown 三件套,\n * 而 ExtensionManager.registerNodeView 合成的节点声明不了这些,所以不走那条路\n */\nimport type { EditorOptions } from '@tiptap/core';\nimport type { AtriMediaConfig } from '../types';\nimport type { MediaRuntime } from '../media/MediaRuntime';\nimport { AtriImage } from './AtriImage';\nimport { Attachment } from './Attachment';\nimport { AttachmentLink } from './AttachmentLink';\nimport { MediaFiles } from './MediaFiles';\n\n/** 缩放下限:再小手柄就点不中了 */\nconst MIN_RESIZE_SIZE = 60;\n\nexport function createMediaExtensions(\n media?: AtriMediaConfig | false,\n runtime?: MediaRuntime | null\n): NonNullable<EditorOptions['extensions']> {\n if (media === false) return [];\n\n const image = media?.image;\n const resizable = image?.resize ?? true;\n\n // 附件的 !file[...]() 语法由 Markdown 扩展注册到 marked 后才生效;\n // Markdown 未启用时该节点仍可正常插入与序列化 HTML,只是读不到这行字面语法\n return [\n AtriImage.configure({\n inline: image?.inline ?? false,\n // allowBase64 只管一件事:img[src^=\"data:\"] 解不解得回来(stock Image 里它只出现在\n // parseHTML)。失败后内联写进文档的就是 data URL,而重建编辑器按 getHTML() 回填,\n // 不一起开的话那张图重建一次就静默没了\n allowBase64: (image?.allowBase64 ?? false) || (image?.fallbackToBase64 ?? false),\n resize: resizable\n ? {\n enabled: true,\n minWidth: MIN_RESIZE_SIZE,\n minHeight: MIN_RESIZE_SIZE,\n alwaysPreserveAspectRatio: true,\n }\n : false,\n }),\n Attachment,\n // 行内链接形态:与卡片是两个节点类型(PM 的 inline/group 静态声明),切换靠命令换节点\n AttachmentLink,\n // 拖放与剪贴板投放都汇到 runtime.handleFiles,与面板选文件同一条路\n MediaFiles.configure({ runtime: runtime ?? null }),\n ];\n}\n","/**\n * HTML 转义\n *\n * 只服务一件事:把用户或远端来的文本拼成 HTML 字符串(见 utils/markdown.ts 的降级路径)。\n * 走 ProseMirror schema 的正常渲染不需要这里——DOMOutputSpec 的文本与属性由 PM 自己处理,\n * 手工转义反而转出新实体。\n */\n\nconst TEXT_CHARS: Readonly<Record<string, string>> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n};\n\nconst QUOTE_CHARS: Readonly<Record<string, string>> = {\n '\"': '&quot;',\n \"'\": '&#39;',\n};\n\nfunction replaceAll(\n value: string,\n table: Readonly<Record<string, string>>,\n pattern: RegExp\n): string {\n return value.replace(pattern, (char) => table[char] ?? char);\n}\n\n/**\n * 转义文本节点内容:挡住 `<img src=x onerror=…>` 这类标签破出。\n * 不动引号,所以已确定要进属性的文本还得再过一道 escapeQuotes。\n */\nexport function escapeHtml(value: string): string {\n return replaceAll(value, TEXT_CHARS, /[&<>]/g);\n}\n\n/**\n * 转义引号:`[x](y\" onclick=\"z)` 会闭合掉属性定界符,把 URL 尾巴变成新属性。\n *\n * 与 escapeHtml 是两个独立步骤而不是一个 escapeAttr,因为拼 HTML 的正确顺序是\n * \"整段先过 escapeHtml、再按语法片段插值\"——那时捕获到的文本里 `&` 已经是 `&amp;`,\n * 再跑一遍全量转义就成了 `&amp;amp;`,双重转义会把地址改坏。\n */\nexport function escapeQuotes(value: string): string {\n return replaceAll(value, QUOTE_CHARS, /[\"']/g);\n}\n\nconst DECODED: Readonly<Record<string, string>> = {\n '&amp;': '&',\n '&lt;': '<',\n '&gt;': '>',\n '&quot;': '\"',\n '&#39;': \"'\",\n};\n\n/**\n * escapeHtml + escapeQuotes 的逆运算。\n *\n * 只有一处需要它:内联 HTML 片段的 Markdown 往返(见 extensions/markdown-inline-html.ts)。\n * 那边发出时把值转义成 &amp;,读回来却不会再解码,于是同一个值每导出一次就多背一层\n * 转义(& → &amp; → &amp;amp;),getMarkdown→setMarkdown→getMarkdown 直接不幂等。\n * 发与收必须成对改动,只改一边就把幂等性换掉了。\n */\nexport function decodeEntities(value: string): string {\n return value.replace(/&(?:amp|lt|gt|quot|#39);/g, (entity) => DECODED[entity] ?? entity);\n}\n","/**\n * 内联 HTML 片段的 Markdown 双向支持\n *\n * Markdown 没有\"颜色 / 字号 / 上下标\"的表示法,上游对这些 mark 一个 renderMarkdown 钩子\n * 都没给,于是 @tiptap/markdown 会把它们**静默丢掉**(MarkdownManager 的 getMarkOpening\n * 在没有钩子时返回空串),正文原样输出、样式无痕消失。这里给它们补上钩子,落点是内联 HTML。\n *\n * 为什么必须自己写 tokenizer、而不能只挂 renderMarkdown 让 marked 的通用 html token 接回去:\n * 实测那样做 `<span style=\"…\">**b**</span>` 里的 `**b**` 会回来成字面文本——因为\n * parseHTMLToken 走的是 generateJSON,即 **HTML 解析而不是 Markdown 解析**,\n * 片段内部的语法永远不会被重新分词。后果不只是丢格式:正文里的反斜杠每导出一次翻一倍,\n * getMarkdown → setMarkdown → getMarkdown 不幂等,对内容编辑器是最不能接受的一类缺陷。\n * 所以内层必须显式重新分词——用 tokenizer 收到的第三个参数 lexer.inlineTokens,\n * 它的类型注释就写着\"useful while tokenizing child tokens\"。\n */\nimport type { MarkdownLexerConfiguration, MarkdownParseHelpers, MarkdownToken } from '@tiptap/core';\n\n/** 从 style 属性里认出来的属性名 → mark 属性名 */\nexport type StyleProp = 'color' | 'backgroundColor' | 'fontSize' | 'fontFamily';\n\nconst STYLE_TO_ATTR: Record<string, StyleProp> = {\n color: 'color',\n 'background-color': 'backgroundColor',\n 'font-size': 'fontSize',\n 'font-family': 'fontFamily',\n};\n\nconst ATTR_TO_STYLE: Record<StyleProp, string> = {\n color: 'color',\n backgroundColor: 'background-color',\n fontSize: 'font-size',\n fontFamily: 'font-family',\n};\n\n/**\n * 空串要当\"没有\":上游那几个 addGlobalAttributes 的默认值在被 HTML 解析回来时\n * 给出的是空串而不是 null(`<span style=\"\">` 甚至真的会产出一个空属性 textStyle mark),\n * 只判 null 就会拼出 `<span style=\";;\">` 这种噪音。\n */\nfunction hasValue(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\n/** `style=\"color: red; font-size: 20px\"` → { color: 'red', fontSize: '20px' } */\nexport function styleAttrToAttrs(styleText: string): Partial<Record<StyleProp, string>> {\n const attrs: Partial<Record<StyleProp, string>> = {};\n for (const declaration of styleText.split(';')) {\n const separator = declaration.indexOf(':');\n if (separator < 1) continue;\n const prop = declaration.slice(0, separator).trim().toLowerCase();\n const value = declaration.slice(separator + 1).trim();\n const mapped = STYLE_TO_ATTR[prop];\n if (mapped && hasValue(value)) attrs[mapped] = value;\n }\n return attrs;\n}\n\n/** mark 属性 → style 字符串;一个都没有时返回空串,调用方据此不发标签 */\nexport function attrsToStyleCss(attrs: Record<string, unknown> = {}): string {\n const declarations: string[] = [];\n for (const attr of Object.keys(ATTR_TO_STYLE) as StyleProp[]) {\n const value = attrs[attr];\n if (hasValue(value)) declarations.push(`${ATTR_TO_STYLE[attr]}: ${value}`);\n }\n return declarations.join('; ');\n}\n\nexport interface InlineHtmlToken extends MarkdownToken {\n /** 开始标签与它之间的原始文本,如 ` style=\"color: red\"` */\n openAttrs?: string;\n /** 内层已重新分词的结果,由 tokenize 通过官方 lexer.inlineTokens 产出 */\n tokens?: MarkdownToken[];\n}\n\n/**\n * 一个内联标签的 tokenizer + parseMarkdown。\n *\n * 只负责\"读进来\"这一侧;发出去那一侧(renderMarkdown)必须挂在 mark 自己身上,\n * 因为序列化器是按 mark 类型去查钩子的。\n */\nexport function makeInlineHtmlReader(options: {\n /** marked 的 token 名,同时是 markdownTokenName */\n tokenName: string;\n /** 起始标签匹配片段,需自带捕获组给出标签名与内层文本 */\n pattern: RegExp;\n /** 快速定位用,避免每次都对整段源码跑正则 */\n begins: string[];\n /** token → { markName, attrs };返回 null 表示这个 token 不该产出 mark */\n resolve: (token: InlineHtmlToken) => { markName: string; attrs: Record<string, string> } | null;\n}) {\n const { tokenName, pattern, begins, resolve } = options;\n\n return {\n markdownTokenName: tokenName,\n\n markdownTokenizer: {\n name: tokenName,\n level: 'inline' as const,\n start: (src: string) => {\n let earliest = -1;\n for (const needle of begins) {\n const at = src.indexOf(needle);\n if (at >= 0 && (earliest < 0 || at < earliest)) earliest = at;\n }\n return earliest;\n },\n tokenize: (src: string, _tokens: MarkdownToken[], lexer: MarkdownLexerConfiguration) => {\n const match = pattern.exec(src);\n if (!match) return undefined;\n return {\n type: tokenName,\n raw: match[0],\n text: match[2] ?? '',\n openAttrs: match[1] ?? '',\n // 这一步是整个设计的关键:不在这里重分词,内层的 **粗体** 与转义就会\n // 在 parse 阶段被当成字面文本,进而每导出一次转义翻一倍\n tokens: lexer.inlineTokens(match[2] ?? ''),\n };\n },\n },\n\n /**\n * token.tokens 已经是分好词的内层,直接 parseInline。\n *\n * resolve 认不出属性时(`<span class=\"x\">` 这种)照样解析内层、只是不套 mark——\n * 把整个 token 丢给 parseInline 会让里面的文字一起消失。\n */\n parseMarkdown: (token: InlineHtmlToken, helpers: MarkdownParseHelpers) => {\n const content = helpers.parseInline(token.tokens ?? []);\n const resolved = resolve(token);\n return resolved ? helpers.applyMark(resolved.markName, content, resolved.attrs) : content;\n },\n };\n}\n","/**\n * 基础格式扩展装配(文字颜色 / 背景色 / 字号 / 字体 / 高亮 / 上下标)\n *\n * 这些 mark 在 Tiptap 里都是零运行时依赖的小包,代价全在\" Markdown 往返\"这一侧:\n * 上游除 Highlight 的 `==x==` 之外一个 renderMarkdown 都没给,序列化器对没钩子的 mark\n * 是静默丢弃。所以本文件的主体不是注册扩展,而是给它们补发与补收,详见 markdown-inline-html.ts。\n *\n * 行高(LineHeight)刻意不装:上游它是 span 级属性,挂在选中的文字片段上,\n * 而用户心里的行高是段落属性——span 级改一下几乎看不出变化,还不如不给这个按钮。\n * 它和颜色同属一个 textStyle mark,将来要给就走同一条 attrsToStyleCss 路径加一项即可。\n */\nimport { Extension, type EditorOptions } from '@tiptap/core';\nimport Highlight from '@tiptap/extension-highlight';\nimport Subscript from '@tiptap/extension-subscript';\nimport Superscript from '@tiptap/extension-superscript';\nimport {\n BackgroundColor,\n Color,\n FontFamily,\n FontSize,\n TextStyle,\n} from '@tiptap/extension-text-style';\nimport { escapeHtml, escapeQuotes, decodeEntities } from '../utils/escape';\nimport { attrsToStyleCss, makeInlineHtmlReader, styleAttrToAttrs } from './markdown-inline-html';\n\n/** 发出时转义,读回时 decodeEntities 逆掉——两边必须成对 */\nfunction emitAttrValue(value: string): string {\n return escapeQuotes(escapeHtml(value));\n}\n\n/** mark 属性 → `<span style=\"…\">`;一个属性都没有就不发标签,免得产出一堆空 span */\nfunction renderStyleSpan(node: { attrs?: Record<string, unknown> }, inner: string): string {\n const css = attrsToStyleCss(node.attrs);\n if (!css) return inner;\n return `<span style=\"${emitAttrValue(css)}\">${inner}</span>`;\n}\n\nconst StyledTextStyle = TextStyle.extend({\n renderMarkdown: (node, helpers) => renderStyleSpan(node, helpers.renderChildren(node)),\n});\n\nconst StyledSubscript = Subscript.extend({\n renderMarkdown: (_node, helpers) => `<sub>${helpers.renderChildren(_node)}</sub>`,\n});\n\nconst StyledSuperscript = Superscript.extend({\n renderMarkdown: (_node, helpers) => `<sup>${helpers.renderChildren(_node)}</sup>`,\n});\n\n/**\n * 高亮有两个出口,分色与否决定用哪个:\n * 无色走 Markdown 原生的 `==x==`(上游本来就这样,但我们要覆盖 renderMarkdown 来加彩色分支,\n * 所以这一半也得自己写回来);有色走 `<mark data-color>`——上游的 `==${children}==`\n * 从不读 attrs,multicolor 一开颜色就在导出时丢掉。\n */\nconst StyledHighlight = Highlight.configure({ multicolor: true }).extend({\n renderMarkdown: (node, helpers) => {\n const inner = helpers.renderChildren(node);\n const color = node.attrs?.color;\n if (typeof color !== 'string' || color.trim() === '') return `==${inner}==`;\n return `<mark data-color=\"${emitAttrValue(color)}\" style=\"background-color: ${emitAttrValue(color)}; color: inherit\">${inner}</mark>`;\n },\n});\n\nconst StyledSpanReader = Extension.create({\n // Reader 也得有名有姓:Extension.create 不给 name 就会落成空串,\n // 三个匿名扩展同名会撞上 tiptap 的 Duplicate extension names 警告\n name: 'atriStyledSpanReader',\n ...makeInlineHtmlReader({\n tokenName: 'atriStyledSpan',\n // 非贪婪:我们的发出侧不会产出血缘嵌套的 span(同一个 mark 只出一个标签),\n // 遇到外来的嵌套就退回内层文本,不抛\n pattern: /^<span([^>]*)>([\\s\\S]*?)<\\/span>/,\n begins: ['<span'],\n resolve: (token) => {\n const style = /(?:^|\\s)style=\"([^\"]*)\"/i.exec(token.openAttrs ?? '')?.[1];\n if (!style) return null;\n const attrs = styleAttrToAttrs(decodeEntities(style));\n return Object.keys(attrs).length > 0 ? { markName: 'textStyle', attrs } : null;\n },\n }),\n});\n\n/**\n * `<sub>` / `<sup>` 不能借用 `~x~` / `^x^` 那类写法:实测 marked 把单波浪线当 GFM 删除线,\n * `H~2~O` 回来是 `<s>2</s>`,下标没做成还平白吃掉一个删除线语法。\n * 而上游这两个 mark 自己也没有任何 markdown 钩子,所以只有内联 HTML 这一条路。\n */\nconst SubSupReader = Extension.create({\n name: 'atriInlineTagReader',\n ...makeInlineHtmlReader({\n tokenName: 'atriInlineTag',\n pattern: /^<(sub|sup)>([\\s\\S]*?)<\\/\\1>/,\n begins: ['<sub>', '<sup>'],\n resolve: (token) => {\n const tag = (token.openAttrs ?? '').toLowerCase();\n if (tag !== 'sub' && tag !== 'sup') return null;\n return { markName: tag === 'sup' ? 'superscript' : 'subscript', attrs: {} };\n },\n }),\n});\n\n/**\n * `<mark>` 的 data-color 上游的 parseHTML 本来就认(入向免费),但上游的 tokenizer 只认\n * `==`;这里补的是\"发出去那版的对偶\",让 `<mark data-color>` 形式的输入也走重分词那条路,\n * 从而和内层 markdown 共存。\n */\nconst HighlightReader = Extension.create({\n name: 'atriStyledMarkReader',\n ...makeInlineHtmlReader({\n tokenName: 'atriStyledMark',\n pattern: /^<mark([^>]*)>([\\s\\S]*?)<\\/mark>/,\n begins: ['<mark'],\n resolve: (token) => {\n const color = /data-color=\"([^\"]*)\"/i.exec(token.openAttrs ?? '')?.[1];\n if (!color) return null;\n return { markName: 'highlight', attrs: { color: decodeEntities(color) } };\n },\n }),\n});\n\nexport interface AtriFormattingOptions {\n /** 'drop' 时不挂发出侧的钩子,颜色类样式导出 Markdown 即丢失(入向仍会解析) */\n inlineStyles?: 'html' | 'drop';\n}\n\n/**\n * 构造基础格式扩展。\n *\n * 三个 Reader 与 inlineStyles 无关,'drop' 只关掉发出侧:粘贴与外来 .md 里的\n * `<span style>` 仍然应该解析成 mark,否则关掉一个导出偏好会连带把导入能力也削掉。\n */\nexport function createFormattingExtensions(\n options: AtriFormattingOptions = {}\n): NonNullable<EditorOptions['extensions']> {\n const emit = options.inlineStyles !== 'drop';\n\n return [\n emit ? StyledTextStyle : TextStyle,\n Color,\n BackgroundColor,\n FontFamily,\n FontSize,\n emit ? StyledHighlight : Highlight.configure({ multicolor: true }),\n emit ? StyledSubscript : Subscript,\n emit ? StyledSuperscript : Superscript,\n StyledSpanReader,\n SubSupReader,\n HighlightReader,\n ];\n}\n","/**\n * Uploader - 上传通道\n *\n * 两条路:用户给 handler 就完全接管;给 endpoint 就走内置 XHR。\n * 选 XHR 而不是 fetch,是因为 fetch 没有可移植的上传进度事件(xhr.upload.onprogress)。\n */\nimport type { AtriUploadConfig, UploadContext, UploadHandler, UploadResult } from '../types';\nimport { safeMediaSrc } from '../utils/url';\n\nexport interface Uploader {\n upload(file: File, context: UploadContext): Promise<UploadResult>;\n}\n\n/**\n * 响应体不是 JSON 时,整段字符串会被当作地址——但网关返回 HTML 错误页也走这条路,\n * 于是 `src=\"<!DOCTYPE html>\"` 就写进了文档。尖括号与换行是 HTML 的可靠特征,\n * 这里只按它们判:空格不动,同源相对路径 `/files/my doc.pdf` 还得留得住。\n */\nfunction usableUrl(candidate: string): string | null {\n const value = candidate.trim();\n if (!value || /[<>\\n\\r]/.test(value)) return null;\n return safeMediaSrc(value);\n}\n\n/** 从五花八门的响应体里挑出可用的地址 */\nfunction pickUrl(body: unknown): string | undefined {\n if (!body || typeof body !== 'object') return undefined;\n\n const record = body as Record<string, unknown>;\n // 部分接口把地址包在 data 里,响应体不是 JSON 时整段当地址字符串处理(见调用处)\n const nested = record.data;\n const candidates = [\n record.url,\n record.location,\n nested && typeof nested === 'object' ? (nested as Record<string, unknown>).url : undefined,\n ];\n const url = candidates.find(\n (value): value is string => typeof value === 'string' && value !== ''\n );\n\n return url;\n}\n\nfunction uploadViaEndpoint(\n config: AtriUploadConfig,\n file: File,\n context: UploadContext\n): Promise<UploadResult> {\n return new Promise<UploadResult>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.open('POST', config.endpoint, true);\n xhr.withCredentials = config.withCredentials ?? false;\n\n Object.entries(config.headers ?? {}).forEach(([key, value]) => {\n xhr.setRequestHeader(key, value);\n });\n\n xhr.upload.onprogress = (event) => {\n if (!event.lengthComputable || !event.total) return;\n context.onProgress({\n percent: Math.round((event.loaded / event.total) * 100),\n loaded: event.loaded,\n total: event.total,\n });\n };\n\n xhr.onload = () => {\n if (xhr.status < 200 || xhr.status >= 300) {\n reject(new Error(`上传失败:HTTP ${xhr.status}`));\n return;\n }\n\n let body: unknown = null;\n try {\n body = xhr.responseText ? JSON.parse(xhr.responseText) : null;\n } catch {\n // 有些接口直接返回地址字符串\n body = xhr.responseText;\n }\n\n if (config.transformResult) {\n // 宿主函数在 XHR 回调里同步调用:抛出来这条 promise 就永远不落定,\n // 卡片停在\"上传中\"、hasPendingUploads() 一直 true——宿主的保存闸门被卡死\n let transformed: unknown;\n try {\n transformed = config.transformResult(body, file);\n } catch (cause) {\n reject(new Error(`上传失败:upload.transformResult 抛出了异常(${String(cause)})`));\n return;\n }\n\n const result = transformed as Partial<UploadResult> | null;\n const picked = result && typeof result === 'object' ? result.url : undefined;\n const url = typeof picked === 'string' ? usableUrl(picked) : null;\n\n if (!url) {\n reject(\n new Error(\n `上传失败:upload.transformResult 没有返回带可用 url 的 UploadResult(拿到 ${JSON.stringify(picked)})`\n )\n );\n return;\n }\n\n resolve({ ...(result as UploadResult), url });\n return;\n }\n\n // 响应本身就是地址字符串时也认,省掉一个必填配置\n const candidate = typeof body === 'string' ? body : pickUrl(body);\n const url = candidate ? usableUrl(candidate) : null;\n\n if (!url) {\n reject(new Error('上传失败:响应里找不到文件地址,请用 upload.transformResult 指定'));\n return;\n }\n\n resolve({ url });\n };\n\n xhr.onerror = () => reject(new Error('上传失败:网络错误'));\n xhr.onabort = () => reject(new DOMException('Aborted', 'AbortError'));\n xhr.ontimeout = () => reject(new Error(`上传失败:超过 ${config.timeout}ms 未响应`));\n // 0 正是 XHR 的\"不超时\"默认值,所以没配 timeout 时行为与之前完全一致\n xhr.timeout = config.timeout ?? 0;\n\n if (context.signal.aborted) {\n xhr.abort();\n return;\n }\n context.signal.addEventListener('abort', () => xhr.abort(), { once: true });\n\n const form = new FormData();\n form.append(config.fieldName ?? 'file', file, config.requestName?.(file) ?? file.name);\n xhr.send(form);\n });\n}\n\nexport function createUploader(upload?: AtriUploadConfig | UploadHandler): Uploader | null {\n if (!upload) return null;\n\n if (typeof upload === 'function') {\n return { upload: (file, context) => upload(file, context) };\n }\n\n if (!upload.endpoint) return null;\n\n return { upload: (file, context) => uploadViaEndpoint(upload, file, context) };\n}\n","/**\n * 宿主回调隔离\n *\n * 编辑器在 Tiptap 的事件里同步调用宿主传入的 onCreate/onChange/onError/…,\n * 一个抛错的回调会把整条 dispatch 链打断——症状是\"敲了键但文档没变\",\n * 排查方向还会被带到编辑器身上。回调是外部输入,边界处就得自己兜住。\n */\n\n/** 同一个回调引用只报一次:坏掉的 onUpdate 每敲一键触发一次,不去重就是刷屏 */\nconst reported = new WeakSet<object>();\n\n/**\n * 调用宿主回调,异常降级成一条日志。\n *\n * 非函数(宿主没配这个钩子)静默返回,不占用去重名额。\n * 只用于包宿主的函数,库内部逻辑不要经这里——那会把真正的 bug 也吞成日志。\n */\nexport function runSafely(callback: unknown, label: string, args: unknown[] = []): void {\n if (typeof callback !== 'function') return;\n\n try {\n callback(...args);\n } catch (error) {\n if (reported.has(callback)) return;\n reported.add(callback);\n console.error(`[Atri Editor] The \"${label}\" callback threw and was skipped:`, error);\n }\n}\n","/**\n * MediaRuntime - 上传队列与节点瞬时态\n *\n * 生命周期比 CoreEditor 长:registerNodeView 会重建编辑器,进行中的上传不能跟着丢,\n * 所以它由 AtriEditor 持有一个实例,每次重建通过 bind() 换绑到新的 Editor。\n */\nimport type { Content, Editor } from '@tiptap/core';\nimport type { Node as PmNode } from '@tiptap/pm/model';\nimport type {\n AtriMediaConfig,\n MediaKind,\n MediaRejectReason,\n UploadProgress,\n UploadResult,\n} from '../types';\nimport { createUploader, type Uploader } from './Uploader';\nimport { mediaInsertTarget } from './insert-position';\nimport { runSafely } from '../utils/safe-call';\nimport { DEFAULT_MAX_FILES, acceptAttribute, isImageFile, validateFile } from './file-policy';\n\n/** 节点瞬时态,写进 data-atri-upload-status */\nexport type MediaStatus = 'uploading' | 'error';\n\n/** 队列快照,供状态条这类外部展示消费 */\nexport interface MediaState {\n uploading: number;\n /** 失败且内容还只存在于本地预览里的,丢了就是真丢了 */\n failed: number;\n /** 失败后已按配置内联成 data URL 的图片:不必再传也不该丢 */\n inline: number;\n /** 按文件体积加权的整体进度,无进行中任务时为 0 */\n percent: number;\n}\n\nexport interface HandleFilesOptions {\n /** 缺省时按 MIME 分流到图片与附件 */\n kind?: MediaKind;\n /** 落点位置(拖放用);省略时插在当前选区 */\n pos?: number;\n}\n\n/** 通过校验、排队待插的文件与它要落成的节点类型 */\ninterface MediaEntry {\n file: File;\n kind: MediaKind;\n}\n\ninterface MediaTask {\n id: string;\n kind: MediaKind;\n file: File;\n /** 落地前的本地预览地址,也是编辑器重建后找回节点的钥匙(uploadId 不进 HTML) */\n previewUrl: string | null;\n status: MediaStatus;\n /** 未收到任何进度事件时为 null,让进度条走不定长动画 */\n percent: number | null;\n /** 失败后已内联成 data URL:src 不再指向 previewUrl,但节点仍标着 error 等重试 */\n inlined: boolean;\n controller: AbortController;\n}\n\n/** 读文件为 data URL:本地文件仅有的两条不经过服务端的退路都靠它,前提是图片认 data URL */\nfunction readFileAsDataUrl(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(String(reader.result));\n reader.onerror = () => reject(reader.error ?? new Error('读取文件失败'));\n reader.readAsDataURL(file);\n });\n}\n\nexport class MediaRuntime {\n private config: AtriMediaConfig;\n private uploader: Uploader | null;\n private editor: Editor | null = null;\n private tasks: Map<string, MediaTask> = new Map();\n private listeners: Set<(state: MediaState) => void> = new Set();\n private sequence = 0;\n\n constructor(config: AtriMediaConfig = {}) {\n this.config = config;\n this.uploader = createUploader(config.upload);\n }\n\n /**\n * 换绑到新的 Editor 实例,并把瞬时态补回重建后的文档\n */\n bind(editor: Editor): void {\n this.editor = editor;\n this.restoreTransientState();\n\n // 上传中删掉卡片、或撤销掉刚插入的节点时,顺手把任务摘掉,\n // 否则 hasPendingUploads() 会一直为 true\n editor.on('transaction', ({ transaction }) => {\n if (transaction.docChanged && this.tasks.size) this.prune();\n });\n }\n\n /**\n * 处理一批文件:校验 → 插节点 → 上传 → 回写\n * kind 缺省时按 MIME 分流;MediaKind 与节点名一一对应,直接当 type 用\n */\n async handleFiles(files: File[] | FileList, options: HandleFilesOptions = {}): Promise<void> {\n if (!this.editor) return;\n\n const { kind, pos } = options;\n const maxFiles = this.config.maxFiles ?? DEFAULT_MAX_FILES;\n const queue: MediaEntry[] = [];\n\n for (const file of Array.from(files)) {\n const mediaKind = kind ?? (isImageFile(file) ? 'image' : 'attachment');\n const accept =\n mediaKind === 'image' ? this.config.image?.accept : this.config.attachment?.accept;\n const validation = validateFile(file, { accept, maxFileSize: this.config.maxFileSize });\n\n if (!validation.ok) {\n this.reject(file, validation.reason);\n continue;\n }\n\n // 先校验后限流:既不合格又超额的文件只报它自己的毛病,不报 too-many\n if (queue.length >= maxFiles) {\n this.reject(file, 'too-many');\n continue;\n }\n\n queue.push({ file, kind: mediaKind });\n }\n\n if (!this.uploader) {\n await this.inlineImages(queue, pos);\n return;\n }\n\n const tasks = queue.map(({ file, kind: mediaKind }) => this.createTask(file, mediaKind));\n this.insertContents(\n tasks.map((task) => this.nodeSpec(task)),\n pos\n );\n await Promise.all(tasks.map((task) => this.startUpload(task)));\n }\n\n /**\n * 该类型的文件白名单,已归一成 <input accept> 能用的写法\n * 面板与校验共用同一份配置,免得选得到却传不了\n */\n acceptFor(kind: MediaKind): string | undefined {\n return acceptAttribute(\n kind === 'image' ? this.config.image?.accept : this.config.attachment?.accept\n );\n }\n\n /**\n * 重试所有失败的上传,卡片原地回到上传中\n * 失败后已内联成 data URL 的图片也在其中:换到服务端地址前它一直算\"失败\"\n */\n retryFailed(): Promise<void> {\n const retried = Array.from(this.tasks.values())\n .filter((task) => task.status === 'error')\n .map((task) => this.startUpload(task));\n\n return Promise.all(retried).then(() => undefined);\n }\n\n /**\n * 文档里是否还有\"现在就保存会丢内容\"的文件:上传中与失败都算,\n * 因为它们的 src 还指着本地预览地址(blob:),刷新即失效\n *\n * 失败后已按配置内联成 data URL 的图片不计入——内容已经安全落进文档,\n * 服务端地址只是可选的优化,别让一个一直失败的上传把保存永久卡住\n */\n hasPendingUploads(): boolean {\n return Array.from(this.tasks.values()).some((task) => !task.inlined);\n }\n\n getState(): MediaState {\n const tasks = Array.from(this.tasks.values());\n const uploading = tasks.filter((task) => task.status === 'uploading');\n const total = uploading.reduce((sum, task) => sum + task.file.size, 0);\n const loaded = uploading.reduce(\n (sum, task) => sum + (task.file.size * clampPercent(task.percent ?? 0)) / 100,\n 0\n );\n\n return {\n uploading: uploading.length,\n failed: tasks.filter((task) => task.status === 'error' && !task.inlined).length,\n inline: tasks.filter((task) => task.inlined).length,\n percent: total ? Math.round((loaded / total) * 100) : 0,\n };\n }\n\n subscribe(listener: (state: MediaState) => void): () => void {\n this.listeners.add(listener);\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n destroy(): void {\n this.tasks.forEach((task) => {\n task.controller.abort();\n revokePreviewUrl(task);\n });\n this.tasks.clear();\n\n this.listeners.clear();\n this.editor = null;\n }\n\n /**\n * 无上传通道:图片按配置退化成 base64 内联,附件没有合理退路\n * 先把文件都读完再一起插,顺序才与选文件时一致\n */\n private async inlineImages(entries: MediaEntry[], pos?: number): Promise<void> {\n const contents: Content[] = [];\n\n for (const { file, kind } of entries) {\n if (kind !== 'image' || !this.config.image?.allowBase64) {\n this.reject(file, 'no-upload');\n continue;\n }\n\n try {\n const src = await readFileAsDataUrl(file);\n contents.push({ type: 'image', attrs: { src, alt: file.name } });\n } catch (cause) {\n this.reject(file, 'upload-failed', cause);\n }\n }\n\n this.insertContents(contents, pos);\n }\n\n private createTask(file: File, kind: MediaKind): MediaTask {\n const task: MediaTask = {\n id: `atri-upload-${(this.sequence += 1)}`,\n kind,\n file,\n previewUrl: createPreviewUrl(file),\n status: 'uploading',\n percent: null,\n inlined: false,\n controller: new AbortController(),\n };\n this.tasks.set(task.id, task);\n return task;\n }\n\n /**\n * 带本地预览的节点内容;上传完这些地址就被服务端地址换掉\n */\n private nodeSpec(task: MediaTask): Content {\n const { file, previewUrl } = task;\n const attrs: Record<string, unknown> = {\n src: previewUrl ?? file.name,\n status: task.status,\n uploadId: task.id,\n };\n\n if (task.kind === 'attachment') {\n attrs.name = file.name;\n attrs.size = file.size;\n attrs.mime = file.type || null;\n } else {\n attrs.alt = file.name;\n }\n\n // 附件按配置的默认形态直接插成行内链接;补丁管线按 attrs 认节点,两种形态不用区分\n const type =\n task.kind === 'attachment' && this.config.attachment?.display === 'link'\n ? 'attachmentLink'\n : task.kind;\n\n return { type, attrs };\n }\n\n /**\n * 一批节点走一次事务:逐个插的话每插一个\"那个位置\"的含义就变一次,顺序会乱;\n * 顺带让一次多文件拖放只需撤销一步\n * pos 为拖放落点,省略时落在当前选区(选中的媒体节点不被顶掉)\n */\n private insertContents(contents: Content[], pos?: number): void {\n const editor = this.editor;\n if (!editor || !contents.length) return;\n\n editor\n .chain()\n .insertContentAt(pos ?? mediaInsertTarget(editor.state.selection), contents)\n .run();\n this.emit();\n }\n\n private async startUpload(task: MediaTask): Promise<void> {\n if (!this.uploader) return;\n\n task.status = 'uploading';\n task.percent = null;\n // 重试一张已内联的图:src 先留着 data URL(换地址前它仍是能看的图),\n // 但闸门回到\"有上传在飞\",再失败也会重新判一次要不要内联\n task.inlined = false;\n task.controller = new AbortController();\n this.patch(task, this.stateAttrs(task, 'uploading'));\n this.emit();\n\n try {\n const result = await this.uploader.upload(task.file, {\n onProgress: (progress) => this.updateProgress(task, progress),\n signal: task.controller.signal,\n });\n\n // 请求期间节点可能已被删掉,那时任务已经清理过了\n if (!this.tasks.has(task.id)) return;\n this.applyResult(task, result);\n } catch (cause) {\n if (task.controller.signal.aborted) {\n this.dropTask(task);\n return;\n }\n\n task.status = 'error';\n task.percent = null;\n\n // 只有图片有 base64 退路:附件内联等于把几 MB 塞进 !file[..](data:…) 那一行\n const canInline = task.kind === 'image' && this.config.image?.fallbackToBase64 === true;\n const inlined = canInline && (await this.inlineAfterFailure(task));\n\n if (!inlined) {\n this.patch(task, this.stateAttrs(task, 'error'));\n // patch 只改节点不广播,失败态得靠这一次 emit 才会上到状态条\n this.emit();\n }\n // 内联成不成功都该让宿主知道这次上传失败了;图片能不能显示是另一件事\n this.reject(task.file, 'upload-failed', cause);\n }\n }\n\n /**\n * 上传失败后把图片读成 data URL,原位换掉 src\n *\n * 是 patch 而不是插新节点:位置与\"撤销一步回到插入前\"这两条语义都靠它才不碎。\n * error 态留着——重试按钮还在,网络恢复后能把 data URL 换成服务端地址\n */\n private async inlineAfterFailure(task: MediaTask): Promise<boolean> {\n // 请求期间节点可能已被删掉,那时任务已经清理过了\n if (!this.tasks.has(task.id)) return false;\n\n let src: string;\n try {\n src = await readFileAsDataUrl(task.file);\n } catch {\n return false;\n }\n\n this.patch(task, { src, ...this.stateAttrs(task, 'error') });\n // patch 找不到节点时会把任务摘掉,那时没什么可内联的\n if (!this.tasks.has(task.id)) return false;\n\n task.inlined = true;\n // blob 地址已经不被文档引用了,留着就是白占一份文件内存\n revokePreviewUrl(task);\n this.emit();\n return true;\n }\n\n private applyResult(task: MediaTask, result: UploadResult): void {\n const { file } = task;\n const attrs: Record<string, unknown> = {\n src: result.url,\n status: null,\n uploadId: null,\n };\n\n if (task.kind === 'attachment') {\n attrs.progress = null;\n attrs.name = result.name ?? file.name;\n attrs.size = result.size ?? file.size;\n attrs.mime = result.mime ?? (file.type || null);\n } else {\n attrs.alt = result.name ?? file.name;\n }\n\n this.patch(task, attrs);\n this.tasks.delete(task.id);\n revokePreviewUrl(task);\n this.emit();\n }\n\n private updateProgress(task: MediaTask, progress: UploadProgress): void {\n if (task.status !== 'uploading') return;\n\n const percent = Number.isFinite(progress.percent) ? clampPercent(progress.percent) : null;\n task.percent = percent;\n\n // 图片节点上没有 progress 属性,逐文件百分比只画在附件卡片上;\n // 图片的整体进度走队列快照,顺带省掉每次进度都重建一次 DOM\n if (task.kind === 'attachment') {\n this.patch(task, { progress: percent }, { silent: true });\n }\n this.emit();\n }\n\n /**\n * 写进节点的那份状态;附件卡片额外带百分比\n */\n private stateAttrs(\n task: MediaTask,\n status: MediaStatus,\n progress: number | null = null\n ): Record<string, unknown> {\n return task.kind === 'attachment' ? { status, progress } : { status };\n }\n\n private patch(\n task: MediaTask,\n attrs: Record<string, unknown>,\n options: { silent?: boolean } = {}\n ): void {\n const editor = this.editor;\n // 编辑器正在重建时丢掉这次更新:bind() 之后按队列现状补齐\n if (!editor || editor.isDestroyed) return;\n\n const position = this.findPosition(task);\n if (position === null) {\n // 找不到节点说明用户删了它,继续传上去也没人接收\n task.controller.abort();\n this.dropTask(task);\n return;\n }\n\n const node = editor.state.doc.nodeAt(position);\n if (!node) return;\n\n const tr = editor.state.tr.setNodeMarkup(position, undefined, {\n ...node.attrs,\n ...attrs,\n });\n\n // 撤销一步回到插入前,而不是回到\"带预览地址的中间态\";\n // 代价是重做后节点退回预览地址,需要重新上传\n tr.setMeta('addToHistory', false);\n if (options.silent) tr.setMeta('preventUpdate', true);\n\n editor.view.dispatch(tr);\n }\n\n /**\n * 编辑器重建后 uploadId 不在 HTML 里,靠本地预览地址把瞬时态补回文档\n */\n private restoreTransientState(): void {\n const editor = this.editor;\n if (!editor || !this.tasks.size) return;\n\n const pending: { position: number; node: PmNode; task: MediaTask }[] = [];\n editor.state.doc.descendants((node, position) => {\n const task = this.taskByPreviewUrl(node.attrs.src);\n if (task && node.attrs.uploadId == null) pending.push({ position, node, task });\n });\n if (!pending.length) return;\n\n const tr = editor.state.tr;\n // setNodeMarkup 不改叶子节点尺寸,倒序写免得位置偏移\n pending\n .sort((a, b) => b.position - a.position)\n .forEach(({ position, node, task }) => {\n tr.setNodeMarkup(position, undefined, {\n ...node.attrs,\n uploadId: task.id,\n ...this.stateAttrs(task, task.status, task.percent),\n });\n });\n tr.setMeta('addToHistory', false);\n tr.setMeta('preventUpdate', true);\n editor.view.dispatch(tr);\n }\n\n /**\n * 丢掉文档里已经没有对应节点的任务,中止其请求\n */\n private prune(): void {\n const editor = this.editor;\n if (!editor || editor.isDestroyed) return;\n\n const alive = new Set<string>();\n editor.state.doc.descendants((node) => {\n if (typeof node.attrs.uploadId === 'string') alive.add(node.attrs.uploadId);\n if (typeof node.attrs.src === 'string') alive.add(node.attrs.src);\n });\n\n this.tasks.forEach((task) => {\n const exists = alive.has(task.id) || (task.previewUrl !== null && alive.has(task.previewUrl));\n if (exists) return;\n\n task.controller.abort();\n this.dropTask(task);\n });\n }\n\n private findPosition(task: MediaTask): number | null {\n const editor = this.editor;\n if (!editor) return null;\n\n return findTaskPosition(editor.state.doc, task);\n }\n\n private taskByPreviewUrl(src: unknown): MediaTask | null {\n if (typeof src !== 'string' || !src) return null;\n for (const task of this.tasks.values()) {\n if (task.previewUrl === src) return task;\n }\n return null;\n }\n\n /**\n * 摘掉任务并回收预览地址;是否 abort 由调用方决定\n */\n private dropTask(task: MediaTask): void {\n if (this.tasks.get(task.id) !== task) return;\n this.tasks.delete(task.id);\n revokePreviewUrl(task);\n this.emit();\n }\n\n private reject(file: File, reason: MediaRejectReason, cause?: unknown): void {\n if (this.config.onError) {\n // 回调里抛错不能把整条投放链路打断:reject 是在 handleFiles 的同步路径上叫的\n runSafely(this.config.onError, 'media.onError', [{ file, reason, cause }]);\n return;\n }\n\n // 没配回调也留一条日志:文件被静默丢弃是最难排查的那种\"点了没反应\"\n console.warn(`[Atri Editor] File \"${file.name}\" was rejected (${reason}).`);\n }\n\n private emit(): void {\n if (!this.listeners.size) return;\n const state = this.getState();\n this.listeners.forEach((listener) => listener(state));\n }\n}\n\nfunction clampPercent(percent: number): number {\n return Math.round(Math.min(100, Math.max(0, percent)));\n}\n\n/**\n * 找任务对应的节点位置。\n *\n * 预览地址这条兜底必须要求 previewUrl 非空:revokePreviewUrl 会把它置成 null,\n * 而 `node.attrs.src === null` 对任何还没落地地址的媒体节点都成立,\n * 于是进度与状态会写到第一张不相干的卡片上,真正的卡片反而永不更新。\n * uploadId 唯一且稳定,所以先按它找,找不到才退到预览地址。\n */\nexport function findTaskPosition(\n doc: PmNode,\n task: { id: string; previewUrl: string | null }\n): number | null {\n let byUploadId: number | null = null;\n let byPreviewUrl: number | null = null;\n\n doc.descendants((node, position) => {\n if (byUploadId === null && node.attrs.uploadId === task.id) byUploadId = position;\n if (byPreviewUrl === null && task.previewUrl !== null && node.attrs.src === task.previewUrl) {\n byPreviewUrl = position;\n }\n });\n\n return byUploadId ?? byPreviewUrl;\n}\n\n/**\n * 预览地址只在浏览器里有;拿不到时节点用文件名占位,上传完就被真实地址替换\n */\nfunction createPreviewUrl(file: File): string | null {\n if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') return null;\n try {\n return URL.createObjectURL(file);\n } catch {\n return null;\n }\n}\n\nfunction revokePreviewUrl(task: MediaTask): void {\n const { previewUrl } = task;\n if (!previewUrl) return;\n if (typeof URL === 'undefined' || typeof URL.revokeObjectURL !== 'function') return;\n\n URL.revokeObjectURL(previewUrl);\n task.previewUrl = null;\n}\n","/**\n * 选区浮动工具栏的判定:这一次要不要浮出、浮出哪一组\n *\n * 只读状态、不碰排版,BubbleMenu 的 shouldShow 直接喂这里的函数,\n * 单测也就不必先给 jsdom 补一套它根本没有的布局能力\n */\nimport { isNodeSelection, type Editor } from '@tiptap/core';\nimport type { EditorState } from '@tiptap/pm/state';\n\n/** 浮层的作用对象:一段文字,还是一个整节点 */\nexport type BubbleGroup = 'text' | 'node';\n\n/**\n * 文字组只放行内格式:块级与列表留在顶栏,浮层短到不遮住所选的字。\n *\n * 只收纯开关——带浮层的选择面板进不了这里(按钮随浮层一起被摘出文档,面板无处可锚,\n * 见 ToolbarManager.attachBubbleToolbar 的告警)。所以是\"高亮\"这个开关而不是\"挑高亮色\"。\n * 项 id 必须与 mark 名同形,allowsBubbleMark 就是拿它去查 schema 的。\n */\nexport const BUBBLE_TEXT_ITEMS = [\n 'bold',\n 'italic',\n 'underline',\n 'strike',\n 'code',\n 'highlight',\n 'subscript',\n 'superscript',\n] as const;\n\n/** 节点组换一批:附件能切形态,图片与附件都能删 */\nexport const BUBBLE_NODE_ITEMS = ['attachmentDisplay', 'delete'] as const;\n\n/** 选中这些节点才有可施加的操作,别的整节点选择(将来的 atom 之类)什么都不浮 */\nexport const BUBBLE_SELECTABLE_NODES = ['image', 'attachment', 'attachmentLink'] as const;\n\n/**\n * 选中的可操作节点叫什么,没选中或选的是别的节点就返回 null\n */\nexport function bubbleNodeNameFor(state: EditorState): string | null {\n if (!isNodeSelection(state.selection)) return null;\n const { name } = state.selection.node.type;\n return (BUBBLE_SELECTABLE_NODES as readonly string[]).includes(name) ? name : null;\n}\n\n/**\n * 这一次浮出哪一组。空选区与空白选区仍算 'text',由 shouldShow 里那句 textBetween 判掉\n */\nexport function bubbleGroupFor(state: EditorState): BubbleGroup | null {\n if (isNodeSelection(state.selection)) return bubbleNodeNameFor(state) ? 'node' : null;\n return 'text';\n}\n\n/**\n * 选区所在的块允许浮层里哪个标记:只问 schema,不问按钮的 disabled\n *\n * 按钮状态是工具栏挂在 transaction 事件上同步的,而插件读 shouldShow 的时机在\n * view.updateState 之内,读到的永远是上一次选区留下的状态(选完附件再选文字就浮不出来)\n */\nfunction allowsBubbleMark(state: EditorState, from: number): boolean {\n const parent = state.doc.resolve(from).parent.type;\n\n return BUBBLE_TEXT_ITEMS.some((markName) => {\n const mark = state.schema.marks[markName];\n return !!mark && parent.allowsMarkType(mark);\n });\n}\n\n/**\n * 交给 BubbleMenu.configure 的 shouldShow。\n *\n * 没照抄官方那句\"编辑器必须有焦点\":失焦隐藏本来就归插件自己的 blurHandler 管,\n * 而 hasFocus() 在 jsdom 里恒假,加上它单测就完全驱动不了这个插件。\n */\nexport function shouldShowBubbleMenu(props: {\n editor: Editor;\n element: HTMLElement;\n state: EditorState;\n from: number;\n to: number;\n}): boolean {\n const { editor, element, state, from, to } = props;\n\n let group = editor.isEditable ? bubbleGroupFor(state) : null;\n const nodeName = group === 'node' ? bubbleNodeNameFor(state) : null;\n\n // 空选区与只框到空白(拖选跨过空段时很常见)都没有可格式化的字:textBetween 空即是 from === to\n if (group === 'text' && !state.doc.textBetween(from, to).trim()) group = null;\n // 代码块里的文字五项标记一个都挂不上,别浮出一排按不动的灰按钮\n if (group === 'text' && !allowsBubbleMark(state, from)) group = null;\n\n // 哪一组该占宽度由 CSS 读这两个属性决定,所以必须赶在返回之前写好:\n // 插件紧接着就拿这个元素算位置,而 transaction 事件晚于 updateState,指望不上下一次状态同步\n element.dataset.atriBubbleMode = group ?? 'none';\n element.dataset.atriBubbleNode = nodeName ?? '';\n\n if (!group) return false;\n\n // 这一组一个按钮都没渲染出来(比如 media:false 把切形态那项连定义一起带走了)就别浮出空盒子\n return !!element.querySelector(`[data-atri-bubble-group=\"${group}\"] [data-toolbar-item]`);\n}\n","/**\n * CoreEditor - Tiptap v3 编辑器封装\n */\nimport { Editor, type Content, type EditorOptions } from '@tiptap/core';\nimport StarterKit from '@tiptap/starter-kit';\nimport { Markdown } from '@tiptap/markdown';\nimport { Placeholder } from '@tiptap/extensions';\nimport TextAlign from '@tiptap/extension-text-align';\nimport BubbleMenu from '@tiptap/extension-bubble-menu';\nimport { TaskItem, TaskList } from '@tiptap/extension-list';\nimport { createTableExtensions, resolveTableConfig } from '../extensions/table';\nimport { createMediaExtensions } from '../extensions/media';\nimport { createFormattingExtensions } from '../extensions/formatting';\nimport { MediaRuntime } from '../media/MediaRuntime';\nimport { shouldShowBubbleMenu } from './bubble-toolbar';\nimport { runSafely } from '../utils/safe-call';\nimport type {\n AtriLinkConfig,\n AtriMarkdownConfig,\n AtriMediaConfig,\n AtriTableConfig,\n} from '../types';\n\n/** 与 Tiptap 的 Link 默认 HTMLAttributes 同值,只在用户改了 target 时用来顶掉默认那份 */\nconst LINK_HTML_ATTRIBUTES = {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n class: null,\n};\n\nexport interface CoreEditorConfig {\n element: HTMLElement;\n content?: string | object;\n contentFormat?: 'html' | 'json' | 'markdown';\n editable?: boolean;\n placeholder?: string;\n extensions?: EditorOptions['extensions'];\n markdown?: AtriMarkdownConfig;\n /** 媒体(图片 / 附件)配置,false 时不注册任何媒体节点 */\n media?: AtriMediaConfig | false;\n /** 表格配置,false 时不注册 Table 扩展,工具栏也不出插入按钮;默认开 */\n table?: boolean | AtriTableConfig;\n /** 链接配置,省略时编辑态点击不导航 */\n link?: AtriLinkConfig;\n /** 基础格式(颜色 / 字号 / 字体 / 高亮 / 上下标)开关,默认开 */\n formatting?: boolean;\n /**\n * 内置代码块开关,默认开。\n * false 时不注册 StarterKit 的 CodeBlock,节点名让给接入方自己的实现\n */\n codeBlock?: boolean;\n /**\n * 复用外部的上传运行时\n * AtriEditor 重建编辑器时会把同一个实例传进来,进行中的上传才不会丢\n */\n mediaRuntime?: MediaRuntime;\n /**\n * 选区浮动工具栏的挂载元素:给了就注册 BubbleMenu,不给就不注册\n * 元素由门面持有 —— 插件隐藏时会把它从文档里摘掉,显示时再 append 回来\n */\n bubbleElement?: HTMLElement | null;\n onCreate?: () => void;\n onUpdate?: () => void;\n onFocus?: () => void;\n onBlur?: () => void;\n onDestroy?: () => void;\n}\n\nexport class CoreEditor {\n private editor: Editor;\n private config: CoreEditorConfig;\n /** 占位符文本:由 Placeholder 扩展以函数形式每次读取,改值即可生效 */\n private placeholderText: string;\n private mediaRuntime: MediaRuntime | null;\n /** 注入的那种要活过重建编辑器(门面持有),只能自己 new 的那个才归这里回收 */\n private ownsMediaRuntime = false;\n\n constructor(config: CoreEditorConfig) {\n this.config = config;\n this.placeholderText = config.placeholder ?? '';\n this.mediaRuntime =\n config.media === false ? null : (config.mediaRuntime ?? new MediaRuntime(config.media));\n this.ownsMediaRuntime = this.mediaRuntime !== null && !config.mediaRuntime;\n this.editor = this.createEditor();\n }\n\n private createEditor(): Editor {\n const { element, content, contentFormat, editable, extensions } = this.config;\n\n const editorExtensions: EditorOptions['extensions'] = [\n StarterKit.configure({\n // StarterKit v3 默认包含 Underline, Link, TrailingNode。\n // 链接:openOnClick 默认关——编辑态点击落在链接上,用户要的是改它,\n // 而 Link 扩展开着导航时会把这次点击吃掉(连光标都放不进去)并打开新标签页,\n // 未保存的内容就跟着这次点击没了。关掉后点击交给 LinkPanel 就地编辑。\n link: {\n openOnClick: this.config.link?.openOnClick ?? false,\n markdownLinks: this.config.link?.markdownLinks ?? false,\n HTMLAttributes: {\n ...LINK_HTML_ATTRIBUTES,\n target: this.config.link?.target ?? LINK_HTML_ATTRIBUTES.target,\n },\n },\n // 关掉内置代码块,让接入方(通常就是 @atri-editor/code-highlight)的实现独占这个节点名。\n // 为什么提供一个开关而不是让宿主直接传同名扩展顶掉:实测同名确实赢(后注册的赢,\n // 节点与标记都是),所以不关也能工作——但 tiptap 会打一句\n // \"Duplicate extension names found: ['codeBlock']\" 的 warning,而且这件事靠的是\n // ExtensionManager 的去重顺序这个内部行为,不是对外承诺。\n // 多一个包就多一处\"看起来正常但踩在实现细节上\"的配置,不如把意图写成显式的一档\n codeBlock: this.config.codeBlock === false ? false : undefined,\n }),\n ];\n\n // 添加 Placeholder 扩展:空段带上 is-empty / is-editor-empty 类与 data-placeholder。\n // 无条件注册,且用函数取文本——插件创建时会快照 options 对象,\n // 之后再改 extension.options 是看不见的,函数则每次重算装饰都重新读取\n editorExtensions.push(\n Placeholder.configure({\n placeholder: () => this.placeholderText,\n })\n );\n\n // 添加 TextAlign 扩展\n editorExtensions.push(\n TextAlign.configure({\n types: ['heading', 'paragraph'],\n })\n );\n\n // 添加 Markdown 扩展\n const markdownEnabled = this.config.markdown?.enabled !== false;\n if (markdownEnabled) {\n editorExtensions.push(\n Markdown.configure({\n indentation: this.config.markdown?.indentation,\n markedOptions: this.config.markdown?.markedOptions,\n })\n );\n }\n\n // markdown 内容依赖 Markdown 扩展解析,扩展未启用时不声明该格式\n const contentType =\n contentFormat === 'markdown' && !markdownEnabled ? undefined : contentFormat;\n\n // 图片 / 附件节点自带 markdown hook,注册时机不影响 Markdown 扩展的收集(它在构造时统一读取所有扩展),\n // 但放在用户扩展之前可以让用户用同名扩展顶掉内置节点\n editorExtensions.push(...createMediaExtensions(this.config.media, this.mediaRuntime));\n\n // 表格四件套:自带 insertTable 等命令与 markdown 序列化钩子(pipe table ↔ 节点),\n // 与媒体扩展同理放在用户扩展之前,让用户能用同名扩展顶掉内置节点\n if (this.config.table !== false) {\n editorExtensions.push(...createTableExtensions(resolveTableConfig(this.config.table)));\n }\n\n // 选区浮动工具栏:默认 appendTo 就是 element(= view.dom.parentElement),\n // 浮层与正文在同一个滚动盒里,所以绝对定位跟着内容走,内滚与窗口滚都不用重算\n if (this.config.bubbleElement) {\n editorExtensions.push(\n BubbleMenu.configure({\n element: this.config.bubbleElement,\n // 官方默认 250ms 防抖,拖选时要等手停下来才浮出;这里直接关掉\n updateDelay: 0,\n shouldShow: shouldShowBubbleMenu,\n options: {\n // 位置重算只监听一个 scrollTarget + window resize,交给真正会滚的那层\n scrollTarget: element,\n // 贴着正文盒的边翻转会看起来像被裁了,留出 4px\n flip: { padding: 4 },\n shift: { padding: 4 },\n },\n })\n );\n }\n\n // 任务列表:与 StarterKit 里的无序 / 有序列表同族,同样无条件注册。\n // 扩展自带 - [ ] / - [x] 的 Markdown 双向钩子,装饰(data-checked / 勾选框)也齐全\n editorExtensions.push(TaskList, TaskItem);\n\n // 基础格式:颜色 / 背景色 / 字号 / 字体 / 高亮 / 上下标。\n // 与媒体、表格同理放在用户扩展之前,让宿主能用同名扩展顶掉内置 mark。\n // 它们的 Markdown 往返要自己补钩子(上游除 ==x== 外一个都没给,没钩子的 mark\n // 会被序列化器静默丢掉),所以这里要把 markdown 那边的偏好一并传进去\n if (this.config.formatting !== false) {\n editorExtensions.push(\n ...createFormattingExtensions({ inlineStyles: this.config.markdown?.inlineStyles })\n );\n }\n\n // 输入实时转换由 StarterKit 各扩展的 input rules 提供,与 Markdown 扩展启停无关\n const enableInputRules = this.config.markdown?.shortcuts !== false;\n\n // 添加用户自定义扩展\n if (extensions) {\n editorExtensions.push(...extensions);\n }\n\n const editor = new Editor({\n element,\n content,\n contentType,\n enableInputRules,\n editable: editable ?? true,\n extensions: editorExtensions,\n editorProps: {\n attributes: {\n class: 'atri-editor-content',\n },\n },\n onCreate: () => {\n runSafely(this.config.onCreate, 'onCreate');\n },\n onUpdate: () => {\n runSafely(this.config.onUpdate, 'onUpdate');\n },\n onFocus: () => {\n runSafely(this.config.onFocus, 'onFocus');\n },\n onBlur: () => {\n runSafely(this.config.onBlur, 'onBlur');\n },\n onDestroy: () => {\n runSafely(this.config.onDestroy, 'onDestroy');\n },\n });\n\n // 上传运行时换绑到新的 Editor 实例,并把进行中的任务状态补回文档\n this.mediaRuntime?.bind(editor);\n\n return editor;\n }\n\n /**\n * 获取上传运行时;media 为 false 时为 null\n */\n getMediaRuntime(): MediaRuntime | null {\n return this.mediaRuntime;\n }\n\n /**\n * 获取 Tiptap Editor 实例\n */\n getEditor(): Editor {\n return this.editor;\n }\n\n /**\n * 获取 HTML 内容\n */\n getHTML(): string {\n try {\n return this.editor.getHTML();\n } catch (err) {\n // 一并输出 schema 的 node/mark 类型清单,帮助定位缺少 toDOM 的类型\n console.error(\n '[CoreEditor] getHTML() failed:',\n err,\n '\\nSchema nodes:',\n Object.keys(this.editor.schema.nodes),\n '\\nSchema marks:',\n Object.keys(this.editor.schema.marks)\n );\n throw err;\n }\n }\n\n /**\n * 获取 JSON 内容\n */\n getJSON(): object {\n return this.editor.getJSON();\n }\n\n /**\n * 设置内容\n */\n setContent(content: string | object, emitUpdate = true): void {\n this.editor.commands.setContent(content as Content, { emitUpdate });\n }\n\n /**\n * 清空内容\n */\n clearContent(): void {\n this.editor.commands.clearContent(true);\n }\n\n /**\n * 是否为空\n */\n isEmpty(): boolean {\n return this.editor.isEmpty;\n }\n\n /**\n * 设置可编辑状态\n */\n setEditable(editable: boolean): void {\n this.editor.setEditable(editable);\n }\n\n /**\n * 是否可编辑\n */\n isEditable(): boolean {\n return this.editor.isEditable;\n }\n\n /**\n * 聚焦\n */\n focus(): void {\n this.editor.commands.focus();\n }\n\n /**\n * 失焦\n */\n blur(): void {\n this.editor.commands.blur();\n }\n\n /**\n * 插入内容\n */\n insertContent(content: string): void {\n this.editor.commands.insertContent(content);\n }\n\n /**\n * 设置占位符\n * 占位符写在节点装饰上,只在状态重算时重新取值,因此派发一个空事务刷新视图;\n * 空事务不改动文档,Tiptap 不会据此触发 update\n */\n setPlaceholder(placeholder: string): void {\n this.placeholderText = placeholder;\n this.editor.view.dispatch(this.editor.state.tr);\n }\n\n /**\n * 销毁编辑器\n */\n destroy(): void {\n this.editor.destroy();\n // 自建的运行时没人回收:在途请求会一直飞,blob: 预览地址也 leak 着不放。\n // 门面那条路径由 AtriEditor.destroy 负责,它持有的实例要活过这里\n if (this.ownsMediaRuntime) this.mediaRuntime?.destroy();\n this.mediaRuntime = null;\n }\n}\n","/**\n * Markdown 工具函数\n */\nimport { escapeHtml, escapeQuotes } from './escape';\nimport { safeHref, safeMediaSrc } from './url';\n\n/**\n * 简单的 Markdown 转 HTML(用于 AI 响应处理)\n * 注意:完整的 Markdown 解析由 @tiptap/markdown 处理\n * 这里仅提供基础的 Markdown 语法转换\n *\n * 定位是\"降级渲染器\":输出的 HTML 仍需交给 schema 解析或宿主自行处理,\n * 本身不构成一道安全的 sanitizer。但它的产物会经 markdownToHTML() 作为裸字符串\n * 直接返回给宿主,所以不可信的 Markdown(AI 端点、用户上传的 .md)进来时,\n * 尖括号与引号必须先转义、地址必须先过协议白名单。\n */\nexport function simpleMarkdownToHtml(markdown: string): string {\n // 整段先转义,后面每条规则产出的标签都是白名单自己拼的,\n // 这一步不做的话原文里的 <img onerror=…> 会原样落到输出里\n let html = escapeHtml(markdown);\n\n // 标题:必须从六个 # 往下匹配。反过来的话 `#### x` 会先被 /^## / 命中,\n // 剩下两个 # 当成正文内容吐出来\n html = html.replace(/^###### (.+)$/gm, '<h6>$1</h6>');\n html = html.replace(/^##### (.+)$/gm, '<h5>$1</h5>');\n html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>');\n html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');\n html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');\n html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');\n\n // 粗体和斜体\n html = html.replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, '<strong><em>$1</em></strong>');\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n\n // 代码块\n html = html.replace(/```(\\w*)\\n([\\s\\S]*?)```/g, '<pre><code class=\"language-$1\">$2</code></pre>');\n html = html.replace(/`(.+?)`/g, '<code>$1</code>');\n\n // 图片:必须先于链接,否则 ![alt](src) 会被下面的链接规则从 ! 之后开始匹配,吞成一个 <a>\n html = html.replace(\n /!\\[([^\\]]*)\\]\\(\\s*([^\\s)]+)(?:\\s+\"([^\"]*)\")?\\s*\\)/g,\n (_all, alt: string, src: string, title?: string) => {\n const url = safeMediaSrc(src);\n // 地址不合法就退化成这几个字符本身:图片显示不出来,但 alt 里的信息不丢,\n // 也不会留一枚指向 javascript: 的 img\n if (url === null) return alt;\n const titleAttr = title ? ` title=\"${escapeQuotes(title)}\"` : '';\n return `<img src=\"${escapeQuotes(url)}\" alt=\"${escapeQuotes(alt)}\"${titleAttr}>`;\n }\n );\n\n // 链接:title 要像图片那样剥掉,否则 `[文字](/x \"T\")` 会把整串当地址\n html = html.replace(\n /\\[([^\\]]*)\\]\\(\\s*([^\\s)]+)(?:\\s+\"[^\"]*\")?\\s*\\)/g,\n (_all, text: string, href: string) => {\n const url = safeHref(href);\n if (url === null) return text;\n return `<a href=\"${escapeQuotes(url)}\">${text}</a>`;\n }\n );\n\n // 列表\n html = html.replace(/^\\* (.+)$/gm, '<li>$1</li>');\n html = html.replace(/^- (.+)$/gm, '<li>$1</li>');\n html = html.replace(/^(\\d+)\\. (.+)$/gm, '<li>$2</li>');\n\n // 段落\n html = html.replace(/\\n\\n/g, '</p><p>');\n html = `<p>${html}</p>`;\n\n // 清理空段落\n html = html.replace(/<p><\\/p>/g, '');\n html = html.replace(/<p>(<h[1-6]>)/g, '$1');\n html = html.replace(/(<\\/h[1-6]>)<\\/p>/g, '$1');\n html = html.replace(/<p>(<pre>)/g, '$1');\n html = html.replace(/(<\\/pre>)<\\/p>/g, '$1');\n html = html.replace(/<p>(<li>)/g, '$1');\n html = html.replace(/(<\\/li>)<\\/p>/g, '$1');\n\n return html;\n}\n\n/**\n * 简单的 HTML 转 Markdown\n */\nexport function simpleHtmlToMarkdown(html: string): string {\n let md = html;\n\n // 标题\n md = md.replace(/<h1[^>]*>(.+?)<\\/h1>/gi, '# $1\\n\\n');\n md = md.replace(/<h2[^>]*>(.+?)<\\/h2>/gi, '## $1\\n\\n');\n md = md.replace(/<h3[^>]*>(.+?)<\\/h3>/gi, '### $1\\n\\n');\n md = md.replace(/<h4[^>]*>(.+?)<\\/h4>/gi, '#### $1\\n\\n');\n md = md.replace(/<h5[^>]*>(.+?)<\\/h5>/gi, '##### $1\\n\\n');\n md = md.replace(/<h6[^>]*>(.+?)<\\/h6>/gi, '###### $1\\n\\n');\n\n // 粗体和斜体\n md = md.replace(/<strong[^>]*>(.+?)<\\/strong>/gi, '**$1**');\n md = md.replace(/<b[^>]*>(.+?)<\\/b>/gi, '**$1**');\n md = md.replace(/<em[^>]*>(.+?)<\\/em>/gi, '*$1*');\n md = md.replace(/<i[^>]*>(.+?)<\\/i>/gi, '*$1*');\n\n // 链接\n md = md.replace(/<a[^>]*href=\"([^\"]*)\"[^>]*>(.+?)<\\/a>/gi, '[$2]($1)');\n\n // 图片:必须赶在剥除剩余标签之前,<img> 是自闭合的,被剥掉就再也找不回 src\n md = md.replace(/<img\\b[^>]*>/gi, (tag) => {\n const src = /src=\"([^\"]*)\"/i.exec(tag)?.[1] ?? '';\n const alt = /alt=\"([^\"]*)\"/i.exec(tag)?.[1] ?? '';\n return src ? `![${alt}](${src})` : alt;\n });\n\n // 代码\n md = md.replace(/<code[^>]*>(.+?)<\\/code>/gi, '`$1`');\n md = md.replace(/<pre[^>]*><code[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi, '```\\n$1\\n```\\n\\n');\n\n // 段落和换行\n md = md.replace(/<br\\s*\\/?>/gi, '\\n');\n md = md.replace(/<p[^>]*>([\\s\\S]*?)<\\/p>/gi, '$1\\n\\n');\n\n // 移除剩余标签\n md = md.replace(/<[^>]+>/g, '');\n\n // 解码 HTML 实体\n md = md.replace(/&amp;/g, '&');\n md = md.replace(/&lt;/g, '<');\n md = md.replace(/&gt;/g, '>');\n md = md.replace(/&quot;/g, '\"');\n md = md.replace(/&#39;/g, \"'\");\n\n // 清理多余空行\n md = md.replace(/\\n{3,}/g, '\\n\\n');\n\n return md.trim();\n}\n","/**\n * MarkdownService - Markdown 解析/序列化服务\n */\nimport {\n elementFromString,\n getHTMLFromFragment,\n type Editor,\n type JSONContent,\n} from '@tiptap/core';\nimport { DOMParser as ProseMirrorDOMParser, Node as ProseMirrorNode } from '@tiptap/pm/model';\nimport type { MarkdownManager } from '@tiptap/markdown';\nimport type { AtriMarkdownConfig } from '../types';\nimport { simpleMarkdownToHtml, simpleHtmlToMarkdown } from '../utils/markdown';\n\nexport class MarkdownService {\n private editor: Editor;\n private config: AtriMarkdownConfig;\n\n constructor(editor: Editor, config: AtriMarkdownConfig = {}) {\n this.editor = editor;\n this.config = {\n enabled: true,\n ...config,\n };\n }\n\n /**\n * Tiptap Markdown 序列化器,未注册 Markdown 扩展时为 null\n */\n private get manager(): MarkdownManager | null {\n return this.editor.markdown ?? null;\n }\n\n /**\n * 获取编辑器内容为 Markdown\n * 自定义节点的输出由其 renderMarkdown 钩子负责(见 ExtensionManager.registerNodeView),\n * 因此这里始终走 Tiptap 序列化器,不对整篇文档做额外替换。\n */\n getMarkdown(): string {\n const manager = this.manager;\n if (!manager) {\n return simpleHtmlToMarkdown(this.editor.getHTML());\n }\n return manager.serialize(this.editor.getJSON());\n }\n\n /**\n * 设置 Markdown 内容\n */\n setMarkdown(content: string, emitUpdate = true): void {\n if (this.manager) {\n this.editor.commands.setContent(content, { contentType: 'markdown', emitUpdate });\n return;\n }\n this.editor.commands.setContent(simpleMarkdownToHtml(content), { emitUpdate });\n }\n\n /**\n * Markdown 转 HTML\n * 与 htmlToMarkdown 方向对称:解析为 JSON 后再按当前 schema 序列化为 HTML\n */\n markdownToHTML(markdown: string): string {\n const manager = this.manager;\n if (!manager) {\n return simpleMarkdownToHtml(markdown);\n }\n const { schema } = this.editor;\n return getHTMLFromFragment(\n ProseMirrorNode.fromJSON(schema, manager.parse(markdown)).content,\n schema\n );\n }\n\n /**\n * HTML 转 Markdown\n */\n htmlToMarkdown(html: string): string {\n const manager = this.manager;\n if (!manager) {\n return simpleHtmlToMarkdown(html);\n }\n const parsed = ProseMirrorDOMParser.fromSchema(this.editor.schema).parse(\n elementFromString(html)\n );\n return manager.serialize(parsed.toJSON());\n }\n\n /**\n * Markdown 转 JSON\n */\n markdownToJSON(markdown: string): JSONContent {\n const manager = this.manager;\n if (manager) {\n return manager.parse(markdown);\n }\n // 降级:返回简单结构\n return {\n type: 'doc',\n content: [\n {\n type: 'paragraph',\n content: [{ type: 'text', text: markdown }],\n },\n ],\n };\n }\n\n /**\n * 是否启用\n */\n isEnabled(): boolean {\n return this.config.enabled !== false;\n }\n}\n","/**\n * ThemeManager - 主题管理\n */\n\n/** 内置亮/暗两套,带字面量提示;仍需允许任意自定义主题名 */\nexport type ThemeType = 'light' | 'dark' | (string & {});\n\n/** 主题类的统一前缀;换主题时要按前缀摘干净,而不是只摘那两个已知名字 */\nconst THEME_PREFIX = 'atri-theme-';\n\nexport class ThemeManager {\n private container: HTMLElement;\n private currentTheme: ThemeType;\n\n constructor(container: HTMLElement, theme: ThemeType = 'light') {\n this.container = container;\n this.currentTheme = theme;\n this.applyTheme();\n }\n\n /**\n * 应用当前主题:内置主题与自定义主题走同一条 class 规则,只是名字不同\n */\n private applyTheme(): void {\n // 按前缀摘,而不是写死 remove('atri-theme-light', 'atri-theme-dark'):\n // ThemeType 接受任意自定义主题名(README 就宣传 setTheme('sepia')),\n // 只摘那两个名字的话自定义主题永远留在容器上——\n // setTheme('sepia') 再 setTheme('light') 之后是\n // \"atri-theme-sepia atri-theme-light\",两套变量同时生效,\n // 谁赢取决于样式里的声明顺序,而不是用户最后选了哪个\n Array.from(this.container.classList)\n .filter((name) => name.startsWith(THEME_PREFIX))\n .forEach((name) => this.container.classList.remove(name));\n\n this.container.classList.add(`${THEME_PREFIX}${this.currentTheme}`);\n\n // 设置 data 属性\n this.container.setAttribute('data-atri-theme', this.currentTheme);\n }\n\n /**\n * 设置主题\n */\n setTheme(theme: ThemeType): void {\n this.currentTheme = theme;\n this.applyTheme();\n }\n\n /**\n * 切换亮/暗主题\n */\n toggleTheme(): void {\n if (this.currentTheme === 'light') {\n this.setTheme('dark');\n } else if (this.currentTheme === 'dark') {\n this.setTheme('light');\n } else {\n // 自定义主题切换为 light\n this.setTheme('light');\n }\n }\n}\n","/**\n * I18nManager - 国际化支持\n */\nimport i18next, { type i18n as I18nInstance } from 'i18next';\n\n// 默认中文资源\nconst zhResources = {\n editor: {\n placeholder: '请输入内容...',\n bold: '加粗',\n italic: '斜体',\n underline: '下划线',\n strikethrough: '删除线',\n code: '行内代码',\n heading: '标题',\n heading1: '标题 1',\n heading2: '标题 2',\n heading3: '标题 3',\n heading4: '标题 4',\n heading5: '标题 5',\n heading6: '标题 6',\n paragraph: '正文',\n bulletList: '无序列表',\n orderedList: '有序列表',\n taskList: '任务列表',\n codeBlock: '代码块',\n blockquote: '引用',\n horizontalRule: '分割线',\n link: '链接',\n image: '图片',\n attachment: '附件',\n attachmentDisplay: '附件样式',\n delete: '删除',\n video: '视频',\n table: '表格',\n undo: '撤销',\n redo: '重做',\n align: '对齐',\n alignLeft: '左对齐',\n alignCenter: '居中',\n alignRight: '右对齐',\n alignJustify: '两端对齐',\n textColor: '文字颜色',\n highlight: '高亮',\n highlightColor: '高亮色',\n fontSize: '字号',\n fontFamily: '字体',\n subscript: '下标',\n superscript: '上标',\n clearFormat: '清除格式',\n },\n formatting: {\n customColor: '其他颜色…',\n noColor: '无',\n defaultValue: '默认',\n fontSans: '无衬线',\n fontSerif: '衬线',\n fontMono: '等宽',\n },\n media: {\n uploading: '上传中 {{files}} 个 · {{percent}}%',\n uploadFailed: '{{files}} 个文件上传失败',\n inlined: '{{files}} 张图片未上传(已随文档保存)',\n retry: '重试',\n browse: '选择文件',\n dropHere: '或将文件拖到这里',\n urlPlaceholder: '图片地址(https://…)',\n altPlaceholder: '替代文字',\n insert: '插入',\n },\n table: {\n menuLabel: '表格操作',\n rowBefore: '上方插入行',\n rowAfter: '下方插入行',\n deleteRow: '删除行',\n columnBefore: '左侧插入列',\n columnAfter: '右侧插入列',\n deleteColumn: '删除列',\n mergeCells: '合并单元格',\n splitCell: '拆分单元格',\n toggleHeaderRow: '切换表头行',\n alignLeft: '单元格左对齐',\n alignCenter: '单元格居中',\n alignRight: '单元格右对齐',\n deleteTable: '删除表格',\n },\n link: {\n panelLabel: '链接',\n urlPlaceholder: '链接地址(https://…)',\n newWindow: '在新窗口打开',\n apply: '确定',\n remove: '移除链接',\n },\n ai: {\n menu: 'AI 菜单',\n continue: 'AI 续写',\n translate: 'AI 翻译',\n optimize: 'AI 优化',\n summarize: '生成摘要',\n processing: 'AI 处理中...',\n error: 'AI 处理出错',\n },\n};\n\n// 英文资源\nconst enResources = {\n editor: {\n placeholder: 'Type something...',\n bold: 'Bold',\n italic: 'Italic',\n underline: 'Underline',\n strikethrough: 'Strikethrough',\n code: 'Inline Code',\n heading: 'Heading',\n heading1: 'Heading 1',\n heading2: 'Heading 2',\n heading3: 'Heading 3',\n heading4: 'Heading 4',\n heading5: 'Heading 5',\n heading6: 'Heading 6',\n paragraph: 'Paragraph',\n bulletList: 'Bullet List',\n orderedList: 'Ordered List',\n taskList: 'Task List',\n codeBlock: 'Code Block',\n blockquote: 'Blockquote',\n horizontalRule: 'Divider',\n link: 'Link',\n image: 'Image',\n attachment: 'Attachment',\n attachmentDisplay: 'Attachment style',\n delete: 'Delete',\n video: 'Video',\n table: 'Table',\n undo: 'Undo',\n redo: 'Redo',\n align: 'Align',\n alignLeft: 'Align Left',\n alignCenter: 'Align Center',\n alignRight: 'Align Right',\n alignJustify: 'Justify',\n textColor: 'Text Color',\n highlight: 'Highlight',\n highlightColor: 'Highlight Color',\n fontSize: 'Font Size',\n fontFamily: 'Font',\n subscript: 'Subscript',\n superscript: 'Superscript',\n clearFormat: 'Clear Formatting',\n },\n formatting: {\n customColor: 'Custom color…',\n noColor: 'None',\n defaultValue: 'Default',\n fontSans: 'Sans',\n fontSerif: 'Serif',\n fontMono: 'Monospace',\n },\n media: {\n uploading: 'Uploading {{files}} · {{percent}}%',\n uploadFailed: '{{files}} file(s) failed to upload',\n inlined: '{{files}} image(s) saved with the document (not uploaded)',\n retry: 'Retry',\n browse: 'Browse',\n dropHere: 'or drop files here',\n urlPlaceholder: 'Image URL (https://…)',\n altPlaceholder: 'Alternative text',\n insert: 'Insert',\n },\n table: {\n menuLabel: 'Table actions',\n rowBefore: 'Insert row above',\n rowAfter: 'Insert row below',\n deleteRow: 'Delete row',\n columnBefore: 'Insert column left',\n columnAfter: 'Insert column right',\n deleteColumn: 'Delete column',\n mergeCells: 'Merge cells',\n splitCell: 'Split cell',\n toggleHeaderRow: 'Toggle header row',\n alignLeft: 'Align cell left',\n alignCenter: 'Center cell',\n alignRight: 'Align cell right',\n deleteTable: 'Delete table',\n },\n link: {\n panelLabel: 'Link',\n urlPlaceholder: 'Link URL (https://…)',\n newWindow: 'Open in new window',\n apply: 'Apply',\n remove: 'Remove link',\n },\n ai: {\n menu: 'AI Menu',\n continue: 'AI Continue',\n translate: 'AI Translate',\n optimize: 'AI Optimize',\n summarize: 'AI Summarize',\n processing: 'AI Processing...',\n error: 'AI Error',\n },\n};\n\n/**\n * 深拷贝内置词条。\n *\n * 不能把模块级字面量直接交给 i18next:init({ resources }) 存的是引用,而\n * addResourceBundle 的 deepMerge 是就地合并。于是接入方调一次 addResources 就会永久\n * 改掉 zhResources / enResources 本身——页面上所有编辑器实例、以及之后新建的实例,\n * 全都跟着被污染。zhText() 读的同样是这两个字面量,它作为内置默认文案的单源也会一起坏掉。\n * 每个实例持有一份自己的拷贝,注册才真的是\"注册到这个编辑器\"。\n */\nfunction cloneResources<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\nexport class I18nManager {\n private instance: I18nInstance;\n\n constructor(lang: string = 'zh') {\n // 每个编辑器独立实例:i18next 默认导出是全局单例,共用会让多个编辑器互相切换语言\n this.instance = i18next.createInstance();\n // 资源为内联对象,initImmediate: false 让初始化同步完成,构造后 t() 立即可用\n this.instance.init({\n lng: lang,\n fallbackLng: 'zh',\n initImmediate: false,\n resources: {\n zh: { translation: cloneResources(zhResources) },\n en: { translation: cloneResources(enResources) },\n },\n });\n }\n\n /**\n * 翻译\n */\n t(key: string, options?: Record<string, unknown>): string {\n return this.instance.t(key, options);\n }\n\n /**\n * 翻译,词条缺失时回退到 fallback:i18next 缺词条时原样返回 key\n */\n tOr(key: string, fallback: string, options?: Record<string, unknown>): string {\n const translated = this.t(key, options);\n return translated && translated !== key ? translated : fallback;\n }\n\n /**\n * 切换语言\n */\n async changeLanguage(lang: string): Promise<void> {\n await this.instance.changeLanguage(lang);\n }\n\n /**\n * 注册语言包:给已存在的语言补词条,或引入一门内置清单之外的新语言。\n *\n * 内置词条是源码里的两个字面量对象,接入方改不到,于是 lang: 'ja' 只能静默回退中文\n * ——看起来像\"支持可扩展\",实际没有入口。这里补上那个入口。\n *\n * overwrite 传 false:同名 key 以先注册的一方为准,防止后加载的包把内置文案悄悄换掉。\n * 要自定义措辞走 toolbar.items 的 tooltip,那本来就是优先级更高的显式入口。\n * 注册当前语言时补发一次 languageChanged,让已挂载的界面重取文案;\n * 不是当前语言就不发,白重刷一遍界面没有意义。\n */\n addResources(lang: string, resources: Record<string, unknown>): void {\n this.instance.addResourceBundle(lang, 'translation', resources, true, false);\n if (lang === this.instance.language) {\n this.instance.emit('languageChanged', lang);\n }\n }\n\n /**\n * 该语言是否已有语言包。未注册的语言不会抛错,只会按 fallbackLng 回退,\n * 所以想知道有没有真的生效得问这里。\n */\n hasLanguage(lang: string): boolean {\n return this.instance.hasResourceBundle(lang, 'translation');\n }\n\n /**\n * 监听语言变更,返回取消订阅的函数\n */\n onLanguageChanged(handler: (lang: string) => void): () => void {\n this.instance.on('languageChanged', handler);\n return () => this.instance.off('languageChanged', handler);\n }\n\n /**\n * 获取当前语言\n */\n getLanguage(): string {\n return this.instance.language || 'zh';\n }\n}\n\n/**\n * 浮层类 UI 共用的文案回退:未注入 i18n 或词条缺失时用内置文案\n * (t() 缺词条时原样返回 key,所以 tOr 已是类里的规矩,这里只补\"可能压根没有 i18n\"这一层)\n */\nexport function tOrFallback(i18n: I18nManager | undefined, key: string, fallback: string): string {\n return i18n?.tOr(key, fallback) ?? fallback;\n}\n\n/**\n * 直接取中文原文。\n *\n * 中文词条本身就是这些控件的默认文案,再在组件里抄一份字面量就是给漂移留位置:\n * 改一处、另一处不变,而 tOr 的回退值恰好是最不容易被发现出错的那条路径\n * (只在没注入 i18n 或词条缺失时才露出来)。有词条可指的地方用它。\n */\nexport function zhText(key: string): string {\n const found = key\n .split('.')\n .reduce<unknown>(\n (node, part) =>\n node && typeof node === 'object' ? (node as Record<string, unknown>)[part] : undefined,\n zhResources\n );\n\n return typeof found === 'string' ? found : '';\n}\n","/**\n * ExtensionManager - 扩展注册管理\n */\nimport { Node, mergeAttributes } from '@tiptap/core';\nimport type { AnyExtension } from '@tiptap/core';\nimport type { AtriNodeViewConfig } from '../types';\n\nexport class ExtensionManager {\n private extensions: Map<string, AnyExtension> = new Map();\n private nodeViews: Map<string, AtriNodeViewConfig> = new Map();\n\n /**\n * 注册扩展\n */\n register(extension: AnyExtension): void {\n const name = extension.name;\n if (this.extensions.has(name)) {\n console.warn(`Extension \"${name}\" already registered, overwriting.`);\n }\n this.extensions.set(name, extension);\n }\n\n /**\n * 注册自定义 NodeView\n */\n registerNodeView(config: AtriNodeViewConfig): void {\n if (this.nodeViews.has(config.name)) {\n console.warn(`NodeView \"${config.name}\" already registered, overwriting.`);\n }\n\n this.nodeViews.set(config.name, config);\n\n // 动态创建 Tiptap Node 扩展\n const nodeExtension = Node.create({\n name: config.name,\n group: config.group || (config.inline ? 'inline' : 'block'),\n atom: config.atom ?? true,\n\n addAttributes() {\n return config.attributes || {};\n },\n\n parseHTML() {\n return config.parseHTML?.() || [{ tag: config.name }];\n },\n\n renderHTML({ HTMLAttributes, node }) {\n if (config.renderHTML) {\n return config.renderHTML({ HTMLAttributes, node });\n }\n return [config.name, mergeAttributes(HTMLAttributes)];\n },\n\n addNodeView() {\n return config.nodeView;\n },\n\n // 由 Tiptap 的 MarkdownManager 按节点名调用,签名见 @tiptap/core NodeConfig.renderMarkdown\n renderMarkdown: config.markdownSerialize\n ? (node) => config.markdownSerialize!(node)\n : undefined,\n });\n\n this.register(nodeExtension);\n }\n\n /**\n * 批量注册 NodeView\n */\n registerNodeViews(configs: AtriNodeViewConfig[]): void {\n configs.forEach((config) => this.registerNodeView(config));\n }\n\n /**\n * 获取扩展\n */\n get(name: string): AnyExtension | undefined {\n return this.extensions.get(name);\n }\n\n /**\n * 获取所有扩展\n */\n getAll(): AnyExtension[] {\n return Array.from(this.extensions.values());\n }\n\n /**\n * 获取所有已注册的 NodeView\n */\n getNodeViews(): Map<string, AtriNodeViewConfig> {\n return this.nodeViews;\n }\n\n /**\n * 是否已注册\n */\n has(name: string): boolean {\n return this.extensions.has(name);\n }\n}\n","/**\n * floating-panel - 工具栏浮层的公共底盘\n *\n * 浮层统一挂 document.body + fixed 定位:工具栏链路任何一处 overflow 都会裁掉子元素。\n * InsertPanel / TableGridPanel / LinkPanel / TableMenu / 格式选择面板共用这里的\n * 定位、外点与 Escape 关闭、主题类跟随三段。\n */\nimport { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';\n\nexport interface FloatingPanel {\n /** 停止位置跟随;重复调用无副作用 */\n stop: () => void;\n}\n\n/** 浮层与锚点之间的间隙 */\nconst PANEL_OFFSET = 6;\n/** 贴到视口边缘时留出的安全内边距 */\nconst PANEL_SHIFT_PADDING = 8;\n\n/**\n * 浮层共用的三段中间件:溢出翻转 + 贴边收拢。\n *\n * 各处只有 placement 不同(工具栏向下、表格菜单贴着单元格向上),\n * 这三个参数此前被抄成 offset(6)/offset(6)/offset(8)、shift(8)/shift(8)/shift(),\n * 差的那 2px 没人说得清是有意还是漏改\n */\nexport function panelMiddleware(): ReturnType<typeof offset | typeof flip | typeof shift>[] {\n return [offset(PANEL_OFFSET), flip(), shift({ padding: PANEL_SHIFT_PADDING })];\n}\n\n/** 落位:浮层统一 fixed,坐标是 computePosition 给的视口值 */\nexport function placePanel(panel: HTMLElement, x: number, y: number): void {\n panel.style.position = 'fixed';\n panel.style.left = `${x}px`;\n panel.style.top = `${y}px`;\n}\n\n/**\n * 把面板锚定到 trigger 下方并持续跟随。\n * isCurrent 用于在异步计算间隙确认面板还没被关掉/换掉——关掉的实例不该再写样式\n */\nexport function positionPanel(\n trigger: HTMLElement,\n panel: HTMLElement,\n isCurrent: () => boolean\n): FloatingPanel {\n let stopAutoUpdate: (() => void) | null = null;\n\n const update = async () => {\n if (!isCurrent()) return;\n try {\n const { x, y } = await computePosition(trigger, panel, {\n placement: 'bottom-start',\n middleware: panelMiddleware(),\n });\n // 算位置的这几微秒里面板可能已经关了\n if (!isCurrent()) return;\n placePanel(panel, x, y);\n } catch {\n // 量不到也要让面板看得见:宁可位置不理想,也不能点了没反应\n }\n };\n\n void update().then(() => {\n if (isCurrent()) {\n stopAutoUpdate = autoUpdate(trigger, panel, () => void update());\n }\n });\n\n return {\n stop: () => {\n stopAutoUpdate?.();\n stopAutoUpdate = null;\n },\n };\n}\n\n/**\n * 点外面与 Escape 关闭;trigger 本身交给调用方的 toggle,\n * 否则关了又立刻被这次点击重开\n */\nexport function listenPanelDismiss(\n trigger: HTMLElement,\n panel: HTMLElement,\n onClose: () => void\n): () => void {\n const onPointerDown = (event: Event) => {\n const target = event.target as Node | null;\n if (!target || panel.contains(target) || trigger.contains(target)) return;\n onClose();\n };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n event.preventDefault();\n event.stopPropagation();\n onClose();\n };\n\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('keydown', onKeyDown, true);\n return () => {\n document.removeEventListener('pointerdown', onPointerDown, true);\n document.removeEventListener('keydown', onKeyDown, true);\n };\n}\n\nconst THEME_PREFIX = 'atri-theme-';\n\n/** 把 target 上的主题类换成 source 当前那一套;先摘后加,所以重复调用不会攒出两套主题 */\nfunction syncThemeClasses(target: HTMLElement, root: Element): void {\n Array.from(target.classList)\n .filter((name) => name.startsWith(THEME_PREFIX))\n .forEach((name) => target.classList.remove(name));\n\n const themes = Array.from(root.classList).filter((name) => name.startsWith(THEME_PREFIX));\n target.classList.add(...themes);\n}\n\n/**\n * 主题变量写在 .atri-editor 上,浮层挂在 body 上够不着,只能把主题类复制一份。\n * 面板此刻往往还没入 DOM,closest 搜不到编辑器根,所以源元素必须由编辑器侧给(如 view.dom)\n *\n * 只复制一次是不够的:ThemeManager 换主题只动容器上的 class,已经打开的浮层无从得知,\n * 于是\"开着面板切主题\"会得到一个浅色面板配深色正文。这里改成持续跟随:\n * 观察者盯的是容器自己的 class(面板自己的改动不会触发它,没有回环),\n * 返回的 disposer 由面板在关闭与销毁时调用。\n */\nexport function followThemeClasses(source: Element | null, panel: HTMLElement): () => void {\n const root = source?.closest('.atri-editor') ?? null;\n if (root) syncThemeClasses(panel, root);\n if (!root || typeof MutationObserver === 'undefined') return () => {};\n\n const observer = new MutationObserver(() => syncThemeClasses(panel, root));\n observer.observe(root, { attributes: true, attributeFilter: ['class'] });\n return () => observer.disconnect();\n}\n","/**\n * InsertPanel - 工具栏下方的插入浮层\n *\n * 挂在 document.body 上并用 fixed 定位:工具栏那一层链路上任何一处 overflow 都会把浮层裁掉。\n *\n * 注:media/ 目录同时承载上传运行时(MediaRuntime/Uploader/file-policy)与 UI\n * (本文件与 MediaStatusStrip),两者通过 MediaRuntime 的订阅接口协作,暂不拆分子目录。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n followThemeClasses,\n listenPanelDismiss,\n positionPanel,\n type FloatingPanel,\n} from '../core/floating-panel';\nimport { tOrFallback, type I18nManager } from '../core/I18nManager';\nimport type { MediaKind } from '../types';\nimport type { MediaRuntime } from './MediaRuntime';\nimport { filesOf } from '../utils/dom';\n\nexport type InsertPanelMode = 'image' | 'attachment';\n\nexport interface InsertPanelOptions {\n editor: Editor;\n runtime: MediaRuntime;\n i18n?: I18nManager;\n /** 开合时把控制权交回工具栏:按钮的 active 与 aria-expanded 由它维护 */\n onOpenChange?: (mode: InsertPanelMode | null) => void;\n}\n\n/** 每种模式给哪些字段:附件没有\"填个网络地址\"这条路 */\nconst FIELDS: Record<InsertPanelMode, { url: boolean; alt: boolean; kind: MediaKind }> = {\n image: { url: true, alt: true, kind: 'image' },\n attachment: { url: false, alt: false, kind: 'attachment' },\n};\n\ninterface LabelledElement {\n el: HTMLElement;\n key: string;\n fallback: string;\n}\n\nexport class InsertPanel {\n private editor: Editor;\n private runtime: MediaRuntime;\n private i18n?: I18nManager;\n private onOpenChange?: (mode: InsertPanelMode | null) => void;\n private unsubscribeLanguage?: () => void;\n\n private element: HTMLDivElement | null = null;\n private mode: InsertPanelMode | null = null;\n private floating: FloatingPanel | null = null;\n private detachDismiss: (() => void) | null = null;\n private detachTheme: (() => void) | null = null;\n private labelled: LabelledElement[] = [];\n\n constructor(options: InsertPanelOptions) {\n this.editor = options.editor;\n this.runtime = options.runtime;\n this.i18n = options.i18n;\n this.onOpenChange = options.onOpenChange;\n // 面板是即时创建的,但可能开着的时候宿主切了语言\n this.unsubscribeLanguage = this.i18n?.onLanguageChanged(() => this.applyLabels());\n }\n\n get openMode(): InsertPanelMode | null {\n return this.mode;\n }\n\n /**\n * 同一个按钮再点一次是关闭;换按钮则直接把浮层挪过去\n */\n toggle(anchor: HTMLElement, mode: InsertPanelMode): void {\n if (this.mode === mode) {\n this.close();\n return;\n }\n\n this.close();\n this.mode = mode;\n this.build(mode);\n this.position(anchor);\n this.listenForDismiss(anchor);\n this.onOpenChange?.(mode);\n }\n\n close(): void {\n this.detachDismiss?.();\n this.detachDismiss = null;\n this.detachTheme?.();\n this.detachTheme = null;\n this.floating?.stop();\n this.floating = null;\n this.element?.remove();\n this.element = null;\n this.labelled = [];\n\n if (this.mode === null) return;\n this.mode = null;\n this.onOpenChange?.(null);\n }\n\n destroy(): void {\n this.close();\n this.unsubscribeLanguage?.();\n this.unsubscribeLanguage = undefined;\n }\n\n private t(key: string, fallback: string): string {\n return tOrFallback(this.i18n, key, fallback);\n }\n\n /**\n * 登记一处随语言重画的文案:input 画到 placeholder,其他画成文字\n */\n private label(el: HTMLElement, key: string, fallback: string): void {\n this.labelled.push({ el, key, fallback });\n this.applyLabel({ el, key, fallback });\n }\n\n private applyLabels(): void {\n this.labelled.forEach((entry) => this.applyLabel(entry));\n }\n\n private applyLabel({ el, key, fallback }: LabelledElement): void {\n const text = this.t(key, fallback);\n if (el instanceof HTMLInputElement) el.placeholder = text;\n else el.textContent = text;\n }\n\n private build(mode: InsertPanelMode): void {\n const fields = FIELDS[mode];\n const panel = document.createElement('div');\n panel.className = 'atri-media-panel';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute(\n 'aria-label',\n this.t(`editor.${mode === 'image' ? 'image' : 'attachment'}`, '插入')\n );\n\n let urlInput: HTMLInputElement | null = null;\n let altInput: HTMLInputElement | null = null;\n\n if (fields.url) {\n urlInput = document.createElement('input');\n urlInput.type = 'url';\n urlInput.className = 'atri-media-panel-url';\n this.label(urlInput, 'media.urlPlaceholder', '图片地址(https://…)');\n urlInput.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n this.insertFromUrl(urlInput, altInput);\n }\n });\n panel.appendChild(urlInput);\n }\n\n if (fields.alt) {\n altInput = document.createElement('input');\n altInput.type = 'text';\n altInput.className = 'atri-media-panel-alt';\n this.label(altInput, 'media.altPlaceholder', '替代文字');\n panel.appendChild(altInput);\n }\n\n const fileInput = document.createElement('input');\n fileInput.type = 'file';\n fileInput.className = 'atri-media-panel-file';\n fileInput.hidden = true;\n fileInput.multiple = true;\n const accept = this.runtime.acceptFor(fields.kind);\n if (accept) fileInput.accept = accept;\n fileInput.addEventListener('change', () => {\n this.upload(Array.from(fileInput.files ?? []), fields.kind);\n // 同一个文件连选两次也要还能再触发 change\n fileInput.value = '';\n });\n panel.appendChild(fileInput);\n\n const drop = document.createElement('div');\n drop.className = 'atri-media-panel-drop';\n this.label(drop, 'media.dropHere', '或将文件拖到这里');\n drop.addEventListener('dragover', (event) => event.preventDefault());\n drop.addEventListener('drop', (event) => {\n // 不挡下默认行为,整个页面会跳去那个文件\n event.preventDefault();\n this.upload(filesOf((event as DragEvent).dataTransfer), fields.kind);\n });\n\n const browse = document.createElement('button');\n browse.type = 'button';\n browse.className = 'atri-media-panel-browse';\n this.label(browse, 'media.browse', '选择文件');\n browse.addEventListener('click', () => fileInput.click());\n drop.appendChild(browse);\n panel.appendChild(drop);\n\n let insert: HTMLButtonElement | null = null;\n if (fields.url) {\n insert = document.createElement('button');\n insert.type = 'button';\n insert.className = 'atri-media-panel-insert';\n this.label(insert, 'media.insert', '插入');\n insert.addEventListener('click', () => this.insertFromUrl(urlInput, altInput));\n panel.appendChild(insert);\n }\n\n document.body.appendChild(panel);\n this.element = panel;\n this.detachTheme = followThemeClasses(this.editor.view.dom, panel);\n\n // 焦点落进面板:图片先落在地址栏,附件没有输入框就落在选择文件按钮上\n (urlInput ?? altInput ?? browse).focus();\n }\n\n /**\n * 外链图片当场就插,不进上传队列:地址本来就是可用的\n */\n private insertFromUrl(\n urlInput: HTMLInputElement | null,\n altInput: HTMLInputElement | null\n ): void {\n const src = urlInput?.value.trim();\n if (!src) return;\n\n this.editor\n .chain()\n .focus()\n .setImage({ src, alt: altInput?.value.trim() || undefined })\n .run();\n this.close();\n }\n\n private upload(files: File[], kind: MediaKind): void {\n if (!files.length) return;\n\n void this.runtime.handleFiles(files, { kind });\n this.close();\n }\n\n private position(anchor: HTMLElement): void {\n const panel = this.element;\n if (!panel) return;\n this.floating = positionPanel(anchor, panel, () => this.element === panel);\n }\n\n /**\n * 点外面与 Escape 关闭;锚点按钮交给 toggle,否则关了又立刻被这次点击重开\n */\n private listenForDismiss(anchor: HTMLElement): void {\n const panel = this.element;\n if (!panel) return;\n this.detachDismiss = listenPanelDismiss(anchor, panel, () => this.close());\n }\n}\n","/**\n * StyleOptionPanel - 颜色 / 字号 / 字体的选择面板\n *\n * 这些项不是按钮:它们要的是一组取值,所以是一个挂在 document.body 上的浮层,\n * 定位、外点/Escape 关闭、主题类复制三段全走 floating-panel 公共底盘(与\n * TableGridPanel、InsertPanel 同一套)。\n *\n * 面板只有一份实例,由 ToolbarManager 持有:同时最多开一个,四个入口共用一个实例\n * 也比四个各自留着 DOM 干净。代价是 toggle 要分清\"点的是同一个触发器\"还是\"换了一个\"\n * ——换了就重建,不能因为已经开着就直接关,否则点字号再点字体会得到\"什么都没发生\"。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n followThemeClasses,\n listenPanelDismiss,\n positionPanel,\n type FloatingPanel,\n} from './floating-panel';\nimport { tOrFallback, type I18nManager } from './I18nManager';\nimport type { StyleOption } from './style-presets';\n\nexport interface StylePanelSpec {\n /**\n * 这个入口的标识(如 'textColor')。面板是四个入口共用的一个实例,\n * 而每个触发按钮的按下态只能属于自己——没有这个 key 就会四个一起亮\n */\n key: string;\n /** 面板的无障碍名字,也是列表形状下的提示行 */\n label: string;\n /** grid 是色块方阵,list 是一行一个文字选项 */\n shape: 'grid' | 'list';\n options: StyleOption[];\n /** 选定一个取值。关闭面板由内部负责 */\n onPick: (value: string) => void;\n /** 「清除」行:把该属性 unset 掉而不是设成某个值 */\n clearLabel?: string;\n /** 「其他颜色」行,用原生取色器取任意值 */\n customColorLabel?: string;\n}\n\nexport interface StyleOptionPanelOptions {\n editor: Editor;\n i18n?: I18nManager;\n /** 开合时把控制权交回工具栏:按钮的 active 与 aria-expanded 由它维护 */\n onOpenChange?: (open: boolean) => void;\n}\n\nexport class StyleOptionPanel {\n private editor: Editor;\n private i18n?: I18nManager;\n private onOpenChange?: (open: boolean) => void;\n\n private element: HTMLDivElement | null = null;\n private floating: FloatingPanel | null = null;\n private detachDismiss: (() => void) | null = null;\n private detachTheme: (() => void) | null = null;\n /** 当前锚在哪个触发器上:同一个再点是关,换一个是改开那个 */\n private anchor: HTMLElement | null = null;\n /** 当前开着的是哪个入口的 spec.key,工具栏据此把按下态只给那一个按钮 */\n private currentKey: string | null = null;\n private rows: HTMLButtonElement[] = [];\n\n constructor(options: StyleOptionPanelOptions) {\n this.editor = options.editor;\n this.i18n = options.i18n;\n this.onOpenChange = options.onOpenChange;\n }\n\n get isOpen(): boolean {\n return this.element !== null;\n }\n\n get openKey(): string | null {\n return this.currentKey;\n }\n\n /** 面板是否正开在某个指定触发器上——工具栏据此决定按钮的按下态给谁 */\n isOpenFor(anchor: HTMLElement): boolean {\n return this.isOpen && this.anchor === anchor;\n }\n\n toggle(anchor: HTMLElement, spec: StylePanelSpec): void {\n if (this.isOpen) {\n const switching = this.anchor !== anchor;\n this.close();\n // 换一个入口点进来时不要\"先关再不动\",用户要的是新面板\n if (switching) this.open(anchor, spec);\n return;\n }\n this.open(anchor, spec);\n }\n\n close(): void {\n if (!this.element) return;\n this.detachDismiss?.();\n this.detachDismiss = null;\n this.detachTheme?.();\n this.detachTheme = null;\n this.floating?.stop();\n this.floating = null;\n this.element.remove();\n this.element = null;\n this.anchor = null;\n this.currentKey = null;\n this.rows = [];\n this.onOpenChange?.(false);\n }\n\n destroy(): void {\n this.close();\n }\n\n private open(anchor: HTMLElement, spec: StylePanelSpec): void {\n const panel = this.build(spec);\n document.body.appendChild(panel);\n this.detachTheme = followThemeClasses(this.editor.view.dom, panel);\n this.element = panel;\n this.anchor = anchor;\n this.currentKey = spec.key;\n this.floating = positionPanel(anchor, panel, () => this.element === panel);\n this.detachDismiss = listenPanelDismiss(anchor, panel, () => this.close());\n this.onOpenChange?.(true);\n panel.focus();\n }\n\n private labelOf(option: StyleOption): string {\n if (!option.i18nKey) return option.label;\n return tOrFallback(this.i18n, option.i18nKey, option.label);\n }\n\n private build(spec: StylePanelSpec): HTMLDivElement {\n const panel = document.createElement('div');\n panel.className = `atri-style-panel atri-style-panel--${spec.shape}`;\n panel.setAttribute('role', 'menu');\n panel.setAttribute('aria-label', spec.label);\n // 面板本身可聚焦:方向键与 Escape 才有落点,键盘导航从这一行起步\n panel.tabIndex = -1;\n panel.addEventListener('keydown', (event) => this.onKeyDown(event));\n\n const body = document.createElement('div');\n body.className = 'atri-style-panel-body';\n this.rows = [];\n\n spec.options.forEach((option) => {\n body.appendChild(this.makeRow(option, spec));\n });\n\n if (spec.customColorLabel) {\n body.appendChild(this.makeCustomColorRow(spec));\n }\n\n if (spec.clearLabel) {\n const clear: StyleOption = {\n value: '',\n label: spec.clearLabel,\n isClear: true,\n };\n body.appendChild(this.makeRow(clear, spec));\n }\n\n panel.appendChild(body);\n return panel;\n }\n\n private makeRow(option: StyleOption, spec: StylePanelSpec): HTMLButtonElement {\n const row = document.createElement('button');\n row.type = 'button';\n row.className = 'atri-style-panel-row';\n row.setAttribute('role', 'menuitem');\n row.dataset.styleValue = option.value;\n if (option.isClear) row.classList.add('atri-style-panel-row--clear');\n\n const label = this.labelOf(option);\n row.title = label;\n row.setAttribute('aria-label', label);\n\n if (option.swatch) {\n // 色块是装饰,语义已经挂在 aria-label 上,所以这块本身对读屏隐藏\n const swatch = document.createElement('span');\n swatch.className = 'atri-style-panel-swatch';\n swatch.style.backgroundColor = option.swatch;\n swatch.setAttribute('aria-hidden', 'true');\n row.appendChild(swatch);\n // 色块方阵里不排文字,CSS 值只作为 title 与无障碍名存在\n if (spec.shape === 'grid') row.classList.add('atri-style-panel-row--swatch-only');\n }\n\n if (spec.shape === 'list' || !option.swatch) {\n const text = document.createElement('span');\n text.className = 'atri-style-panel-text';\n text.textContent = label;\n if (option.preview) text.style.fontFamily = option.preview;\n row.appendChild(text);\n }\n\n row.addEventListener('click', () => {\n if (option.isClear) {\n spec.onPick('');\n } else {\n spec.onPick(option.value);\n }\n this.close();\n });\n\n this.rows.push(row);\n return row;\n }\n\n /**\n * 原生取色器:不在预设色板里时用它。\n *\n * 监听 change 而不是 input——input 在拖动取色器的过程中每移一像素就触发一次,\n * 每一次都是一条进撤销历史的事务,用户按一次撤销只能退一格,得按上百次才回到起点。\n */\n private makeCustomColorRow(spec: StylePanelSpec): HTMLElement {\n const row = document.createElement('label');\n row.className = 'atri-style-panel-custom';\n\n const input = document.createElement('input');\n input.type = 'color';\n input.className = 'atri-style-panel-custom-input';\n input.value = '#1890ff';\n input.addEventListener('change', () => {\n spec.onPick(input.value);\n this.close();\n });\n\n const text = document.createElement('span');\n text.textContent = spec.customColorLabel ?? '';\n\n row.appendChild(input);\n row.appendChild(text);\n return row;\n }\n\n private onKeyDown(event: KeyboardEvent): void {\n const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];\n\n if (event.key === 'Enter' || event.key === ' ') return;\n\n if (step === undefined) return;\n event.preventDefault();\n if (this.rows.length === 0) return;\n\n const focused = document.activeElement;\n const current = this.rows.indexOf(focused as HTMLButtonElement);\n // 还没进过列表就从头(或从尾)开始,而不是把焦点丢回面板\n const next =\n current < 0\n ? step > 0\n ? 0\n : this.rows.length - 1\n : (current + step + this.rows.length) % this.rows.length;\n this.rows[next].focus();\n }\n}\n","/**\n * 格式选择面板的内置预设(色板 / 字号 / 字体)\n *\n * 先给固定默认值,不做成配置项:本轮要解决的是\"开箱看不见这些能力\",让宿主换一套品牌\n * 色板是独立需求,向后兼容地加 `formatting.colors` 即可;现在预留一份没人读的配置\n * 只会多一处要同步的文档。\n *\n * 这里的文字一律**不写中文词条**:色块与字号本来就没有名字,用 CSS 值自己当无障碍标签\n * (`rgb(24, 144, 255)` 比\"蓝\"更准确,也不需要同步两份);只有字体族的通用名需要翻译,\n * 那几条走 i18n key 写在 label 字段上,由面板取词。工具栏 40 项文案收成单源那次\n * (见提交 20c6a5a)说的就是\"抄一份兜底字面量等于给漂移留位置\",这里不重犯。\n */\n\n/** 面板的一行 */\nexport interface StyleOption {\n /** 写进 mark 属性的值 */\n value: string;\n /**\n * 行内文字。颜色与字号直接用 CSS 值;\n * 需要翻译的行由面板按 i18nKey 取词,取不到才回落到这里\n */\n label: string;\n /** 填了就用这个 CSS 值画色块,代替文字预览 */\n swatch?: string;\n /** 行文字的 font-family 预览样式 */\n preview?: string;\n /** 需要翻译的行指向的词条 key */\n i18nKey?: string;\n /** 这一行是\"清除该属性\"而不是设置某个值 */\n isClear?: boolean;\n}\n\n/**\n * 文字色:前 9 个对齐包内既有的 Ant 系主题色,后 3 个补常用正文色。\n * 不放纯黑纯白——正文本来就不是\"黑\",而白字在亮色主题下等于隐形。\n */\nexport const TEXT_COLORS: StyleOption[] = [\n 'rgb(31, 31, 31)',\n 'rgb(89, 89, 89)',\n 'rgb(24, 144, 255)',\n 'rgb(82, 196, 26)',\n 'rgb(250, 173, 20)',\n 'rgb(245, 34, 45)',\n 'rgb(114, 46, 209)',\n 'rgb(235, 47, 150)',\n 'rgb(19, 194, 194)',\n 'rgb(47, 84, 235)',\n 'rgb(160, 97, 42)',\n 'rgb(120, 120, 120)',\n].map((value) => ({ value, label: value, swatch: value }));\n\n/**\n * 高亮色一律偏浅:深底配黑字刺眼,深底配白字则把\"高亮\"变成\"遮挡\"。\n * 值写成 rgb() 与 TEXT_COLORS 同形,避免同一份导出里混着 #hex 与 rgb() 两种写法。\n */\nexport const HIGHLIGHT_COLORS: StyleOption[] = [\n 'rgb(255, 241, 184)',\n 'rgb(200, 250, 170)',\n 'rgb(168, 223, 255)',\n 'rgb(255, 192, 203)',\n 'rgb(223, 190, 255)',\n 'rgb(255, 214, 168)',\n 'rgb(178, 235, 242)',\n 'rgb(230, 230, 230)',\n].map((value) => ({ value, label: value, swatch: value }));\n\nexport const FONT_SIZES: StyleOption[] = [\n '12px',\n '14px',\n '16px',\n '18px',\n '20px',\n '24px',\n '28px',\n '32px',\n '40px',\n '48px',\n].map((value) => ({ value, label: value }));\n\n/**\n * 字体族。前三项按用途选,中文栈必须自己列——只写 sans-serif 时中文会落到系统默认宋体上,\n * 与英文部分的观感差一截。后三项按名字选,装了才有用,没装就退回系统默认,不报错。\n */\nexport const FONT_FAMILIES: StyleOption[] = [\n {\n value: '-apple-system, \"Segoe UI\", Roboto, \"PingFang SC\", \"Microsoft YaHei\", sans-serif',\n label: 'Sans',\n i18nKey: 'formatting.fontSans',\n preview: 'sans-serif',\n },\n {\n value: 'Georgia, \"Times New Roman\", \"Songti SC\", SimSun, serif',\n label: 'Serif',\n i18nKey: 'formatting.fontSerif',\n preview: 'serif',\n },\n {\n value: '\"SFMono-Regular\", Consolas, Menlo, monospace',\n label: 'Monospace',\n i18nKey: 'formatting.fontMono',\n preview: 'monospace',\n },\n { value: '\"PingFang SC\", \"Microsoft YaHei\", sans-serif', label: 'PingFang / YaHei' },\n { value: 'SimSun, \"Songti SC\", serif', label: 'SimSun' },\n { value: 'SimHei, \"Heiti SC\", sans-serif', label: 'SimHei' },\n { value: 'KaiTi, \"Kaiti SC\", serif', label: 'KaiTi' },\n];\n","/**\n * TableGridPanel - 工具栏「插入表格」的网格悬停选择器\n *\n * Notion 式交互:悬停高亮左上 (row × col) 区域、点击即插入。\n * 与 InsertPanel 同为挂在 document.body 上的浮层,定位/关闭/主题三段走 floating-panel 公共底盘。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n followThemeClasses,\n listenPanelDismiss,\n positionPanel,\n type FloatingPanel,\n} from './floating-panel';\nimport { tOrFallback, type I18nManager } from './I18nManager';\n\nexport interface TableGridPanelOptions {\n editor: Editor;\n i18n?: I18nManager;\n /** 开合时把控制权交回工具栏:按钮的 active 与 aria-expanded 由它维护 */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** 网格上限:再大就该用数字输入面板了,Notion 也是 8×6 左右 */\nconst MAX_COLS = 8;\nconst MAX_ROWS = 6;\n\nexport class TableGridPanel {\n private editor: Editor;\n private i18n?: I18nManager;\n private onOpenChange?: (open: boolean) => void;\n\n private element: HTMLDivElement | null = null;\n private floating: FloatingPanel | null = null;\n private detachDismiss: (() => void) | null = null;\n private detachTheme: (() => void) | null = null;\n /** 键盘高亮的当前格,-1 表示还没进入键盘导航 */\n private cursorRow = -1;\n private cursorCol = -1;\n /** 单元格按 row * MAX_COLS + col 存放,方便方向键直接换算 */\n private cells: HTMLElement[] = [];\n\n constructor(options: TableGridPanelOptions) {\n this.editor = options.editor;\n this.i18n = options.i18n;\n this.onOpenChange = options.onOpenChange;\n }\n\n get isOpen(): boolean {\n return this.element !== null;\n }\n\n toggle(anchor: HTMLElement): void {\n if (this.isOpen) {\n this.close();\n return;\n }\n\n this.build();\n const panel = this.element;\n if (!panel) return;\n this.floating = positionPanel(anchor, panel, () => this.element === panel);\n this.detachDismiss = listenPanelDismiss(anchor, panel, () => this.close());\n this.onOpenChange?.(true);\n }\n\n close(): void {\n if (!this.element) return;\n this.detachDismiss?.();\n this.detachDismiss = null;\n this.detachTheme?.();\n this.detachTheme = null;\n this.floating?.stop();\n this.floating = null;\n this.element.remove();\n this.element = null;\n this.cells = [];\n this.cursorRow = -1;\n this.cursorCol = -1;\n this.onOpenChange?.(false);\n }\n\n destroy(): void {\n this.close();\n }\n\n private t(key: string, fallback: string): string {\n return tOrFallback(this.i18n, key, fallback);\n }\n\n private build(): void {\n const panel = document.createElement('div');\n panel.className = 'atri-table-panel';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-label', this.t('editor.table', '表格'));\n // 面板本身可聚焦:方向键与 Escape 才有事件落点,jsdom/真实浏览器都靠这个键盘入口\n panel.tabIndex = -1;\n\n const grid = document.createElement('div');\n grid.className = 'atri-table-panel-grid';\n this.cells = [];\n\n for (let row = 0; row < MAX_ROWS; row++) {\n for (let col = 0; col < MAX_COLS; col++) {\n const cell = document.createElement('div');\n cell.className = 'atri-table-panel-cell';\n // 网格用 table 语义 + role=gridcell,读屏能报出行列坐标\n cell.setAttribute('role', 'gridcell');\n cell.dataset.row = String(row);\n cell.dataset.col = String(col);\n cell.addEventListener('mouseenter', () => this.highlight(row + 1, col + 1));\n cell.addEventListener('click', () => this.insert(row + 1, col + 1));\n grid.appendChild(cell);\n this.cells.push(cell);\n }\n }\n\n const hint = document.createElement('div');\n hint.className = 'atri-table-panel-hint';\n hint.textContent = this.t('editor.table', '表格');\n\n panel.addEventListener('keydown', (event) => this.onKeyDown(event));\n panel.appendChild(grid);\n panel.appendChild(hint);\n\n document.body.appendChild(panel);\n this.element = panel;\n this.detachTheme = followThemeClasses(this.editor.view.dom, panel);\n panel.focus();\n }\n\n /**\n * 高亮左上角 (rows × cols) 区域并在提示行显示尺寸;-1 清空\n */\n private highlight(rows: number, cols: number): void {\n this.cursorRow = rows < 0 ? -1 : rows - 1;\n this.cursorCol = cols < 0 ? -1 : cols - 1;\n\n this.cells.forEach((cell) => {\n const r = Number(cell.dataset.row);\n const c = Number(cell.dataset.col);\n const active = rows > 0 && cols > 0 && r < rows && c < cols;\n cell.classList.toggle('atri-table-panel-cell--active', active);\n if (active) {\n cell.setAttribute('aria-selected', 'true');\n } else {\n cell.removeAttribute('aria-selected');\n }\n });\n\n const hint = this.element?.querySelector('.atri-table-panel-hint');\n if (hint) {\n hint.textContent = rows > 0 ? `${rows} × ${cols}` : this.t('editor.table', '表格');\n }\n }\n\n private insert(rows: number, cols: number): void {\n this.editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();\n this.close();\n }\n\n private onKeyDown(event: KeyboardEvent): void {\n const deltas: Record<string, [number, number]> = {\n ArrowLeft: [0, -1],\n ArrowRight: [0, 1],\n ArrowUp: [-1, 0],\n ArrowDown: [1, 0],\n };\n\n if (event.key === 'Enter') {\n event.preventDefault();\n // 还没导航过就没有高亮格,回车不插任何表格\n if (this.cursorRow < 0 || this.cursorCol < 0) return;\n this.insert(this.cursorRow + 1, this.cursorCol + 1);\n return;\n }\n\n const delta = deltas[event.key];\n if (!delta) return;\n event.preventDefault();\n\n const baseRow = this.cursorRow < 0 ? 0 : this.cursorRow;\n const baseCol = this.cursorCol < 0 ? 0 : this.cursorCol;\n const row = Math.min(MAX_ROWS - 1, Math.max(0, baseRow + delta[0]));\n const col = Math.min(MAX_COLS - 1, Math.max(0, baseCol + delta[1]));\n this.highlight(row + 1, col + 1);\n }\n}\n","/**\n * ToolbarMenuPanel - 工具栏项的下拉菜单(ToolbarItem.children 的落地)\n *\n * 与 StyleOptionPanel 是同一类东西的两个特例,所以结构上有八成相似\n * (锚定、外点与 Escape 关闭、主题跟随、方向键走完列表、共用实例时按 key 归属按下态)。\n * 没有在这里合并成一个带 mode 开关的类,是跟本仓库自己的先例走:\n * 浮层公共底盘是在第三份出现时才抽出去的(见提交 2764ef6),两份先并存。\n * 第三份浮层出现时一起抽,比现在为了少 60 行而给两边都加条件分支更划算。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n followThemeClasses,\n listenPanelDismiss,\n positionPanel,\n type FloatingPanel,\n} from './floating-panel';\n\n/** 菜单里的一行。active / disabled 在打开那一刻算好——开着菜单时选区不会变,\n * 因为点进正文的那一下 pointerdown 就已经把菜单关掉了 */\nexport interface ToolbarMenuItemRow {\n id: string;\n label: string;\n active: boolean;\n disabled: boolean;\n run: () => void;\n}\n\nexport interface ToolbarMenuSpec {\n /** 触发这一菜单的工具栏项 id,按下态只归它 */\n key: string;\n /** 面板的无障碍名字 */\n label: string;\n rows: ToolbarMenuItemRow[];\n}\n\nexport interface ToolbarMenuPanelOptions {\n editor: Editor;\n /** 开合时把控制权交回工具栏:按钮的 active 与 aria-expanded 由它维护 */\n onOpenChange?: (open: boolean) => void;\n}\n\nexport class ToolbarMenuPanel {\n private editor: Editor;\n private onOpenChange?: (open: boolean) => void;\n\n private element: HTMLDivElement | null = null;\n private floating: FloatingPanel | null = null;\n private detachDismiss: (() => void) | null = null;\n private detachTheme: (() => void) | null = null;\n private anchor: HTMLElement | null = null;\n private currentKey: string | null = null;\n private rows: HTMLButtonElement[] = [];\n\n constructor(options: ToolbarMenuPanelOptions) {\n this.editor = options.editor;\n this.onOpenChange = options.onOpenChange;\n }\n\n get isOpen(): boolean {\n return this.element !== null;\n }\n\n get openKey(): string | null {\n return this.currentKey;\n }\n\n toggle(anchor: HTMLElement, spec: ToolbarMenuSpec): void {\n if (this.isOpen) {\n const switching = this.anchor !== anchor;\n this.close();\n if (switching) this.open(anchor, spec);\n return;\n }\n this.open(anchor, spec);\n }\n\n close(): void {\n if (!this.element) return;\n this.detachDismiss?.();\n this.detachDismiss = null;\n this.detachTheme?.();\n this.detachTheme = null;\n this.floating?.stop();\n this.floating = null;\n this.element.remove();\n this.element = null;\n this.anchor = null;\n this.currentKey = null;\n this.rows = [];\n this.onOpenChange?.(false);\n }\n\n destroy(): void {\n this.close();\n }\n\n private open(anchor: HTMLElement, spec: ToolbarMenuSpec): void {\n const panel = this.build(spec);\n document.body.appendChild(panel);\n this.detachTheme = followThemeClasses(this.editorViewDom(anchor), panel);\n this.element = panel;\n this.anchor = anchor;\n this.currentKey = spec.key;\n this.floating = positionPanel(anchor, panel, () => this.element === panel);\n this.detachDismiss = listenPanelDismiss(anchor, panel, () => this.close());\n this.onOpenChange?.(true);\n panel.focus();\n }\n\n /**\n * 主题类要从编辑器根上取,而这里只拿得到一个锚在工具栏上的按钮:\n * 沿 DOM 往上找 .atri-editor 比让调用方再多传一个参数省事,且工具栏就在那个根里面\n */\n private editorViewDom(anchor: HTMLElement): Element | null {\n return anchor.closest('.atri-editor');\n }\n\n private build(spec: ToolbarMenuSpec): HTMLDivElement {\n const panel = document.createElement('div');\n panel.className = 'atri-toolbar-menu';\n panel.setAttribute('role', 'menu');\n panel.setAttribute('aria-label', spec.label);\n panel.tabIndex = -1;\n panel.addEventListener('keydown', (event) => this.onKeyDown(event));\n\n this.rows = [];\n spec.rows.forEach((row) => panel.appendChild(this.makeRow(row)));\n return panel;\n }\n\n private makeRow(row: ToolbarMenuItemRow): HTMLButtonElement {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'atri-toolbar-menu-item';\n // 标题级别与对齐方式都是\"多选一\",所以是 menuitemradio + aria-checked,\n // 而不是 menuitem——读屏因此能报出当前选中的是哪一项\n button.setAttribute('role', 'menuitemradio');\n button.setAttribute('aria-checked', String(row.active));\n button.dataset.menuItem = row.id;\n button.textContent = row.label;\n button.disabled = row.disabled;\n if (row.active) button.classList.add('active');\n\n button.addEventListener('click', () => {\n row.run();\n this.close();\n // 焦点要还给编辑器:顶栏普通按钮的路径是在 createButton 里补这一手的,\n // 走菜单不经过那里,而此刻持有焦点的那个行元素刚被 remove 掉——\n // 不还的话用户选完\"标题 2\"接着打字,字就丢了\n this.editor.commands.focus();\n });\n\n this.rows.push(button);\n return button;\n }\n\n private onKeyDown(event: KeyboardEvent): void {\n if (event.key === 'Enter' || event.key === ' ') return;\n\n const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];\n if (step === undefined) return;\n event.preventDefault();\n if (this.rows.length === 0) return;\n\n const current = this.rows.indexOf(document.activeElement as HTMLButtonElement);\n const next =\n current < 0\n ? step > 0\n ? 0\n : this.rows.length - 1\n : (current + step + this.rows.length) % this.rows.length;\n this.rows[next].focus();\n }\n}\n","/**\n * 基础格式能力探测:工具栏决定\"这个按钮摆不摆\"的唯一依据\n *\n * 内部使用,不进 utils barrel、不随包出口——判据(mark 的 spec.attrs 上有没有那一项)\n * 是 extension-text-style 的实现细节,公共面只承诺\"这一下真能生效\"。\n *\n * 为什么不看配置而看 schema:CoreEditor 把内置扩展放在用户扩展之前,就是为了让宿主能用\n * 同名扩展顶掉内置 mark。顶掉之后配置里写着 formatting: true,但那四类属性可能一个都不剩,\n * 于是按钮摆出来、点了没反应——正是本包反复在消除的那类\"命令返回成功、什么都没发生\"。\n */\nimport type { Editor } from '@tiptap/core';\n\n/** textStyle 这一个 mark 承载的四个属性,颜色类与字号字体共用同一个 mark */\nexport type TextStyleAttr = 'color' | 'backgroundColor' | 'fontSize' | 'fontFamily';\n\n/**\n * 属性 → 设置它的命令名。\n * 上游这几个是 addGlobalAttributes 挂上去的 Extension,命令名与属性名并不同形\n * (backgroundColor → setBackgroundColor,但首字母大写规则一致,这里仍列死而不做字符串拼接:\n * 拼出来的命令名读不出错,列成表才能一眼看出漏了谁)\n */\nconst SET_COMMAND: Record<TextStyleAttr, string> = {\n color: 'setColor',\n backgroundColor: 'setBackgroundColor',\n fontSize: 'setFontSize',\n fontFamily: 'setFontFamily',\n};\n\n/** 这个属性是否既设得了也存得住 */\nexport function canUseTextStyle(editor: Editor, attr: TextStyleAttr): boolean {\n const command = (editor.commands as Record<string, unknown>)[SET_COMMAND[attr]];\n if (typeof command !== 'function') return false;\n return !!editor.schema.marks.textStyle?.spec.attrs?.[attr];\n}\n\n/** 独立 mark(高亮 / 下标 / 上标)是否在册 */\nexport function hasMark(editor: Editor, name: string): boolean {\n return !!editor.schema.marks[name];\n}\n","/**\n * Lucide Icons - 工具栏图标映射\n * 来源: https://lucide.dev (MIT License)\n */\n\nexport const icons: Record<string, string> = {\n undo: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 7v6h6\"/><path d=\"M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13\"/></svg>',\n\n redo: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 7v6h-6\"/><path d=\"M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13\"/></svg>',\n\n heading1:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12h8\"/><path d=\"M4 18V6\"/><path d=\"M12 18V6\"/><path d=\"M17 12l3-2v8\"/></svg>',\n\n heading2:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12h8\"/><path d=\"M4 18V6\"/><path d=\"M12 18V6\"/><path d=\"M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1\"/></svg>',\n\n heading3:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12h8\"/><path d=\"M4 18V6\"/><path d=\"M12 18V6\"/><path d=\"M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 0 1-2 2\"/><path d=\"M17 17.5c2 1.5 4 .3 4-1.5a2 2 0 0 0-2-2\"/></svg>',\n\n paragraph:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M13 4v16\"/><path d=\"M17 4v16\"/><path d=\"M19 4H9.5a4.5 4.5 0 0 0 0 9H13\"/></svg>',\n\n bold: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8\"/></svg>',\n\n italic:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n underline:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 4v6a6 6 0 0 0 12 0V4\"/><line x1=\"4\" x2=\"20\" y1=\"20\" y2=\"20\"/></svg>',\n\n strikethrough:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n code: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/></svg>',\n\n list: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/><line x1=\"3\" x2=\"3.01\" y1=\"6\" y2=\"6\"/><line x1=\"3\" x2=\"3.01\" y1=\"12\" y2=\"12\"/><line x1=\"3\" x2=\"3.01\" y1=\"18\" y2=\"18\"/></svg>',\n\n listOrdered:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n listChecks:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m3 17 2 2 4-4\"/><path d=\"m3 7 2 2 4-4\"/><path d=\"M13 6h8\"/><path d=\"M13 12h8\"/><path d=\"M13 18h8\"/></svg>',\n\n horizontalRule:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"5\" x2=\"19\" y1=\"12\" y2=\"12\"/></svg>',\n\n quote:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\"/><path d=\"M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\"/></svg>',\n\n codeBlock:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/></svg>',\n\n alignLeft:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n alignCenter:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n alignRight:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n alignJustify:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"21\" x2=\"3\" y1=\"6\" y2=\"6\"/><line x1=\"21\" x2=\"3\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"3\" y1=\"18\" y2=\"18\"/></svg>',\n\n image:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n paperclip:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m16 6-8.4 8.6a2 2 0 0 0 2.8 2.8l8.4-8.6a4 4 0 1 0-5.6-5.6l-8.4 8.5a6 6 0 1 0 8.5 8.5l8.4-8.4\"/></svg>',\n\n link: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 17H7A5 5 0 1 1 7 7h2\"/><path d=\"M15 7h2a5 5 0 1 1 0 10h-2\"/><line x1=\"8\" x2=\"16\" y1=\"12\" y2=\"12\"/></svg>',\n\n table:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3v18\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/></svg>',\n\n trash:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/><path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"/><line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\"/><line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\"/></svg>',\n\n // 表格操作菜单:统一用「表格边框 + 方向标记」一套句法——\n // 行操作画横分隔线、列操作画竖分隔线,加号落在哪一格就是插到哪一侧,\n // 减号落在哪一格就是删哪一格(两种删除靠分隔线方向区分)\n tableRowBefore:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/><path d=\"M12 4.5v3\"/><path d=\"M10.5 6h3\"/></svg>',\n\n tableRowAfter:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/><path d=\"M12 16.5v3\"/><path d=\"M10.5 18h3\"/></svg>',\n\n tableRowDelete:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/><path d=\"M10.5 12h3\"/></svg>',\n\n tableColumnBefore:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/><path d=\"M4.5 12h3\"/><path d=\"M6 10.5v3\"/></svg>',\n\n tableColumnAfter:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/><path d=\"M16.5 12h3\"/><path d=\"M18 10.5v3\"/></svg>',\n\n tableColumnDelete:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/><path d=\"M10.5 12h3\"/></svg>',\n\n tableMergeCells:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M8 12h8\"/><path d=\"m10.5 9.5-2.5 2.5 2.5 2.5\"/><path d=\"m13.5 9.5 2.5 2.5-2.5 2.5\"/></svg>',\n\n tableSplitCell:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M12 3v18\"/><path d=\"M9 12H6\"/><path d=\"M15 12h3\"/></svg>',\n\n tableHeaderRow:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M7 6h4\"/></svg>',\n\n textColor:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"13.5\" cy=\"6.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"17.5\" cy=\"10.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"8.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"6.5\" cy=\"12.5\" r=\".5\" fill=\"currentColor\"/><path d=\"M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.436-.652-.436-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2Z\"/></svg>',\n\n highlight:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n highlightColor:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m14 7 3-3a2.12 2.12 0 0 1 3 3l-3 3\"/><path d=\"m13 6 5 5\"/><path d=\"M14.7 8.3 8 15v4h4l6.7-6.7a2 2 0 0 0-4-4\"/></svg>',\n\n fontSize:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m2 18 4.5-11L11 18\"/><path d=\"M3.6 14h5.8\"/><path d=\"m14 18 3-7 3 7\"/><path d=\"M14.9 15.6h4.2\"/></svg>',\n\n fontFamily:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n subscript:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m4 5 6 8\"/><path d=\"m10 5-6 8\"/><path d=\"M14 14.5c.7-1.4 2.8-1.4 3.4 0 .5 1.2-.5 2-1.7 2.6-1.4.7-2.2 1.4-2.2 2.4h4.5\"/></svg>',\n\n superscript:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m4 9 6 8\"/><path d=\"m10 9-6 8\"/><path d=\"M14 5.5c.7-1.4 2.8-1.4 3.4 0 .5 1.2-.5 2-1.7 2.6-1.4.7-2.2 1.4-2.2 2.4h4.5\"/></svg>',\n\n clearFormat:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21\"/><path d=\"M22 21H7\"/><path d=\"m5 11 9 9\"/></svg>',\n\n // 下拉触发器用的通用字形:标题菜单与对齐菜单在\"当前没有选中任何一级/一种对齐\"时用它,\n // 有当前值时触发器换成那一项自己的图标,所见即所得\n heading:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 4v16\"/><path d=\"M18 4v16\"/><path d=\"M6 12h12\"/></svg>',\n\n align:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 6h16\"/><path d=\"M4 10h10\"/><path d=\"M4 14h16\"/><path d=\"M4 18h8\"/></svg>',\n\n chevronDown:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>',\n};\n","/**\n * ToolbarManager - 工具栏管理器\n * 负责创建工具栏 DOM、绑定按钮事件、更新按钮状态\n */\nimport { isNodeSelection, type Editor } from '@tiptap/core';\nimport type { ToolbarConfig, ToolbarItem } from '../types';\nimport { InsertPanel, type InsertPanelMode } from '../media/InsertPanel';\nimport type { MediaRuntime } from '../media/MediaRuntime';\nimport { BUBBLE_NODE_ITEMS, BUBBLE_TEXT_ITEMS } from './bubble-toolbar';\nimport { zhText, type I18nManager } from './I18nManager';\nimport type { LinkPanel } from './LinkPanel';\nimport { StyleOptionPanel, type StylePanelSpec } from './StyleOptionPanel';\nimport {\n FONT_FAMILIES,\n FONT_SIZES,\n HIGHLIGHT_COLORS,\n TEXT_COLORS,\n type StyleOption,\n} from './style-presets';\nimport { TableGridPanel } from './TableGridPanel';\nimport {\n ToolbarMenuPanel,\n type ToolbarMenuItemRow,\n type ToolbarMenuSpec,\n} from './ToolbarMenuPanel';\nimport { canInsertTable } from '../utils/table';\nimport { canUseTextStyle, hasMark, type TextStyleAttr } from '../utils/formatting';\nimport { icons } from './icons';\n\n/**\n * 工具栏项定义\n */\ninterface ToolbarItemDef {\n id: string;\n icon: string;\n /** 没有词条映射时的回退文案;内置项都走 TOOLTIP_KEYS -> 中文词条,不在此抄一份 */\n tooltip?: string;\n /**\n * 会打开浮层。true(或 'dialog')是带输入框的面板,'menu' 是下拉菜单:\n * 两者的 aria-haspopup 不同,读屏报出来的语义也不同。\n * 不管是哪种,都不能把焦点抢回编辑器——抢了浮层里的输入框就根本打不了字\n */\n popup?: boolean | 'menu';\n /**\n * 下拉菜单里的子项 id,走的仍是同一张 itemDefs 表:\n * 子项因此和顶栏项一样受能力门控(比如表格被顶掉时 insertTable 就不会出现在菜单里)\n */\n children?: string[];\n command: (editor: Editor, button: HTMLButtonElement) => void;\n isActive: (editor: Editor) => boolean;\n isDisabled: (editor: Editor) => boolean;\n}\n\n/**\n * 工具栏项与 i18n 词条的映射(这些项的默认文案就是中文词条本身,见 zhText)\n */\nconst TOOLTIP_KEYS: Record<string, string> = {\n undo: 'editor.undo',\n redo: 'editor.redo',\n heading1: 'editor.heading1',\n heading2: 'editor.heading2',\n heading3: 'editor.heading3',\n heading4: 'editor.heading4',\n heading5: 'editor.heading5',\n heading6: 'editor.heading6',\n // 两个下拉触发器。'editor.heading' 这条词条躺了很久、背后没有任何功能,\n // 这批终于有了真正读它的人\n heading: 'editor.heading',\n align: 'editor.align',\n paragraph: 'editor.paragraph',\n bold: 'editor.bold',\n italic: 'editor.italic',\n underline: 'editor.underline',\n strike: 'editor.strikethrough',\n code: 'editor.code',\n bulletList: 'editor.bulletList',\n orderedList: 'editor.orderedList',\n taskList: 'editor.taskList',\n blockquote: 'editor.blockquote',\n codeBlock: 'editor.codeBlock',\n horizontalRule: 'editor.horizontalRule',\n alignLeft: 'editor.alignLeft',\n alignCenter: 'editor.alignCenter',\n alignRight: 'editor.alignRight',\n alignJustify: 'editor.alignJustify',\n insertImage: 'editor.image',\n insertAttachment: 'editor.attachment',\n attachmentDisplay: 'editor.attachmentDisplay',\n insertTable: 'editor.table',\n insertLink: 'editor.link',\n delete: 'editor.delete',\n textColor: 'editor.textColor',\n highlight: 'editor.highlight',\n highlightColor: 'editor.highlightColor',\n fontSize: 'editor.fontSize',\n fontFamily: 'editor.fontFamily',\n subscript: 'editor.subscript',\n superscript: 'editor.superscript',\n clearFormat: 'editor.clearFormat',\n};\n\n/**\n * 工具栏管理器\n */\nexport class ToolbarManager {\n private container: HTMLElement;\n private editor: Editor;\n private i18n?: I18nManager;\n private config?: ToolbarConfig;\n private mediaRuntime: MediaRuntime | null;\n /** 链接浮层由门面持有:编辑态点击链接也要能打开它,跟工具栏渲染不渲染无关 */\n private linkPanel: LinkPanel | null;\n private unsubscribeLanguage?: () => void;\n private unsubscribeEditor?: () => void;\n /** 同一个项可能同时渲染在顶栏与浮动工具栏里,所以一个 id 对应一组按钮 */\n private buttons: Map<string, Set<HTMLButtonElement>> = new Map();\n /** 按 id 记的覆盖文案:顶栏与浮层共用一份,改一处两处一起改,这是有意的 */\n private customTooltips: Map<string, string> = new Map();\n private itemDefs: Map<string, ToolbarItemDef>;\n private createdElements: HTMLElement[] = [];\n private panel: InsertPanel | null = null;\n private tablePanel: TableGridPanel | null = null;\n /** 颜色 / 字号 / 字体四个入口共用一个面板实例:同时只会开一个 */\n private stylePanel: StyleOptionPanel | null = null;\n /** 下拉菜单同理,所有 children 菜单共用一个实例 */\n private menuPanel: ToolbarMenuPanel | null = null;\n\n constructor(\n editor: Editor,\n container: HTMLElement,\n config?: ToolbarConfig,\n i18n?: I18nManager,\n mediaRuntime?: MediaRuntime | null,\n linkPanel?: LinkPanel | null\n ) {\n this.editor = editor;\n this.container = container;\n this.config = config;\n this.i18n = i18n;\n this.mediaRuntime = mediaRuntime ?? null;\n this.linkPanel = linkPanel ?? null;\n this.itemDefs = this.getDefaultItems();\n this.createToolbarDOM();\n this.bindEditorEvents();\n\n this.unsubscribeLanguage = this.i18n?.onLanguageChanged(() => this.applyTooltips());\n }\n\n /**\n * 把浮动工具栏的按钮渲染进给定的元素\n *\n * 元素由门面创建并持有:BubbleMenu 每次隐藏都会把它从文档里摘掉,重建编辑器后\n * 还得拿同一个元素重新 attach,所以它不进 createdElements、也不在这里 remove\n */\n attachBubbleToolbar(element: HTMLElement): void {\n // 重建编辑器后是\"新的管理器 + 同一个元素\"再走一遍这里:先清干净,\n // 否则上一任的按钮还留在盒子里,却再也等不到状态同步\n element.replaceChildren();\n\n // 两组一次都渲染出来,靠 CSS 读容器上的 data-atri-bubble-mode 决定哪组占宽度\n const groups: [string, readonly string[]][] = [\n ['text', BUBBLE_TEXT_ITEMS],\n ['node', BUBBLE_NODE_ITEMS],\n ];\n\n groups.forEach(([groupId, itemIds]) => {\n const group = document.createElement('div');\n group.className = 'atri-editor-bubble-group';\n group.setAttribute('data-atri-bubble-group', groupId);\n\n itemIds.forEach((itemId) => {\n const itemDef = this.itemDefs.get(itemId);\n if (!itemDef) return;\n\n // 带浮层的项不能进这里:面板锚在按钮上,而按钮跟着浮层一起被摘出文档\n if (itemDef.popup) {\n console.warn(\n `[Atri Editor] Toolbar item \"${itemId}\" opens a panel, which bubble toolbar cannot host.`\n );\n return;\n }\n\n group.appendChild(this.createButton(itemId, itemDef));\n });\n\n element.appendChild(group);\n });\n\n this.applyTooltips();\n this.updateButtonStates();\n }\n\n /**\n * 插入浮层按需创建:没点开过就不该有一个实例挂在编辑器上\n */\n private get insertPanel(): InsertPanel | null {\n if (!this.mediaRuntime) return null;\n this.panel ??= new InsertPanel({\n editor: this.editor,\n runtime: this.mediaRuntime,\n i18n: this.i18n,\n // 开合不改文档,等不到 transaction,得主动刷一次按钮状态\n onOpenChange: () => this.updateButtonStates(),\n });\n return this.panel;\n }\n\n private openInsert(mode: InsertPanelMode, button: HTMLButtonElement): void {\n this.insertPanel?.toggle(button, mode);\n }\n\n /**\n * 表格网格面板按需创建:没点开过就不该有一个实例挂在编辑器上\n */\n private get gridPanel(): TableGridPanel | null {\n if (!this.tableExtensionRegistered) return null;\n this.tablePanel ??= new TableGridPanel({\n editor: this.editor,\n i18n: this.i18n,\n // 开合不改文档,等不到 transaction,得主动刷一次按钮状态\n onOpenChange: () => this.updateButtonStates(),\n });\n return this.tablePanel;\n }\n\n /** 内置表格真可用才摆按钮:table:false 未注册、或被用户同名扩展顶掉都不摆(判定见 canInsertTable) */\n private get tableExtensionRegistered(): boolean {\n return canInsertTable(this.editor);\n }\n\n /** 格式面板按需创建:没点开过就不该有一个实例挂在编辑器上 */\n private get styleOptionPanel(): StyleOptionPanel {\n this.stylePanel ??= new StyleOptionPanel({\n editor: this.editor,\n i18n: this.i18n,\n // 开合不改文档,等不到 transaction,得主动刷一次按钮状态\n onOpenChange: () => this.updateButtonStates(),\n });\n return this.stylePanel;\n }\n\n private tOr(key: string, fallback: string): string {\n return this.i18n?.tOr(key, fallback) ?? fallback;\n }\n\n /** 下拉菜单面板按需创建:没点开过就不该有一个实例挂在编辑器上 */\n private get menu(): ToolbarMenuPanel {\n this.menuPanel ??= new ToolbarMenuPanel({\n editor: this.editor,\n // 开合不改文档,等不到 transaction,得主动刷一次按钮状态\n onOpenChange: () => this.updateButtonStates(),\n });\n return this.menuPanel;\n }\n\n /**\n * 把一个项做成下拉触发器。\n * 子项走的仍是同一张 itemDefs 表,所以它们和顶栏项一样受能力门控——\n * 表格被顶掉时 insertTable 就不会出现在菜单里,而不是摆一行按了没反应的\n */\n private menuItem(key: string, icon: string, children: string[]): ToolbarItemDef {\n const def: ToolbarItemDef = {\n id: key,\n icon,\n popup: 'menu',\n children,\n command: (_editor, button) => {\n // 必须现读而不是用上面那个闭包参数:宿主用 { id: 'heading', children: [...] }\n // 覆盖时改的就是 def.children,闭包里那份还是内置默认值,\n // 用闭包会让\"覆盖写在类型上、点在旧列表上\"这种错配静默生效\n const live = this.itemDefs.get(key)?.children ?? children;\n this.menu.toggle(button, this.menuSpec(key, live, button));\n },\n isActive: () => this.menuPanel?.openKey === key,\n isDisabled: (editor) => !editor.isEditable,\n };\n return def;\n }\n\n /** 当前命中的子项:触发器的图标与提示都跟着它走,用户不必点开就知道现在的级别 */\n private activeChild(def: ToolbarItemDef): ToolbarItemDef | null {\n for (const childId of def.children ?? []) {\n const child = this.itemDefs.get(childId);\n if (child && !child.popup && child.isActive(this.editor) && !child.isDisabled(this.editor)) {\n return child;\n }\n }\n return null;\n }\n\n /** 子项词条 key;没有映射的(宿主的自定义项)退回定义里的兜底文案 */\n private rowLabel(childId: string, child: ToolbarItemDef): string {\n const key = TOOLTIP_KEYS[childId];\n return key ? this.tOr(key, zhText(key)) : (child.tooltip ?? childId);\n }\n\n /**\n * 菜单里带浮层的子项一律不收:子项被点中后菜单就关了,\n * 而面板要锚在触发器上——锚一个即将从文档里消失的行没有意义。\n * 这与 attachBubbleToolbar 拒绝浮层项是同一条理由。\n */\n private menuSpec(key: string, children: string[], trigger: HTMLButtonElement): ToolbarMenuSpec {\n const menuKey = TOOLTIP_KEYS[key];\n return {\n key,\n label: menuKey ? this.tOr(menuKey, zhText(menuKey)) : key,\n rows: children.reduce<ToolbarMenuItemRow[]>((rows, childId) => {\n const child = this.itemDefs.get(childId);\n if (!child) {\n console.warn(`[Atri Editor] Menu \"${key}\" declares unknown item \"${childId}\".`);\n return rows;\n }\n if (child.popup) {\n console.warn(\n `[Atri Editor] Menu item \"${childId}\" opens a panel, which menus cannot host.`\n );\n return rows;\n }\n rows.push({\n id: childId,\n label: this.rowLabel(childId, child),\n active: child.isActive(this.editor),\n disabled: child.isDisabled(this.editor),\n // 子项自己不需要触发器元素(只有浮层项会用),传过去是为了共用同一份定义\n run: () => child.command(this.editor, trigger),\n });\n return rows;\n }, []),\n };\n }\n\n /**\n * 面板文案在这里现取,不在建项时算好存下来:\n * 语言切换后重开面板要拿到新词条,而项定义只在建栏时求值一次\n */\n private styleSpec(\n key: string,\n parts: {\n shape: 'grid' | 'list';\n options: StyleOption[];\n swatch: boolean;\n attr: TextStyleAttr | 'highlight';\n }\n ): StylePanelSpec {\n return {\n key,\n label: this.tOr(`editor.${key}`, key),\n shape: parts.shape,\n options: parts.options,\n // 色板才有\"其他颜色\"这一行;字号字体用原生控件选不出任意值\n customColorLabel: parts.swatch ? this.tOr('formatting.customColor', '其他颜色…') : undefined,\n // 清除行的措辞按形状分:颜色去掉是\"无\",字号字体退回的是\"默认\"\n clearLabel: parts.swatch\n ? this.tOr('formatting.noColor', '无')\n : this.tOr('formatting.defaultValue', '默认'),\n onPick: (value) => this.applyStyle(parts.attr, value),\n };\n }\n\n /**\n * 空串是\"清除\"而不是\"设成空值\":设成空串会留下一个属性为空串的 mark,\n * 而 textStyle 的 renderHTML 对空值照样会吐出一个 `<span style=\"\">`,\n * 那种节点既看不见又会在往返里产噪音(见 extensions/formatting.ts 的发出侧)\n */\n private applyStyle(attr: TextStyleAttr | 'highlight', value: string): void {\n const chain = this.editor.chain().focus();\n if (attr === 'highlight') {\n if (value === '') chain.unsetHighlight().run();\n else chain.setHighlight({ color: value }).run();\n return;\n }\n switch (attr) {\n case 'color':\n if (value === '') chain.unsetColor().run();\n else chain.setColor(value).run();\n return;\n case 'backgroundColor':\n if (value === '') chain.unsetBackgroundColor().run();\n else chain.setBackgroundColor(value).run();\n return;\n case 'fontSize':\n if (value === '') chain.unsetFontSize().run();\n else chain.setFontSize(value).run();\n return;\n case 'fontFamily':\n if (value === '') chain.unsetFontFamily().run();\n else chain.setFontFamily(value).run();\n return;\n }\n }\n\n /**\n * 创建工具栏 DOM - 直接将按钮插入 container\n */\n private createToolbarDOM(): void {\n const items = this.config?.items;\n\n if (items) {\n items.forEach((item) => {\n const resolved = this.resolveItem(item);\n if (!resolved) return;\n const button = this.createButton(resolved.id, resolved.def, resolved.overrides);\n this.container.appendChild(button);\n this.createdElements.push(button);\n });\n this.applyTooltips();\n return;\n }\n\n // 默认工具栏布局(分组)\n const layout = [\n ['undo', 'redo'],\n // 标题收成一下拉:一到六级加正文七个取值平铺要占七格,而一次只用得上一个\n ['heading'],\n // 链接跟着行内格式走:它也是一段文字上的标记,只是要填地址所以带浮层\n ['bold', 'italic', 'underline', 'strike', 'code', 'insertLink'],\n // 颜色与字号字体自成一组:都是\"这段文字长什么样\",且除 highlight 外都带浮层\n ['textColor', 'highlight', 'highlightColor', 'fontSize', 'fontFamily'],\n ['bulletList', 'orderedList', 'taskList', 'blockquote', 'codeBlock'],\n // 对齐同样收成一下拉\n ['align'],\n // 分割线与表格是一组:都是\"往正文里插一块\"的动作,没有激活态\n ['horizontalRule', 'insertTable'],\n // 上下标与清除格式:作用于选区已有的格式,而不是加一种新格式\n ['subscript', 'superscript', 'clearFormat'],\n ['insertImage', 'insertAttachment'],\n ];\n\n // 整组一项都不存在就不摆这一组:分隔符是按组序插的,留下一组空的就会多出\n // 一根光杆(formatting:false 时那两组全空,table:false / media:false 同理)\n const groups = layout.filter((group) => group.some((itemId) => this.itemDefs.has(itemId)));\n\n groups.forEach((group, groupIndex) => {\n if (groupIndex > 0) {\n const separator = document.createElement('div');\n separator.className = 'atri-editor-toolbar-separator';\n this.container.appendChild(separator);\n this.createdElements.push(separator);\n }\n\n group.forEach((itemId) => {\n const itemDef = this.itemDefs.get(itemId);\n if (!itemDef) return;\n\n const button = this.createButton(itemId, itemDef);\n this.container.appendChild(button);\n this.createdElements.push(button);\n });\n });\n\n this.applyTooltips();\n }\n\n /**\n * 解析配置项:字符串按 id 取内置定义,ToolbarItem 允许覆盖展示文案\n */\n private resolveItem(\n item: string | ToolbarItem\n ): { id: string; def: ToolbarItemDef; overrides?: ToolbarItem } | null {\n const id = typeof item === 'string' ? item : item.id;\n const def = this.itemDefs.get(id);\n\n if (!def) {\n console.warn(`[Atri Editor] Unknown toolbar item \"${id}\" was skipped.`);\n return null;\n }\n\n if (typeof item === 'string') {\n return { id, def };\n }\n\n if (item.children?.length) {\n // 目前只有内置的下拉项(heading / align)能挂子项:公开注册自定义项还没有入口,\n // 让宿主把 children 写在普通按钮上只会得到一个静默无效的声明——\n // 那正是本包在别处反复消除的\"写了没反应\",所以这里明确告警而不是默默吞掉\n if (def.popup !== 'menu') {\n console.warn(\n `[Atri Editor] Toolbar item \"${id}\" declares children, but it is not a dropdown; ` +\n 'only the built-in dropdown items (heading, align) accept children.'\n );\n } else {\n // 只支持一层:子项再带 children 就变成\"菜单里套菜单\",而菜单面板没有嵌套定位能力。\n // 覆盖写在 def 上而不是只记这一次渲染,因为同一个项可能同时出现在顶栏与浮层,\n // 与 customTooltips 一样是\"一份定义两处共用\"的有意选择\n const nested = item.children.filter((child) => child.children?.length);\n if (nested.length) {\n console.warn(\n `[Atri Editor] Nested children on \"${nested\n .map((child) => child.id)\n .join(', ')}\" are not supported and were ignored.`\n );\n }\n def.children = item.children.map((child) => child.id);\n }\n }\n\n return { id, def, overrides: item };\n }\n\n private createButton(\n itemId: string,\n itemDef: ToolbarItemDef,\n overrides?: ToolbarItem\n ): HTMLButtonElement {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'atri-editor-toolbar-btn';\n button.setAttribute('data-toolbar-item', itemId);\n\n if (overrides?.icon) {\n button.innerHTML = overrides.icon;\n } else if (overrides?.label) {\n button.textContent = overrides.label;\n } else {\n button.innerHTML = itemDef.icon;\n }\n\n if (overrides?.tooltip) {\n this.customTooltips.set(itemId, overrides.tooltip);\n }\n\n if (itemDef.popup) {\n button.setAttribute('aria-haspopup', itemDef.popup === 'menu' ? 'menu' : 'dialog');\n button.setAttribute('aria-expanded', 'false');\n }\n\n if (itemDef.popup === 'menu') {\n // 下拉触发器要带一个下箭头:不带的话它和旁边的普通按钮长得一模一样,\n // 用户得点下去才知道里面还有一层。\n // 基础图标单独存一份:状态刷新要按\"当前命中的子项\"换图标,\n // 换完还得能退回建按钮时那一版(里面可能带着宿主覆盖的图标)\n button.classList.add('atri-editor-toolbar-btn--menu');\n button.dataset.menuIcon = button.innerHTML;\n button.innerHTML += icons.chevronDown;\n }\n\n button.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n itemDef.command(this.editor, button);\n // 浮层项自己管焦点:抢回编辑器会让浮层里的输入框根本打不了字\n if (!itemDef.popup) this.editor.commands.focus();\n });\n\n let group = this.buttons.get(itemId);\n if (!group) {\n group = new Set();\n this.buttons.set(itemId, group);\n }\n group.add(button);\n return button;\n }\n\n /**\n * 按当前语言刷新按钮 tooltip\n */\n private applyTooltips(): void {\n this.buttons.forEach((buttons, itemId) => {\n const itemDef = this.itemDefs.get(itemId);\n if (!itemDef) return;\n\n // 有词条映射的项,中文原文就是词条本身;只有不带词条的自定义项才用定义里的 tooltip。\n // 下拉触发器与宿主覆盖的优先级都收在 itemTitle 里,与状态刷新共用同一份判定\n const title = this.itemTitle(itemId, itemDef);\n\n buttons.forEach((button) => {\n button.title = title;\n });\n });\n }\n\n /**\n * 获取默认工具栏项定义\n */\n private getDefaultItems(): Map<string, ToolbarItemDef> {\n const items = new Map<string, ToolbarItemDef>();\n\n // 媒体节点没注册(media:false)时这两个命令压根不存在,按钮也不提供\n if (this.mediaRuntime) {\n items.set('insertImage', {\n id: 'insertImage',\n icon: icons.image,\n popup: true,\n command: (_editor, button) => this.openInsert('image', button),\n isActive: () => this.panel?.openMode === 'image',\n isDisabled: (editor) => !editor.isEditable,\n });\n\n items.set('insertAttachment', {\n id: 'insertAttachment',\n icon: icons.paperclip,\n popup: true,\n command: (_editor, button) => this.openInsert('attachment', button),\n isActive: () => this.panel?.openMode === 'attachment',\n isDisabled: (editor) => !editor.isEditable,\n });\n\n // 卡片↔行内链接是选中态上的操作:没选中附件就没有作用对象\n items.set('attachmentDisplay', {\n id: 'attachmentDisplay',\n icon: icons.link,\n command: (editor) => editor.chain().focus().toggleAttachmentDisplay().run(),\n isActive: (editor) => editor.isActive('attachment') || editor.isActive('attachmentLink'),\n isDisabled: (editor) =>\n !editor.isActive('attachment') && !editor.isActive('attachmentLink'),\n });\n }\n\n // 内置表格不可用(table:false 或被用户顶掉)时 insertTable 命令跑不通,按钮也不提供\n if (this.tableExtensionRegistered) {\n items.set('insertTable', {\n id: 'insertTable',\n icon: icons.table,\n popup: true,\n command: (_editor, button) => this.gridPanel?.toggle(button),\n isActive: () => this.tablePanel?.isOpen === true,\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n // 链接入口:浮层归门面所有(点链接就地编辑也要用它),这里只摆一个触发按钮\n if (this.linkPanel) {\n items.set('insertLink', {\n id: 'insertLink',\n icon: icons.link,\n popup: true,\n command: (_editor, button) => this.linkPanel?.toggle(button),\n // 浮层开着时按下去是关,所以按钮的\"按下态\"跟的是浮层,不是选区里有没有链接\n isActive: (editor) => this.linkPanel?.isOpen === true || editor.isActive('link'),\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n // 基础格式:按能力摆项。formatting:false 没注册、或宿主用同名扩展顶掉内置 mark 时,\n // 一条都不出——判据是\"命令在不在 + schema 里有没有那个属性\"两条,见 utils/formatting.ts。\n // 这一组全部按能力各自判定,不是一荣俱荣:宿主完全可能只顶掉 textStyle 而留着 highlight。\n const textStyleReady = (attr: TextStyleAttr) => canUseTextStyle(this.editor, attr);\n\n if (textStyleReady('color')) {\n items.set('textColor', {\n id: 'textColor',\n icon: icons.textColor,\n popup: true,\n command: (_editor, button) =>\n this.styleOptionPanel.toggle(\n button,\n this.styleSpec('textColor', {\n shape: 'grid',\n options: TEXT_COLORS,\n swatch: true,\n attr: 'color',\n })\n ),\n // 浮层项的按下态只跟浮层走:选区里有没有颜色是文档事实,不是\"这个按钮正按着\"\n isActive: () => this.stylePanel?.openKey === 'textColor',\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n if (hasMark(this.editor, 'highlight')) {\n // 无色的 ==text== 是个纯开关,与\"挑一个高亮色\"是两件事,所以分成两项\n items.set('highlight', {\n id: 'highlight',\n icon: icons.highlight,\n command: (editor) => editor.chain().focus().toggleHighlight().run(),\n isActive: (editor) => editor.isActive('highlight'),\n isDisabled: (editor) => !editor.isEditable,\n });\n\n items.set('highlightColor', {\n id: 'highlightColor',\n icon: icons.highlightColor,\n popup: true,\n command: (_editor, button) =>\n this.styleOptionPanel.toggle(\n button,\n this.styleSpec('highlightColor', {\n shape: 'grid',\n options: HIGHLIGHT_COLORS,\n swatch: true,\n attr: 'highlight',\n })\n ),\n isActive: () => this.stylePanel?.openKey === 'highlightColor',\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n if (textStyleReady('fontSize')) {\n items.set('fontSize', {\n id: 'fontSize',\n icon: icons.fontSize,\n popup: true,\n command: (_editor, button) =>\n this.styleOptionPanel.toggle(\n button,\n this.styleSpec('fontSize', {\n shape: 'list',\n options: FONT_SIZES,\n swatch: false,\n attr: 'fontSize',\n })\n ),\n isActive: () => this.stylePanel?.openKey === 'fontSize',\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n if (textStyleReady('fontFamily')) {\n items.set('fontFamily', {\n id: 'fontFamily',\n icon: icons.fontFamily,\n popup: true,\n command: (_editor, button) =>\n this.styleOptionPanel.toggle(\n button,\n this.styleSpec('fontFamily', {\n shape: 'list',\n options: FONT_FAMILIES,\n swatch: false,\n attr: 'fontFamily',\n })\n ),\n isActive: () => this.stylePanel?.openKey === 'fontFamily',\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n if (hasMark(this.editor, 'subscript')) {\n items.set('subscript', {\n id: 'subscript',\n icon: icons.subscript,\n command: (editor) => editor.chain().focus().toggleSubscript().run(),\n isActive: (editor) => editor.isActive('subscript'),\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n if (hasMark(this.editor, 'superscript')) {\n items.set('superscript', {\n id: 'superscript',\n icon: icons.superscript,\n command: (editor) => editor.chain().focus().toggleSuperscript().run(),\n isActive: (editor) => editor.isActive('superscript'),\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n // 清除格式:把选区上所有 mark 与块级类型一起退回默认。\n // 常驻不禁用——unsetAllMarks 对空格式光标是无操作,而不是报错,\n // 要为\"有没有可清除的东西\"做判定就得遍历选区,不值当\n items.set('clearFormat', {\n id: 'clearFormat',\n icon: icons.clearFormat,\n command: (editor) => editor.chain().focus().unsetAllMarks().clearNodes().run(),\n isActive: () => false,\n isDisabled: (editor) => !editor.isEditable,\n });\n\n // 删除作用于选中的整节点:一段文字该交给 Delete 键,所以它不进顶栏默认布局\n items.set('delete', {\n id: 'delete',\n icon: icons.trash,\n command: (editor) => editor.chain().deleteSelection().run(),\n isActive: () => false,\n isDisabled: (editor) => !isNodeSelection(editor.state.selection),\n });\n\n // 撤销\n items.set('undo', {\n id: 'undo',\n icon: icons.undo,\n command: (editor) => editor.chain().undo().run(),\n isActive: () => false,\n isDisabled: (editor) => !editor.can().undo(),\n });\n\n // 重做\n items.set('redo', {\n id: 'redo',\n icon: icons.redo,\n command: (editor) => editor.chain().redo().run(),\n isActive: () => false,\n isDisabled: (editor) => !editor.can().redo(),\n });\n\n // 标题 1–6。原来只到 3,而 schema 与输入规则一直是 1..6(StarterKit 默认 levels),\n // 缺的只是入口——4/5/6 落在标题菜单里,不用点开顶栏也够得着。\n // 四个新级别用通用 heading 图标:它们只在菜单里以文字标签出现,\n // 而我要是真去手搓三个数字字形,既没法验证好不好看、又和 tooltip 里的\"标题 4\"重复\n [1, 2, 3, 4, 5, 6].forEach((level) => {\n const id = `heading${level}`;\n items.set(id, {\n id,\n icon: level <= 3 ? icons[id] : icons.heading,\n command: (editor) =>\n editor\n .chain()\n .toggleHeading({ level } as never)\n .run(),\n isActive: (editor) => editor.isActive('heading', { level } as never),\n isDisabled: (editor) => !editor.can().toggleHeading({ level } as never),\n });\n });\n\n // 正文\n items.set('paragraph', {\n id: 'paragraph',\n icon: icons.paragraph,\n command: (editor) => editor.chain().setParagraph().run(),\n isActive: (editor) => editor.isActive('paragraph'),\n isDisabled: (editor) => !editor.can().setParagraph(),\n });\n\n // 加粗\n items.set('bold', {\n id: 'bold',\n icon: icons.bold,\n command: (editor) => editor.chain().toggleBold().run(),\n isActive: (editor) => editor.isActive('bold'),\n isDisabled: (editor) => !editor.can().toggleBold(),\n });\n\n // 斜体\n items.set('italic', {\n id: 'italic',\n icon: icons.italic,\n command: (editor) => editor.chain().toggleItalic().run(),\n isActive: (editor) => editor.isActive('italic'),\n isDisabled: (editor) => !editor.can().toggleItalic(),\n });\n\n // 下划线\n items.set('underline', {\n id: 'underline',\n icon: icons.underline,\n command: (editor) => editor.chain().toggleUnderline().run(),\n isActive: (editor) => editor.isActive('underline'),\n isDisabled: (editor) => !editor.can().toggleUnderline(),\n });\n\n // 删除线\n items.set('strike', {\n id: 'strike',\n icon: icons.strikethrough,\n command: (editor) => editor.chain().toggleStrike().run(),\n isActive: (editor) => editor.isActive('strike'),\n isDisabled: (editor) => !editor.can().toggleStrike(),\n });\n\n // 行内代码\n items.set('code', {\n id: 'code',\n icon: icons.code,\n command: (editor) => editor.chain().toggleCode().run(),\n isActive: (editor) => editor.isActive('code'),\n isDisabled: (editor) => !editor.can().toggleCode(),\n });\n\n // 无序列表\n items.set('bulletList', {\n id: 'bulletList',\n icon: icons.list,\n command: (editor) => editor.chain().toggleBulletList().run(),\n isActive: (editor) => editor.isActive('bulletList'),\n isDisabled: (editor) => !editor.can().toggleBulletList(),\n });\n\n // 有序列表\n items.set('orderedList', {\n id: 'orderedList',\n icon: icons.listOrdered,\n command: (editor) => editor.chain().toggleOrderedList().run(),\n isActive: (editor) => editor.isActive('orderedList'),\n isDisabled: (editor) => !editor.can().toggleOrderedList(),\n });\n\n // 任务列表\n items.set('taskList', {\n id: 'taskList',\n icon: icons.listChecks,\n command: (editor) => editor.chain().toggleTaskList().run(),\n isActive: (editor) => editor.isActive('taskList'),\n isDisabled: (editor) => !editor.can().toggleTaskList(),\n });\n\n // 引用\n items.set('blockquote', {\n id: 'blockquote',\n icon: icons.quote,\n command: (editor) => editor.chain().toggleBlockquote().run(),\n isActive: (editor) => editor.isActive('blockquote'),\n isDisabled: (editor) => !editor.can().toggleBlockquote(),\n });\n\n // 代码块。节点不在就不注册这一项:codeBlock:false 且接入方没给替代实现时,\n // toggleCodeBlock 这个命令根本不存在,而 isDisabled 会在每次状态刷新里调它——\n // 不注册的话编辑器直接抛(实测 editor.can(...).toggleCodeBlock is not a function,\n // 每次事务都抛一遍)。与表格、媒体同一套做法:能力不在,项就不在\n if (this.editor.schema.nodes.codeBlock) {\n items.set('codeBlock', {\n id: 'codeBlock',\n icon: icons.codeBlock,\n command: (editor) => editor.chain().toggleCodeBlock().run(),\n isActive: (editor) => editor.isActive('codeBlock'),\n isDisabled: (editor) => !editor.can().toggleCodeBlock(),\n });\n }\n\n // 分割线:插入型操作,没有\"激活\"态可言\n items.set('horizontalRule', {\n id: 'horizontalRule',\n icon: icons.horizontalRule,\n command: (editor) => editor.chain().setHorizontalRule().run(),\n isActive: () => false,\n isDisabled: (editor) => !editor.can().setHorizontalRule(),\n });\n\n // 左对齐\n items.set('alignLeft', {\n id: 'alignLeft',\n icon: icons.alignLeft,\n command: (editor) => editor.chain().setTextAlign('left').run(),\n isActive: (editor) => editor.isActive({ textAlign: 'left' }),\n isDisabled: (editor) => !editor.can().setTextAlign('left'),\n });\n\n // 居中\n items.set('alignCenter', {\n id: 'alignCenter',\n icon: icons.alignCenter,\n command: (editor) => editor.chain().setTextAlign('center').run(),\n isActive: (editor) => editor.isActive({ textAlign: 'center' }),\n isDisabled: (editor) => !editor.can().setTextAlign('center'),\n });\n\n // 右对齐\n items.set('alignRight', {\n id: 'alignRight',\n icon: icons.alignRight,\n command: (editor) => editor.chain().setTextAlign('right').run(),\n isActive: (editor) => editor.isActive({ textAlign: 'right' }),\n isDisabled: (editor) => !editor.can().setTextAlign('right'),\n });\n\n // 两端对齐:命令与词条早就有,缺的只是这个按钮\n items.set('alignJustify', {\n id: 'alignJustify',\n icon: icons.alignJustify,\n command: (editor) => editor.chain().setTextAlign('justify').run(),\n isActive: (editor) => editor.isActive({ textAlign: 'justify' }),\n isDisabled: (editor) => !editor.can().setTextAlign('justify'),\n });\n\n // 两个内置下拉。之所以收成菜单而不是平铺:标题本来就有六级、对齐四种,\n // 平铺在顶栏上要占十个格子,而用户一次只关心\"现在改成哪一级\"。\n // children 走同一张 itemDefs,所以能力门控自动生效(段落在 schema 里没有时不会出现在菜单)\n items.set(\n 'heading',\n this.menuItem('heading', icons.heading, [\n 'heading1',\n 'heading2',\n 'heading3',\n 'heading4',\n 'heading5',\n 'heading6',\n 'paragraph',\n ])\n );\n\n items.set(\n 'align',\n this.menuItem('align', icons.align, [\n 'alignLeft',\n 'alignCenter',\n 'alignRight',\n 'alignJustify',\n ])\n );\n\n return items;\n }\n\n /**\n * 绑定编辑器事件,更新按钮状态\n */\n private bindEditorEvents(): void {\n const refresh = () => this.updateButtonStates();\n\n this.editor.on('transaction', refresh);\n this.editor.on('selectionUpdate', refresh);\n\n // 工具栏是公开导出的类:宿主单独用它、只销毁工具栏而留着编辑器时,\n // 这两条订阅会常驻到编辑器结束,每次事务空跑一遍按钮刷新\n this.unsubscribeEditor = () => {\n this.editor.off('transaction', refresh);\n this.editor.off('selectionUpdate', refresh);\n };\n\n // 初始状态\n this.updateButtonStates();\n }\n\n /**\n * 更新所有按钮状态\n *\n * 公开是因为浮层开合不改文档,等不到 transaction:挂在门面上的浮层(链接)\n * 由门面在开合时叫一次,挂在这里的浮层则在 onOpenChange 里叫\n */\n updateButtonStates(): void {\n this.buttons.forEach((buttons, itemId) => {\n const itemDef = this.itemDefs.get(itemId);\n if (!itemDef) return;\n\n // 更新 active 状态\n const active = itemDef.isActive(this.editor);\n const disabled = itemDef.isDisabled(this.editor);\n\n buttons.forEach((button) => {\n button.classList.toggle('active', active);\n if (itemDef.popup) button.setAttribute('aria-expanded', String(active));\n\n // 更新 disabled 状态\n button.disabled = disabled;\n\n if (itemDef.popup === 'menu') this.refreshMenuTrigger(itemDef, button);\n });\n });\n }\n\n /**\n * 一项当前该显示的文字。下拉触发器跟着命中的子项走(现在是三级标题就写「标题 3」),\n * 宿主显式写的 tooltip 优先级最高。\n * 抽出来是因为图标与提示必须由同一份判定得出:只换其中一个就会出现\n * \"按钮画着 H3、悬停写标题\"这种一半新一半旧的界面\n */\n private itemTitle(itemId: string, def: ToolbarItemDef): string {\n const custom = this.customTooltips.get(itemId);\n if (custom) return custom;\n\n let key = TOOLTIP_KEYS[itemId];\n if (def.popup === 'menu') {\n const activeKey = TOOLTIP_KEYS[this.activeChild(def)?.id ?? ''];\n if (activeKey) key = activeKey;\n }\n\n const fallback = key ? zhText(key) : (def.tooltip ?? '');\n return key ? this.tOr(key, fallback) : fallback;\n }\n\n /**\n * 下拉触发器的图标与提示跟着当前值走:现在是二级标题就显示 H2,\n * 一个都不匹配才退回通用图标。只有菜单项会走到这里,不是每次刷新对所有按钮都做一遍\n */\n private refreshMenuTrigger(def: ToolbarItemDef, button: HTMLButtonElement): void {\n const base = button.dataset.menuIcon ?? def.icon;\n const next = `${this.activeChild(def)?.icon ?? base}${icons.chevronDown}`;\n if (button.innerHTML !== next) button.innerHTML = next;\n\n const title = this.itemTitle(def.id, def);\n if (button.title !== title) button.title = title;\n }\n\n /**\n * 销毁工具栏\n */\n destroy(): void {\n this.unsubscribeLanguage?.();\n this.unsubscribeLanguage = undefined;\n this.unsubscribeEditor?.();\n this.unsubscribeEditor = undefined;\n // 面板挂在 document.body 上,不跟着工具栏一起收就会留在页面上\n this.panel?.destroy();\n this.panel = null;\n this.tablePanel?.destroy();\n this.tablePanel = null;\n this.stylePanel?.destroy();\n this.stylePanel = null;\n this.menuPanel?.destroy();\n this.menuPanel = null;\n this.buttons.clear();\n this.customTooltips.clear();\n // 浮层元素归门面所有,要活过重建编辑器,所以这里既不 remove 也不清空它\n this.createdElements.forEach((el) => el.remove());\n this.createdElements = [];\n }\n}\n","/**\n * LinkPanel - 链接插入 / 编辑浮层\n *\n * 与 InsertPanel 同为挂在 document.body 上的浮层,定位 / 关闭 / 主题三段走 floating-panel 公共底盘。\n *\n * 链接此前只有 schema 与命令、没有任何入口:工具栏没有按钮,插进去也改不了,\n * 而编辑态点击又会按链接扩展的默认行为直接导航,等于把当前页面连同未保存的内容一起交出去。\n * 这个浮层补上入口,同时把\"点链接\"改成\"就地编辑\"。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n followThemeClasses,\n listenPanelDismiss,\n positionPanel,\n type FloatingPanel,\n} from './floating-panel';\nimport { tOrFallback, type I18nManager } from './I18nManager';\nimport type { AtriLinkTarget } from '../types';\n\nexport interface LinkPanelOptions {\n editor: Editor;\n /**\n * 编辑器根容器(.atri-editor)\n *\n * 点击监听与主题类都挂在它身上,不碰 editor.view.dom:Tiptap v3 的视图是迟到且会被销毁的,\n * 构造时未必拿得到、销毁时已经没了,而这个容器由门面创建,生命周期与编辑器实例一致\n */\n root: HTMLElement;\n i18n?: I18nManager;\n /** 与交给链接扩展的 openOnClick 同源:扩展负责导航时浮层不抢点击 */\n openOnClick?: boolean;\n /** 新链接的打开方式,默认 '_blank' */\n target?: AtriLinkTarget;\n /** 开合时把控制权交回工具栏:按钮的 active 与 aria-expanded 由它维护 */\n onOpenChange?: (open: boolean) => void;\n}\n\ninterface LabelledElement {\n el: HTMLElement;\n key: string;\n fallback: string;\n}\n\n/**\n * 补全协议:用户多半只贴 example.com,补成 https:// 浏览器与 Markdown 两边才都认。\n * 已有协议的(https:、mailto:…)与站内绝对路径 / 锚点 / 相对路径原样保留\n */\nexport function normalizeHref(value: string): string {\n const href = value.trim();\n if (!href) return '';\n if (/^[a-z][a-z0-9+.-]*:/i.test(href) || /^[/#.]/.test(href)) return href;\n return `https://${href}`;\n}\n\nexport class LinkPanel {\n private editor: Editor;\n private root: HTMLElement;\n private i18n?: I18nManager;\n private openOnClick: boolean;\n private defaultTarget: AtriLinkTarget;\n private onOpenChange?: (open: boolean) => void;\n private unsubscribeLanguage?: () => void;\n\n private element: HTMLDivElement | null = null;\n private floating: FloatingPanel | null = null;\n private detachDismiss: (() => void) | null = null;\n private detachTheme: (() => void) | null = null;\n private labelled: LabelledElement[] = [];\n private urlInput: HTMLInputElement | null = null;\n private targetInput: HTMLInputElement | null = null;\n /** 打开时选区内是否已有链接:决定\"确定\"是改这一条还是新插一条 */\n private editing = false;\n\n constructor(options: LinkPanelOptions) {\n this.editor = options.editor;\n this.root = options.root;\n this.i18n = options.i18n;\n this.openOnClick = options.openOnClick ?? false;\n this.defaultTarget = options.target ?? '_blank';\n this.onOpenChange = options.onOpenChange;\n\n // 面板是即时创建的,但可能开着的时候宿主切了语言\n this.unsubscribeLanguage = this.i18n?.onLanguageChanged(() => this.applyLabels());\n this.root.addEventListener('click', this.onEditorClick);\n }\n\n get isOpen(): boolean {\n return this.element !== null;\n }\n\n toggle(anchor: HTMLElement): void {\n if (this.isOpen) {\n this.close();\n return;\n }\n this.open(anchor);\n }\n\n open(anchor: HTMLElement): void {\n this.close();\n this.editing = this.editor.isActive('link');\n this.build();\n const panel = this.element;\n if (!panel) return;\n\n this.floating = positionPanel(anchor, panel, () => this.element === panel);\n this.detachDismiss = listenPanelDismiss(anchor, panel, () => this.close());\n this.onOpenChange?.(true);\n }\n\n close(): void {\n const wasOpen = this.element !== null;\n\n this.detachDismiss?.();\n this.detachDismiss = null;\n this.detachTheme?.();\n this.detachTheme = null;\n this.floating?.stop();\n this.floating = null;\n this.element?.remove();\n this.element = null;\n this.urlInput = null;\n this.targetInput = null;\n this.labelled = [];\n this.editing = false;\n\n if (wasOpen) this.onOpenChange?.(false);\n }\n\n destroy(): void {\n this.root.removeEventListener('click', this.onEditorClick);\n this.close();\n this.unsubscribeLanguage?.();\n this.unsubscribeLanguage = undefined;\n }\n\n /**\n * 编辑态点击链接就地编辑(与 wangEditor / AIEditor 一致)\n *\n * 拖选收尾也会派发 click,所以只在选区收拢时接管;openOnClick 开着说明点击归扩展导航\n */\n private onEditorClick = (event: MouseEvent): void => {\n if (this.openOnClick || !this.editor.isEditable) return;\n // 选区不空就是拖选(或双击选词)的收尾,不该弹编辑框\n if (!this.editor.state.selection.empty) return;\n\n const target = event.target;\n if (!(target instanceof Element)) return;\n const anchor = target.closest('a');\n if (!anchor || !this.root.contains(anchor)) return;\n\n // 接管这一下点击,同时挡掉 <a> 的默认导航:真实浏览器里 contenteditable 本来就不导航,\n // 但缺这层语义的环境(jsdom、扩展注入的 DOM)会真的把页面带走\n event.preventDefault();\n this.open(anchor);\n };\n\n private t(key: string, fallback: string): string {\n return tOrFallback(this.i18n, key, fallback);\n }\n\n /** 登记一处随语言重画的文案:input 画到 placeholder,其他画成文字 */\n private label(el: HTMLElement, key: string, fallback: string): void {\n this.labelled.push({ el, key, fallback });\n this.applyLabel({ el, key, fallback });\n }\n\n private applyLabels(): void {\n this.labelled.forEach((entry) => this.applyLabel(entry));\n }\n\n private applyLabel({ el, key, fallback }: LabelledElement): void {\n const text = this.t(key, fallback);\n if (el instanceof HTMLInputElement) el.placeholder = text;\n else el.textContent = text;\n }\n\n private build(): void {\n const panel = document.createElement('div');\n panel.className = 'atri-link-panel';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-label', this.t('link.panelLabel', '链接'));\n\n const url = document.createElement('input');\n url.type = 'url';\n url.className = 'atri-link-panel-url';\n this.label(url, 'link.urlPlaceholder', '链接地址(https://…)');\n // 已有链接带出原地址:编辑的是这一条,不该让用户重抄一遍\n url.value = this.editing ? String(this.editor.getAttributes('link').href ?? '') : '';\n url.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n this.apply();\n }\n });\n panel.appendChild(url);\n this.urlInput = url;\n\n const targetLabel = document.createElement('label');\n targetLabel.className = 'atri-link-panel-target';\n const target = document.createElement('input');\n target.type = 'checkbox';\n target.checked = this.editing\n ? this.editor.getAttributes('link').target !== '_self'\n : this.defaultTarget === '_blank';\n const targetText = document.createElement('span');\n this.label(targetText, 'link.newWindow', '在新窗口打开');\n targetLabel.append(target, targetText);\n panel.appendChild(targetLabel);\n this.targetInput = target;\n\n const actions = document.createElement('div');\n actions.className = 'atri-link-panel-actions';\n\n // 移除只在编辑既有链接时出现:没链接可移除的时候摆个灰按钮没有意义\n if (this.editing) {\n const remove = document.createElement('button');\n remove.type = 'button';\n remove.className = 'atri-link-panel-remove';\n this.label(remove, 'link.remove', '移除链接');\n remove.addEventListener('click', () => this.remove());\n actions.appendChild(remove);\n }\n\n const apply = document.createElement('button');\n apply.type = 'button';\n apply.className = 'atri-link-panel-apply';\n this.label(apply, 'link.apply', '确定');\n apply.addEventListener('click', () => this.apply());\n actions.appendChild(apply);\n\n panel.appendChild(actions);\n document.body.appendChild(panel);\n this.element = panel;\n this.detachTheme = followThemeClasses(this.root, panel);\n\n url.focus();\n url.select();\n }\n\n /** 有链接就整条改(extendMarkRange 把范围摊到整段链接),没链接就看选区:套住它,或者把地址本身当链接文字插进去 */\n private apply(): void {\n const href = normalizeHref(this.urlInput?.value ?? '');\n if (!href) return;\n\n const target: AtriLinkTarget = this.targetInput?.checked ? '_blank' : '_self';\n const { editor } = this;\n const chain = editor.chain().focus();\n\n if (this.editing) {\n chain.extendMarkRange('link').setLink({ href, target }).run();\n } else if (editor.state.selection.empty) {\n chain\n .insertContent({\n type: 'text',\n text: href,\n marks: [{ type: 'link', attrs: { href, target } }],\n })\n .run();\n } else {\n chain.setLink({ href, target }).run();\n }\n\n this.close();\n }\n\n private remove(): void {\n this.editor.chain().focus().extendMarkRange('link').unsetLink().run();\n this.close();\n }\n}\n","/**\n * TableMenu - 光标落在表格里时浮出的上下文操作菜单\n *\n * 与工具栏浮层(InsertPanel / TableGridPanel)同一套外观,但触发方式不同:它没有锚点按钮,\n * 靠选区进入表格自动浮出,所以定位锚是「当前单元格的 DOM 盒子」,量不到再退回光标坐标\n * (AICommandMenu 的虚拟锚点写法)。\n *\n * 行列增删合并这些命令由 prosemirror-tables 提供,此前只有命令没有入口,\n * 表格因此是\"插得出来、改不动\"的半成品。\n */\nimport type { Editor } from '@tiptap/core';\nimport type { EditorState } from '@tiptap/pm/state';\nimport { computePosition } from '@floating-ui/dom';\nimport { followThemeClasses, panelMiddleware, placePanel } from './floating-panel';\nimport { tOrFallback, zhText, type I18nManager } from './I18nManager';\nimport { icons } from './icons';\n\nexport interface TableMenuOptions {\n editor: Editor;\n /**\n * 编辑器根容器(.atri-editor):只用来复制主题类\n *\n * 不取 editor.view.dom:那个视图是迟到且会被销毁的,而这个容器由门面创建,生命周期一致\n */\n root: HTMLElement;\n i18n?: I18nManager;\n}\n\n/** 单元格对齐取值:与单元格 align 属性的取值域一致 */\ntype CellAlignment = 'left' | 'center' | 'right';\n\ninterface TableMenuItemDef {\n id: string;\n icon: string;\n labelKey: string;\n /** 组序号,组与组之间画一条竖线 */\n group: number;\n command: (editor: Editor) => void;\n isDisabled?: (editor: Editor) => boolean;\n isActive?: (editor: Editor) => boolean;\n}\n\n/** 选区所在的单元格(或表头)节点位置;不在表格里返回 null */\nfunction cellPos(state: EditorState): number | null {\n const { $from } = state.selection;\n for (let depth = $from.depth; depth > 0; depth--) {\n const name = $from.node(depth).type.name;\n if (name === 'tableCell' || name === 'tableHeader') return $from.before(depth);\n }\n return null;\n}\n\nfunction cellAlignmentActive(align: CellAlignment) {\n return (editor: Editor): boolean =>\n editor.isActive('tableCell', { align }) || editor.isActive('tableHeader', { align });\n}\n\nfunction buildItems(): TableMenuItemDef[] {\n const items: TableMenuItemDef[] = [\n {\n id: 'addRowBefore',\n icon: icons.tableRowBefore,\n labelKey: 'table.rowBefore',\n group: 0,\n command: (editor) => editor.chain().focus().addRowBefore().run(),\n },\n {\n id: 'addRowAfter',\n icon: icons.tableRowAfter,\n labelKey: 'table.rowAfter',\n group: 0,\n command: (editor) => editor.chain().focus().addRowAfter().run(),\n },\n {\n id: 'deleteRow',\n icon: icons.tableRowDelete,\n labelKey: 'table.deleteRow',\n group: 0,\n command: (editor) => editor.chain().focus().deleteRow().run(),\n },\n {\n id: 'addColumnBefore',\n icon: icons.tableColumnBefore,\n labelKey: 'table.columnBefore',\n group: 1,\n command: (editor) => editor.chain().focus().addColumnBefore().run(),\n },\n {\n id: 'addColumnAfter',\n icon: icons.tableColumnAfter,\n labelKey: 'table.columnAfter',\n group: 1,\n command: (editor) => editor.chain().focus().addColumnAfter().run(),\n },\n {\n id: 'deleteColumn',\n icon: icons.tableColumnDelete,\n labelKey: 'table.deleteColumn',\n group: 1,\n command: (editor) => editor.chain().focus().deleteColumn().run(),\n },\n {\n // 合并要有两个以上单元格被框住,单选一个时按不动——禁用而不是隐藏,位置别跳\n id: 'mergeCells',\n icon: icons.tableMergeCells,\n labelKey: 'table.mergeCells',\n group: 2,\n command: (editor) => editor.chain().focus().mergeCells().run(),\n isDisabled: (editor) => !editor.can().mergeCells(),\n },\n {\n id: 'splitCell',\n icon: icons.tableSplitCell,\n labelKey: 'table.splitCell',\n group: 2,\n command: (editor) => editor.chain().focus().splitCell().run(),\n isDisabled: (editor) => !editor.can().splitCell(),\n },\n {\n id: 'toggleHeaderRow',\n icon: icons.tableHeaderRow,\n labelKey: 'table.toggleHeaderRow',\n group: 2,\n command: (editor) => editor.chain().focus().toggleHeaderRow().run(),\n isActive: (editor) => editor.isActive('tableHeader'),\n },\n ];\n\n // 对齐写在单元格自带的 align 属性上:Markdown 的 pipe table 分隔行(:---)也读它,\n // 于是这一下对齐在导出 Markdown 后仍然成立\n const alignments: [CellAlignment, string][] = [\n ['left', 'table.alignLeft'],\n ['center', 'table.alignCenter'],\n ['right', 'table.alignRight'],\n ];\n alignments.forEach(([align, labelKey]) => {\n const suffix = `${align[0].toUpperCase()}${align.slice(1)}`;\n items.push({\n id: `cellAlign${suffix}`,\n icon: icons[`align${suffix}`],\n labelKey,\n group: 3,\n command: (editor) => editor.chain().focus().setCellAttribute('align', align).run(),\n isActive: cellAlignmentActive(align),\n });\n });\n\n items.push({\n id: 'deleteTable',\n icon: icons.trash,\n labelKey: 'table.deleteTable',\n group: 4,\n command: (editor) => editor.chain().focus().deleteTable().run(),\n });\n\n return items;\n}\n\nexport class TableMenu {\n private editor: Editor;\n private root: HTMLElement;\n private i18n?: I18nManager;\n private items: TableMenuItemDef[];\n private unsubscribeLanguage?: () => void;\n\n private element: HTMLDivElement | null = null;\n private detachEscape: (() => void) | undefined;\n private detachTheme: (() => void) | undefined;\n /** Escape 收起后不再自动浮出,直到光标离开表格(否则下一次状态更新立刻又把它拉出来) */\n private dismissed = false;\n\n constructor(options: TableMenuOptions) {\n this.editor = options.editor;\n this.root = options.root;\n this.i18n = options.i18n;\n this.items = buildItems();\n\n this.editor.on('update', this.sync);\n this.editor.on('selectionUpdate', this.sync);\n this.editor.on('focus', this.sync);\n this.editor.on('blur', this.onBlur);\n // 浮层是 fixed 定位,滚动不会带着它走:滚的是哪一层不确定,捕获阶段挂在 document 上\n document.addEventListener('scroll', this.onScroll, true);\n this.unsubscribeLanguage = this.i18n?.onLanguageChanged(() => this.applyLabels());\n\n this.sync();\n }\n\n get isOpen(): boolean {\n return this.element !== null;\n }\n\n destroy(): void {\n this.editor.off('update', this.sync);\n this.editor.off('selectionUpdate', this.sync);\n this.editor.off('focus', this.sync);\n this.editor.off('blur', this.onBlur);\n document.removeEventListener('scroll', this.onScroll, true);\n this.unsubscribeLanguage?.();\n this.unsubscribeLanguage = undefined;\n this.hide();\n }\n\n private t(key: string): string {\n return tOrFallback(this.i18n, key, zhText(key));\n }\n\n /**\n * 按当前选区决定浮出还是收起。光标在表格里是唯一条件——\n * 空选区、单元格内选一段字、框选多个单元格都算\n *\n * 听的是 update 而不是 transaction:setEditable 只发 update(造一个空事务),\n * 切只读时若不跟着重判,菜单会留在只读的表格上\n */\n private sync = (): void => {\n const inTable = cellPos(this.editor.state) !== null;\n if (!inTable) {\n // 离开表格,Escape 那一次收起到此为止\n this.dismissed = false;\n this.hide();\n return;\n }\n\n if (this.dismissed || !this.editor.isEditable) {\n this.hide();\n return;\n }\n\n this.show();\n };\n\n /** 点到编辑器外面就收起来:菜单没有\"关了还要在\"的理由,焦点回来时 sync 会重新浮出 */\n private onBlur = (): void => {\n this.hide();\n };\n\n private onScroll = (): void => {\n if (this.element) void this.position();\n };\n\n private dismiss = (): void => {\n this.dismissed = true;\n this.hide();\n };\n\n private show(): void {\n if (!this.element) this.build();\n this.updateStates();\n void this.position();\n }\n\n private hide(): void {\n this.detachEscape?.();\n this.detachEscape = undefined;\n this.detachTheme?.();\n this.detachTheme = undefined;\n if (!this.element) return;\n this.element.remove();\n this.element = null;\n }\n\n /**\n * Escape 收起菜单。这里不用 floating-panel 的 listenPanelDismiss:那一套还管外点关闭,\n * 而点外面多半仍落在同一个单元格里,关了立刻被下一次状态更新重新浮出,只会闪\n */\n private listenEscape(): () => void {\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n event.preventDefault();\n event.stopPropagation();\n this.dismiss();\n };\n document.addEventListener('keydown', onKeyDown, true);\n return () => document.removeEventListener('keydown', onKeyDown, true);\n }\n\n private build(): void {\n const panel = document.createElement('div');\n panel.className = 'atri-table-menu';\n panel.setAttribute('role', 'toolbar');\n panel.setAttribute('aria-label', this.t('table.menuLabel'));\n\n // 按钮不抢焦点:抢走会让编辑器 blur,而 blur 正是收起菜单的条件,\n // 于是 mousedown 一按菜单就没了、click 根本派发不到按钮上\n panel.addEventListener('mousedown', (event) => event.preventDefault());\n\n let lastGroup: number | null = null;\n this.items.forEach((item) => {\n if (lastGroup !== null && item.group !== lastGroup) {\n const separator = document.createElement('div');\n separator.className = 'atri-table-menu-separator';\n panel.appendChild(separator);\n }\n lastGroup = item.group;\n\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'atri-editor-toolbar-btn';\n button.setAttribute('data-table-item', item.id);\n button.title = this.t(item.labelKey);\n button.innerHTML = item.icon;\n button.addEventListener('click', (event) => {\n event.preventDefault();\n event.stopPropagation();\n item.command(this.editor);\n });\n panel.appendChild(button);\n });\n\n document.body.appendChild(panel);\n this.element = panel;\n this.detachTheme = followThemeClasses(this.root, panel);\n this.detachEscape = this.listenEscape();\n }\n\n private applyLabels(): void {\n const panel = this.element;\n if (!panel) return;\n panel.setAttribute('aria-label', this.t('table.menuLabel'));\n this.items.forEach((item) => {\n const button = panel.querySelector<HTMLButtonElement>(`[data-table-item=\"${item.id}\"]`);\n if (button) button.title = this.t(item.labelKey);\n });\n }\n\n private updateStates(): void {\n const panel = this.element;\n if (!panel) return;\n\n this.items.forEach((item) => {\n const button = panel.querySelector<HTMLButtonElement>(`[data-table-item=\"${item.id}\"]`);\n if (!button) return;\n button.disabled = item.isDisabled?.(this.editor) ?? false;\n button.classList.toggle('active', item.isActive?.(this.editor) ?? false);\n });\n }\n\n /**\n * 当前单元格的虚拟锚点;量不到返回 null,调用方保持面板可见但不改坐标\n *\n * 优先锚在单元格盒子上(菜单跟着格子走,比跟着光标稳),拿不到盒子才退回光标坐标\n */\n private anchor(): { getBoundingClientRect(): DOMRect } | null {\n const { view, state } = this.editor;\n const pos = cellPos(state);\n\n if (pos !== null) {\n try {\n const dom = view.nodeDOM(pos);\n if (dom instanceof HTMLElement) {\n const rect = dom.getBoundingClientRect();\n // jsdom 里所有盒子都是 0×0,靠这个判据自觉退回光标坐标\n if (rect.width || rect.height) return { getBoundingClientRect: () => rect };\n }\n } catch {\n // 量不到就往下走,别让定位问题变成\"菜单不出现\"\n }\n }\n\n try {\n const coords = view.coordsAtPos(state.selection.from);\n return {\n getBoundingClientRect: () =>\n new DOMRect(coords.left, coords.top, 0, coords.bottom - coords.top),\n };\n } catch {\n return null;\n }\n }\n\n private async position(): Promise<void> {\n const panel = this.element;\n if (!panel) return;\n\n const reference = this.anchor();\n if (!reference) return;\n\n let x: number;\n let y: number;\n try {\n ({ x, y } = await computePosition(reference, panel, {\n // 贴在单元格上沿:菜单跟着当前格走,长表格里也不会跑到视口外\n placement: 'top-start',\n middleware: panelMiddleware(),\n }));\n } catch {\n // 定位失败也要让菜单可见:宁可位置不理想,也不能点了没反应\n return;\n }\n\n // 异步算位置的间隙里菜单可能已经收起或被重建\n if (this.element !== panel) return;\n placePanel(panel, x, y);\n }\n}\n","/**\n * MediaStatusStrip - 上传队列状态条\n *\n * 卡片与图片上没有一处文字状态(那都由样式表靠 data 属性表达),\n * \"上传中 2 个\"\"1 个失败\"\"重试\"这些要读得懂的话都集中在这里,本地化只发生在这一处。\n */\nimport type { I18nManager } from '../core/I18nManager';\nimport type { MediaRuntime, MediaState } from './MediaRuntime';\n\ninterface Labels {\n uploading: (files: number, percent: number) => string;\n failed: (files: number) => string;\n inline: (files: number) => string;\n retry: () => string;\n}\n\nexport class MediaStatusStrip {\n private labels: Labels;\n private element: HTMLDivElement;\n private text: HTMLSpanElement;\n private retry: HTMLButtonElement;\n private unsubscribe?: () => void;\n private unsubscribeLanguage?: () => void;\n\n constructor(runtime: MediaRuntime, container: HTMLElement, i18n?: I18nManager) {\n // t() 在词条缺失时会原样返回 key,此时回退到内置文案\n const t = (key: string, fallback: string, options?: Record<string, unknown>) =>\n i18n?.tOr(key, fallback, options) ?? fallback;\n\n this.labels = {\n uploading: (files, percent) =>\n t('media.uploading', '上传中 {{files}} 个 · {{percent}}%', { files, percent }),\n failed: (files) => t('media.uploadFailed', '{{files}} 个文件上传失败', { files }),\n inline: (files) => t('media.inlined', '{{files}} 张图片未上传(已随文档保存)', { files }),\n retry: () => t('media.retry', '重试'),\n };\n\n this.element = document.createElement('div');\n this.element.className = 'atri-media-status';\n // role=status 已隐含 aria-live=polite,不必再写一遍\n this.element.setAttribute('role', 'status');\n this.element.hidden = true;\n\n this.text = document.createElement('span');\n this.text.className = 'atri-media-status-text';\n\n this.retry = document.createElement('button');\n this.retry.type = 'button';\n this.retry.className = 'atri-media-status-retry';\n this.retry.hidden = true;\n // 重试失败要落地成一条日志:这条 promise 此前被 void 丢在半路,\n // 一直失败时表现为\"点了没反应\"外加一片 unhandled rejection\n this.retry.addEventListener('click', () => {\n runtime.retryFailed().catch((error: unknown) => {\n console.error('[Atri Editor] Retrying the failed uploads did not work:', error);\n });\n });\n\n this.element.append(this.text, this.retry);\n container.appendChild(this.element);\n\n this.unsubscribe = runtime.subscribe((state) => this.render(state));\n // 上传能持续很久,中途切语言时这条读数不能还停在旧语言\n this.unsubscribeLanguage = i18n?.onLanguageChanged(() => this.render(runtime.getState()));\n }\n\n destroy(): void {\n this.unsubscribe?.();\n this.unsubscribeLanguage?.();\n this.element.remove();\n }\n\n private render(state: MediaState): void {\n const { failed, inline } = state;\n const parts: string[] = [];\n if (state.uploading) parts.push(this.labels.uploading(state.uploading, state.percent));\n if (failed) parts.push(this.labels.failed(failed));\n if (inline) parts.push(this.labels.inline(inline));\n // 同一个文件只落进 failed 或 inline 一档,两句同时出现是队列里有不同的文件\n\n const needsAttention = failed > 0 || inline > 0;\n // 失败与已内联共用红色这一档,靠 :has() 找子元素太脆\n this.element.setAttribute('data-atri-media-status', needsAttention ? 'error' : 'uploading');\n this.element.hidden = !parts.length;\n // 每段话各占一个 span,分隔交给样式表:拼接用的标点没有中立写法\n this.text.replaceChildren(\n ...parts.map((part) => {\n const span = document.createElement('span');\n span.textContent = part;\n return span;\n })\n );\n // 已内联的图仍可重试:换到服务端地址之前,这条读数不该只留着当提示\n this.retry.hidden = !needsAttention;\n this.retry.textContent = this.labels.retry();\n }\n}\n","/**\n * AIInterceptor - AI 拦截器链执行\n */\nimport type { AIRequestContext, AIResponse, AtriAIInterceptors } from '../types';\nimport type { MarkdownService } from '../core/MarkdownService';\n\n/**\n * 端点由宿主实现,返回值没有任何编译期保证:\n * 不校验的话 4xx 的 HTML 响应体一路走到 insertCallback,最后什么也不发生\n *\n * 只判形状,不判空:空 content 是合法结果(\"看过之后无需改写\"),\n * 拦它等于把宿主的一个正常业务分支变成报错\n */\nfunction requireResponse(response: unknown, stage: string): AIResponse {\n if (!response || typeof response !== 'object') {\n throw new TypeError(`AI ${stage} must resolve to an AIResponse object.`);\n }\n\n if (typeof (response as AIResponse).content !== 'string') {\n throw new TypeError(`AI ${stage} resolved with a non-string content.`);\n }\n\n return response as AIResponse;\n}\n\nexport class AIInterceptor {\n private interceptors: AtriAIInterceptors;\n private markdownService: MarkdownService | null;\n private autoTranslateMarkdown: boolean;\n\n constructor(\n interceptors: AtriAIInterceptors = {},\n markdownService: MarkdownService | null = null,\n autoTranslateMarkdown: boolean = true\n ) {\n this.interceptors = interceptors;\n this.markdownService = markdownService;\n this.autoTranslateMarkdown = autoTranslateMarkdown;\n }\n\n /**\n * 执行 beforePost 拦截器\n * @returns 处理后的 context,返回 false 表示取消请求\n */\n async runBeforePost(context: AIRequestContext): Promise<AIRequestContext | false> {\n if (!this.interceptors.beforePost) {\n return context;\n }\n const result = this.interceptors.beforePost(context);\n return result;\n }\n\n /**\n * 执行 afterPost 拦截器\n */\n async runAfterPost(response: AIResponse, context: AIRequestContext): Promise<AIResponse> {\n if (!this.interceptors.afterPost) {\n return response;\n }\n return this.interceptors.afterPost(response, context);\n }\n\n /**\n * 执行 beforeRender 拦截器\n */\n async runBeforeRender(content: string, context: AIRequestContext): Promise<string> {\n if (!this.interceptors.beforeRender) {\n return content;\n }\n return this.interceptors.beforeRender(content, context);\n }\n\n /**\n * 执行 autoTranslateMarkdownToHTML\n * 在 beforeRender 之后执行,自动将 Markdown 转为 HTML\n */\n async runAutoTranslateMarkdown(content: string, response: AIResponse): Promise<string> {\n if (!this.autoTranslateMarkdown) {\n return content;\n }\n\n // 仅当 contentType 为 markdown 时自动转换\n if (response.contentType !== 'markdown') {\n return content;\n }\n\n if (this.markdownService) {\n return this.markdownService.markdownToHTML(content);\n }\n\n // 降级:使用简单转换\n const { simpleMarkdownToHtml } = await import('../utils/markdown');\n return simpleMarkdownToHtml(content);\n }\n\n /**\n * 执行 afterRender 回调\n */\n async runAfterRender(content: string, context: AIRequestContext): Promise<void> {\n if (!this.interceptors.afterRender) {\n return;\n }\n this.interceptors.afterRender(content, context);\n }\n\n /**\n * 完整执行拦截器链\n * 流程: beforePost -> requestEndpoint -> afterPost -> beforeRender -> autoTranslateMarkdown -> insert -> afterRender\n */\n async executeChain(\n context: AIRequestContext,\n requestEndpoint: (ctx: AIRequestContext) => Promise<AIResponse>,\n insertCallback: (content: string) => void\n ): Promise<void> {\n // 1. beforePost\n const processedContext = await this.runBeforePost(context);\n if (processedContext === false) {\n return; // 取消请求\n }\n\n // 2. requestEndpoint\n let response = requireResponse(\n await requestEndpoint(processedContext),\n `requestEndpoint(\"${processedContext.functionId}\")`\n );\n\n // 3. afterPost\n response = requireResponse(await this.runAfterPost(response, processedContext), 'afterPost');\n\n // 4. beforeRender\n let content = await this.runBeforeRender(response.content, processedContext);\n\n // 5. autoTranslateMarkdownToHTML\n content = await this.runAutoTranslateMarkdown(content, response);\n\n // 6. 插入编辑器\n insertCallback(content);\n\n // 7. afterRender\n await this.runAfterRender(content, processedContext);\n }\n}\n","/**\n * 选区工具函数\n */\nimport type { Editor } from '@tiptap/core';\nimport { getTextBetween } from '@tiptap/core';\n\n/**\n * 获取选中文本\n */\nexport function getSelectedText(editor: Editor): string {\n const { selection } = editor.state;\n if (selection.empty) return '';\n return getTextBetween(editor.state.doc, {\n from: selection.from,\n to: selection.to,\n });\n}\n\n/**\n * 获取光标前的文本(当前段落)\n */\nexport function getCursorContext(editor: Editor, maxLength: number = 500): string {\n const { selection, doc } = editor.state;\n const pos = selection.from;\n\n // 获取当前段落的起始位置\n const startPos = pos - editor.state.doc.resolve(pos).parentOffset;\n const from = Math.max(0, startPos - maxLength);\n\n return getTextBetween(doc, { from, to: pos });\n}\n\n/**\n * 获取文档纯文本\n */\nexport function getDocumentText(editor: Editor): string {\n return editor.state.doc.textContent;\n}\n","/**\n * AIService - AI 功能调度服务\n */\nimport type { Editor } from '@tiptap/core';\nimport type { AtriAIConfig, AtriAIFunction, AIRequestContext } from '../types';\nimport { AIInterceptor } from './AIInterceptor';\nimport { MarkdownService } from '../core/MarkdownService';\nimport { runSafely } from '../utils/safe-call';\nimport { getSelectedText, getCursorContext, getDocumentText } from '../utils/selection';\n\nexport class AIService {\n private editor: Editor;\n private config: AtriAIConfig;\n private interceptor: AIInterceptor;\n private markdownService: MarkdownService | null = null;\n\n constructor(\n editor: Editor,\n config: AtriAIConfig,\n markdownService: MarkdownService | null = null\n ) {\n this.editor = editor;\n this.config = {\n autoTranslateMarkdownToHTML: true,\n ...config,\n };\n this.markdownService = markdownService;\n this.interceptor = new AIInterceptor(\n config.interceptors || {},\n markdownService,\n this.config.autoTranslateMarkdownToHTML\n );\n }\n\n /**\n * 获取所有 AI 功能\n */\n getFunctions(): AtriAIFunction[] {\n return this.config.functions || [];\n }\n\n /**\n * 根据 ID 获取 AI 功能\n */\n getFunction(id: string): AtriAIFunction | undefined {\n return this.config.functions?.find((f) => f.id === id);\n }\n\n /**\n * 构建请求上下文\n */\n private buildContext(func: AtriAIFunction): AIRequestContext {\n const selection = getSelectedText(this.editor);\n const cursorContext = getCursorContext(this.editor);\n const documentText = getDocumentText(this.editor);\n\n // scope 只决定「主输入」{content},三个上下文字段始终完整提供给端点\n const scopeContent: Record<NonNullable<AtriAIFunction['scope']>, string> = {\n selection,\n cursor: cursorContext,\n document: documentText,\n };\n const values: Record<string, string> = {\n selection,\n cursor: cursorContext,\n document: documentText,\n content: func.scope ? scopeContent[func.scope] : selection || cursorContext,\n };\n\n // 未识别的 {xxx} 原样保留,交给端点自行处理\n const prompt = func.prompt?.replace(/\\{(\\w+)\\}/g, (raw, key: string) => values[key] ?? raw);\n\n return {\n functionId: func.id,\n scope: func.scope,\n selection,\n cursorContext,\n document: documentText,\n prompt,\n extra: {},\n };\n }\n\n /**\n * 执行 AI 功能\n */\n async execute(functionId: string): Promise<void> {\n const func = this.getFunction(functionId);\n if (!func) {\n console.error(`AI function \"${functionId}\" not found.`);\n return;\n }\n\n const context = this.buildContext(func);\n const timeout = this.config.timeout ?? 0;\n // 超时只能\"停止等待\":请求是宿主发的,库无权取消。\n // 晚到的响应靠这个标记挡在插入之前,不能让它覆盖用户之后的光标位置\n let timedOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const chain = this.interceptor.executeChain(context, this.config.requestEndpoint, (content) => {\n if (timedOut) return;\n this.insertContent(content, func.outputMode || 'insert');\n });\n\n try {\n if (timeout > 0) {\n await Promise.race([\n chain,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n timedOut = true;\n reject(new Error(`AI function \"${functionId}\" timed out after ${timeout}ms.`));\n }, timeout);\n }),\n ]);\n } else {\n await chain;\n }\n } catch (error) {\n // 不 rethrow:门面调用 execute 时不 await,抛出只会变成 unhandled rejection。\n // 但没配 onError 也不能就这么静默——端点 500 与\"这次确实没结果\"得区分得开\n const err = error instanceof Error ? error : new Error(String(error));\n if (this.config.onError) {\n runSafely(this.config.onError, 'ai.onError', [err, context]);\n } else {\n console.error(`[Atri Editor] AI function \"${functionId}\" failed:`, err);\n }\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n }\n\n /**\n * 插入内容到编辑器\n */\n private insertContent(content: string, mode: 'replace' | 'insert' | 'append'): void {\n const { selection } = this.editor.state;\n\n switch (mode) {\n case 'replace':\n // 替换选区内容\n if (!selection.empty) {\n this.editor\n .chain()\n .deleteRange({ from: selection.from, to: selection.to })\n .insertContent(content)\n .run();\n } else {\n this.editor.commands.insertContent(content);\n }\n break;\n\n case 'insert':\n // 插入到光标位置\n this.editor.commands.insertContent(content);\n break;\n\n case 'append':\n // 追加到文档末尾\n this.editor.commands.insertContentAt(this.editor.state.doc.content.size, content);\n break;\n }\n }\n\n /**\n * 更新配置\n */\n updateConfig(config: Partial<AtriAIConfig>): void {\n this.config = { ...this.config, ...config };\n this.interceptor = new AIInterceptor(\n config.interceptors || this.config.interceptors || {},\n this.markdownService,\n this.config.autoTranslateMarkdownToHTML ?? true\n );\n }\n\n /**\n * 设置 Markdown 服务\n */\n setMarkdownService(service: MarkdownService): void {\n this.markdownService = service;\n this.interceptor = new AIInterceptor(\n this.config.interceptors || {},\n service,\n this.config.autoTranslateMarkdownToHTML ?? true\n );\n }\n}\n","/**\n * AICommandMenu - AI 命令菜单(使用 Floating UI)\n */\nimport type { Editor } from '@tiptap/core';\nimport type { Transaction } from '@tiptap/pm/state';\nimport { computePosition } from '@floating-ui/dom';\nimport { panelMiddleware, placePanel } from '../core/floating-panel';\nimport type { AtriAIFunction } from '../types';\n\nexport interface AICommandMenuOptions {\n /** AI 功能列表 */\n functions: AtriAIFunction[];\n /** 触发字符(默认 /) */\n triggerChar?: string;\n /** 选择回调 */\n onSelect?: (func: AtriAIFunction) => void;\n}\n\n/**\n * AI 命令菜单管理器\n * 提供 / 触发 AI 命令菜单功能\n */\nexport class AICommandMenuManager {\n private editor: Editor;\n private functions: AtriAIFunction[];\n private menuElement: HTMLDivElement | null = null;\n private isVisible = false;\n private selectedIndex = 0;\n private triggerChar: string;\n private onSelect?: (func: AtriAIFunction) => void;\n private detachKeyListener?: () => void;\n private detachTransactionListener?: () => void;\n\n constructor(\n editor: Editor,\n functions: AtriAIFunction[],\n triggerChar: string = '/',\n onSelect?: (func: AtriAIFunction) => void\n ) {\n this.editor = editor;\n this.functions = functions;\n this.triggerChar = triggerChar;\n this.onSelect = onSelect;\n this.setupKeyboardListener();\n this.setupMenuKeyListener();\n }\n\n /**\n * 让键盘导航可达\n * 监听挂在 document 捕获阶段:ProseMirror 在 view.dom 上已有 keymap,\n * 若等它先处理,Enter 会被用来拆段而不是选中菜单项\n */\n private setupMenuKeyListener(): void {\n const handler = (event: KeyboardEvent) => {\n if (!this.isVisible || !this.editor.isFocused) return;\n if (this.handleKeyDown(event)) {\n event.stopPropagation();\n }\n };\n document.addEventListener('keydown', handler, true);\n this.detachKeyListener = () => document.removeEventListener('keydown', handler, true);\n }\n\n private setupKeyboardListener(): void {\n const handler = ({ transaction }: { editor: Editor; transaction: Transaction }) => {\n // 检测触发字符输入\n if (transaction.docChanged) {\n const lastChar = this.getLastTypedChar();\n if (lastChar === this.triggerChar && !this.isVisible) {\n this.show();\n }\n }\n };\n this.editor.on('transaction', handler);\n // 这条订阅也要能解除:菜单是公开导出的类,宿主单独用它时,\n // destroy 之后编辑器还活着的话,输入触发字符就仍能浮出菜单\n this.detachTransactionListener = () => this.editor.off('transaction', handler);\n }\n\n private getLastTypedChar(): string {\n const { doc, selection } = this.editor.state;\n if (selection.from <= 0) return '';\n return doc.textBetween(selection.from - 1, selection.from);\n }\n\n /**\n * 显示菜单\n */\n show(): void {\n // 没有可选项就没什么可浮出的:空菜单是个看不懂的空盒子,\n // 还会把 Enter/方向键吃掉(selectedIndex 对 0 取模得 NaN)\n if (this.isVisible || !this.functions.length) return;\n this.isVisible = true;\n this.selectedIndex = 0;\n this.createMenuElement();\n this.positionMenu();\n }\n\n /**\n * 隐藏菜单\n */\n hide(): void {\n if (!this.isVisible) return;\n this.isVisible = false;\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n }\n }\n\n private createMenuElement(): void {\n this.menuElement = document.createElement('div');\n this.menuElement.className = 'atri-ai-command-menu';\n this.menuElement.setAttribute('role', 'listbox');\n\n this.functions.forEach((func, index) => {\n const item = document.createElement('div');\n item.className = 'atri-ai-command-menu-item';\n item.setAttribute('role', 'option');\n item.setAttribute('data-index', String(index));\n\n if (index === this.selectedIndex) {\n item.classList.add('active');\n }\n\n if (func.icon) {\n const iconSpan = document.createElement('span');\n iconSpan.className = 'atri-ai-command-menu-icon';\n iconSpan.innerHTML = func.icon;\n item.appendChild(iconSpan);\n }\n\n const nameSpan = document.createElement('span');\n nameSpan.className = 'atri-ai-command-menu-name';\n nameSpan.textContent = func.name;\n item.appendChild(nameSpan);\n\n if (func.description) {\n item.title = func.description;\n }\n\n item.addEventListener('click', () => this.selectItem(index));\n item.addEventListener('mouseenter', () => this.highlightItem(index));\n\n this.menuElement!.appendChild(item);\n });\n\n document.body.appendChild(this.menuElement);\n }\n\n private async positionMenu(): Promise<void> {\n const menuElement = this.menuElement;\n if (!menuElement) return;\n\n const { view } = this.editor;\n const { from } = view.state.selection;\n\n let x: number;\n let y: number;\n try {\n const coords = view.coordsAtPos(from);\n // 用光标坐标合成一个虚拟锚点:VirtualElement 只需要 getBoundingClientRect\n const virtualReference = {\n getBoundingClientRect(): DOMRect {\n return new DOMRect(coords.left, coords.top, 0, coords.bottom - coords.top);\n },\n };\n\n ({ x, y } = await computePosition(virtualReference, menuElement, {\n placement: 'bottom-start',\n middleware: panelMiddleware(),\n }));\n } catch {\n // 测量或定位失败时保持菜单可见,仅跳过定位\n return;\n }\n\n // 异步定位期间菜单可能已关闭或被重建\n if (this.menuElement !== menuElement) return;\n\n placePanel(menuElement, x, y);\n }\n\n private selectItem(index: number): void {\n const func = this.functions[index];\n if (func && this.onSelect) {\n // 删除触发字符\n this.deleteTriggerChar();\n this.onSelect(func);\n }\n this.hide();\n }\n\n private highlightItem(index: number): void {\n this.selectedIndex = index;\n this.updateHighlight();\n }\n\n private updateHighlight(): void {\n if (!this.menuElement) return;\n const items = this.menuElement.querySelectorAll('.atri-ai-command-menu-item');\n items.forEach((item, i) => {\n if (i === this.selectedIndex) {\n item.classList.add('active');\n } else {\n item.classList.remove('active');\n }\n });\n }\n\n private deleteTriggerChar(): void {\n const { state, view } = this.editor;\n const { from } = state.selection;\n if (from <= 0 || state.doc.textBetween(from - 1, from) !== this.triggerChar) {\n return;\n }\n const tr = state.tr.delete(from - 1, from);\n view.dispatch(tr);\n }\n\n /**\n * 处理键盘事件\n */\n handleKeyDown(event: KeyboardEvent): boolean {\n if (!this.isVisible) return false;\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n this.selectedIndex = (this.selectedIndex + 1) % this.functions.length;\n this.updateHighlight();\n return true;\n\n case 'ArrowUp':\n event.preventDefault();\n this.selectedIndex =\n (this.selectedIndex - 1 + this.functions.length) % this.functions.length;\n this.updateHighlight();\n return true;\n\n case 'Enter':\n event.preventDefault();\n this.selectItem(this.selectedIndex);\n return true;\n\n case 'Escape':\n event.preventDefault();\n this.hide();\n return true;\n }\n\n return false;\n }\n\n /**\n * 销毁\n */\n destroy(): void {\n this.detachKeyListener?.();\n this.detachKeyListener = undefined;\n this.detachTransactionListener?.();\n this.detachTransactionListener = undefined;\n this.hide();\n }\n}\n","/**\n * AtriEditor - 编辑器主类\n */\nimport type { Editor, JSONContent } from '@tiptap/core';\nimport type {\n AtriEditorOptions,\n IAtriEditor,\n InsertAttachmentOptions,\n InsertImageOptions,\n InsertTableOptions,\n MediaKind,\n SetContentOptions,\n AtriAIConfig,\n AtriNodeViewConfig,\n} from './types';\nimport { CoreEditor } from './core/CoreEditor';\nimport { MarkdownService } from './core/MarkdownService';\nimport { ThemeManager } from './core/ThemeManager';\nimport { I18nManager } from './core/I18nManager';\nimport { ExtensionManager } from './core/ExtensionManager';\nimport type { ThemeType } from './core/ThemeManager';\nimport { ToolbarManager } from './core/ToolbarManager';\nimport { LinkPanel } from './core/LinkPanel';\nimport { TableMenu } from './core/TableMenu';\nimport { MediaRuntime } from './media/MediaRuntime';\nimport { MediaStatusStrip } from './media/MediaStatusStrip';\nimport { AIService } from './ai/AIService';\nimport { AICommandMenuManager } from './ai/AICommandMenu';\nimport { resolveElement, createContainer } from './utils/dom';\nimport { canInsertTable } from './utils/table';\nimport { resolveTableConfig } from './extensions/table';\nimport { getSelectedText as getSelectedTextFromSelection } from './utils/selection';\n\nexport class AtriEditor implements IAtriEditor {\n // 以下两个字段经 createCoreEditor()/setupSubsystems() 在构造函数内建立,\n // 类型上标注「必已赋值」而非可空:门面所有公开方法都假定它们存在\n private coreEditor!: CoreEditor;\n private markdownService!: MarkdownService;\n private themeManager: ThemeManager;\n private i18nManager: I18nManager;\n private extensionManager: ExtensionManager;\n private toolbarManager: ToolbarManager | null = null;\n /** 工具栏容器引用:重建编辑器时复用同一个挂载点,不重新查询 DOM */\n private toolbarContainer: HTMLDivElement | null = null;\n private aiService: AIService | null = null;\n private aiMenuManager: AICommandMenuManager | null = null;\n /** 链接浮层与表格操作菜单归门面所有:两者都跟着\"编辑行为\"走,与工具栏渲染与否无关 */\n private linkPanel: LinkPanel | null = null;\n private tableMenu: TableMenu | null = null;\n private mediaRuntime: MediaRuntime | null;\n private mediaStatus: MediaStatusStrip | null = null;\n /** 重建后待恢复的选区,新视图就绪时应用 */\n private pendingViewState: { from: number; to: number } | null = null;\n /**\n * 浮动工具栏容器:BubbleMenu 显示时把它塞进编辑区、隐藏时直接摘掉,\n * 所以引用必须握在门面手里,重建编辑器后才能拿同一个元素再挂回去\n */\n private bubbleElement: HTMLDivElement | null = null;\n private container: HTMLDivElement;\n private options: AtriEditorOptions;\n /** 销毁后就不再重建:registerNodeView 会往已经 remove() 的容器里复活一个编辑器 */\n private destroyed = false;\n\n constructor(options: AtriEditorOptions) {\n this.options = options;\n\n // 解析挂载元素\n const rootElement = resolveElement(options.element);\n\n // 创建容器\n this.container = createContainer('atri-editor');\n rootElement.appendChild(this.container);\n\n // 初始化主题\n this.themeManager = new ThemeManager(this.container, options.theme || 'light');\n\n // 初始化国际化\n this.i18nManager = new I18nManager(options.lang || 'zh');\n\n // 初始化扩展管理器\n this.extensionManager = new ExtensionManager();\n\n // 注册 NodeView 自定义组件(在创建编辑器之前)\n if (options.nodeViews && options.nodeViews.length > 0) {\n this.extensionManager.registerNodeViews(options.nodeViews);\n }\n\n // 创建工具栏容器(如果 toolbar 不为 false)\n if (options.toolbar !== false) {\n this.toolbarContainer = createContainer('atri-editor-toolbar');\n this.container.appendChild(this.toolbarContainer);\n }\n\n // 创建编辑区域\n const editorElement = createContainer('atri-editor-content-wrapper');\n this.container.appendChild(editorElement);\n\n // 上传运行时只建一次:registerNodeView 会重建编辑器,进行中的上传得跟着这个实例走\n this.mediaRuntime = options.media === false ? null : new MediaRuntime(options.media);\n\n // 状态条也只建一次,挂在根容器上:卡片上没有文字状态,上传反馈全在这一条里\n this.mediaStatus = this.mediaRuntime\n ? new MediaStatusStrip(this.mediaRuntime, this.container, this.i18nManager)\n : null;\n\n this.createCoreEditor({\n element: editorElement,\n content: options.content,\n contentFormat: options.contentFormat,\n });\n this.setupSubsystems();\n }\n\n /** bubble 默认开,显式 false 才不建元素;元素故意不插进文档:挂载与定位全归 BubbleMenu 插件管 */\n private ensureBubbleElement(toolbar: AtriEditorOptions['toolbar']): HTMLDivElement | null {\n if (toolbar === false || toolbar?.bubble === false) return null;\n this.bubbleElement ??= createContainer('atri-editor-bubble-toolbar');\n return this.bubbleElement;\n }\n\n private onEditorCreated(): void {\n // 视图就绪后才能恢复重建前的选区\n if (this.pendingViewState) {\n const { from, to } = this.pendingViewState;\n this.pendingViewState = null;\n\n const limit = this.editor.state.doc.content.size;\n this.editor.commands.setTextSelection({\n from: Math.min(from, limit),\n to: Math.min(to, limit),\n });\n }\n\n // 初始化 AI 命令菜单:空数组也是真值,得判长度,否则挂一个没有选项的菜单管理器\n if (this.options.ai?.functions?.length) {\n this.aiMenuManager = new AICommandMenuManager(\n this.editor,\n this.options.ai.functions,\n this.options.ai.triggerChar ?? '/',\n (func) => {\n this.aiService?.execute(func.id);\n }\n );\n }\n\n this.options.onCreate?.(this);\n }\n\n private initAI(config: AtriAIConfig): void {\n this.aiService = new AIService(this.editor, config, this.markdownService);\n }\n\n /**\n * 创建核心编辑器:构造函数与 recreateEditor 共用的装配点。\n * 只有编辑器壳(挂载元素/初始内容/内容格式)随重建变化,其余全部来自 this.options,\n * 逐项手抄两遍必然漂移——出错的第一现场就是两处不齐\n */\n private createCoreEditor(init: {\n element: HTMLElement;\n content?: string | object;\n contentFormat?: 'html' | 'json' | 'markdown';\n }): void {\n this.coreEditor = new CoreEditor({\n element: init.element,\n content: init.content,\n contentFormat: init.contentFormat,\n editable: this.options.editable,\n placeholder: this.options.placeholder,\n extensions: [...(this.options.extensions || []), ...this.extensionManager.getAll()],\n markdown: this.options.markdown,\n media: this.options.media,\n table: this.options.table,\n link: this.options.link,\n formatting: this.options.formatting,\n codeBlock: this.options.codeBlock,\n mediaRuntime: this.mediaRuntime ?? undefined,\n bubbleElement: this.ensureBubbleElement(this.options.toolbar),\n onCreate: () => {\n this.onEditorCreated();\n },\n onUpdate: () => {\n this.options.onChange?.(this);\n },\n onFocus: () => {\n this.options.onFocus?.(this);\n },\n onBlur: () => {\n this.options.onBlur?.(this);\n },\n onDestroy: () => {\n this.options.onDestroy?.(this);\n },\n });\n }\n\n /** 初始化依赖编辑器实例的子系统:Markdown 服务、链接浮层、工具栏、表格菜单、AI 服务,重建后同样要重新走一遍 */\n private setupSubsystems(): void {\n this.markdownService = new MarkdownService(this.editor, this.options.markdown);\n\n // 链接 mark 不在 schema 里就没得插也没得改,浮层与按钮一并不提供。\n // 这里不走\"能不能改\"的探测:同名 mark 顶掉内置链接时,两边的属性定义仍会按类型名\n // 并到同一个 mark 上,判不出\"被顶掉\",判了也是假判据\n this.linkPanel = this.editor.schema.marks.link\n ? new LinkPanel({\n editor: this.editor,\n root: this.container,\n i18n: this.i18nManager,\n openOnClick: this.options.link?.openOnClick ?? false,\n target: this.options.link?.target ?? '_blank',\n // 开合不改文档,工具栏等不到 transaction,按钮的按下态得主动推一次\n onOpenChange: () => this.toolbarManager?.updateButtonStates(),\n })\n : null;\n\n if (this.toolbarContainer && this.options.toolbar !== false) {\n this.toolbarManager = new ToolbarManager(\n this.editor,\n this.toolbarContainer,\n this.options.toolbar,\n this.i18nManager,\n this.mediaRuntime,\n this.linkPanel\n );\n if (this.bubbleElement) this.toolbarManager.attachBubbleToolbar(this.bubbleElement);\n }\n\n // 表格菜单与表格扩展共用同一份解析后的配置:两处对\"表格开着没开\"必须有一致的判断\n const tableConfig = resolveTableConfig(this.options.table);\n this.tableMenu =\n tableConfig.menu && canInsertTable(this.editor)\n ? new TableMenu({\n editor: this.editor,\n root: this.container,\n i18n: this.i18nManager,\n })\n : null;\n\n if (this.options.ai) {\n this.initAI(this.options.ai);\n }\n }\n\n /**\n * 重新创建编辑器(用于动态注册 NodeView 后更新 schema)\n * 会整体替换编辑器视图,因此内容与选区都要手动恢复\n */\n private recreateEditor(): void {\n // 保存当前内容与光标位置,待新视图就绪后恢复选区\n const previousEditor = this.coreEditor.getEditor();\n const currentContent = this.coreEditor.getHTML();\n const { from, to } = previousEditor.state.selection;\n this.pendingViewState = { from, to };\n\n // 先收 UI 再收编辑器:浮层与菜单挂在 document.body 上,不跟着 coreEditor 一起离开文档,\n // 而它们的事件订阅都指向这个即将销毁的 Editor 实例\n this.toolbarManager?.destroy();\n this.tableMenu?.destroy();\n this.linkPanel?.destroy();\n this.aiMenuManager?.destroy();\n this.coreEditor.destroy();\n\n // 换一个新的编辑区容器:旧容器连同内部视图一起离开文档\n const editorElement = this.container.querySelector(\n '.atri-editor-content-wrapper'\n ) as HTMLElement;\n const newEditorElement = createContainer('atri-editor-content-wrapper');\n if (editorElement) {\n editorElement.replaceWith(newEditorElement);\n } else {\n this.container.appendChild(newEditorElement);\n }\n\n this.createCoreEditor({\n element: newEditorElement,\n content: currentContent,\n // 当前内容取自 getHTML(),重建时按 html 回填,不能沿用用户的 contentFormat\n contentFormat: 'html',\n });\n this.setupSubsystems();\n }\n\n /**\n * 获取 Tiptap Editor 实例\n */\n get editor(): Editor {\n return this.coreEditor.getEditor();\n }\n\n /**\n * 获取 HTML 内容\n */\n getHTML(): string {\n return this.coreEditor.getHTML();\n }\n\n /**\n * 获取 JSON 内容\n */\n getJSON(): object {\n return this.coreEditor.getJSON();\n }\n\n /**\n * 获取 Markdown 内容\n */\n getMarkdown(): string {\n return this.markdownService.getMarkdown();\n }\n\n /**\n * 设置内容\n */\n setContent(content: string | object, options?: SetContentOptions): void {\n const format = options?.format || this.options.contentFormat;\n const emitUpdate = options?.emitUpdate ?? true;\n\n if (format === 'markdown' && typeof content === 'string') {\n this.markdownService.setMarkdown(content, emitUpdate);\n } else {\n this.coreEditor.setContent(content, emitUpdate);\n }\n this.collapseSelectionAfterReplace();\n }\n\n /**\n * 设置 Markdown 内容\n */\n setMarkdown(content: string): void {\n this.markdownService.setMarkdown(content);\n this.collapseSelectionAfterReplace();\n }\n\n /**\n * 整体替换内容后收拢选区:替换前若是全选(Ctrl+A),ProseMirror 会把它映射成\n * 覆盖整篇新文档的 AllSelection——下一个字或下一次插入就把整篇吞掉,\n * 而\"设置内容\"之后用户的预期是安全的光标态,不是\"再输入即全删\"\n */\n private collapseSelectionAfterReplace(): void {\n const editor = this.coreEditor.getEditor();\n if (!editor || editor.state.selection.empty) return;\n editor.commands.setTextSelection(editor.state.doc.content.size);\n }\n\n /**\n * 清空内容\n */\n clearContent(): void {\n this.coreEditor.clearContent();\n }\n\n /**\n * 是否为空\n */\n isEmpty(): boolean {\n return this.coreEditor.isEmpty();\n }\n\n /**\n * Markdown 转 HTML\n */\n markdownToHTML(markdown: string): string {\n return this.markdownService.markdownToHTML(markdown);\n }\n\n /**\n * HTML 转 Markdown\n */\n htmlToMarkdown(html: string): string {\n return this.markdownService.htmlToMarkdown(html);\n }\n\n /**\n * Markdown 转 JSON\n */\n markdownToJSON(markdown: string): JSONContent {\n return this.markdownService.markdownToJSON(markdown);\n }\n\n /**\n * 获取选中文本\n */\n getSelectedText(): string {\n return getSelectedTextFromSelection(this.editor);\n }\n\n /**\n * 插入内容\n */\n insertContent(content: string): void {\n this.coreEditor.insertContent(content);\n }\n\n /**\n * 在选区处插入图片\n */\n insertImage(options: InsertImageOptions): void {\n this.editor.chain().focus().setImage(options).run();\n }\n\n /**\n * 在选区处插入附件(形态由 options.display 或 media.attachment.display 决定)\n */\n insertAttachment(options: InsertAttachmentOptions): void {\n const media = this.options.media;\n const configured = media === false ? undefined : media?.attachment?.display;\n const display = options.display ?? configured ?? 'card';\n const chain = this.editor.chain().focus();\n\n if (display === 'link') {\n chain.setAttachmentLink(options).run();\n } else {\n chain.setAttachment(options).run();\n }\n }\n\n /**\n * 在选区处插入表格(默认 3×3 带表头)\n */\n insertTable(options?: InsertTableOptions): void {\n if (!canInsertTable(this.editor)) {\n console.warn('[Atri Editor] insertTable() ignored: table extensions are disabled.');\n return;\n }\n const { rows = 3, cols = 3, withHeaderRow = true } = options ?? {};\n this.editor.chain().focus().insertTable({ rows, cols, withHeaderRow }).run();\n }\n\n /**\n * 走上传管线插入本地文件\n * kind 缺省时按 MIME 分流;promise 在这批文件全部落定(成功或失败)后 resolve\n */\n async uploadFiles(files: File[] | FileList, kind?: MediaKind): Promise<void> {\n if (!this.mediaRuntime) {\n console.warn('[Atri Editor] uploadFiles() ignored: media extensions are disabled.');\n return;\n }\n\n await this.mediaRuntime.handleFiles(files, { kind });\n }\n\n /**\n * 是否有文件还没落到服务端(上传中与失败都算)\n * 此时保存会把本地预览地址写进内容,接入方应据此提示或阻断\n */\n hasPendingUploads(): boolean {\n return this.mediaRuntime?.hasPendingUploads() ?? false;\n }\n\n /**\n * 重试所有失败的上传,卡片原地回到上传中\n */\n async retryFailedUploads(): Promise<void> {\n await this.mediaRuntime?.retryFailed();\n }\n\n /**\n * 设置占位符\n */\n setPlaceholder(placeholder: string): void {\n // 重建编辑器时占位符取自 options,不同步会退回旧值\n this.options.placeholder = placeholder;\n this.coreEditor.setPlaceholder(placeholder);\n }\n\n /**\n * 设置可编辑状态\n */\n setEditable(editable: boolean): void {\n this.coreEditor.setEditable(editable);\n }\n\n /**\n * 是否可编辑\n */\n isEditable(): boolean {\n return this.coreEditor.isEditable();\n }\n\n /**\n * 聚焦\n */\n focus(): void {\n this.coreEditor.focus();\n }\n\n /**\n * 失焦\n */\n blur(): void {\n this.coreEditor.blur();\n }\n\n /**\n * 获取 AI 服务\n */\n get ai(): AIService | null {\n return this.aiService;\n }\n\n /**\n * 获取主题管理器\n */\n get theme(): ThemeManager {\n return this.themeManager;\n }\n\n /**\n * 获取国际化管理器\n */\n get i18n(): I18nManager {\n return this.i18nManager;\n }\n\n /**\n * 获取扩展管理器\n */\n get extensions(): ExtensionManager {\n return this.extensionManager;\n }\n\n /**\n * 设置主题\n */\n setTheme(theme: ThemeType): void {\n this.themeManager.setTheme(theme);\n }\n\n /**\n * 切换主题\n */\n toggleTheme(): void {\n this.themeManager.toggleTheme();\n }\n\n /**\n * 切换语言\n */\n async setLanguage(lang: string): Promise<void> {\n await this.i18nManager.changeLanguage(lang);\n }\n\n /**\n * 更新 AI 配置\n */\n updateAIConfig(config: Partial<AtriAIConfig>): void {\n if (this.aiService) {\n this.aiService.updateConfig(config);\n }\n }\n\n /**\n * 注册自定义 NodeView 组件\n * 注意:编辑器已创建时,每次调用都会重建一次编辑器以更新 schema;\n * 连续注册多个组件请改用 registerNodeViews,只需重建一次\n */\n registerNodeView(config: AtriNodeViewConfig): void {\n if (this.refuseAfterDestroy('registerNodeView')) return;\n\n this.extensionManager.registerNodeView(config);\n // 如果编辑器已创建,需要重新创建以更新 schema\n if (this.coreEditor) {\n this.recreateEditor();\n }\n }\n\n /**\n * 批量注册 NodeView\n * 注意:如果编辑器已创建,会重新创建编辑器以更新 schema\n */\n registerNodeViews(configs: AtriNodeViewConfig[]): void {\n if (this.refuseAfterDestroy('registerNodeViews')) return;\n\n this.extensionManager.registerNodeViews(configs);\n // 如果编辑器已创建,需要重新创建以更新 schema\n if (this.coreEditor) {\n this.recreateEditor();\n }\n }\n\n /**\n * 销毁后重建类 API 的统一守卫:判在改状态之前,\n * 否则配置进了 extensionManager 却没有对应的编辑器,看起来就像\"注册了但没生效\"\n */\n private refuseAfterDestroy(api: string): boolean {\n if (!this.destroyed) return false;\n console.warn(`[Atri Editor] ${api}() was called on a destroyed editor and did nothing.`);\n return true;\n }\n\n /**\n * 获取所有已注册的 NodeView\n */\n getNodeViews(): Map<string, AtriNodeViewConfig> {\n return this.extensionManager.getNodeViews();\n }\n\n /**\n * 销毁编辑器\n */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n\n this.toolbarManager?.destroy();\n this.tableMenu?.destroy();\n this.linkPanel?.destroy();\n this.aiMenuManager?.destroy();\n this.coreEditor.destroy();\n this.mediaStatus?.destroy();\n // 在途请求随编辑器一起取消,不然回调会打到已销毁的视图上\n this.mediaRuntime?.destroy();\n // 浮层挂在编辑区内部,随 container 一起离开文档;引用断掉,销毁后的门面就再挂不回它\n this.bubbleElement = null;\n this.container.remove();\n }\n}\n","/**\n * AtriEditorElement - Web Component 封装\n */\nimport { AtriEditor } from './AtriEditor';\nimport type {\n AtriEditorOptions,\n InsertAttachmentOptions,\n InsertImageOptions,\n InsertTableOptions,\n MediaKind,\n} from './types';\n\nconst ELEMENT_NAME = 'atri-editor';\n\n/**\n * SSR / 纯 Node 安全基类:类声明会在模块加载时求值 extends 后的表达式,\n * 直接 `extends HTMLElement` 会让无 DOM 的环境(Next/Nuxt 服务端、vitest node 池)\n * import 即抛 \"HTMLElement is not defined\"。自定义元素只有在浏览器才真正生效,\n * 这里回退到空基类,注册仍由 registerAtriElement 里的 customElements 守卫把关。\n */\nconst AtriEditorBase: typeof HTMLElement =\n typeof HTMLElement !== 'undefined' ? HTMLElement : (class {} as unknown as typeof HTMLElement);\n\n/**\n * 注册 Web Component\n */\nexport function registerAtriElement(): void {\n if (typeof customElements === 'undefined') {\n console.warn('Custom Elements not supported');\n return;\n }\n\n if (!customElements.get(ELEMENT_NAME)) {\n customElements.define(ELEMENT_NAME, AtriEditorElement);\n }\n}\n\n/**\n * AtriEditor Web Component\n */\nexport class AtriEditorElement extends AtriEditorBase {\n private editor: AtriEditor | null = null;\n private _options: Partial<AtriEditorOptions> = {};\n /** 连接与显式销毁都递增它,用来作废已经排队的拆除任务 */\n private teardownToken = 0;\n /** 断连时留下的内容,重连时接回去 */\n private detachedContent: string | null = null;\n /** 显式销毁过:之后重挂不再自动重建,也不该带任何快照 */\n private destroyed = false;\n\n static get observedAttributes(): string[] {\n return ['theme', 'editable', 'lang', 'placeholder'];\n }\n\n constructor() {\n super();\n }\n\n connectedCallback(): void {\n this.teardownToken += 1;\n if (this.destroyed) return;\n this.initEditor();\n }\n\n disconnectedCallback(): void {\n // 搬动节点(appendChild 重挂、框架 re-parent、虚拟列表滚动)会先同步 disconnect\n // 再同步 connect。拆到微任务边界上,isConnected 已经恢复 true,据此把\"搬家\"\n // 和\"真的卸载\"分开——同步拆的话用户输入连同在途上传一起没了。\n // token 则挡住另一种情形:这一批里被重挂过,就不该再拆后来那个实例\n const token = this.teardownToken;\n queueMicrotask(() => {\n if (token === this.teardownToken && !this.isConnected) this.teardownEditor();\n });\n }\n\n /**\n * 显式销毁:之后重挂不再自动重建,内容快照也一并丢掉\n *\n * 框架里对应 unmount 钩子(Vue 的 unmounted、React effect 的 cleanup)。\n * 断连本身已经会延迟拆除,这个方法的意义在于\"别接回去了\"\n */\n destroy(): void {\n // 顺序要紧:先置 destroyed,teardownEditor 才不会把快照又写回来\n this.destroyed = true;\n this.teardownToken += 1;\n this.detachedContent = null;\n this.teardownEditor();\n }\n\n private teardownEditor(): void {\n if (!this.editor) return;\n // 快照优先于 data-content:后者是用户动手之前的初值,拿它重建等于回滚输入。\n // 但显式 destroy 不留快照——宿主说的是\"结束了\",重挂不该拿回已销毁实例的内容\n if (!this.destroyed) this.detachedContent = this.editor.getHTML();\n this.editor.destroy();\n this.editor = null;\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'theme':\n if (this.editor && newValue) {\n this.editor.setTheme(newValue);\n }\n break;\n case 'editable':\n if (this.editor) {\n this.editor.setEditable(newValue !== 'false');\n }\n break;\n case 'lang':\n if (this.editor && newValue) {\n this.editor.setLanguage(newValue);\n }\n break;\n case 'placeholder':\n if (this.editor) {\n this.editor.setPlaceholder(newValue ?? '');\n }\n break;\n }\n }\n\n /**\n * 设置编辑器选项(在 connectedCallback 之前调用)\n */\n setOptions(options: Partial<AtriEditorOptions>): void {\n this._options = { ...this._options, ...options };\n // 宿主显式给了内容就以它为准,断连快照不能压过它\n if ('content' in options) this.detachedContent = null;\n }\n\n private initEditor(): void {\n if (this.editor) return;\n\n const options: AtriEditorOptions = {\n element: this,\n ...this._options,\n };\n\n // 从 HTML 属性读取配置\n const theme = this.getAttribute('theme');\n if (theme) options.theme = theme;\n\n const editable = this.getAttribute('editable');\n if (editable !== null) options.editable = editable !== 'false';\n\n const lang = this.getAttribute('lang');\n if (lang) options.lang = lang;\n\n const placeholder = this.getAttribute('placeholder');\n if (placeholder) options.placeholder = placeholder;\n\n // 从 data 属性读取初始内容\n const content = this.getAttribute('data-content');\n if (content) options.content = content;\n\n const contentFormat = this.getAttribute('data-content-format') as\n | 'html'\n | 'json'\n | 'markdown'\n | undefined;\n if (contentFormat) options.contentFormat = contentFormat;\n\n // 快照排在最后:它是重连前那一刻的 HTML,优先级高于作为\"初值\"的 data-content。\n // contentFormat 也得跟着钉成 html,否则属性写着 markdown 时会拿 Markdown 去解这段 HTML。\n // 只消费一次,第二次重连(宿主自己 destroy 过)就该回到配置的初值\n if (this.detachedContent !== null) {\n options.content = this.detachedContent;\n options.contentFormat = 'html';\n this.detachedContent = null;\n }\n\n this.editor = new AtriEditor(options);\n\n // 派发创建事件\n this.dispatchEvent(\n new CustomEvent('editor-created', {\n detail: { editor: this.editor },\n bubbles: true,\n })\n );\n }\n\n /**\n * 获取编辑器实例\n */\n getEditor(): AtriEditor | null {\n return this.editor;\n }\n\n /**\n * 获取 HTML 内容\n */\n getHTML(): string {\n return this.editor?.getHTML() || '';\n }\n\n /**\n * 获取 JSON 内容\n */\n getJSON(): object | null {\n return this.editor?.getJSON() || null;\n }\n\n /**\n * 获取 Markdown 内容\n */\n getMarkdown(): string {\n return this.editor?.getMarkdown() || '';\n }\n\n /**\n * 设置内容\n */\n setContent(content: string | object, format?: 'html' | 'json' | 'markdown'): void {\n this.editor?.setContent(content, { format });\n }\n\n /**\n * 清空内容\n */\n clearContent(): void {\n this.editor?.clearContent();\n }\n\n /**\n * 在选区处插入图片\n */\n insertImage(options: InsertImageOptions): void {\n this.editor?.insertImage(options);\n }\n\n /**\n * 在选区处插入附件卡片\n */\n insertAttachment(options: InsertAttachmentOptions): void {\n this.editor?.insertAttachment(options);\n }\n\n /**\n * 在选区处插入表格\n */\n insertTable(options?: InsertTableOptions): void {\n this.editor?.insertTable(options);\n }\n\n /**\n * 走上传管线插入本地文件\n */\n async uploadFiles(files: File[] | FileList, kind?: MediaKind): Promise<void> {\n await this.editor?.uploadFiles(files, kind);\n }\n\n /**\n * 重试所有失败的上传\n */\n async retryFailedUploads(): Promise<void> {\n await this.editor?.retryFailedUploads();\n }\n\n /**\n * 是否有文件还没落到服务端(上传中与失败都算),保存前可据此提示\n */\n hasPendingUploads(): boolean {\n return this.editor?.hasPendingUploads() ?? false;\n }\n\n /**\n * 聚焦\n */\n focus(): void {\n this.editor?.focus();\n }\n}\n\n// 自动注册\nregisterAtriElement();\n"],"names":["url","markdown","ProseMirrorNode","ProseMirrorDOMParser","THEME_PREFIX","_a","simpleMarkdownToHtml","getSelectedTextFromSelection"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/utils/table.ts","../src/extensions/table.ts","../src/media/insert-position.ts","../src/extensions/AtriImage.ts","../src/media/file-policy.ts","../src/utils/url.ts","../src/extensions/attachment-attrs.ts","../src/extensions/attachment-markdown.ts","../src/extensions/Attachment.ts","../src/extensions/AttachmentLink.ts","../src/utils/dom.ts","../src/extensions/MediaFiles.ts","../src/extensions/media.ts","../src/utils/escape.ts","../src/extensions/markdown-inline-html.ts","../src/extensions/formatting.ts","../src/media/Uploader.ts","../src/utils/safe-call.ts","../src/media/MediaRuntime.ts","../src/core/bubble-toolbar.ts","../src/core/CoreEditor.ts","../src/utils/markdown.ts","../src/core/markdown-losses.ts","../src/core/MarkdownService.ts","../src/core/ThemeManager.ts","../src/core/I18nManager.ts","../src/core/ExtensionManager.ts","../src/core/floating-panel.ts","../src/media/InsertPanel.ts","../src/core/StyleOptionPanel.ts","../src/core/style-presets.ts","../src/core/TableGridPanel.ts","../src/core/ToolbarMenuPanel.ts","../src/utils/formatting.ts","../src/core/icons.ts","../src/core/ToolbarManager.ts","../src/core/LinkPanel.ts","../src/core/TableMenu.ts","../src/media/MediaStatusStrip.ts","../src/ai/AIInterceptor.ts","../src/utils/selection.ts","../src/ai/AIService.ts","../src/ai/AICommandMenu.ts","../src/AtriEditor.ts","../src/AtriEditorElement.ts"],"sourcesContent":["/**\n * 表格能力探测:工具栏按钮与门面 insertTable 共用的 gate\n *\n * 内部使用,不进 utils barrel、不随包出口——检测口径(schema spec 上的 tableRole)\n * 是 extension-table 的实现细节,公共面只承诺\"能不能插\"这件事本身\n */\nimport type { Editor } from '@tiptap/core';\n\n/**\n * 内置表格是否真的赢下了 schema\n *\n * 不能扫 extensionManager 里的名字:tiptap 对重名扩展只告警不去重,顶掉场景下\n * 内置 Table 仍在列表里、赢的却是用户的 schema 节点,而它没有 tableRole。\n * prosemirror-tables 的列宽插件启动时就按 tableRole 找 table 节点,找不到直接抛错,\n * 所以这个判据同时决定\"按钮摆不摆\"和\"这套插件注不注册\"\n */\nexport function isBuiltinTableActive(editor: Editor): boolean {\n // tableRole 由 extension-table 的 extendNodeSchema 写进 node spec,prosemirror 的类型里没有它\n const spec = editor.schema.nodes.table?.spec as { tableRole?: string } | undefined;\n return spec?.tableRole === 'table';\n}\n\n/**\n * 内置表格是否真的可用:table:false 不注册、用户用同名扩展顶掉,两种情况都该判 false\n *\n * 命令存在性 + schema 角色两条一起看,判定的才是\"这一下真能插出来\"——\n * 内置 insertTable 命令按 spec.tableRole 找型,找不到就在点击当场抛错\n */\nexport function canInsertTable(editor: Editor): boolean {\n if (typeof editor.commands.insertTable !== 'function') return false;\n return isBuiltinTableActive(editor);\n}\n","/**\n * 表格扩展装配\n *\n * 增量只有一处:列宽拖拽默认打开。单元格对齐不必动 schema —— extension-table 的单元格\n * 自带 align 属性(解析 style=\"text-align: …\",Markdown 的 pipe table 分隔行也对到它,\n * 双向都通),缺的只是按得动的按钮,那是操作菜单的事。\n */\nimport type { EditorOptions } from '@tiptap/core';\nimport { Table, TableCell, TableHeader, TableRow } from '@tiptap/extension-table';\nimport type { AtriTableConfig } from '../types';\nimport { isBuiltinTableActive } from '../utils/table';\n\nconst DEFAULT_TABLE_CONFIG: Required<AtriTableConfig> = {\n resizable: true,\n menu: true,\n};\n\n/**\n * 补上默认值,供扩展装配与操作菜单共用:两处必须看到同一个配置,\n * 否则会出现\"菜单浮得出来,节点却没注册\"这种按了没反应的组合\n *\n * 只归一化 true / undefined / 对象三种写法,false(不注册表格)由调用方先行拦掉\n */\nexport function resolveTableConfig(table?: boolean | AtriTableConfig): Required<AtriTableConfig> {\n return { ...DEFAULT_TABLE_CONFIG, ...(typeof table === 'object' ? table : undefined) };\n}\n\n/**\n * 列宽拖拽由 prosemirror-tables 的列宽插件实现,它启动时按 spec.tableRole 找 table 节点,\n * 而用户用同名扩展顶掉内置 table 时赢的那个节点没有 tableRole——插件照常注册就会直接抛。\n * 插件建立时 schema 已经就绪,所以在此探测而不是在配置里假设\n */\nconst AtriTable = Table.extend({\n addProseMirrorPlugins() {\n if (!isBuiltinTableActive(this.editor)) return [];\n return this.parent?.() ?? [];\n },\n});\n\n/**\n * 构造表格扩展\n *\n * 四件套在这里集中,调用方不必知道 resizable 落在那一个扩展上\n */\nexport function createTableExtensions(\n table?: AtriTableConfig\n): NonNullable<EditorOptions['extensions']> {\n const config = resolveTableConfig(table);\n\n return [AtriTable.configure({ resizable: config.resizable }), TableRow, TableHeader, TableCell];\n}\n","/**\n * 媒体节点的插入落点\n */\nimport { NodeSelection, type Selection } from '@tiptap/pm/state';\n\n/**\n * 插完一个 atom 媒体节点,它会处于选中态;插入命令替换的是整个选区,\n * 于是空文档里连插两张图只剩一张。选中的正是媒体节点时,把落点挪到它后面。\n */\nexport function mediaInsertTarget(selection: Selection): number | { from: number; to: number } {\n if (\n selection instanceof NodeSelection &&\n (selection.node.type.name === 'image' ||\n selection.node.type.name === 'attachment' ||\n selection.node.type.name === 'attachmentLink')\n ) {\n return selection.to;\n }\n\n return { from: selection.from, to: selection.to };\n}\n","/**\n * AtriImage - 图片节点\n *\n * 只往 stock Image 上加\"上传中\"这类临时态:命令、输入规则、markdown 双向 hook\n * 与缩放的 ResizableNodeView 全部沿用原实现,不去 fork 它的 NodeView。\n *\n * 图片没有 progress 属性——逐文件百分比画在附件卡片上,图片的整体进度由\n * MediaRuntime 的队列快照(subscribe)表达,状态条一处说清一批文件。\n */\nimport Image from '@tiptap/extension-image';\nimport { mediaInsertTarget } from '../media/insert-position';\n\nexport const AtriImage = Image.extend({\n addAttributes() {\n return {\n ...this.parent?.(),\n // 只写不读:保存下来的 HTML 里不该留着一个永远不会完成的\"上传中\"\n status: {\n default: null,\n parseHTML: () => null,\n renderHTML: (attributes) =>\n attributes.status ? { 'data-atri-upload-status': attributes.status } : {},\n },\n uploadId: {\n default: null,\n parseHTML: () => null,\n rendered: false,\n },\n };\n },\n\n addCommands() {\n return {\n ...this.parent?.(),\n // stock 版走 insertContent,替换的是整个选区;而插入一张图之后选区正好盖住这张图,\n // 于是空文档里连插两张只剩一张。落点交给 mediaInsertTarget 判断\n setImage:\n (options) =>\n ({ state, commands }) =>\n commands.insertContentAt(mediaInsertTarget(state.selection), {\n type: this.name,\n attrs: options,\n }),\n };\n },\n});\n","/**\n * 文件策略:大小展示与类型白名单\n */\n\nconst UNITS = ['B', 'KB', 'MB', 'GB', 'TB'] as const;\nconst UNIT_FACTOR = 1024;\n\n/**\n * 字节数 → 人类可读标签,1024 进制\n * B 之外的单位保留一位小数,整数值去掉多余的 .0\n */\nexport function formatFileSize(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes < UNIT_FACTOR) return `${Math.round(bytes)} B`;\n\n let value = bytes;\n let unit = 0;\n while (value >= UNIT_FACTOR && unit < UNITS.length - 1) {\n value /= UNIT_FACTOR;\n unit += 1;\n }\n\n const label = Number.isInteger(value) ? String(value) : value.toFixed(1);\n return `${label} ${UNITS[unit]}`;\n}\n\n/**\n * 人类可读标签 → 字节数\n * 与 formatFileSize 成对使用;标签只有一位小数精度,因此还原值是近似值\n */\nexport function parseFileSize(label?: string | null): number | undefined {\n if (!label) return undefined;\n const match = /^\\s*([\\d.]+)\\s*(B|KB|MB|GB|TB)\\s*$/i.exec(label);\n if (!match) return undefined;\n\n const value = Number(match[1]);\n if (!Number.isFinite(value)) return undefined;\n\n const unit = UNITS.indexOf(match[2].toUpperCase() as (typeof UNITS)[number]);\n return Math.round(value * UNIT_FACTOR ** unit);\n}\n\n/** 单个文件默认上限:10MB */\nexport const DEFAULT_MAX_FILE_SIZE = 10 * UNIT_FACTOR ** 2;\n\n/** 一次投放/选择的默认文件数上限 */\nexport const DEFAULT_MAX_FILES = 10;\n\n/** accept 支持的三种写法:`.pdf`、`image/*`、`image/png` */\nexport function matchesAccept(file: File, accept?: string | string[]): boolean {\n if (!accept) return true;\n\n const patterns = (Array.isArray(accept) ? accept : accept.split(','))\n .map((pattern) => pattern.trim().toLowerCase())\n .filter(Boolean);\n\n if (!patterns.length) return true;\n\n const name = file.name.toLowerCase();\n const type = (file.type || '').toLowerCase();\n\n return patterns.some((pattern) => {\n if (pattern.startsWith('.')) return name.endsWith(pattern);\n if (pattern.endsWith('/*')) return type.startsWith(pattern.slice(0, -1));\n return type === pattern;\n });\n}\n\n/** 把配置的 accept 归一成 <input accept> 需要的逗号串:选择器与校验得是同一份白名单 */\nexport function acceptAttribute(accept?: string | string[]): string | undefined {\n if (!accept) return undefined;\n const patterns = (Array.isArray(accept) ? accept : accept.split(','))\n .map((pattern) => pattern.trim())\n .filter(Boolean);\n\n return patterns.length ? patterns.join(',') : undefined;\n}\n\nexport type FileValidation = { ok: true } | { ok: false; reason: 'too-large' | 'unsupported-type' };\n\nexport function validateFile(\n file: File,\n options: { accept?: string | string[]; maxFileSize?: number }\n): FileValidation {\n const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;\n\n if (file.size > maxFileSize) return { ok: false, reason: 'too-large' };\n if (!matchesAccept(file, options.accept)) return { ok: false, reason: 'unsupported-type' };\n\n return { ok: true };\n}\n\n/** 拖放与粘贴进来的文件按 MIME 分流到图片还是附件 */\nexport function isImageFile(file: File): boolean {\n return (file.type || '').toLowerCase().startsWith('image/');\n}\n","/**\n * URL 协议白名单\n *\n * 库里有五条来源会把 URL 写进可点击的 DOM 属性:宿主调 insertImage/insertAttachment、\n * 上传服务的响应体、外部粘贴的 HTML、setJSON 回灌、以及节点属性的 setNodeMarkup。\n * 逐条在入口校验拦不住全部——只有渲染层是唯一的收口点,所以校验放在 renderHTML。\n */\n\n/** 可点击地址:data: 会让 javascript: / data:text/html 点击即导航执行,故不在列 */\nconst HREF_SCHEMES = ['http', 'https', 'mailto', 'tel', 'ftp', 'ftps'] as const;\n\n/** 媒体资源地址:blob: 是本地预览、data: 是上传失败后的内联退路,两者都必须留 */\nconst MEDIA_SCHEMES = ['http', 'https', 'data', 'blob'] as const;\n\n/**\n * 浏览器解析 URL 前会做的清理:任意位置的 tab/CR/LF 被删掉,首尾的 C0 控制符与空格被剥掉。\n * 不先做这一步,`java\\tscript:` 与 `\\x01javascript:` 这类写法就会绕过前缀匹配。\n * 用 charCodeAt 而不是正则字符类,是为了不在库源码里压制 no-control-regex。\n */\nfunction stripUrlNoise(value: string): string {\n const collapsed = value.replace(/[\\t\\n\\r]/g, '');\n\n let start = 0;\n let end = collapsed.length;\n while (start < end && isStripped(collapsed.charCodeAt(start))) start += 1;\n while (end > start && isStripped(collapsed.charCodeAt(end - 1))) end -= 1;\n\n return collapsed.slice(start, end);\n}\n\nfunction isStripped(code: number): boolean {\n return code <= 0x20 || code === 0x7f;\n}\n\n/** scheme 必须是字母开头,后跟字母数字或 + - . */\nconst SCHEME_PREFIX = /^([a-z][a-z0-9+.-]*):/i;\n\n/**\n * 校验 URL 的协议是否安全,返回清洗后的地址;不接受时返回 null。\n *\n * 没有 scheme 的相对路径(/files/a.pdf、a.pdf、#anchor、?q=1)一律放行:\n * 附件与图片的常见部署形态就是同源相对路径,按协议拦会把正常用法打断。\n */\nexport function safeHref(value: unknown, schemes: readonly string[] = HREF_SCHEMES): string | null {\n if (typeof value !== 'string') return null;\n\n const url = stripUrlNoise(value);\n if (!url) return null;\n\n const scheme = SCHEME_PREFIX.exec(url)?.[1];\n if (!scheme) return url;\n\n return schemes.includes(scheme.toLowerCase()) ? url : null;\n}\n\n/**\n * 校验图片 / 附件的资源地址:比 href 多允许 data: 与 blob:,\n * 因为「本地预览 → 上传完成后换成服务端地址」这条链路全程依赖它们。\n */\nexport function safeMediaSrc(value: unknown): string | null {\n return safeHref(value, MEDIA_SCHEMES);\n}\n","/**\n * 附件两种形态(块级卡片 attachment / 行内链接 attachmentLink)共享的节点属性\n *\n * PM 的 inline/group 是 schema 级声明,一个类型当不了两种形态,只能拆成两个节点;\n * 但属性集必须完全一致,否则切换形态会丢字段,上传管线也得按类型开分支\n */\nimport type { Attribute, CommandProps } from '@tiptap/core';\nimport { NodeSelection, type Transaction } from '@tiptap/pm/state';\nimport type { InsertAttachmentOptions } from '../types';\nimport { mediaInsertTarget } from '../media/insert-position';\nimport { safeHref, safeMediaSrc } from '../utils/url';\n\n/** 两种形态的节点名,判一处用 */\nexport function isAttachmentForm(name: string): boolean {\n return name === 'attachment' || name === 'attachmentLink';\n}\n\n/** 把选区放到离 from 最近的、指定形态的附件节点上——只认\"刚插的那种\",\n * 否则句中建卡片时旧链接会和新卡片打平距离,选错对象 */\nexport function selectNearestForm(tr: Transaction, from: number, typeName: string): void {\n let best = -1;\n let bestDist = Number.POSITIVE_INFINITY;\n tr.doc.descendants((node, pos) => {\n if (node.type.name !== typeName) return;\n const dist = Math.abs(pos - from);\n if (dist < bestDist) {\n bestDist = dist;\n best = pos;\n }\n });\n if (best < 0) return;\n try {\n tr.setSelection(NodeSelection.create(tr.doc, best));\n } catch {\n // 选不上就算了,节点已经进文档\n }\n}\n\n/**\n * 插入附件节点并把选区落在它身上\n *\n * tiptap 的 insertContentAt 收尾只给 TextSelection,而这两种 atom 没有 NodeView\n * 不会自己选中——切形态、删除都以\"选中的附件\"为作用对象,插完就该是选中的\n */\nexport function insertAttachmentContent(\n props: CommandProps,\n typeName: string,\n options: InsertAttachmentOptions\n): boolean {\n const { state, commands, tr, dispatch } = props;\n const target = mediaInsertTarget(state.selection);\n const anchor = typeof target === 'number' ? target : target.to;\n\n // 判据与渲染层一致用 safeHref:附件的 src 最终就是要进 href 的那个值。\n // 用更宽的 safeMediaSrc 会放行 blob:/data:,命令返回 true 而节点渲染出来没有链接,\n // 正是\"成功了但什么都没发生\"。上传管线不经这条命令(直接 setNodeMarkup 写 src),\n // 所以本地预览地址照常工作\n if (safeHref(options.src) === null) {\n console.warn(\n `[Atri Editor] insertAttachment rejected: \"${options.src}\" is not a clickable address.`\n );\n return false;\n }\n\n if (\n !commands.insertContentAt(target, {\n type: typeName,\n attrs: {\n src: options.src,\n name: options.name ?? null,\n size: options.size ?? null,\n mime: options.mime ?? null,\n },\n })\n ) {\n return false;\n }\n\n if (dispatch) selectNearestForm(tr, anchor, typeName);\n return true;\n}\n\n/** 属性值一律走 data-* 或文本节点,不进 innerHTML:文件名可能来自上传方,不可信 */\nexport function dataAttr(\n name: string,\n value: string | number | null | undefined\n): Record<string, string> {\n return value === null || value === undefined || value === '' ? {} : { [name]: String(value) };\n}\n\n/** 百分比同源写两份:data-* 是\"知道进度\"的开关,内联自定义属性给样式表取值 */\nexport function progressAttrs(progress: number | null | undefined): Record<string, string> {\n if (typeof progress !== 'number' || !Number.isFinite(progress)) return {};\n const percent = Math.min(100, Math.max(0, Math.round(progress)));\n return {\n 'data-atri-upload-progress': String(percent),\n style: `--atri-upload-progress: ${percent}%`,\n };\n}\n\n/** 展示名缺省时从地址尾巴推导 */\nexport function attachmentLabel(name: unknown, src: unknown): string {\n return (\n (name as string) ||\n String(src || '')\n .split('/')\n .pop() ||\n ''\n );\n}\n\nexport function attachmentAttributes(): Record<string, Attribute> {\n return {\n src: {\n default: null,\n // 粘贴进来的 data-src 也洗一遍:不洗的话它绕开 AttachmentLink 那处 getAttrs,\n // 直接落进节点属性,再由渲染层的 safeHref 挡掉——属性里仍留着地址,\n // 而 setNodeMarkup / JSON 回灌都不经过渲染层\n parseHTML: (element) => safeMediaSrc(element.getAttribute('data-src')),\n renderHTML: (attributes) => dataAttr('data-src', attributes.src),\n },\n name: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-name'),\n renderHTML: (attributes) => dataAttr('data-name', attributes.name),\n },\n size: {\n default: null,\n parseHTML: (element) => {\n const raw = element.getAttribute('data-size');\n const parsed = raw === null ? Number.NaN : Number(raw);\n return Number.isFinite(parsed) ? parsed : null;\n },\n renderHTML: (attributes) => dataAttr('data-size', attributes.size),\n },\n mime: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-mime'),\n renderHTML: (attributes) => dataAttr('data-mime', attributes.mime),\n },\n // 下面三项是上传过程中的瞬时态:写得出、读不回,\n // 免得保存下来的 HTML 里带着一个永远不会完成的\"上传中\"\n status: {\n default: null,\n parseHTML: () => null,\n renderHTML: (attributes) => dataAttr('data-atri-upload-status', attributes.status),\n },\n // 行内链接没有卡片那条进度条,百分比只落 data 属性;\n // 照常定义是为了让运行时的补丁不分形态,切换后下一个进度 tick 也接得上\n progress: {\n default: null,\n parseHTML: () => null,\n renderHTML: (attributes) => progressAttrs(attributes.progress),\n },\n uploadId: {\n default: null,\n parseHTML: () => null,\n rendered: false,\n },\n };\n}\n","/**\n * 附件两类节点(卡片 file / 行内链接 filelink)的 Markdown 三件套工厂。\n *\n * tokenize/parse/render 的结构完全相同,只差 token 名、语法层级与落点节点名,\n * 收拢成一份实现,避免两处手抄漂移。\n */\nimport type {\n JSONContent,\n MarkdownParseHelpers,\n MarkdownParseResult,\n MarkdownRendererHelpers,\n MarkdownToken,\n MarkdownTokenizer,\n RenderContext,\n} from '@tiptap/core';\nimport { formatFileSize, parseFileSize } from '../media/file-policy';\n\nexport interface AttachmentMarkdownSpec {\n markdownTokenName: string;\n markdownTokenizer: MarkdownTokenizer;\n parseMarkdown: (token: MarkdownToken, helpers: MarkdownParseHelpers) => MarkdownParseResult;\n renderMarkdown: (\n node: JSONContent,\n helpers: MarkdownRendererHelpers,\n ctx: RenderContext\n ) => string;\n}\n\nexport function makeAttachmentMarkdown(options: {\n /** token 名,语法前缀按 `!<name>[` 拼出:file / filelink */\n name: string;\n /** 语法层级:卡片是块级(要求行尾收束),行内链接是 inline */\n level: 'block' | 'inline';\n /** Markdown 解析落点节点名:attachment / attachmentLink */\n nodeName: string;\n}): AttachmentMarkdownSpec {\n const { name, level, nodeName } = options;\n // 语法指令:!file / !filelink,方括号是名称组的开括号,不并入指令\n const directive = `!${name}`;\n // 块级语法要求行尾收束,行内语法不限制\n const trailing = level === 'block' ? '(?:\\\\n|$)' : '';\n const pattern = new RegExp(\n `^${directive.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\[([^\\\\]]*)\\\\]\\\\(\\\\s*([^\\\\s)]+)(?:\\\\s+\"([^\"]*)\")?\\\\s*\\\\)${trailing}`\n );\n\n return {\n markdownTokenName: name,\n\n markdownTokenizer: {\n name,\n level,\n start: (src) => src.indexOf(`${directive}[`),\n tokenize: (src): MarkdownToken | undefined => {\n const match = pattern.exec(src);\n if (!match) return undefined;\n\n return {\n type: name,\n raw: match[0],\n text: match[1] ?? '',\n href: match[2] ?? '',\n title: match[3],\n };\n },\n },\n\n parseMarkdown: (token, helpers) =>\n helpers.createNode(\n nodeName,\n {\n src: token.href,\n name: token.text || null,\n size: parseFileSize(token.title) ?? null,\n mime: null,\n },\n []\n ),\n\n renderMarkdown: (node) => {\n const src = node.attrs?.src ?? '';\n const label = node.attrs?.name ?? '';\n const size = node.attrs?.size;\n\n return size\n ? `${directive}[${label}](${src} \"${formatFileSize(size)}\")`\n : `${directive}[${label}](${src})`;\n },\n };\n}\n","/**\n * Attachment - 文件附件节点\n *\n * 不能走 ExtensionManager.registerNodeView:那条路合成的节点声明不了 addCommands 与\n * markdown 三件套(只有 renderMarkdown,导出得回来不去),附件要能往返。\n */\nimport { Node, mergeAttributes } from '@tiptap/core';\nimport type { DOMOutputSpec, Node as PmNode, NodeType } from '@tiptap/pm/model';\nimport {\n NodeSelection,\n Plugin,\n PluginKey,\n type EditorState,\n type Transaction,\n} from '@tiptap/pm/state';\nimport type { AtriAttachmentDisplay, InsertAttachmentOptions } from '../types';\nimport { formatFileSize } from '../media/file-policy';\nimport { safeHref } from '../utils/url';\nimport {\n attachmentAttributes,\n attachmentLabel,\n dataAttr,\n insertAttachmentContent,\n isAttachmentForm,\n} from './attachment-attrs';\nimport { makeAttachmentMarkdown } from './attachment-markdown';\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n attachment: {\n /** 在选区处插入一个附件卡片 */\n setAttachment: (options: InsertAttachmentOptions) => ReturnType;\n /** 把选中的附件换成指定显示形态(卡片 / 行内链接) */\n setAttachmentDisplay: (options: { display: AtriAttachmentDisplay }) => ReturnType;\n /** 在卡片与行内链接之间切换选中的附件 */\n toggleAttachmentDisplay: () => ReturnType;\n };\n }\n}\n\nconst FORM_TYPES: Record<AtriAttachmentDisplay, 'attachment' | 'attachmentLink'> = {\n card: 'attachment',\n link: 'attachmentLink',\n};\n\n/** 选区指在哪枚附件节点上:NodeSelection、光标紧贴其前/其后都算 */\nfunction locateAttachment(state: EditorState): { node: PmNode; pos: number } | null {\n const isForm = (type: NodeType) => isAttachmentForm(type.name);\n\n const { selection } = state;\n if (selection instanceof NodeSelection) {\n return isForm(selection.node.type) ? { node: selection.node, pos: selection.from } : null;\n }\n if (!('$from' in selection)) return null;\n\n const after = selection.$from.nodeAfter;\n if (after && isForm(after.type)) return { node: after, pos: selection.from };\n const before = selection.$from.nodeBefore;\n if (before && isForm(before.type)) return { node: before, pos: selection.from - before.nodeSize };\n return null;\n}\n\nfunction switchAttachmentDisplay(\n state: EditorState,\n dispatch: ((tr: Transaction) => void) | undefined,\n display: AtriAttachmentDisplay\n): boolean {\n const located = locateAttachment(state);\n if (!located) return false;\n\n const wantName = FORM_TYPES[display];\n if (located.node.type.name === wantName) return false;\n\n const target = state.schema.nodes[wantName];\n if (!target) return false;\n if (!dispatch) return true;\n\n // 不能 setNodeMarkup:block↔inline 换的是父结构(卡片进段落要 wrap,\n // 句中链接转卡片要劈段),replaceRangeWith 会按 schema 把这两件事都做对\n const next = target.create(located.node.attrs);\n const tr = state.tr.replaceRangeWith(located.pos, located.pos + located.node.nodeSize, next);\n\n // 换形态动了结构,映射后的位置不保证还指着节点本身;对不上就按属性找回来,\n // 让选区跟着落到新形态上,连着切换或继续输入都不用再点一次\n let newPos = tr.mapping.map(located.pos, 1);\n if (tr.doc.nodeAt(newPos)?.type.name !== wantName) {\n newPos = -1;\n tr.doc.descendants((n, pos) => {\n if (\n newPos < 0 &&\n n.type.name === wantName &&\n n.attrs.src === located.node.attrs.src &&\n n.attrs.uploadId === located.node.attrs.uploadId\n ) {\n newPos = pos;\n }\n });\n }\n if (newPos >= 0 && target.spec.selectable !== false) {\n const nodeSelection = NodeSelection.create(tr.doc, newPos);\n tr.setSelection(nodeSelection);\n }\n\n dispatch(tr);\n return true;\n}\n\nexport const Attachment = Node.create({\n name: 'attachment',\n\n group: 'block',\n\n atom: true,\n\n selectable: true,\n\n draggable: true,\n\n addAttributes() {\n return attachmentAttributes();\n },\n\n parseHTML() {\n return [{ tag: 'div[data-atri-attachment]' }];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n const { src, name, size, status } = node.attrs;\n const label = attachmentLabel(name, src);\n\n const children: DOMOutputSpec[] = [\n ['span', { class: 'atri-attachment-icon', 'aria-hidden': 'true' }],\n ];\n\n const body: DOMOutputSpec[] = [\n // src 有五条来路(宿主 insertAttachment、上传响应、外部粘贴、setNodeMarkup、\n // setJSON 回灌),逐条在入口校验拦不全,渲染层是唯一都经过的地方。\n // 不过 dataAttr 的\"空值即省略键\"是关键:传 null 会被 mergeAttributes 留下,\n // 序列化出来就成了 href=\"null\",反而多一枚假链接\n [\n 'a',\n {\n class: 'atri-attachment-name',\n ...dataAttr('href', safeHref(src)),\n rel: 'noopener noreferrer',\n },\n label,\n ],\n ];\n if (size) {\n body.push(['span', { class: 'atri-attachment-size' }, formatFileSize(size)]);\n }\n // 名字与大小各占一行:单行挤不下长文件名,省略号又藏掉了最关键的信息\n children.push(['div', { class: 'atri-attachment-body' }, ...body]);\n\n // 卡片上没有一处硬编码文案:状态只靠进度条与配色表达,\n // \"上传中 2/3\"、\"重试\"这类带文字的信息在本地化的状态条里\n if (status) {\n children.push([\n 'span',\n { class: 'atri-attachment-progress' },\n ['span', { class: 'atri-attachment-progress-bar' }],\n ]);\n }\n\n return [\n 'div',\n mergeAttributes(HTMLAttributes, { 'data-atri-attachment': '', class: 'atri-attachment' }),\n ...children,\n ];\n },\n\n addCommands() {\n return {\n setAttachment: (options) => (props) => insertAttachmentContent(props, this.name, options),\n\n setAttachmentDisplay:\n (options) =>\n ({ state, dispatch }) =>\n switchAttachmentDisplay(state, dispatch, options.display),\n\n toggleAttachmentDisplay:\n () =>\n ({ state, dispatch }) => {\n const located = locateAttachment(state);\n if (!located) return false;\n return switchAttachmentDisplay(\n state,\n dispatch,\n located.node.type.name === 'attachment' ? 'link' : 'card'\n );\n },\n };\n },\n\n addProseMirrorPlugins() {\n const findForm = (event: Event) => {\n const target = event.target instanceof Element ? event.target : null;\n const dom = target?.closest('[data-atri-attachment], [data-atri-attachment-link]');\n if (!dom) return null;\n\n const view = this.editor.view;\n const pos = view.posAtDOM(dom as HTMLElement, 0);\n const node = view.state.doc.nodeAt(pos);\n return node && isAttachmentForm(node.type.name) ? { dom, pos, node } : null;\n };\n\n // 一次物理点击 = mousedown 紧跟 click,判定只能在 mousedown 里做、交给 click 兑现:\n // click 时刻的选区分不清\"这次选中的\"与\"上次就选中的\"\n let pendingClick: { pos: number; navigate: boolean } | null = null;\n\n return [\n new Plugin({\n key: new PluginKey('atriAttachmentSelection'),\n props: {\n handleDOMEvents: {\n // 这两种 atom 没有 NodeView,鼠标点了不会自己变成 NodeSelection——\n // 而切形态、删除都以\"选中的附件\"为作用对象;点了却切不动比多一次点击更糟\n mousedown: (view, event) => {\n const hit = findForm(event);\n if (!hit) {\n pendingClick = null;\n return false;\n }\n\n const sel = view.state.selection;\n if (sel instanceof NodeSelection && sel.from === hit.pos) {\n // 已选中:这一笔是\"打开文件\",两个事件都原样放行——\n // 连 mousedown 的默认行为一起拦掉,浏览器就不跳了\n pendingClick = { pos: hit.pos, navigate: true };\n return false;\n }\n\n event.preventDefault();\n pendingClick = { pos: hit.pos, navigate: false };\n view.dispatch(\n view.state.tr.setSelection(NodeSelection.create(view.state.doc, hit.pos))\n );\n return true;\n },\n // 附件里的 <a>(卡片的名字链接、行内链接本体):第一次点击只选中附件,\n // 已选中再点才真的打开文件——否则\"点一下选中去切换\"与\"点开文件\"互相打架。\n // 判据取 mousedown 记下的那一笔而不是 click 时刻的选区:\n // 放行的 mousedown 会被 PM 改写成 TextSelection,选区就当不得证据\n click: (_view, event) => {\n const hit = findForm(event);\n const pending = pendingClick;\n pendingClick = null;\n if (!hit || !pending || pending.pos !== hit.pos) return false;\n\n const inAnchor = event.target instanceof Element && !!event.target.closest('a[href]');\n if (pending.navigate && (inAnchor || hit.node.type.name === 'attachmentLink')) {\n return false;\n }\n\n event.preventDefault();\n return true;\n },\n },\n },\n }),\n ];\n },\n\n ...makeAttachmentMarkdown({\n name: 'file',\n level: 'block',\n nodeName: 'attachment',\n }),\n});\n","/**\n * AttachmentLink - 行内链接形态的文件附件\n *\n * 与 attachment(块级卡片)是两个节点类型:PM 的 inline/group 在 schema 里静态声明,\n * 同一类型当不了两种形态。属性集、瞬时态补丁与 Markdown 语法家族都走共享模块,\n * 切换命令(Attachment.ts 的 setAttachmentDisplay)只在两者间换节点。\n */\nimport { Node, mergeAttributes } from '@tiptap/core';\nimport type { InsertAttachmentOptions } from '../types';\nimport { safeHref } from '../utils/url';\nimport {\n attachmentAttributes,\n attachmentLabel,\n dataAttr,\n insertAttachmentContent,\n} from './attachment-attrs';\nimport { makeAttachmentMarkdown } from './attachment-markdown';\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n attachmentLink: {\n /** 在选区处插入一个行内附件链接 */\n setAttachmentLink: (options: InsertAttachmentOptions) => ReturnType;\n };\n }\n}\n\nexport const AttachmentLink = Node.create({\n name: 'attachmentLink',\n\n inline: true,\n\n group: 'inline',\n\n atom: true,\n\n selectable: true,\n\n draggable: true,\n\n addAttributes() {\n return attachmentAttributes();\n },\n\n parseHTML() {\n // priority 不能省:StarterKit 默认带 Link 扩展,它的 a[href] 规则会把这枚\n // <a> 抢成\"链接 mark 包文字\",节点就静默退化成普通超链接\n return [\n {\n tag: 'a[data-atri-attachment-link]',\n priority: 100,\n // 手写或从别处粘来的 <a> 只带 href,没有 data-src:认下来,别留一枚空链接。\n // data-src 那条来路在 attachmentAttributes().src.parseHTML 里已经洗过\n getAttrs: (element) =>\n element.getAttribute('data-src') ? {} : { src: safeHref(element.getAttribute('href')) },\n },\n ];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n const { src, name } = node.attrs;\n\n return [\n 'a',\n mergeAttributes(HTMLAttributes, {\n 'data-atri-attachment-link': '',\n class: 'atri-attachment-link',\n // 协议不合法就整枚 href 都不写,退化成一枚没有地址的文字,\n // 而不是留着 href=\"javascript:…\" 等用户点\n ...dataAttr('href', safeHref(src)),\n // download 让同源地址点击即下载(跨域浏览器会退化成导航);\n // 值为 null 时 PM 序列化会跳过,没名字就只剩普通链接\n download: name,\n rel: 'noopener noreferrer',\n }),\n ['span', { class: 'atri-attachment-icon', 'aria-hidden': 'true' }],\n attachmentLabel(name, src),\n ];\n },\n\n addCommands() {\n return {\n setAttachmentLink: (options) => (props) => insertAttachmentContent(props, this.name, options),\n };\n },\n\n ...makeAttachmentMarkdown({\n name: 'filelink',\n level: 'inline',\n nodeName: 'attachmentLink',\n }),\n});\n","/**\n * DOM 工具函数\n */\n\n/**\n * 解析元素选择器,返回 HTMLElement\n */\nexport function resolveElement(element: string | HTMLElement): HTMLElement {\n if (typeof element === 'string') {\n const el = document.querySelector(element);\n if (!el) {\n throw new Error(`Element not found: ${element}`);\n }\n return el as HTMLElement;\n }\n return element;\n}\n\n/**\n * 创建带 class 的 div 元素\n */\nexport function createContainer(className?: string): HTMLDivElement {\n const div = document.createElement('div');\n if (className) {\n div.className = className;\n }\n return div;\n}\n\n/**\n * 从拖放/剪贴板事件里取文件列表\n * 剪贴板里的截图与文件都在 files 上,text 为空不代表没有可投的东西\n */\nexport function filesOf(transfer: DataTransfer | null | undefined): File[] {\n if (!transfer) return [];\n return Array.from(transfer.files);\n}\n","/**\n * MediaFiles - 拖放与剪贴板投放本地文件\n *\n * 只做事件到 MediaRuntime 的转接:校验、限流、插入与回写都在运行时里,\n * 面板选文件走的是同一条路。\n */\nimport { Extension } from '@tiptap/core';\nimport { Plugin } from '@tiptap/pm/state';\nimport type { EditorView } from '@tiptap/pm/view';\nimport type { MediaRuntime } from '../media/MediaRuntime';\nimport { filesOf } from '../utils/dom';\n\nexport interface MediaFilesOptions {\n runtime: MediaRuntime | null;\n}\n\n/**\n * 投放点:坐标落在哪个块就插到哪儿,而不是永远跟着当前选区走。\n * 只算位置、不动选区——TextSelection.near 在找不到文本位置时会退化成\n * AllSelection,那次插入就会把整篇文档替换掉。\n */\nfunction dropPos(view: EditorView, event: DragEvent): number | undefined {\n const result = view.posAtCoords({ left: event.clientX, top: event.clientY });\n if (!result) return undefined;\n\n // inside 是坐标所在块的 content 起点,比外层边界位置更贴近用户看到的那个位置\n return result.inside > -1 ? result.inside : result.pos;\n}\n\nexport const MediaFiles = Extension.create<MediaFilesOptions>({\n name: 'mediaFiles',\n\n addOptions() {\n return { runtime: null };\n },\n\n addProseMirrorPlugins() {\n const runtime = this.options.runtime;\n if (!runtime) return [];\n\n return [\n new Plugin({\n props: {\n /** 先于 PM 的剪贴板解析:带文件的粘贴(截图)交给上传管线,纯文本照常走默认链路 */\n handlePaste: (view, event) => {\n const files = filesOf(event.clipboardData);\n if (!files.length) return false;\n\n event.preventDefault();\n if (view.editable) void runtime.handleFiles(files);\n return true;\n },\n\n handleDrop: (view, event) => {\n const dragEvent = event as DragEvent;\n const files = filesOf(dragEvent.dataTransfer);\n if (!files.length) return false;\n\n // 不管能不能插,都要挡下浏览器默认的文件导航\n dragEvent.preventDefault();\n if (!view.editable) return true;\n\n void runtime.handleFiles(files, { pos: dropPos(view, dragEvent) });\n return true;\n },\n },\n }),\n ];\n },\n});\n","/**\n * 媒体扩展装配\n * 图片与附件是一等 Tiptap 扩展:需要 addCommands / addProseMirrorPlugins / markdown 三件套,\n * 而 ExtensionManager.registerNodeView 合成的节点声明不了这些,所以不走那条路\n */\nimport type { EditorOptions } from '@tiptap/core';\nimport type { AtriMediaConfig } from '../types';\nimport type { MediaRuntime } from '../media/MediaRuntime';\nimport { AtriImage } from './AtriImage';\nimport { Attachment } from './Attachment';\nimport { AttachmentLink } from './AttachmentLink';\nimport { MediaFiles } from './MediaFiles';\n\n/** 缩放下限:再小手柄就点不中了 */\nconst MIN_RESIZE_SIZE = 60;\n\nexport function createMediaExtensions(\n media?: AtriMediaConfig | false,\n runtime?: MediaRuntime | null\n): NonNullable<EditorOptions['extensions']> {\n if (media === false) return [];\n\n const image = media?.image;\n const resizable = image?.resize ?? true;\n\n // 附件的 !file[...]() 语法由 Markdown 扩展注册到 marked 后才生效;\n // Markdown 未启用时该节点仍可正常插入与序列化 HTML,只是读不到这行字面语法\n return [\n AtriImage.configure({\n inline: image?.inline ?? false,\n // allowBase64 只管一件事:img[src^=\"data:\"] 解不解得回来(stock Image 里它只出现在\n // parseHTML)。失败后内联写进文档的就是 data URL,而重建编辑器按 getHTML() 回填,\n // 不一起开的话那张图重建一次就静默没了\n allowBase64: (image?.allowBase64 ?? false) || (image?.fallbackToBase64 ?? false),\n resize: resizable\n ? {\n enabled: true,\n minWidth: MIN_RESIZE_SIZE,\n minHeight: MIN_RESIZE_SIZE,\n alwaysPreserveAspectRatio: true,\n }\n : false,\n }),\n Attachment,\n // 行内链接形态:与卡片是两个节点类型(PM 的 inline/group 静态声明),切换靠命令换节点\n AttachmentLink,\n // 拖放与剪贴板投放都汇到 runtime.handleFiles,与面板选文件同一条路\n MediaFiles.configure({ runtime: runtime ?? null }),\n ];\n}\n","/**\n * HTML 转义\n *\n * 只服务一件事:把用户或远端来的文本拼成 HTML 字符串(见 utils/markdown.ts 的降级路径)。\n * 走 ProseMirror schema 的正常渲染不需要这里——DOMOutputSpec 的文本与属性由 PM 自己处理,\n * 手工转义反而转出新实体。\n */\n\nconst TEXT_CHARS: Readonly<Record<string, string>> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n};\n\nconst QUOTE_CHARS: Readonly<Record<string, string>> = {\n '\"': '&quot;',\n \"'\": '&#39;',\n};\n\nfunction replaceAll(\n value: string,\n table: Readonly<Record<string, string>>,\n pattern: RegExp\n): string {\n return value.replace(pattern, (char) => table[char] ?? char);\n}\n\n/**\n * 转义文本节点内容:挡住 `<img src=x onerror=…>` 这类标签破出。\n * 不动引号,所以已确定要进属性的文本还得再过一道 escapeQuotes。\n */\nexport function escapeHtml(value: string): string {\n return replaceAll(value, TEXT_CHARS, /[&<>]/g);\n}\n\n/**\n * 转义引号:`[x](y\" onclick=\"z)` 会闭合掉属性定界符,把 URL 尾巴变成新属性。\n *\n * 与 escapeHtml 是两个独立步骤而不是一个 escapeAttr,因为拼 HTML 的正确顺序是\n * \"整段先过 escapeHtml、再按语法片段插值\"——那时捕获到的文本里 `&` 已经是 `&amp;`,\n * 再跑一遍全量转义就成了 `&amp;amp;`,双重转义会把地址改坏。\n */\nexport function escapeQuotes(value: string): string {\n return replaceAll(value, QUOTE_CHARS, /[\"']/g);\n}\n\nconst DECODED: Readonly<Record<string, string>> = {\n '&amp;': '&',\n '&lt;': '<',\n '&gt;': '>',\n '&quot;': '\"',\n '&#39;': \"'\",\n};\n\n/**\n * escapeHtml + escapeQuotes 的逆运算。\n *\n * 只有一处需要它:内联 HTML 片段的 Markdown 往返(见 extensions/markdown-inline-html.ts)。\n * 那边发出时把值转义成 &amp;,读回来却不会再解码,于是同一个值每导出一次就多背一层\n * 转义(& → &amp; → &amp;amp;),getMarkdown→setMarkdown→getMarkdown 直接不幂等。\n * 发与收必须成对改动,只改一边就把幂等性换掉了。\n */\nexport function decodeEntities(value: string): string {\n return value.replace(/&(?:amp|lt|gt|quot|#39);/g, (entity) => DECODED[entity] ?? entity);\n}\n","/**\n * 内联 HTML 片段的 Markdown 双向支持\n *\n * Markdown 没有\"颜色 / 字号 / 上下标\"的表示法,上游对这些 mark 一个 renderMarkdown 钩子\n * 都没给,于是 @tiptap/markdown 会把它们**静默丢掉**(MarkdownManager 的 getMarkOpening\n * 在没有钩子时返回空串),正文原样输出、样式无痕消失。这里给它们补上钩子,落点是内联 HTML。\n *\n * 为什么必须自己写 tokenizer、而不能只挂 renderMarkdown 让 marked 的通用 html token 接回去:\n * 实测那样做 `<span style=\"…\">**b**</span>` 里的 `**b**` 会回来成字面文本——因为\n * parseHTMLToken 走的是 generateJSON,即 **HTML 解析而不是 Markdown 解析**,\n * 片段内部的语法永远不会被重新分词。后果不只是丢格式:正文里的反斜杠每导出一次翻一倍,\n * getMarkdown → setMarkdown → getMarkdown 不幂等,对内容编辑器是最不能接受的一类缺陷。\n * 所以内层必须显式重新分词——用 tokenizer 收到的第三个参数 lexer.inlineTokens,\n * 它的类型注释就写着\"useful while tokenizing child tokens\"。\n */\nimport type { MarkdownLexerConfiguration, MarkdownParseHelpers, MarkdownToken } from '@tiptap/core';\n\n/** 从 style 属性里认出来的属性名 → mark 属性名 */\nexport type StyleProp = 'color' | 'backgroundColor' | 'fontSize' | 'fontFamily';\n\nconst STYLE_TO_ATTR: Record<string, StyleProp> = {\n color: 'color',\n 'background-color': 'backgroundColor',\n 'font-size': 'fontSize',\n 'font-family': 'fontFamily',\n};\n\nconst ATTR_TO_STYLE: Record<StyleProp, string> = {\n color: 'color',\n backgroundColor: 'background-color',\n fontSize: 'font-size',\n fontFamily: 'font-family',\n};\n\n/**\n * 空串要当\"没有\":上游那几个 addGlobalAttributes 的默认值在被 HTML 解析回来时\n * 给出的是空串而不是 null(`<span style=\"\">` 甚至真的会产出一个空属性 textStyle mark),\n * 只判 null 就会拼出 `<span style=\";;\">` 这种噪音。\n */\nfunction hasValue(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\n/** `style=\"color: red; font-size: 20px\"` → { color: 'red', fontSize: '20px' } */\nexport function styleAttrToAttrs(styleText: string): Partial<Record<StyleProp, string>> {\n const attrs: Partial<Record<StyleProp, string>> = {};\n for (const declaration of styleText.split(';')) {\n const separator = declaration.indexOf(':');\n if (separator < 1) continue;\n const prop = declaration.slice(0, separator).trim().toLowerCase();\n const value = declaration.slice(separator + 1).trim();\n const mapped = STYLE_TO_ATTR[prop];\n if (mapped && hasValue(value)) attrs[mapped] = value;\n }\n return attrs;\n}\n\n/** mark 属性 → style 字符串;一个都没有时返回空串,调用方据此不发标签 */\nexport function attrsToStyleCss(attrs: Record<string, unknown> = {}): string {\n const declarations: string[] = [];\n for (const attr of Object.keys(ATTR_TO_STYLE) as StyleProp[]) {\n const value = attrs[attr];\n if (hasValue(value)) declarations.push(`${ATTR_TO_STYLE[attr]}: ${value}`);\n }\n return declarations.join('; ');\n}\n\nexport interface InlineHtmlToken extends MarkdownToken {\n /** 开始标签与它之间的原始文本,如 ` style=\"color: red\"` */\n openAttrs?: string;\n /** 内层已重新分词的结果,由 tokenize 通过官方 lexer.inlineTokens 产出 */\n tokens?: MarkdownToken[];\n}\n\n/**\n * 一个内联标签的 tokenizer + parseMarkdown。\n *\n * 只负责\"读进来\"这一侧;发出去那一侧(renderMarkdown)必须挂在 mark 自己身上,\n * 因为序列化器是按 mark 类型去查钩子的。\n */\nexport function makeInlineHtmlReader(options: {\n /** marked 的 token 名,同时是 markdownTokenName */\n tokenName: string;\n /** 起始标签匹配片段,需自带捕获组给出标签名与内层文本 */\n pattern: RegExp;\n /** 快速定位用,避免每次都对整段源码跑正则 */\n begins: string[];\n /** token → { markName, attrs };返回 null 表示这个 token 不该产出 mark */\n resolve: (token: InlineHtmlToken) => { markName: string; attrs: Record<string, string> } | null;\n}) {\n const { tokenName, pattern, begins, resolve } = options;\n\n return {\n markdownTokenName: tokenName,\n\n markdownTokenizer: {\n name: tokenName,\n level: 'inline' as const,\n start: (src: string) => {\n let earliest = -1;\n for (const needle of begins) {\n const at = src.indexOf(needle);\n if (at >= 0 && (earliest < 0 || at < earliest)) earliest = at;\n }\n return earliest;\n },\n tokenize: (src: string, _tokens: MarkdownToken[], lexer: MarkdownLexerConfiguration) => {\n const match = pattern.exec(src);\n if (!match) return undefined;\n return {\n type: tokenName,\n raw: match[0],\n text: match[2] ?? '',\n openAttrs: match[1] ?? '',\n // 这一步是整个设计的关键:不在这里重分词,内层的 **粗体** 与转义就会\n // 在 parse 阶段被当成字面文本,进而每导出一次转义翻一倍\n tokens: lexer.inlineTokens(match[2] ?? ''),\n };\n },\n },\n\n /**\n * token.tokens 已经是分好词的内层,直接 parseInline。\n *\n * resolve 认不出属性时(`<span class=\"x\">` 这种)照样解析内层、只是不套 mark——\n * 把整个 token 丢给 parseInline 会让里面的文字一起消失。\n */\n parseMarkdown: (token: InlineHtmlToken, helpers: MarkdownParseHelpers) => {\n const content = helpers.parseInline(token.tokens ?? []);\n const resolved = resolve(token);\n return resolved ? helpers.applyMark(resolved.markName, content, resolved.attrs) : content;\n },\n };\n}\n","/**\n * 基础格式扩展装配(文字颜色 / 背景色 / 字号 / 字体 / 高亮 / 上下标)\n *\n * 这些 mark 在 Tiptap 里都是零运行时依赖的小包,代价全在\" Markdown 往返\"这一侧:\n * 上游除 Highlight 的 `==x==` 之外一个 renderMarkdown 都没给,序列化器对没钩子的 mark\n * 是静默丢弃。所以本文件的主体不是注册扩展,而是给它们补发与补收,详见 markdown-inline-html.ts。\n *\n * 行高(LineHeight)刻意不装:上游它是 span 级属性,挂在选中的文字片段上,\n * 而用户心里的行高是段落属性——span 级改一下几乎看不出变化,还不如不给这个按钮。\n * 它和颜色同属一个 textStyle mark,将来要给就走同一条 attrsToStyleCss 路径加一项即可。\n */\nimport { Extension, type EditorOptions } from '@tiptap/core';\nimport Highlight from '@tiptap/extension-highlight';\nimport Subscript from '@tiptap/extension-subscript';\nimport Superscript from '@tiptap/extension-superscript';\nimport {\n BackgroundColor,\n Color,\n FontFamily,\n FontSize,\n TextStyle,\n} from '@tiptap/extension-text-style';\nimport { escapeHtml, escapeQuotes, decodeEntities } from '../utils/escape';\nimport { attrsToStyleCss, makeInlineHtmlReader, styleAttrToAttrs } from './markdown-inline-html';\n\n/** 发出时转义,读回时 decodeEntities 逆掉——两边必须成对 */\nfunction emitAttrValue(value: string): string {\n return escapeQuotes(escapeHtml(value));\n}\n\n/** mark 属性 → `<span style=\"…\">`;一个属性都没有就不发标签,免得产出一堆空 span */\nfunction renderStyleSpan(node: { attrs?: Record<string, unknown> }, inner: string): string {\n const css = attrsToStyleCss(node.attrs);\n if (!css) return inner;\n return `<span style=\"${emitAttrValue(css)}\">${inner}</span>`;\n}\n\nconst StyledTextStyle = TextStyle.extend({\n renderMarkdown: (node, helpers) => renderStyleSpan(node, helpers.renderChildren(node)),\n});\n\nconst StyledSubscript = Subscript.extend({\n renderMarkdown: (_node, helpers) => `<sub>${helpers.renderChildren(_node)}</sub>`,\n});\n\nconst StyledSuperscript = Superscript.extend({\n renderMarkdown: (_node, helpers) => `<sup>${helpers.renderChildren(_node)}</sup>`,\n});\n\n/**\n * 高亮有两个出口,分色与否决定用哪个:\n * 无色走 Markdown 原生的 `==x==`(上游本来就这样,但我们要覆盖 renderMarkdown 来加彩色分支,\n * 所以这一半也得自己写回来);有色走 `<mark data-color>`——上游的 `==${children}==`\n * 从不读 attrs,multicolor 一开颜色就在导出时丢掉。\n */\nconst StyledHighlight = Highlight.configure({ multicolor: true }).extend({\n renderMarkdown: (node, helpers) => {\n const inner = helpers.renderChildren(node);\n const color = node.attrs?.color;\n if (typeof color !== 'string' || color.trim() === '') return `==${inner}==`;\n return `<mark data-color=\"${emitAttrValue(color)}\" style=\"background-color: ${emitAttrValue(color)}; color: inherit\">${inner}</mark>`;\n },\n});\n\nconst StyledSpanReader = Extension.create({\n // Reader 也得有名有姓:Extension.create 不给 name 就会落成空串,\n // 三个匿名扩展同名会撞上 tiptap 的 Duplicate extension names 警告\n name: 'atriStyledSpanReader',\n ...makeInlineHtmlReader({\n tokenName: 'atriStyledSpan',\n // 非贪婪:我们的发出侧不会产出血缘嵌套的 span(同一个 mark 只出一个标签),\n // 遇到外来的嵌套就退回内层文本,不抛\n pattern: /^<span([^>]*)>([\\s\\S]*?)<\\/span>/,\n begins: ['<span'],\n resolve: (token) => {\n const style = /(?:^|\\s)style=\"([^\"]*)\"/i.exec(token.openAttrs ?? '')?.[1];\n if (!style) return null;\n const attrs = styleAttrToAttrs(decodeEntities(style));\n return Object.keys(attrs).length > 0 ? { markName: 'textStyle', attrs } : null;\n },\n }),\n});\n\n/**\n * `<sub>` / `<sup>` 不能借用 `~x~` / `^x^` 那类写法:实测 marked 把单波浪线当 GFM 删除线,\n * `H~2~O` 回来是 `<s>2</s>`,下标没做成还平白吃掉一个删除线语法。\n * 而上游这两个 mark 自己也没有任何 markdown 钩子,所以只有内联 HTML 这一条路。\n */\nconst SubSupReader = Extension.create({\n name: 'atriInlineTagReader',\n ...makeInlineHtmlReader({\n tokenName: 'atriInlineTag',\n pattern: /^<(sub|sup)>([\\s\\S]*?)<\\/\\1>/,\n begins: ['<sub>', '<sup>'],\n resolve: (token) => {\n const tag = (token.openAttrs ?? '').toLowerCase();\n if (tag !== 'sub' && tag !== 'sup') return null;\n return { markName: tag === 'sup' ? 'superscript' : 'subscript', attrs: {} };\n },\n }),\n});\n\n/**\n * `<mark>` 的 data-color 上游的 parseHTML 本来就认(入向免费),但上游的 tokenizer 只认\n * `==`;这里补的是\"发出去那版的对偶\",让 `<mark data-color>` 形式的输入也走重分词那条路,\n * 从而和内层 markdown 共存。\n */\nconst HighlightReader = Extension.create({\n name: 'atriStyledMarkReader',\n ...makeInlineHtmlReader({\n tokenName: 'atriStyledMark',\n pattern: /^<mark([^>]*)>([\\s\\S]*?)<\\/mark>/,\n begins: ['<mark'],\n resolve: (token) => {\n const color = /data-color=\"([^\"]*)\"/i.exec(token.openAttrs ?? '')?.[1];\n if (!color) return null;\n return { markName: 'highlight', attrs: { color: decodeEntities(color) } };\n },\n }),\n});\n\nexport interface AtriFormattingOptions {\n /** 'drop' 时不挂发出侧的钩子,颜色类样式导出 Markdown 即丢失(入向仍会解析) */\n inlineStyles?: 'html' | 'drop';\n}\n\n/**\n * 构造基础格式扩展。\n *\n * 三个 Reader 与 inlineStyles 无关,'drop' 只关掉发出侧:粘贴与外来 .md 里的\n * `<span style>` 仍然应该解析成 mark,否则关掉一个导出偏好会连带把导入能力也削掉。\n */\nexport function createFormattingExtensions(\n options: AtriFormattingOptions = {}\n): NonNullable<EditorOptions['extensions']> {\n const emit = options.inlineStyles !== 'drop';\n\n return [\n emit ? StyledTextStyle : TextStyle,\n Color,\n BackgroundColor,\n FontFamily,\n FontSize,\n emit ? StyledHighlight : Highlight.configure({ multicolor: true }),\n emit ? StyledSubscript : Subscript,\n emit ? StyledSuperscript : Superscript,\n StyledSpanReader,\n SubSupReader,\n HighlightReader,\n ];\n}\n","/**\n * Uploader - 上传通道\n *\n * 两条路:用户给 handler 就完全接管;给 endpoint 就走内置 XHR。\n * 选 XHR 而不是 fetch,是因为 fetch 没有可移植的上传进度事件(xhr.upload.onprogress)。\n */\nimport type { AtriUploadConfig, UploadContext, UploadHandler, UploadResult } from '../types';\nimport { safeMediaSrc } from '../utils/url';\n\nexport interface Uploader {\n upload(file: File, context: UploadContext): Promise<UploadResult>;\n}\n\n/**\n * 响应体不是 JSON 时,整段字符串会被当作地址——但网关返回 HTML 错误页也走这条路,\n * 于是 `src=\"<!DOCTYPE html>\"` 就写进了文档。尖括号与换行是 HTML 的可靠特征,\n * 这里只按它们判:空格不动,同源相对路径 `/files/my doc.pdf` 还得留得住。\n */\nfunction usableUrl(candidate: string): string | null {\n const value = candidate.trim();\n if (!value || /[<>\\n\\r]/.test(value)) return null;\n return safeMediaSrc(value);\n}\n\n/** 从五花八门的响应体里挑出可用的地址 */\nfunction pickUrl(body: unknown): string | undefined {\n if (!body || typeof body !== 'object') return undefined;\n\n const record = body as Record<string, unknown>;\n // 部分接口把地址包在 data 里,响应体不是 JSON 时整段当地址字符串处理(见调用处)\n const nested = record.data;\n const candidates = [\n record.url,\n record.location,\n nested && typeof nested === 'object' ? (nested as Record<string, unknown>).url : undefined,\n ];\n const url = candidates.find(\n (value): value is string => typeof value === 'string' && value !== ''\n );\n\n return url;\n}\n\nfunction uploadViaEndpoint(\n config: AtriUploadConfig,\n file: File,\n context: UploadContext\n): Promise<UploadResult> {\n return new Promise<UploadResult>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.open('POST', config.endpoint, true);\n xhr.withCredentials = config.withCredentials ?? false;\n\n Object.entries(config.headers ?? {}).forEach(([key, value]) => {\n xhr.setRequestHeader(key, value);\n });\n\n xhr.upload.onprogress = (event) => {\n if (!event.lengthComputable || !event.total) return;\n context.onProgress({\n percent: Math.round((event.loaded / event.total) * 100),\n loaded: event.loaded,\n total: event.total,\n });\n };\n\n xhr.onload = () => {\n if (xhr.status < 200 || xhr.status >= 300) {\n reject(new Error(`上传失败:HTTP ${xhr.status}`));\n return;\n }\n\n let body: unknown = null;\n try {\n body = xhr.responseText ? JSON.parse(xhr.responseText) : null;\n } catch {\n // 有些接口直接返回地址字符串\n body = xhr.responseText;\n }\n\n if (config.transformResult) {\n // 宿主函数在 XHR 回调里同步调用:抛出来这条 promise 就永远不落定,\n // 卡片停在\"上传中\"、hasPendingUploads() 一直 true——宿主的保存闸门被卡死\n let transformed: unknown;\n try {\n transformed = config.transformResult(body, file);\n } catch (cause) {\n reject(new Error(`上传失败:upload.transformResult 抛出了异常(${String(cause)})`));\n return;\n }\n\n const result = transformed as Partial<UploadResult> | null;\n const picked = result && typeof result === 'object' ? result.url : undefined;\n const url = typeof picked === 'string' ? usableUrl(picked) : null;\n\n if (!url) {\n reject(\n new Error(\n `上传失败:upload.transformResult 没有返回带可用 url 的 UploadResult(拿到 ${JSON.stringify(picked)})`\n )\n );\n return;\n }\n\n resolve({ ...(result as UploadResult), url });\n return;\n }\n\n // 响应本身就是地址字符串时也认,省掉一个必填配置\n const candidate = typeof body === 'string' ? body : pickUrl(body);\n const url = candidate ? usableUrl(candidate) : null;\n\n if (!url) {\n reject(new Error('上传失败:响应里找不到文件地址,请用 upload.transformResult 指定'));\n return;\n }\n\n resolve({ url });\n };\n\n xhr.onerror = () => reject(new Error('上传失败:网络错误'));\n xhr.onabort = () => reject(new DOMException('Aborted', 'AbortError'));\n xhr.ontimeout = () => reject(new Error(`上传失败:超过 ${config.timeout}ms 未响应`));\n // 0 正是 XHR 的\"不超时\"默认值,所以没配 timeout 时行为与之前完全一致\n xhr.timeout = config.timeout ?? 0;\n\n if (context.signal.aborted) {\n xhr.abort();\n return;\n }\n context.signal.addEventListener('abort', () => xhr.abort(), { once: true });\n\n const form = new FormData();\n form.append(config.fieldName ?? 'file', file, config.requestName?.(file) ?? file.name);\n xhr.send(form);\n });\n}\n\nexport function createUploader(upload?: AtriUploadConfig | UploadHandler): Uploader | null {\n if (!upload) return null;\n\n if (typeof upload === 'function') {\n return { upload: (file, context) => upload(file, context) };\n }\n\n if (!upload.endpoint) return null;\n\n return { upload: (file, context) => uploadViaEndpoint(upload, file, context) };\n}\n","/**\n * 宿主回调隔离\n *\n * 编辑器在 Tiptap 的事件里同步调用宿主传入的 onCreate/onChange/onError/…,\n * 一个抛错的回调会把整条 dispatch 链打断——症状是\"敲了键但文档没变\",\n * 排查方向还会被带到编辑器身上。回调是外部输入,边界处就得自己兜住。\n */\n\n/** 同一个回调引用只报一次:坏掉的 onUpdate 每敲一键触发一次,不去重就是刷屏 */\nconst reported = new WeakSet<object>();\n\n/**\n * 调用宿主回调,异常降级成一条日志。\n *\n * 非函数(宿主没配这个钩子)静默返回,不占用去重名额。\n * 只用于包宿主的函数,库内部逻辑不要经这里——那会把真正的 bug 也吞成日志。\n */\nexport function runSafely(callback: unknown, label: string, args: unknown[] = []): void {\n if (typeof callback !== 'function') return;\n\n try {\n callback(...args);\n } catch (error) {\n if (reported.has(callback)) return;\n reported.add(callback);\n console.error(`[Atri Editor] The \"${label}\" callback threw and was skipped:`, error);\n }\n}\n","/**\n * MediaRuntime - 上传队列与节点瞬时态\n *\n * 生命周期比 CoreEditor 长:registerNodeView 会重建编辑器,进行中的上传不能跟着丢,\n * 所以它由 AtriEditor 持有一个实例,每次重建通过 bind() 换绑到新的 Editor。\n */\nimport type { Content, Editor } from '@tiptap/core';\nimport type { Node as PmNode } from '@tiptap/pm/model';\nimport type {\n AtriMediaConfig,\n MediaKind,\n MediaRejectReason,\n UploadProgress,\n UploadResult,\n} from '../types';\nimport { createUploader, type Uploader } from './Uploader';\nimport { mediaInsertTarget } from './insert-position';\nimport { runSafely } from '../utils/safe-call';\nimport { DEFAULT_MAX_FILES, acceptAttribute, isImageFile, validateFile } from './file-policy';\n\n/** 节点瞬时态,写进 data-atri-upload-status */\nexport type MediaStatus = 'uploading' | 'error';\n\n/** 队列快照,供状态条这类外部展示消费 */\nexport interface MediaState {\n uploading: number;\n /** 失败且内容还只存在于本地预览里的,丢了就是真丢了 */\n failed: number;\n /** 失败后已按配置内联成 data URL 的图片:不必再传也不该丢 */\n inline: number;\n /** 按文件体积加权的整体进度,无进行中任务时为 0 */\n percent: number;\n}\n\nexport interface HandleFilesOptions {\n /** 缺省时按 MIME 分流到图片与附件 */\n kind?: MediaKind;\n /** 落点位置(拖放用);省略时插在当前选区 */\n pos?: number;\n}\n\n/** 通过校验、排队待插的文件与它要落成的节点类型 */\ninterface MediaEntry {\n file: File;\n kind: MediaKind;\n}\n\ninterface MediaTask {\n id: string;\n kind: MediaKind;\n file: File;\n /** 落地前的本地预览地址,也是编辑器重建后找回节点的钥匙(uploadId 不进 HTML) */\n previewUrl: string | null;\n status: MediaStatus;\n /** 未收到任何进度事件时为 null,让进度条走不定长动画 */\n percent: number | null;\n /** 失败后已内联成 data URL:src 不再指向 previewUrl,但节点仍标着 error 等重试 */\n inlined: boolean;\n controller: AbortController;\n}\n\n/** 读文件为 data URL:本地文件仅有的两条不经过服务端的退路都靠它,前提是图片认 data URL */\nfunction readFileAsDataUrl(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(String(reader.result));\n reader.onerror = () => reject(reader.error ?? new Error('读取文件失败'));\n reader.readAsDataURL(file);\n });\n}\n\nexport class MediaRuntime {\n private config: AtriMediaConfig;\n private uploader: Uploader | null;\n private editor: Editor | null = null;\n private tasks: Map<string, MediaTask> = new Map();\n private listeners: Set<(state: MediaState) => void> = new Set();\n private sequence = 0;\n\n constructor(config: AtriMediaConfig = {}) {\n this.config = config;\n this.uploader = createUploader(config.upload);\n }\n\n /**\n * 换绑到新的 Editor 实例,并把瞬时态补回重建后的文档\n */\n bind(editor: Editor): void {\n this.editor = editor;\n this.restoreTransientState();\n\n // 上传中删掉卡片、或撤销掉刚插入的节点时,顺手把任务摘掉,\n // 否则 hasPendingUploads() 会一直为 true\n editor.on('transaction', ({ transaction }) => {\n if (transaction.docChanged && this.tasks.size) this.prune();\n });\n }\n\n /**\n * 处理一批文件:校验 → 插节点 → 上传 → 回写\n * kind 缺省时按 MIME 分流;MediaKind 与节点名一一对应,直接当 type 用\n */\n async handleFiles(files: File[] | FileList, options: HandleFilesOptions = {}): Promise<void> {\n if (!this.editor) return;\n\n const { kind, pos } = options;\n const maxFiles = this.config.maxFiles ?? DEFAULT_MAX_FILES;\n const queue: MediaEntry[] = [];\n\n for (const file of Array.from(files)) {\n const mediaKind = kind ?? (isImageFile(file) ? 'image' : 'attachment');\n const accept =\n mediaKind === 'image' ? this.config.image?.accept : this.config.attachment?.accept;\n const validation = validateFile(file, { accept, maxFileSize: this.config.maxFileSize });\n\n if (!validation.ok) {\n this.reject(file, validation.reason);\n continue;\n }\n\n // 先校验后限流:既不合格又超额的文件只报它自己的毛病,不报 too-many\n if (queue.length >= maxFiles) {\n this.reject(file, 'too-many');\n continue;\n }\n\n queue.push({ file, kind: mediaKind });\n }\n\n if (!this.uploader) {\n await this.inlineImages(queue, pos);\n return;\n }\n\n const tasks = queue.map(({ file, kind: mediaKind }) => this.createTask(file, mediaKind));\n this.insertContents(\n tasks.map((task) => this.nodeSpec(task)),\n pos\n );\n await Promise.all(tasks.map((task) => this.startUpload(task)));\n }\n\n /**\n * 该类型的文件白名单,已归一成 <input accept> 能用的写法\n * 面板与校验共用同一份配置,免得选得到却传不了\n */\n acceptFor(kind: MediaKind): string | undefined {\n return acceptAttribute(\n kind === 'image' ? this.config.image?.accept : this.config.attachment?.accept\n );\n }\n\n /**\n * 重试所有失败的上传,卡片原地回到上传中\n * 失败后已内联成 data URL 的图片也在其中:换到服务端地址前它一直算\"失败\"\n */\n retryFailed(): Promise<void> {\n const retried = Array.from(this.tasks.values())\n .filter((task) => task.status === 'error')\n .map((task) => this.startUpload(task));\n\n return Promise.all(retried).then(() => undefined);\n }\n\n /**\n * 文档里是否还有\"现在就保存会丢内容\"的文件:上传中与失败都算,\n * 因为它们的 src 还指着本地预览地址(blob:),刷新即失效\n *\n * 失败后已按配置内联成 data URL 的图片不计入——内容已经安全落进文档,\n * 服务端地址只是可选的优化,别让一个一直失败的上传把保存永久卡住\n */\n hasPendingUploads(): boolean {\n return Array.from(this.tasks.values()).some((task) => !task.inlined);\n }\n\n getState(): MediaState {\n const tasks = Array.from(this.tasks.values());\n const uploading = tasks.filter((task) => task.status === 'uploading');\n const total = uploading.reduce((sum, task) => sum + task.file.size, 0);\n const loaded = uploading.reduce(\n (sum, task) => sum + (task.file.size * clampPercent(task.percent ?? 0)) / 100,\n 0\n );\n\n return {\n uploading: uploading.length,\n failed: tasks.filter((task) => task.status === 'error' && !task.inlined).length,\n inline: tasks.filter((task) => task.inlined).length,\n percent: total ? Math.round((loaded / total) * 100) : 0,\n };\n }\n\n subscribe(listener: (state: MediaState) => void): () => void {\n this.listeners.add(listener);\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n destroy(): void {\n this.tasks.forEach((task) => {\n task.controller.abort();\n revokePreviewUrl(task);\n });\n this.tasks.clear();\n\n this.listeners.clear();\n this.editor = null;\n }\n\n /**\n * 无上传通道:图片按配置退化成 base64 内联,附件没有合理退路\n * 先把文件都读完再一起插,顺序才与选文件时一致\n */\n private async inlineImages(entries: MediaEntry[], pos?: number): Promise<void> {\n const contents: Content[] = [];\n\n for (const { file, kind } of entries) {\n if (kind !== 'image' || !this.config.image?.allowBase64) {\n this.reject(file, 'no-upload');\n continue;\n }\n\n try {\n const src = await readFileAsDataUrl(file);\n contents.push({ type: 'image', attrs: { src, alt: file.name } });\n } catch (cause) {\n this.reject(file, 'upload-failed', cause);\n }\n }\n\n this.insertContents(contents, pos);\n }\n\n private createTask(file: File, kind: MediaKind): MediaTask {\n const task: MediaTask = {\n id: `atri-upload-${(this.sequence += 1)}`,\n kind,\n file,\n previewUrl: createPreviewUrl(file),\n status: 'uploading',\n percent: null,\n inlined: false,\n controller: new AbortController(),\n };\n this.tasks.set(task.id, task);\n return task;\n }\n\n /**\n * 带本地预览的节点内容;上传完这些地址就被服务端地址换掉\n */\n private nodeSpec(task: MediaTask): Content {\n const { file, previewUrl } = task;\n const attrs: Record<string, unknown> = {\n src: previewUrl ?? file.name,\n status: task.status,\n uploadId: task.id,\n };\n\n if (task.kind === 'attachment') {\n attrs.name = file.name;\n attrs.size = file.size;\n attrs.mime = file.type || null;\n } else {\n attrs.alt = file.name;\n }\n\n // 附件按配置的默认形态直接插成行内链接;补丁管线按 attrs 认节点,两种形态不用区分\n const type =\n task.kind === 'attachment' && this.config.attachment?.display === 'link'\n ? 'attachmentLink'\n : task.kind;\n\n return { type, attrs };\n }\n\n /**\n * 一批节点走一次事务:逐个插的话每插一个\"那个位置\"的含义就变一次,顺序会乱;\n * 顺带让一次多文件拖放只需撤销一步\n * pos 为拖放落点,省略时落在当前选区(选中的媒体节点不被顶掉)\n */\n private insertContents(contents: Content[], pos?: number): void {\n const editor = this.editor;\n if (!editor || !contents.length) return;\n\n editor\n .chain()\n .insertContentAt(pos ?? mediaInsertTarget(editor.state.selection), contents)\n .run();\n this.emit();\n }\n\n private async startUpload(task: MediaTask): Promise<void> {\n if (!this.uploader) return;\n\n task.status = 'uploading';\n task.percent = null;\n // 重试一张已内联的图:src 先留着 data URL(换地址前它仍是能看的图),\n // 但闸门回到\"有上传在飞\",再失败也会重新判一次要不要内联\n task.inlined = false;\n task.controller = new AbortController();\n this.patch(task, this.stateAttrs(task, 'uploading'));\n this.emit();\n\n try {\n const result = await this.uploader.upload(task.file, {\n onProgress: (progress) => this.updateProgress(task, progress),\n signal: task.controller.signal,\n });\n\n // 请求期间节点可能已被删掉,那时任务已经清理过了\n if (!this.tasks.has(task.id)) return;\n this.applyResult(task, result);\n } catch (cause) {\n if (task.controller.signal.aborted) {\n this.dropTask(task);\n return;\n }\n\n task.status = 'error';\n task.percent = null;\n\n // 只有图片有 base64 退路:附件内联等于把几 MB 塞进 !file[..](data:…) 那一行\n const canInline = task.kind === 'image' && this.config.image?.fallbackToBase64 === true;\n const inlined = canInline && (await this.inlineAfterFailure(task));\n\n if (!inlined) {\n this.patch(task, this.stateAttrs(task, 'error'));\n // patch 只改节点不广播,失败态得靠这一次 emit 才会上到状态条\n this.emit();\n }\n // 内联成不成功都该让宿主知道这次上传失败了;图片能不能显示是另一件事\n this.reject(task.file, 'upload-failed', cause);\n }\n }\n\n /**\n * 上传失败后把图片读成 data URL,原位换掉 src\n *\n * 是 patch 而不是插新节点:位置与\"撤销一步回到插入前\"这两条语义都靠它才不碎。\n * error 态留着——重试按钮还在,网络恢复后能把 data URL 换成服务端地址\n */\n private async inlineAfterFailure(task: MediaTask): Promise<boolean> {\n // 请求期间节点可能已被删掉,那时任务已经清理过了\n if (!this.tasks.has(task.id)) return false;\n\n let src: string;\n try {\n src = await readFileAsDataUrl(task.file);\n } catch {\n return false;\n }\n\n this.patch(task, { src, ...this.stateAttrs(task, 'error') });\n // patch 找不到节点时会把任务摘掉,那时没什么可内联的\n if (!this.tasks.has(task.id)) return false;\n\n task.inlined = true;\n // blob 地址已经不被文档引用了,留着就是白占一份文件内存\n revokePreviewUrl(task);\n this.emit();\n return true;\n }\n\n private applyResult(task: MediaTask, result: UploadResult): void {\n const { file } = task;\n const attrs: Record<string, unknown> = {\n src: result.url,\n status: null,\n uploadId: null,\n };\n\n if (task.kind === 'attachment') {\n attrs.progress = null;\n attrs.name = result.name ?? file.name;\n attrs.size = result.size ?? file.size;\n attrs.mime = result.mime ?? (file.type || null);\n } else {\n attrs.alt = result.name ?? file.name;\n }\n\n this.patch(task, attrs);\n this.tasks.delete(task.id);\n revokePreviewUrl(task);\n this.emit();\n }\n\n private updateProgress(task: MediaTask, progress: UploadProgress): void {\n if (task.status !== 'uploading') return;\n\n const percent = Number.isFinite(progress.percent) ? clampPercent(progress.percent) : null;\n task.percent = percent;\n\n // 图片节点上没有 progress 属性,逐文件百分比只画在附件卡片上;\n // 图片的整体进度走队列快照,顺带省掉每次进度都重建一次 DOM\n if (task.kind === 'attachment') {\n this.patch(task, { progress: percent }, { silent: true });\n }\n this.emit();\n }\n\n /**\n * 写进节点的那份状态;附件卡片额外带百分比\n */\n private stateAttrs(\n task: MediaTask,\n status: MediaStatus,\n progress: number | null = null\n ): Record<string, unknown> {\n return task.kind === 'attachment' ? { status, progress } : { status };\n }\n\n private patch(\n task: MediaTask,\n attrs: Record<string, unknown>,\n options: { silent?: boolean } = {}\n ): void {\n const editor = this.editor;\n // 编辑器正在重建时丢掉这次更新:bind() 之后按队列现状补齐\n if (!editor || editor.isDestroyed) return;\n\n const position = this.findPosition(task);\n if (position === null) {\n // 找不到节点说明用户删了它,继续传上去也没人接收\n task.controller.abort();\n this.dropTask(task);\n return;\n }\n\n const node = editor.state.doc.nodeAt(position);\n if (!node) return;\n\n const tr = editor.state.tr.setNodeMarkup(position, undefined, {\n ...node.attrs,\n ...attrs,\n });\n\n // 撤销一步回到插入前,而不是回到\"带预览地址的中间态\";\n // 代价是重做后节点退回预览地址,需要重新上传\n tr.setMeta('addToHistory', false);\n if (options.silent) tr.setMeta('preventUpdate', true);\n\n editor.view.dispatch(tr);\n }\n\n /**\n * 编辑器重建后 uploadId 不在 HTML 里,靠本地预览地址把瞬时态补回文档\n */\n private restoreTransientState(): void {\n const editor = this.editor;\n if (!editor || !this.tasks.size) return;\n\n const pending: { position: number; node: PmNode; task: MediaTask }[] = [];\n editor.state.doc.descendants((node, position) => {\n const task = this.taskByPreviewUrl(node.attrs.src);\n if (task && node.attrs.uploadId == null) pending.push({ position, node, task });\n });\n if (!pending.length) return;\n\n const tr = editor.state.tr;\n // setNodeMarkup 不改叶子节点尺寸,倒序写免得位置偏移\n pending\n .sort((a, b) => b.position - a.position)\n .forEach(({ position, node, task }) => {\n tr.setNodeMarkup(position, undefined, {\n ...node.attrs,\n uploadId: task.id,\n ...this.stateAttrs(task, task.status, task.percent),\n });\n });\n tr.setMeta('addToHistory', false);\n tr.setMeta('preventUpdate', true);\n editor.view.dispatch(tr);\n }\n\n /**\n * 丢掉文档里已经没有对应节点的任务,中止其请求\n */\n private prune(): void {\n const editor = this.editor;\n if (!editor || editor.isDestroyed) return;\n\n const alive = new Set<string>();\n editor.state.doc.descendants((node) => {\n if (typeof node.attrs.uploadId === 'string') alive.add(node.attrs.uploadId);\n if (typeof node.attrs.src === 'string') alive.add(node.attrs.src);\n });\n\n this.tasks.forEach((task) => {\n const exists = alive.has(task.id) || (task.previewUrl !== null && alive.has(task.previewUrl));\n if (exists) return;\n\n task.controller.abort();\n this.dropTask(task);\n });\n }\n\n private findPosition(task: MediaTask): number | null {\n const editor = this.editor;\n if (!editor) return null;\n\n return findTaskPosition(editor.state.doc, task);\n }\n\n private taskByPreviewUrl(src: unknown): MediaTask | null {\n if (typeof src !== 'string' || !src) return null;\n for (const task of this.tasks.values()) {\n if (task.previewUrl === src) return task;\n }\n return null;\n }\n\n /**\n * 摘掉任务并回收预览地址;是否 abort 由调用方决定\n */\n private dropTask(task: MediaTask): void {\n if (this.tasks.get(task.id) !== task) return;\n this.tasks.delete(task.id);\n revokePreviewUrl(task);\n this.emit();\n }\n\n private reject(file: File, reason: MediaRejectReason, cause?: unknown): void {\n if (this.config.onError) {\n // 回调里抛错不能把整条投放链路打断:reject 是在 handleFiles 的同步路径上叫的\n runSafely(this.config.onError, 'media.onError', [{ file, reason, cause }]);\n return;\n }\n\n // 没配回调也留一条日志:文件被静默丢弃是最难排查的那种\"点了没反应\"\n console.warn(`[Atri Editor] File \"${file.name}\" was rejected (${reason}).`);\n }\n\n private emit(): void {\n if (!this.listeners.size) return;\n const state = this.getState();\n this.listeners.forEach((listener) => listener(state));\n }\n}\n\nfunction clampPercent(percent: number): number {\n return Math.round(Math.min(100, Math.max(0, percent)));\n}\n\n/**\n * 找任务对应的节点位置。\n *\n * 预览地址这条兜底必须要求 previewUrl 非空:revokePreviewUrl 会把它置成 null,\n * 而 `node.attrs.src === null` 对任何还没落地地址的媒体节点都成立,\n * 于是进度与状态会写到第一张不相干的卡片上,真正的卡片反而永不更新。\n * uploadId 唯一且稳定,所以先按它找,找不到才退到预览地址。\n */\nexport function findTaskPosition(\n doc: PmNode,\n task: { id: string; previewUrl: string | null }\n): number | null {\n let byUploadId: number | null = null;\n let byPreviewUrl: number | null = null;\n\n doc.descendants((node, position) => {\n if (byUploadId === null && node.attrs.uploadId === task.id) byUploadId = position;\n if (byPreviewUrl === null && task.previewUrl !== null && node.attrs.src === task.previewUrl) {\n byPreviewUrl = position;\n }\n });\n\n return byUploadId ?? byPreviewUrl;\n}\n\n/**\n * 预览地址只在浏览器里有;拿不到时节点用文件名占位,上传完就被真实地址替换\n */\nfunction createPreviewUrl(file: File): string | null {\n if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') return null;\n try {\n return URL.createObjectURL(file);\n } catch {\n return null;\n }\n}\n\nfunction revokePreviewUrl(task: MediaTask): void {\n const { previewUrl } = task;\n if (!previewUrl) return;\n if (typeof URL === 'undefined' || typeof URL.revokeObjectURL !== 'function') return;\n\n URL.revokeObjectURL(previewUrl);\n task.previewUrl = null;\n}\n","/**\n * 选区浮动工具栏的判定:这一次要不要浮出、浮出哪一组\n *\n * 只读状态、不碰排版,BubbleMenu 的 shouldShow 直接喂这里的函数,\n * 单测也就不必先给 jsdom 补一套它根本没有的布局能力\n */\nimport { isNodeSelection, type Editor } from '@tiptap/core';\nimport type { EditorState } from '@tiptap/pm/state';\n\n/** 浮层的作用对象:一段文字,还是一个整节点 */\nexport type BubbleGroup = 'text' | 'node';\n\n/**\n * 文字组只放行内格式:块级与列表留在顶栏,浮层短到不遮住所选的字。\n *\n * 只收纯开关——带浮层的选择面板进不了这里(按钮随浮层一起被摘出文档,面板无处可锚,\n * 见 ToolbarManager.attachBubbleToolbar 的告警)。所以是\"高亮\"这个开关而不是\"挑高亮色\"。\n * 项 id 必须与 mark 名同形,allowsBubbleMark 就是拿它去查 schema 的。\n */\nexport const BUBBLE_TEXT_ITEMS = [\n 'bold',\n 'italic',\n 'underline',\n 'strike',\n 'code',\n 'highlight',\n 'subscript',\n 'superscript',\n] as const;\n\n/** 节点组换一批:附件能切形态,图片与附件都能删 */\nexport const BUBBLE_NODE_ITEMS = ['attachmentDisplay', 'delete'] as const;\n\n/** 选中这些节点才有可施加的操作,别的整节点选择(将来的 atom 之类)什么都不浮 */\nexport const BUBBLE_SELECTABLE_NODES = ['image', 'attachment', 'attachmentLink'] as const;\n\n/**\n * 选中的可操作节点叫什么,没选中或选的是别的节点就返回 null\n */\nexport function bubbleNodeNameFor(state: EditorState): string | null {\n if (!isNodeSelection(state.selection)) return null;\n const { name } = state.selection.node.type;\n return (BUBBLE_SELECTABLE_NODES as readonly string[]).includes(name) ? name : null;\n}\n\n/**\n * 这一次浮出哪一组。空选区与空白选区仍算 'text',由 shouldShow 里那句 textBetween 判掉\n */\nexport function bubbleGroupFor(state: EditorState): BubbleGroup | null {\n if (isNodeSelection(state.selection)) return bubbleNodeNameFor(state) ? 'node' : null;\n return 'text';\n}\n\n/**\n * 选区所在的块允许浮层里哪个标记:只问 schema,不问按钮的 disabled\n *\n * 按钮状态是工具栏挂在 transaction 事件上同步的,而插件读 shouldShow 的时机在\n * view.updateState 之内,读到的永远是上一次选区留下的状态(选完附件再选文字就浮不出来)\n */\nfunction allowsBubbleMark(state: EditorState, from: number): boolean {\n const parent = state.doc.resolve(from).parent.type;\n\n return BUBBLE_TEXT_ITEMS.some((markName) => {\n const mark = state.schema.marks[markName];\n return !!mark && parent.allowsMarkType(mark);\n });\n}\n\n/**\n * 交给 BubbleMenu.configure 的 shouldShow。\n *\n * 没照抄官方那句\"编辑器必须有焦点\":失焦隐藏本来就归插件自己的 blurHandler 管,\n * 而 hasFocus() 在 jsdom 里恒假,加上它单测就完全驱动不了这个插件。\n */\nexport function shouldShowBubbleMenu(props: {\n editor: Editor;\n element: HTMLElement;\n state: EditorState;\n from: number;\n to: number;\n}): boolean {\n const { editor, element, state, from, to } = props;\n\n let group = editor.isEditable ? bubbleGroupFor(state) : null;\n const nodeName = group === 'node' ? bubbleNodeNameFor(state) : null;\n\n // 空选区与只框到空白(拖选跨过空段时很常见)都没有可格式化的字:textBetween 空即是 from === to\n if (group === 'text' && !state.doc.textBetween(from, to).trim()) group = null;\n // 代码块里的文字五项标记一个都挂不上,别浮出一排按不动的灰按钮\n if (group === 'text' && !allowsBubbleMark(state, from)) group = null;\n\n // 哪一组该占宽度由 CSS 读这两个属性决定,所以必须赶在返回之前写好:\n // 插件紧接着就拿这个元素算位置,而 transaction 事件晚于 updateState,指望不上下一次状态同步\n element.dataset.atriBubbleMode = group ?? 'none';\n element.dataset.atriBubbleNode = nodeName ?? '';\n\n if (!group) return false;\n\n // 这一组一个按钮都没渲染出来(比如 media:false 把切形态那项连定义一起带走了)就别浮出空盒子\n return !!element.querySelector(`[data-atri-bubble-group=\"${group}\"] [data-toolbar-item]`);\n}\n","/**\n * CoreEditor - Tiptap v3 编辑器封装\n */\nimport { Editor, type Content, type EditorOptions } from '@tiptap/core';\nimport StarterKit from '@tiptap/starter-kit';\nimport { Markdown } from '@tiptap/markdown';\nimport { Placeholder } from '@tiptap/extensions';\nimport TextAlign from '@tiptap/extension-text-align';\nimport BubbleMenu from '@tiptap/extension-bubble-menu';\nimport { TaskItem, TaskList } from '@tiptap/extension-list';\nimport { createTableExtensions, resolveTableConfig } from '../extensions/table';\nimport { createMediaExtensions } from '../extensions/media';\nimport { createFormattingExtensions } from '../extensions/formatting';\nimport { MediaRuntime } from '../media/MediaRuntime';\nimport { shouldShowBubbleMenu } from './bubble-toolbar';\nimport { runSafely } from '../utils/safe-call';\nimport type {\n AtriLinkConfig,\n AtriMarkdownConfig,\n AtriMediaConfig,\n AtriTableConfig,\n} from '../types';\n\n/** 与 Tiptap 的 Link 默认 HTMLAttributes 同值,只在用户改了 target 时用来顶掉默认那份 */\nconst LINK_HTML_ATTRIBUTES = {\n target: '_blank',\n rel: 'noopener noreferrer nofollow',\n class: null,\n};\n\nexport interface CoreEditorConfig {\n element: HTMLElement;\n content?: string | object;\n contentFormat?: 'html' | 'json' | 'markdown';\n editable?: boolean;\n placeholder?: string;\n extensions?: EditorOptions['extensions'];\n markdown?: AtriMarkdownConfig;\n /** 媒体(图片 / 附件)配置,false 时不注册任何媒体节点 */\n media?: AtriMediaConfig | false;\n /** 表格配置,false 时不注册 Table 扩展,工具栏也不出插入按钮;默认开 */\n table?: boolean | AtriTableConfig;\n /** 链接配置,省略时编辑态点击不导航 */\n link?: AtriLinkConfig;\n /** 基础格式(颜色 / 字号 / 字体 / 高亮 / 上下标)开关,默认开 */\n formatting?: boolean;\n /**\n * 内置代码块开关,默认开。\n * false 时不注册 StarterKit 的 CodeBlock,节点名让给接入方自己的实现\n */\n codeBlock?: boolean;\n /**\n * 复用外部的上传运行时\n * AtriEditor 重建编辑器时会把同一个实例传进来,进行中的上传才不会丢\n */\n mediaRuntime?: MediaRuntime;\n /**\n * 选区浮动工具栏的挂载元素:给了就注册 BubbleMenu,不给就不注册\n * 元素由门面持有 —— 插件隐藏时会把它从文档里摘掉,显示时再 append 回来\n */\n bubbleElement?: HTMLElement | null;\n onCreate?: () => void;\n onUpdate?: () => void;\n onFocus?: () => void;\n onBlur?: () => void;\n onDestroy?: () => void;\n}\n\nexport class CoreEditor {\n private editor: Editor;\n private config: CoreEditorConfig;\n /** 占位符文本:由 Placeholder 扩展以函数形式每次读取,改值即可生效 */\n private placeholderText: string;\n private mediaRuntime: MediaRuntime | null;\n /** 注入的那种要活过重建编辑器(门面持有),只能自己 new 的那个才归这里回收 */\n private ownsMediaRuntime = false;\n\n constructor(config: CoreEditorConfig) {\n this.config = config;\n this.placeholderText = config.placeholder ?? '';\n this.mediaRuntime =\n config.media === false ? null : (config.mediaRuntime ?? new MediaRuntime(config.media));\n this.ownsMediaRuntime = this.mediaRuntime !== null && !config.mediaRuntime;\n this.editor = this.createEditor();\n }\n\n private createEditor(): Editor {\n const { element, content, contentFormat, editable, extensions } = this.config;\n\n const editorExtensions: EditorOptions['extensions'] = [\n StarterKit.configure({\n // StarterKit v3 默认包含 Underline, Link, TrailingNode。\n // 链接:openOnClick 默认关——编辑态点击落在链接上,用户要的是改它,\n // 而 Link 扩展开着导航时会把这次点击吃掉(连光标都放不进去)并打开新标签页,\n // 未保存的内容就跟着这次点击没了。关掉后点击交给 LinkPanel 就地编辑。\n link: {\n openOnClick: this.config.link?.openOnClick ?? false,\n markdownLinks: this.config.link?.markdownLinks ?? false,\n HTMLAttributes: {\n ...LINK_HTML_ATTRIBUTES,\n target: this.config.link?.target ?? LINK_HTML_ATTRIBUTES.target,\n },\n },\n // 关掉内置代码块,让接入方(通常就是 @atri-editor/code-highlight)的实现独占这个节点名。\n // 为什么提供一个开关而不是让宿主直接传同名扩展顶掉:实测同名确实赢(后注册的赢,\n // 节点与标记都是),所以不关也能工作——但 tiptap 会打一句\n // \"Duplicate extension names found: ['codeBlock']\" 的 warning,而且这件事靠的是\n // ExtensionManager 的去重顺序这个内部行为,不是对外承诺。\n // 多一个包就多一处\"看起来正常但踩在实现细节上\"的配置,不如把意图写成显式的一档\n codeBlock: this.config.codeBlock === false ? false : undefined,\n }),\n ];\n\n // 添加 Placeholder 扩展:空段带上 is-empty / is-editor-empty 类与 data-placeholder。\n // 无条件注册,且用函数取文本——插件创建时会快照 options 对象,\n // 之后再改 extension.options 是看不见的,函数则每次重算装饰都重新读取\n editorExtensions.push(\n Placeholder.configure({\n placeholder: () => this.placeholderText,\n })\n );\n\n // 添加 TextAlign 扩展\n editorExtensions.push(\n TextAlign.configure({\n types: ['heading', 'paragraph'],\n })\n );\n\n // 添加 Markdown 扩展\n const markdownEnabled = this.config.markdown?.enabled !== false;\n if (markdownEnabled) {\n editorExtensions.push(\n Markdown.configure({\n indentation: this.config.markdown?.indentation,\n markedOptions: this.config.markdown?.markedOptions,\n })\n );\n }\n\n // markdown 内容依赖 Markdown 扩展解析,扩展未启用时不声明该格式\n const contentType =\n contentFormat === 'markdown' && !markdownEnabled ? undefined : contentFormat;\n\n // 图片 / 附件节点自带 markdown hook,注册时机不影响 Markdown 扩展的收集(它在构造时统一读取所有扩展),\n // 但放在用户扩展之前可以让用户用同名扩展顶掉内置节点\n editorExtensions.push(...createMediaExtensions(this.config.media, this.mediaRuntime));\n\n // 表格四件套:自带 insertTable 等命令与 markdown 序列化钩子(pipe table ↔ 节点),\n // 与媒体扩展同理放在用户扩展之前,让用户能用同名扩展顶掉内置节点\n if (this.config.table !== false) {\n editorExtensions.push(...createTableExtensions(resolveTableConfig(this.config.table)));\n }\n\n // 选区浮动工具栏:默认 appendTo 就是 element(= view.dom.parentElement),\n // 浮层与正文在同一个滚动盒里,所以绝对定位跟着内容走,内滚与窗口滚都不用重算\n if (this.config.bubbleElement) {\n editorExtensions.push(\n BubbleMenu.configure({\n element: this.config.bubbleElement,\n // 官方默认 250ms 防抖,拖选时要等手停下来才浮出;这里直接关掉\n updateDelay: 0,\n shouldShow: shouldShowBubbleMenu,\n options: {\n // 位置重算只监听一个 scrollTarget + window resize,交给真正会滚的那层\n scrollTarget: element,\n // 贴着正文盒的边翻转会看起来像被裁了,留出 4px\n flip: { padding: 4 },\n shift: { padding: 4 },\n },\n })\n );\n }\n\n // 任务列表:与 StarterKit 里的无序 / 有序列表同族,同样无条件注册。\n // 扩展自带 - [ ] / - [x] 的 Markdown 双向钩子,装饰(data-checked / 勾选框)也齐全\n editorExtensions.push(TaskList, TaskItem);\n\n // 基础格式:颜色 / 背景色 / 字号 / 字体 / 高亮 / 上下标。\n // 与媒体、表格同理放在用户扩展之前,让宿主能用同名扩展顶掉内置 mark。\n // 它们的 Markdown 往返要自己补钩子(上游除 ==x== 外一个都没给,没钩子的 mark\n // 会被序列化器静默丢掉),所以这里要把 markdown 那边的偏好一并传进去\n if (this.config.formatting !== false) {\n editorExtensions.push(\n ...createFormattingExtensions({ inlineStyles: this.config.markdown?.inlineStyles })\n );\n }\n\n // 输入实时转换由 StarterKit 各扩展的 input rules 提供,与 Markdown 扩展启停无关\n const enableInputRules = this.config.markdown?.shortcuts !== false;\n\n // 添加用户自定义扩展\n if (extensions) {\n editorExtensions.push(...extensions);\n }\n\n const editor = new Editor({\n element,\n content,\n contentType,\n enableInputRules,\n editable: editable ?? true,\n extensions: editorExtensions,\n editorProps: {\n attributes: {\n class: 'atri-editor-content',\n },\n },\n onCreate: () => {\n runSafely(this.config.onCreate, 'onCreate');\n },\n onUpdate: () => {\n runSafely(this.config.onUpdate, 'onUpdate');\n },\n onFocus: () => {\n runSafely(this.config.onFocus, 'onFocus');\n },\n onBlur: () => {\n runSafely(this.config.onBlur, 'onBlur');\n },\n onDestroy: () => {\n runSafely(this.config.onDestroy, 'onDestroy');\n },\n });\n\n // 上传运行时换绑到新的 Editor 实例,并把进行中的任务状态补回文档\n this.mediaRuntime?.bind(editor);\n\n return editor;\n }\n\n /**\n * 获取上传运行时;media 为 false 时为 null\n */\n getMediaRuntime(): MediaRuntime | null {\n return this.mediaRuntime;\n }\n\n /**\n * 获取 Tiptap Editor 实例\n */\n getEditor(): Editor {\n return this.editor;\n }\n\n /**\n * 获取 HTML 内容\n */\n getHTML(): string {\n try {\n return this.editor.getHTML();\n } catch (err) {\n // 一并输出 schema 的 node/mark 类型清单,帮助定位缺少 toDOM 的类型\n console.error(\n '[CoreEditor] getHTML() failed:',\n err,\n '\\nSchema nodes:',\n Object.keys(this.editor.schema.nodes),\n '\\nSchema marks:',\n Object.keys(this.editor.schema.marks)\n );\n throw err;\n }\n }\n\n /**\n * 获取 JSON 内容\n */\n getJSON(): object {\n return this.editor.getJSON();\n }\n\n /**\n * 设置内容\n */\n setContent(content: string | object, emitUpdate = true): void {\n this.editor.commands.setContent(content as Content, { emitUpdate });\n }\n\n /**\n * 清空内容\n */\n clearContent(): void {\n this.editor.commands.clearContent(true);\n }\n\n /**\n * 是否为空\n */\n isEmpty(): boolean {\n return this.editor.isEmpty;\n }\n\n /**\n * 设置可编辑状态\n */\n setEditable(editable: boolean): void {\n this.editor.setEditable(editable);\n }\n\n /**\n * 是否可编辑\n */\n isEditable(): boolean {\n return this.editor.isEditable;\n }\n\n /**\n * 聚焦\n */\n focus(): void {\n this.editor.commands.focus();\n }\n\n /**\n * 失焦\n */\n blur(): void {\n this.editor.commands.blur();\n }\n\n /**\n * 插入内容\n */\n insertContent(content: string): void {\n this.editor.commands.insertContent(content);\n }\n\n /**\n * 设置占位符\n * 占位符写在节点装饰上,只在状态重算时重新取值,因此派发一个空事务刷新视图;\n * 空事务不改动文档,Tiptap 不会据此触发 update\n */\n setPlaceholder(placeholder: string): void {\n this.placeholderText = placeholder;\n this.editor.view.dispatch(this.editor.state.tr);\n }\n\n /**\n * 销毁编辑器\n */\n destroy(): void {\n this.editor.destroy();\n // 自建的运行时没人回收:在途请求会一直飞,blob: 预览地址也 leak 着不放。\n // 门面那条路径由 AtriEditor.destroy 负责,它持有的实例要活过这里\n if (this.ownsMediaRuntime) this.mediaRuntime?.destroy();\n this.mediaRuntime = null;\n }\n}\n","/**\n * Markdown 工具函数\n */\nimport { escapeHtml, escapeQuotes } from './escape';\nimport { safeHref, safeMediaSrc } from './url';\n\n/**\n * 简单的 Markdown 转 HTML(用于 AI 响应处理)\n * 注意:完整的 Markdown 解析由 @tiptap/markdown 处理\n * 这里仅提供基础的 Markdown 语法转换\n *\n * 定位是\"降级渲染器\":输出的 HTML 仍需交给 schema 解析或宿主自行处理,\n * 本身不构成一道安全的 sanitizer。但它的产物会经 markdownToHTML() 作为裸字符串\n * 直接返回给宿主,所以不可信的 Markdown(AI 端点、用户上传的 .md)进来时,\n * 尖括号与引号必须先转义、地址必须先过协议白名单。\n */\nexport function simpleMarkdownToHtml(markdown: string): string {\n // 整段先转义,后面每条规则产出的标签都是白名单自己拼的,\n // 这一步不做的话原文里的 <img onerror=…> 会原样落到输出里\n let html = escapeHtml(markdown);\n\n // 标题:必须从六个 # 往下匹配。反过来的话 `#### x` 会先被 /^## / 命中,\n // 剩下两个 # 当成正文内容吐出来\n html = html.replace(/^###### (.+)$/gm, '<h6>$1</h6>');\n html = html.replace(/^##### (.+)$/gm, '<h5>$1</h5>');\n html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>');\n html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');\n html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');\n html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');\n\n // 粗体和斜体\n html = html.replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, '<strong><em>$1</em></strong>');\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');\n\n // 代码块\n html = html.replace(/```(\\w*)\\n([\\s\\S]*?)```/g, '<pre><code class=\"language-$1\">$2</code></pre>');\n html = html.replace(/`(.+?)`/g, '<code>$1</code>');\n\n // 图片:必须先于链接,否则 ![alt](src) 会被下面的链接规则从 ! 之后开始匹配,吞成一个 <a>\n html = html.replace(\n /!\\[([^\\]]*)\\]\\(\\s*([^\\s)]+)(?:\\s+\"([^\"]*)\")?\\s*\\)/g,\n (_all, alt: string, src: string, title?: string) => {\n const url = safeMediaSrc(src);\n // 地址不合法就退化成这几个字符本身:图片显示不出来,但 alt 里的信息不丢,\n // 也不会留一枚指向 javascript: 的 img\n if (url === null) return alt;\n const titleAttr = title ? ` title=\"${escapeQuotes(title)}\"` : '';\n return `<img src=\"${escapeQuotes(url)}\" alt=\"${escapeQuotes(alt)}\"${titleAttr}>`;\n }\n );\n\n // 链接:title 要像图片那样剥掉,否则 `[文字](/x \"T\")` 会把整串当地址\n html = html.replace(\n /\\[([^\\]]*)\\]\\(\\s*([^\\s)]+)(?:\\s+\"[^\"]*\")?\\s*\\)/g,\n (_all, text: string, href: string) => {\n const url = safeHref(href);\n if (url === null) return text;\n return `<a href=\"${escapeQuotes(url)}\">${text}</a>`;\n }\n );\n\n // 列表\n html = html.replace(/^\\* (.+)$/gm, '<li>$1</li>');\n html = html.replace(/^- (.+)$/gm, '<li>$1</li>');\n html = html.replace(/^(\\d+)\\. (.+)$/gm, '<li>$2</li>');\n\n // 段落\n html = html.replace(/\\n\\n/g, '</p><p>');\n html = `<p>${html}</p>`;\n\n // 清理空段落\n html = html.replace(/<p><\\/p>/g, '');\n html = html.replace(/<p>(<h[1-6]>)/g, '$1');\n html = html.replace(/(<\\/h[1-6]>)<\\/p>/g, '$1');\n html = html.replace(/<p>(<pre>)/g, '$1');\n html = html.replace(/(<\\/pre>)<\\/p>/g, '$1');\n html = html.replace(/<p>(<li>)/g, '$1');\n html = html.replace(/(<\\/li>)<\\/p>/g, '$1');\n\n return html;\n}\n\n/**\n * 简单的 HTML 转 Markdown\n */\nexport function simpleHtmlToMarkdown(html: string): string {\n let md = html;\n\n // 标题\n md = md.replace(/<h1[^>]*>(.+?)<\\/h1>/gi, '# $1\\n\\n');\n md = md.replace(/<h2[^>]*>(.+?)<\\/h2>/gi, '## $1\\n\\n');\n md = md.replace(/<h3[^>]*>(.+?)<\\/h3>/gi, '### $1\\n\\n');\n md = md.replace(/<h4[^>]*>(.+?)<\\/h4>/gi, '#### $1\\n\\n');\n md = md.replace(/<h5[^>]*>(.+?)<\\/h5>/gi, '##### $1\\n\\n');\n md = md.replace(/<h6[^>]*>(.+?)<\\/h6>/gi, '###### $1\\n\\n');\n\n // 粗体和斜体\n md = md.replace(/<strong[^>]*>(.+?)<\\/strong>/gi, '**$1**');\n md = md.replace(/<b[^>]*>(.+?)<\\/b>/gi, '**$1**');\n md = md.replace(/<em[^>]*>(.+?)<\\/em>/gi, '*$1*');\n md = md.replace(/<i[^>]*>(.+?)<\\/i>/gi, '*$1*');\n\n // 链接\n md = md.replace(/<a[^>]*href=\"([^\"]*)\"[^>]*>(.+?)<\\/a>/gi, '[$2]($1)');\n\n // 图片:必须赶在剥除剩余标签之前,<img> 是自闭合的,被剥掉就再也找不回 src\n md = md.replace(/<img\\b[^>]*>/gi, (tag) => {\n const src = /src=\"([^\"]*)\"/i.exec(tag)?.[1] ?? '';\n const alt = /alt=\"([^\"]*)\"/i.exec(tag)?.[1] ?? '';\n return src ? `![${alt}](${src})` : alt;\n });\n\n // 代码\n md = md.replace(/<code[^>]*>(.+?)<\\/code>/gi, '`$1`');\n md = md.replace(/<pre[^>]*><code[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi, '```\\n$1\\n```\\n\\n');\n\n // 段落和换行\n md = md.replace(/<br\\s*\\/?>/gi, '\\n');\n md = md.replace(/<p[^>]*>([\\s\\S]*?)<\\/p>/gi, '$1\\n\\n');\n\n // 移除剩余标签\n md = md.replace(/<[^>]+>/g, '');\n\n // 解码 HTML 实体\n md = md.replace(/&amp;/g, '&');\n md = md.replace(/&lt;/g, '<');\n md = md.replace(/&gt;/g, '>');\n md = md.replace(/&quot;/g, '\"');\n md = md.replace(/&#39;/g, \"'\");\n\n // 清理多余空行\n md = md.replace(/\\n{3,}/g, '\\n\\n');\n\n return md.trim();\n}\n","/**\n * Markdown 丢失报告\n *\n * Tiptap 的序列化器对\"没有 renderMarkdown 钩子的类型\"是静默丢弃:不抛错、不降级成 HTML、\n * 不留任何痕迹(MarkdownManager 的 renderNodeToMarkdown 在查不到 handler 时 return \"\",\n * getMarkOpening 同理)。所以接入方只看 getMarkdown() 的返回值,是看不出这一趟导出丢了东西的。\n * 这里做的事就是把这件事变成一次可查询的内省。\n *\n * 判据是\"能力\"而不是\"内容 diff\":diff 会被良性的归一化骗到——空段落在导出时被补成\n * `&nbsp;`(序列化器自己就有 isEmptyOutput 来认这种占位),markdown 转义又会改写文本字节,\n * 任何一条都能凭空报出丢失。能力判据是确定的:类型有没有 renderer,正是序列化器自己要做的\n * 判断,我们只是替它先算一遍。\n */\nimport { getExtensionField, type Editor } from '@tiptap/core';\nimport type { MarkdownLoss } from '../types';\n\n/**\n * 由父节点的 renderer 亲手渲染的子类型。\n *\n * 它们自己确实没有 renderMarkdown,但内容不丢:table 的 renderer 直接遍历行与单元格拼出\n * pipe table,根本不经过\"按类型查 handler\"那条路。照着 handler 注册表报,每一张表格都会被\n * 误报成丢失——而这是这个 API 最容易踩、也最不该踩的一条。\n */\nconst RENDERED_BY_PARENT: readonly string[] = ['tableRow', 'tableHeader', 'tableCell'];\n\n/**\n * 类型有 renderer、但 renderer 不读的属性。\n *\n * 只能显式列——没有别的办法从外部知道某个 renderer 读了哪些属性。这张表要随能力增减一起维护:\n * 新增带 markdown 钩子的类型时,顺手确认它的属性是否都进了输出。\n *\n * 表格单元格在这里,而不是跟着 RENDERED_BY_PARENT 一起豁免掉:pipe table 拼得出内容和对齐,\n * 拼不出跨列跨行(合并出来的单元格会退化成一格带内容一格空着),也拼不出列宽。\n */\nconst LOSSY_ATTRIBUTES: Readonly<Record<string, readonly string[]>> = {\n paragraph: ['textAlign'],\n heading: ['textAlign'],\n tableCell: ['colspan', 'rowspan', 'colwidth'],\n tableHeader: ['colspan', 'rowspan', 'colwidth'],\n};\n\n/**\n * 仅当 `markdown.inlineStyles: 'drop'` 才算丢失的属性。\n *\n * 同一模式下 textStyle / subscript / superscript 会退回上游那版(没有 renderMarkdown),\n * 它们整个消失,由 kind='mark' 那条通道报出来,不用写在这里;highlight 不一样——上游\n * 有 `==x==`,正文回得来,只有颜色回不来,所以这是一条属性丢失而不是标记丢失。\n */\nconst LOSSY_ATTRIBUTES_WHEN_STYLES_DROPPED: Readonly<Record<string, readonly string[]>> = {\n highlight: ['color'],\n};\n\n/**\n * 这些属性的这些取值,导出不带等于没导出:文档的默认对齐本来就是左对齐,`left` 与不设\n * 在读者眼里一模一样。不豁免它的话,从 Word / Google Docs 粘贴进来的每一段都带着显式\n * `text-align: left`,报告就会段段报警——误报响一次的代价是这份报告从此没人看。\n * (`inherit` 不用列:实测 TextAlign 直接把它解析成 null,走默认值那条豁免。)\n */\nconst RENDERING_NEUTRAL_VALUES: Readonly<Record<string, readonly string[]>> = {\n textAlign: ['left'],\n};\n\n/** 属性值等于 schema 默认值时不算丢失:值没进过 HTML,Markdown 里也就没有对应的东西 */\nfunction isSignificant(value: unknown, defaultValue: unknown, attribute: string): boolean {\n if (value === defaultValue || value === null || value === undefined) return false;\n if (Array.isArray(value)) {\n // colwidth 是每列一个数字的数组,没拖过的列留 null;整列都没设过就不算丢了列宽\n return value.some((item) => typeof item === 'number' && item > 0);\n }\n if (typeof value === 'string') {\n if (value.trim() === '') return false;\n if (RENDERING_NEUTRAL_VALUES[attribute]?.includes(value)) return false;\n }\n return true;\n}\n\n/**\n * \"这个类型有 renderer 吗\"的判据取自 extensionManager 这个公开入口,用 MarkdownManager\n * 自己用的同一个 getExtensionField 算,而不是去读它的私有注册表——踩在私有字段上,\n * 上游一次重构就会把报告变成静默失灵。\n */\nfunction buildRenderableTypes(editor: Editor): Set<string> {\n const schemaTypes = new Set([\n ...Object.keys(editor.schema.nodes),\n ...Object.keys(editor.schema.marks),\n ]);\n const renderable = new Set<string>();\n for (const extension of editor.extensionManager.extensions) {\n if (!schemaTypes.has(extension.name)) continue;\n if (getExtensionField(extension, 'renderMarkdown')) renderable.add(extension.name);\n }\n return renderable;\n}\n\nexport interface CollectLossesOptions {\n /** 与 markdown.inlineStyles 一致 */\n inlineStyles: 'html' | 'drop';\n}\n\n/**\n * 走一遍当前文档,报告 `getMarkdown()` 会丢掉什么。\n *\n * 只统计文档里真实出现的类型,所以空文档的返回值恒为 `[]`,与装了哪些扩展无关。\n */\nexport function collectMarkdownLosses(\n editor: Editor,\n options: CollectLossesOptions\n): MarkdownLoss[] {\n const renderable = buildRenderableTypes(editor);\n const nodes = new Map<string, number>();\n const marks = new Map<string, number>();\n const attributes = new Map<string, { loss: MarkdownLoss; count: number }>();\n\n const countAttribute = (type: string, attribute: string, count: number): void => {\n const key = `${type}/${attribute}`;\n const entry = attributes.get(key);\n if (entry) entry.count += count;\n else attributes.set(key, { loss: { kind: 'attribute', type, attribute, count }, count });\n };\n\n /** 上一次访问的文本节点带着哪些标记,用来把跨多个文本节点的同一标记合成一处 */\n let previousMarkNames: Set<string> = new Set();\n\n const checkAttributes = (\n owner: string,\n attrs: Record<string, unknown>,\n specAttrs: unknown\n ): void => {\n const defaults = (specAttrs as Record<string, { default?: unknown }> | undefined) ?? {};\n const tables =\n options.inlineStyles === 'drop'\n ? [LOSSY_ATTRIBUTES, LOSSY_ATTRIBUTES_WHEN_STYLES_DROPPED]\n : [LOSSY_ATTRIBUTES];\n for (const table of tables) {\n for (const attribute of table[owner] ?? []) {\n if (isSignificant(attrs[attribute], defaults[attribute]?.default, attribute)) {\n countAttribute(owner, attribute, 1);\n }\n }\n }\n };\n\n editor.state.doc.descendants((node, _position, parent) => {\n const name = node.type.name;\n\n if (name === 'text') {\n // 标记写在每个被切出的文本节点上,一个红色短语可能占着三四个;只有新出现的才算一处\n const fresh = node.marks.filter((mark) => !previousMarkNames.has(mark.type.name));\n previousMarkNames = new Set(node.marks.map((mark) => mark.type.name));\n for (const mark of fresh) {\n if (!renderable.has(mark.type.name)) {\n marks.set(mark.type.name, (marks.get(mark.type.name) ?? 0) + 1);\n }\n checkAttributes(mark.type.name, mark.attrs, mark.type.spec.attrs);\n }\n return;\n }\n\n // 属性照查,包括 RENDERED_BY_PARENT 那几位——它们内容不丢,属性照样丢(见那张表的注释)\n checkAttributes(name, node.attrs, node.type.spec.attrs);\n\n // 定位不到 parent 就是 doc 自己,doc 没有\"被谁渲染\"的问题\n if (parent && !renderable.has(name) && !RENDERED_BY_PARENT.includes(name)) {\n nodes.set(name, (nodes.get(name) ?? 0) + 1);\n // 整个子树一起没了,别再把它里面的段落标记成一处处独立丢失——用户要的是\"这块没了\"\n previousMarkNames = new Set();\n return false;\n }\n previousMarkNames = new Set();\n return true;\n });\n\n const byCountThenName = (a: [string, number], b: [string, number]) =>\n b[1] - a[1] || a[0].localeCompare(b[0]);\n\n const ofKind =\n (kind: 'node' | 'mark') =>\n (entry: [string, number]): MarkdownLoss => ({ kind, type: entry[0], count: entry[1] });\n\n const losses: MarkdownLoss[] = [\n ...Array.from(nodes.entries()).sort(byCountThenName).map(ofKind('node')),\n ...Array.from(marks.entries()).sort(byCountThenName).map(ofKind('mark')),\n ];\n for (const entry of attributes.values()) {\n losses.push(entry.loss);\n }\n return losses;\n}\n","/**\n * MarkdownService - Markdown 解析/序列化服务\n */\nimport {\n elementFromString,\n getHTMLFromFragment,\n type Editor,\n type JSONContent,\n} from '@tiptap/core';\nimport { DOMParser as ProseMirrorDOMParser, Node as ProseMirrorNode } from '@tiptap/pm/model';\nimport type { MarkdownManager } from '@tiptap/markdown';\nimport type { AtriMarkdownConfig, MarkdownLoss } from '../types';\nimport { simpleMarkdownToHtml, simpleHtmlToMarkdown } from '../utils/markdown';\nimport { collectMarkdownLosses } from './markdown-losses';\n\nexport class MarkdownService {\n private editor: Editor;\n private config: AtriMarkdownConfig;\n\n constructor(editor: Editor, config: AtriMarkdownConfig = {}) {\n this.editor = editor;\n this.config = {\n enabled: true,\n ...config,\n };\n }\n\n /**\n * Tiptap Markdown 序列化器,未注册 Markdown 扩展时为 null\n */\n private get manager(): MarkdownManager | null {\n return this.editor.markdown ?? null;\n }\n\n /**\n * 获取编辑器内容为 Markdown\n * 自定义节点的输出由其 renderMarkdown 钩子负责(见 ExtensionManager.registerNodeView),\n * 因此这里始终走 Tiptap 序列化器,不对整篇文档做额外替换。\n */\n getMarkdown(): string {\n const manager = this.manager;\n if (!manager) {\n return simpleHtmlToMarkdown(this.editor.getHTML());\n }\n return manager.serialize(this.editor.getJSON());\n }\n\n /**\n * 设置 Markdown 内容\n */\n setMarkdown(content: string, emitUpdate = true): void {\n if (this.manager) {\n this.editor.commands.setContent(content, { contentType: 'markdown', emitUpdate });\n return;\n }\n this.editor.commands.setContent(simpleMarkdownToHtml(content), { emitUpdate });\n }\n\n /**\n * Markdown 转 HTML\n * 与 htmlToMarkdown 方向对称:解析为 JSON 后再按当前 schema 序列化为 HTML\n */\n markdownToHTML(markdown: string): string {\n const manager = this.manager;\n if (!manager) {\n return simpleMarkdownToHtml(markdown);\n }\n const { schema } = this.editor;\n return getHTMLFromFragment(\n ProseMirrorNode.fromJSON(schema, manager.parse(markdown)).content,\n schema\n );\n }\n\n /**\n * HTML 转 Markdown\n */\n htmlToMarkdown(html: string): string {\n const manager = this.manager;\n if (!manager) {\n return simpleHtmlToMarkdown(html);\n }\n const parsed = ProseMirrorDOMParser.fromSchema(this.editor.schema).parse(\n elementFromString(html)\n );\n return manager.serialize(parsed.toJSON());\n }\n\n /**\n * Markdown 转 JSON\n */\n markdownToJSON(markdown: string): JSONContent {\n const manager = this.manager;\n if (manager) {\n return manager.parse(markdown);\n }\n // 降级:返回简单结构\n return {\n type: 'doc',\n content: [\n {\n type: 'paragraph',\n content: [{ type: 'text', text: markdown }],\n },\n ],\n };\n }\n\n /**\n * 这一趟 getMarkdown() 会丢掉什么,判据见 markdown-losses.ts\n *\n * Markdown 关闭时返回空数组:那时 getMarkdown() 走的是整篇 HTML 的降级转换,\n * 不按类型分派,也就没有\"哪个类型没有 renderer\"可问。\n */\n getLosses(): MarkdownLoss[] {\n if (!this.manager) {\n return [];\n }\n return collectMarkdownLosses(this.editor, {\n inlineStyles: this.config.inlineStyles ?? 'html',\n });\n }\n\n /**\n * 有没有内容在导出 Markdown 时会丢,命名对齐 hasPendingUploads()\n */\n hasLossyContent(): boolean {\n return this.getLosses().length > 0;\n }\n\n /**\n * 是否启用\n */\n isEnabled(): boolean {\n return this.config.enabled !== false;\n }\n}\n","/**\n * ThemeManager - 主题管理\n */\n\n/** 内置亮/暗两套,带字面量提示;仍需允许任意自定义主题名 */\nexport type ThemeType = 'light' | 'dark' | (string & {});\n\n/** 主题类的统一前缀;换主题时要按前缀摘干净,而不是只摘那两个已知名字 */\nconst THEME_PREFIX = 'atri-theme-';\n\nexport class ThemeManager {\n private container: HTMLElement;\n private currentTheme: ThemeType;\n\n constructor(container: HTMLElement, theme: ThemeType = 'light') {\n this.container = container;\n this.currentTheme = theme;\n this.applyTheme();\n }\n\n /**\n * 应用当前主题:内置主题与自定义主题走同一条 class 规则,只是名字不同\n */\n private applyTheme(): void {\n // 按前缀摘,而不是写死 remove('atri-theme-light', 'atri-theme-dark'):\n // ThemeType 接受任意自定义主题名(README 就宣传 setTheme('sepia')),\n // 只摘那两个名字的话自定义主题永远留在容器上——\n // setTheme('sepia') 再 setTheme('light') 之后是\n // \"atri-theme-sepia atri-theme-light\",两套变量同时生效,\n // 谁赢取决于样式里的声明顺序,而不是用户最后选了哪个\n Array.from(this.container.classList)\n .filter((name) => name.startsWith(THEME_PREFIX))\n .forEach((name) => this.container.classList.remove(name));\n\n this.container.classList.add(`${THEME_PREFIX}${this.currentTheme}`);\n\n // 设置 data 属性\n this.container.setAttribute('data-atri-theme', this.currentTheme);\n }\n\n /**\n * 设置主题\n */\n setTheme(theme: ThemeType): void {\n this.currentTheme = theme;\n this.applyTheme();\n }\n\n /**\n * 切换亮/暗主题\n */\n toggleTheme(): void {\n if (this.currentTheme === 'light') {\n this.setTheme('dark');\n } else if (this.currentTheme === 'dark') {\n this.setTheme('light');\n } else {\n // 自定义主题切换为 light\n this.setTheme('light');\n }\n }\n}\n","/**\n * I18nManager - 国际化支持\n */\nimport i18next, { type i18n as I18nInstance } from 'i18next';\n\n// 默认中文资源\nconst zhResources = {\n editor: {\n placeholder: '请输入内容...',\n bold: '加粗',\n italic: '斜体',\n underline: '下划线',\n strikethrough: '删除线',\n code: '行内代码',\n heading: '标题',\n heading1: '标题 1',\n heading2: '标题 2',\n heading3: '标题 3',\n heading4: '标题 4',\n heading5: '标题 5',\n heading6: '标题 6',\n paragraph: '正文',\n bulletList: '无序列表',\n orderedList: '有序列表',\n taskList: '任务列表',\n codeBlock: '代码块',\n blockquote: '引用',\n horizontalRule: '分割线',\n link: '链接',\n image: '图片',\n attachment: '附件',\n attachmentDisplay: '附件样式',\n delete: '删除',\n video: '视频',\n table: '表格',\n undo: '撤销',\n redo: '重做',\n align: '对齐',\n alignLeft: '左对齐',\n alignCenter: '居中',\n alignRight: '右对齐',\n alignJustify: '两端对齐',\n textColor: '文字颜色',\n highlight: '高亮',\n highlightColor: '高亮色',\n fontSize: '字号',\n fontFamily: '字体',\n subscript: '下标',\n superscript: '上标',\n clearFormat: '清除格式',\n },\n formatting: {\n customColor: '其他颜色…',\n noColor: '无',\n defaultValue: '默认',\n fontSans: '无衬线',\n fontSerif: '衬线',\n fontMono: '等宽',\n },\n media: {\n uploading: '上传中 {{files}} 个 · {{percent}}%',\n uploadFailed: '{{files}} 个文件上传失败',\n inlined: '{{files}} 张图片未上传(已随文档保存)',\n retry: '重试',\n browse: '选择文件',\n dropHere: '或将文件拖到这里',\n urlPlaceholder: '图片地址(https://…)',\n altPlaceholder: '替代文字',\n insert: '插入',\n },\n table: {\n menuLabel: '表格操作',\n rowBefore: '上方插入行',\n rowAfter: '下方插入行',\n deleteRow: '删除行',\n columnBefore: '左侧插入列',\n columnAfter: '右侧插入列',\n deleteColumn: '删除列',\n mergeCells: '合并单元格',\n splitCell: '拆分单元格',\n toggleHeaderRow: '切换表头行',\n alignLeft: '单元格左对齐',\n alignCenter: '单元格居中',\n alignRight: '单元格右对齐',\n deleteTable: '删除表格',\n },\n link: {\n panelLabel: '链接',\n urlPlaceholder: '链接地址(https://…)',\n newWindow: '在新窗口打开',\n apply: '确定',\n remove: '移除链接',\n },\n ai: {\n menu: 'AI 菜单',\n continue: 'AI 续写',\n translate: 'AI 翻译',\n optimize: 'AI 优化',\n summarize: '生成摘要',\n processing: 'AI 处理中...',\n error: 'AI 处理出错',\n },\n};\n\n// 英文资源\nconst enResources = {\n editor: {\n placeholder: 'Type something...',\n bold: 'Bold',\n italic: 'Italic',\n underline: 'Underline',\n strikethrough: 'Strikethrough',\n code: 'Inline Code',\n heading: 'Heading',\n heading1: 'Heading 1',\n heading2: 'Heading 2',\n heading3: 'Heading 3',\n heading4: 'Heading 4',\n heading5: 'Heading 5',\n heading6: 'Heading 6',\n paragraph: 'Paragraph',\n bulletList: 'Bullet List',\n orderedList: 'Ordered List',\n taskList: 'Task List',\n codeBlock: 'Code Block',\n blockquote: 'Blockquote',\n horizontalRule: 'Divider',\n link: 'Link',\n image: 'Image',\n attachment: 'Attachment',\n attachmentDisplay: 'Attachment style',\n delete: 'Delete',\n video: 'Video',\n table: 'Table',\n undo: 'Undo',\n redo: 'Redo',\n align: 'Align',\n alignLeft: 'Align Left',\n alignCenter: 'Align Center',\n alignRight: 'Align Right',\n alignJustify: 'Justify',\n textColor: 'Text Color',\n highlight: 'Highlight',\n highlightColor: 'Highlight Color',\n fontSize: 'Font Size',\n fontFamily: 'Font',\n subscript: 'Subscript',\n superscript: 'Superscript',\n clearFormat: 'Clear Formatting',\n },\n formatting: {\n customColor: 'Custom color…',\n noColor: 'None',\n defaultValue: 'Default',\n fontSans: 'Sans',\n fontSerif: 'Serif',\n fontMono: 'Monospace',\n },\n media: {\n uploading: 'Uploading {{files}} · {{percent}}%',\n uploadFailed: '{{files}} file(s) failed to upload',\n inlined: '{{files}} image(s) saved with the document (not uploaded)',\n retry: 'Retry',\n browse: 'Browse',\n dropHere: 'or drop files here',\n urlPlaceholder: 'Image URL (https://…)',\n altPlaceholder: 'Alternative text',\n insert: 'Insert',\n },\n table: {\n menuLabel: 'Table actions',\n rowBefore: 'Insert row above',\n rowAfter: 'Insert row below',\n deleteRow: 'Delete row',\n columnBefore: 'Insert column left',\n columnAfter: 'Insert column right',\n deleteColumn: 'Delete column',\n mergeCells: 'Merge cells',\n splitCell: 'Split cell',\n toggleHeaderRow: 'Toggle header row',\n alignLeft: 'Align cell left',\n alignCenter: 'Center cell',\n alignRight: 'Align cell right',\n deleteTable: 'Delete table',\n },\n link: {\n panelLabel: 'Link',\n urlPlaceholder: 'Link URL (https://…)',\n newWindow: 'Open in new window',\n apply: 'Apply',\n remove: 'Remove link',\n },\n ai: {\n menu: 'AI Menu',\n continue: 'AI Continue',\n translate: 'AI Translate',\n optimize: 'AI Optimize',\n summarize: 'AI Summarize',\n processing: 'AI Processing...',\n error: 'AI Error',\n },\n};\n\n/**\n * 深拷贝内置词条。\n *\n * 不能把模块级字面量直接交给 i18next:init({ resources }) 存的是引用,而\n * addResourceBundle 的 deepMerge 是就地合并。于是接入方调一次 addResources 就会永久\n * 改掉 zhResources / enResources 本身——页面上所有编辑器实例、以及之后新建的实例,\n * 全都跟着被污染。zhText() 读的同样是这两个字面量,它作为内置默认文案的单源也会一起坏掉。\n * 每个实例持有一份自己的拷贝,注册才真的是\"注册到这个编辑器\"。\n */\nfunction cloneResources<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\nexport class I18nManager {\n private instance: I18nInstance;\n\n constructor(lang: string = 'zh') {\n // 每个编辑器独立实例:i18next 默认导出是全局单例,共用会让多个编辑器互相切换语言\n this.instance = i18next.createInstance();\n // 资源为内联对象,initImmediate: false 让初始化同步完成,构造后 t() 立即可用\n this.instance.init({\n lng: lang,\n fallbackLng: 'zh',\n initImmediate: false,\n resources: {\n zh: { translation: cloneResources(zhResources) },\n en: { translation: cloneResources(enResources) },\n },\n });\n }\n\n /**\n * 翻译\n */\n t(key: string, options?: Record<string, unknown>): string {\n return this.instance.t(key, options);\n }\n\n /**\n * 翻译,词条缺失时回退到 fallback:i18next 缺词条时原样返回 key\n */\n tOr(key: string, fallback: string, options?: Record<string, unknown>): string {\n const translated = this.t(key, options);\n return translated && translated !== key ? translated : fallback;\n }\n\n /**\n * 切换语言\n */\n async changeLanguage(lang: string): Promise<void> {\n await this.instance.changeLanguage(lang);\n }\n\n /**\n * 注册语言包:给已存在的语言补词条,或引入一门内置清单之外的新语言。\n *\n * 内置词条是源码里的两个字面量对象,接入方改不到,于是 lang: 'ja' 只能静默回退中文\n * ——看起来像\"支持可扩展\",实际没有入口。这里补上那个入口。\n *\n * overwrite 传 false:同名 key 以先注册的一方为准,防止后加载的包把内置文案悄悄换掉。\n * 要自定义措辞走 toolbar.items 的 tooltip,那本来就是优先级更高的显式入口。\n * 注册当前语言时补发一次 languageChanged,让已挂载的界面重取文案;\n * 不是当前语言就不发,白重刷一遍界面没有意义。\n */\n addResources(lang: string, resources: Record<string, unknown>): void {\n this.instance.addResourceBundle(lang, 'translation', resources, true, false);\n if (lang === this.instance.language) {\n this.instance.emit('languageChanged', lang);\n }\n }\n\n /**\n * 该语言是否已有语言包。未注册的语言不会抛错,只会按 fallbackLng 回退,\n * 所以想知道有没有真的生效得问这里。\n */\n hasLanguage(lang: string): boolean {\n return this.instance.hasResourceBundle(lang, 'translation');\n }\n\n /**\n * 监听语言变更,返回取消订阅的函数\n */\n onLanguageChanged(handler: (lang: string) => void): () => void {\n this.instance.on('languageChanged', handler);\n return () => this.instance.off('languageChanged', handler);\n }\n\n /**\n * 获取当前语言\n */\n getLanguage(): string {\n return this.instance.language || 'zh';\n }\n}\n\n/**\n * 浮层类 UI 共用的文案回退:未注入 i18n 或词条缺失时用内置文案\n * (t() 缺词条时原样返回 key,所以 tOr 已是类里的规矩,这里只补\"可能压根没有 i18n\"这一层)\n */\nexport function tOrFallback(i18n: I18nManager | undefined, key: string, fallback: string): string {\n return i18n?.tOr(key, fallback) ?? fallback;\n}\n\n/**\n * 直接取中文原文。\n *\n * 中文词条本身就是这些控件的默认文案,再在组件里抄一份字面量就是给漂移留位置:\n * 改一处、另一处不变,而 tOr 的回退值恰好是最不容易被发现出错的那条路径\n * (只在没注入 i18n 或词条缺失时才露出来)。有词条可指的地方用它。\n */\nexport function zhText(key: string): string {\n const found = key\n .split('.')\n .reduce<unknown>(\n (node, part) =>\n node && typeof node === 'object' ? (node as Record<string, unknown>)[part] : undefined,\n zhResources\n );\n\n return typeof found === 'string' ? found : '';\n}\n","/**\n * ExtensionManager - 扩展注册管理\n */\nimport { Node, mergeAttributes } from '@tiptap/core';\nimport type { AnyExtension } from '@tiptap/core';\nimport type { AtriNodeViewConfig } from '../types';\n\nexport class ExtensionManager {\n private extensions: Map<string, AnyExtension> = new Map();\n private nodeViews: Map<string, AtriNodeViewConfig> = new Map();\n\n /**\n * 注册扩展\n */\n register(extension: AnyExtension): void {\n const name = extension.name;\n if (this.extensions.has(name)) {\n console.warn(`Extension \"${name}\" already registered, overwriting.`);\n }\n this.extensions.set(name, extension);\n }\n\n /**\n * 注册自定义 NodeView\n */\n registerNodeView(config: AtriNodeViewConfig): void {\n if (this.nodeViews.has(config.name)) {\n console.warn(`NodeView \"${config.name}\" already registered, overwriting.`);\n }\n\n this.nodeViews.set(config.name, config);\n\n // 动态创建 Tiptap Node 扩展\n const nodeExtension = Node.create({\n name: config.name,\n group: config.group || (config.inline ? 'inline' : 'block'),\n atom: config.atom ?? true,\n\n addAttributes() {\n return config.attributes || {};\n },\n\n parseHTML() {\n return config.parseHTML?.() || [{ tag: config.name }];\n },\n\n renderHTML({ HTMLAttributes, node }) {\n if (config.renderHTML) {\n return config.renderHTML({ HTMLAttributes, node });\n }\n return [config.name, mergeAttributes(HTMLAttributes)];\n },\n\n addNodeView() {\n return config.nodeView;\n },\n\n // 由 Tiptap 的 MarkdownManager 按节点名调用,签名见 @tiptap/core NodeConfig.renderMarkdown\n renderMarkdown: config.markdownSerialize\n ? (node) => config.markdownSerialize!(node)\n : undefined,\n });\n\n this.register(nodeExtension);\n }\n\n /**\n * 批量注册 NodeView\n */\n registerNodeViews(configs: AtriNodeViewConfig[]): void {\n configs.forEach((config) => this.registerNodeView(config));\n }\n\n /**\n * 获取扩展\n */\n get(name: string): AnyExtension | undefined {\n return this.extensions.get(name);\n }\n\n /**\n * 获取所有扩展\n */\n getAll(): AnyExtension[] {\n return Array.from(this.extensions.values());\n }\n\n /**\n * 获取所有已注册的 NodeView\n */\n getNodeViews(): Map<string, AtriNodeViewConfig> {\n return this.nodeViews;\n }\n\n /**\n * 是否已注册\n */\n has(name: string): boolean {\n return this.extensions.has(name);\n }\n}\n","/**\n * floating-panel - 工具栏浮层的公共底盘\n *\n * 浮层统一挂 document.body + fixed 定位:工具栏链路任何一处 overflow 都会裁掉子元素。\n * InsertPanel / TableGridPanel / LinkPanel / TableMenu / 格式选择面板共用这里的\n * 定位、外点与 Escape 关闭、主题类跟随三段。\n */\nimport { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';\n\nexport interface FloatingPanel {\n /** 停止位置跟随;重复调用无副作用 */\n stop: () => void;\n}\n\n/** 浮层与锚点之间的间隙 */\nconst PANEL_OFFSET = 6;\n/** 贴到视口边缘时留出的安全内边距 */\nconst PANEL_SHIFT_PADDING = 8;\n\n/**\n * 浮层共用的三段中间件:溢出翻转 + 贴边收拢。\n *\n * 各处只有 placement 不同(工具栏向下、表格菜单贴着单元格向上),\n * 这三个参数此前被抄成 offset(6)/offset(6)/offset(8)、shift(8)/shift(8)/shift(),\n * 差的那 2px 没人说得清是有意还是漏改\n */\nexport function panelMiddleware(): ReturnType<typeof offset | typeof flip | typeof shift>[] {\n return [offset(PANEL_OFFSET), flip(), shift({ padding: PANEL_SHIFT_PADDING })];\n}\n\n/** 落位:浮层统一 fixed,坐标是 computePosition 给的视口值 */\nexport function placePanel(panel: HTMLElement, x: number, y: number): void {\n panel.style.position = 'fixed';\n panel.style.left = `${x}px`;\n panel.style.top = `${y}px`;\n}\n\n/**\n * 把面板锚定到 trigger 下方并持续跟随。\n * isCurrent 用于在异步计算间隙确认面板还没被关掉/换掉——关掉的实例不该再写样式\n */\nexport function positionPanel(\n trigger: HTMLElement,\n panel: HTMLElement,\n isCurrent: () => boolean\n): FloatingPanel {\n let stopAutoUpdate: (() => void) | null = null;\n\n const update = async () => {\n if (!isCurrent()) return;\n try {\n const { x, y } = await computePosition(trigger, panel, {\n placement: 'bottom-start',\n middleware: panelMiddleware(),\n });\n // 算位置的这几微秒里面板可能已经关了\n if (!isCurrent()) return;\n placePanel(panel, x, y);\n } catch {\n // 量不到也要让面板看得见:宁可位置不理想,也不能点了没反应\n }\n };\n\n void update().then(() => {\n if (isCurrent()) {\n stopAutoUpdate = autoUpdate(trigger, panel, () => void update());\n }\n });\n\n return {\n stop: () => {\n stopAutoUpdate?.();\n stopAutoUpdate = null;\n },\n };\n}\n\n/**\n * 点外面与 Escape 关闭;trigger 本身交给调用方的 toggle,\n * 否则关了又立刻被这次点击重开\n */\nexport function listenPanelDismiss(\n trigger: HTMLElement,\n panel: HTMLElement,\n onClose: () => void\n): () => void {\n const onPointerDown = (event: Event) => {\n const target = event.target as Node | null;\n if (!target || panel.contains(target) || trigger.contains(target)) return;\n onClose();\n };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n event.preventDefault();\n event.stopPropagation();\n onClose();\n };\n\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('keydown', onKeyDown, true);\n return () => {\n document.removeEventListener('pointerdown', onPointerDown, true);\n document.removeEventListener('keydown', onKeyDown, true);\n };\n}\n\nconst THEME_PREFIX = 'atri-theme-';\n\n/** 把 target 上的主题类换成 source 当前那一套;先摘后加,所以重复调用不会攒出两套主题 */\nfunction syncThemeClasses(target: HTMLElement, root: Element): void {\n Array.from(target.classList)\n .filter((name) => name.startsWith(THEME_PREFIX))\n .forEach((name) => target.classList.remove(name));\n\n const themes = Array.from(root.classList).filter((name) => name.startsWith(THEME_PREFIX));\n target.classList.add(...themes);\n}\n\n/**\n * 主题变量写在 .atri-editor 上,浮层挂在 body 上够不着,只能把主题类复制一份。\n * 面板此刻往往还没入 DOM,closest 搜不到编辑器根,所以源元素必须由编辑器侧给(如 view.dom)\n *\n * 只复制一次是不够的:ThemeManager 换主题只动容器上的 class,已经打开的浮层无从得知,\n * 于是\"开着面板切主题\"会得到一个浅色面板配深色正文。这里改成持续跟随:\n * 观察者盯的是容器自己的 class(面板自己的改动不会触发它,没有回环),\n * 返回的 disposer 由面板在关闭与销毁时调用。\n */\nexport function followThemeClasses(source: Element | null, panel: HTMLElement): () => void {\n const root = source?.closest('.atri-editor') ?? null;\n if (root) syncThemeClasses(panel, root);\n if (!root || typeof MutationObserver === 'undefined') return () => {};\n\n const observer = new MutationObserver(() => syncThemeClasses(panel, root));\n observer.observe(root, { attributes: true, attributeFilter: ['class'] });\n return () => observer.disconnect();\n}\n","/**\n * InsertPanel - 工具栏下方的插入浮层\n *\n * 挂在 document.body 上并用 fixed 定位:工具栏那一层链路上任何一处 overflow 都会把浮层裁掉。\n *\n * 注:media/ 目录同时承载上传运行时(MediaRuntime/Uploader/file-policy)与 UI\n * (本文件与 MediaStatusStrip),两者通过 MediaRuntime 的订阅接口协作,暂不拆分子目录。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n followThemeClasses,\n listenPanelDismiss,\n positionPanel,\n type FloatingPanel,\n} from '../core/floating-panel';\nimport { tOrFallback, type I18nManager } from '../core/I18nManager';\nimport type { MediaKind } from '../types';\nimport type { MediaRuntime } from './MediaRuntime';\nimport { filesOf } from '../utils/dom';\n\nexport type InsertPanelMode = 'image' | 'attachment';\n\nexport interface InsertPanelOptions {\n editor: Editor;\n runtime: MediaRuntime;\n i18n?: I18nManager;\n /** 开合时把控制权交回工具栏:按钮的 active 与 aria-expanded 由它维护 */\n onOpenChange?: (mode: InsertPanelMode | null) => void;\n}\n\n/** 每种模式给哪些字段:附件没有\"填个网络地址\"这条路 */\nconst FIELDS: Record<InsertPanelMode, { url: boolean; alt: boolean; kind: MediaKind }> = {\n image: { url: true, alt: true, kind: 'image' },\n attachment: { url: false, alt: false, kind: 'attachment' },\n};\n\ninterface LabelledElement {\n el: HTMLElement;\n key: string;\n fallback: string;\n}\n\nexport class InsertPanel {\n private editor: Editor;\n private runtime: MediaRuntime;\n private i18n?: I18nManager;\n private onOpenChange?: (mode: InsertPanelMode | null) => void;\n private unsubscribeLanguage?: () => void;\n\n private element: HTMLDivElement | null = null;\n private mode: InsertPanelMode | null = null;\n private floating: FloatingPanel | null = null;\n private detachDismiss: (() => void) | null = null;\n private detachTheme: (() => void) | null = null;\n private labelled: LabelledElement[] = [];\n\n constructor(options: InsertPanelOptions) {\n this.editor = options.editor;\n this.runtime = options.runtime;\n this.i18n = options.i18n;\n this.onOpenChange = options.onOpenChange;\n // 面板是即时创建的,但可能开着的时候宿主切了语言\n this.unsubscribeLanguage = this.i18n?.onLanguageChanged(() => this.applyLabels());\n }\n\n get openMode(): InsertPanelMode | null {\n return this.mode;\n }\n\n /**\n * 同一个按钮再点一次是关闭;换按钮则直接把浮层挪过去\n */\n toggle(anchor: HTMLElement, mode: InsertPanelMode): void {\n if (this.mode === mode) {\n this.close();\n return;\n }\n\n this.close();\n this.mode = mode;\n this.build(mode);\n this.position(anchor);\n this.listenForDismiss(anchor);\n this.onOpenChange?.(mode);\n }\n\n close(): void {\n this.detachDismiss?.();\n this.detachDismiss = null;\n this.detachTheme?.();\n this.detachTheme = null;\n this.floating?.stop();\n this.floating = null;\n this.element?.remove();\n this.element = null;\n this.labelled = [];\n\n if (this.mode === null) return;\n this.mode = null;\n this.onOpenChange?.(null);\n }\n\n destroy(): void {\n this.close();\n this.unsubscribeLanguage?.();\n this.unsubscribeLanguage = undefined;\n }\n\n private t(key: string, fallback: string): string {\n return tOrFallback(this.i18n, key, fallback);\n }\n\n /**\n * 登记一处随语言重画的文案:input 画到 placeholder,其他画成文字\n */\n private label(el: HTMLElement, key: string, fallback: string): void {\n this.labelled.push({ el, key, fallback });\n this.applyLabel({ el, key, fallback });\n }\n\n private applyLabels(): void {\n this.labelled.forEach((entry) => this.applyLabel(entry));\n }\n\n private applyLabel({ el, key, fallback }: LabelledElement): void {\n const text = this.t(key, fallback);\n if (el instanceof HTMLInputElement) el.placeholder = text;\n else el.textContent = text;\n }\n\n private build(mode: InsertPanelMode): void {\n const fields = FIELDS[mode];\n const panel = document.createElement('div');\n panel.className = 'atri-media-panel';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute(\n 'aria-label',\n this.t(`editor.${mode === 'image' ? 'image' : 'attachment'}`, '插入')\n );\n\n let urlInput: HTMLInputElement | null = null;\n let altInput: HTMLInputElement | null = null;\n\n if (fields.url) {\n urlInput = document.createElement('input');\n urlInput.type = 'url';\n urlInput.className = 'atri-media-panel-url';\n this.label(urlInput, 'media.urlPlaceholder', '图片地址(https://…)');\n urlInput.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n this.insertFromUrl(urlInput, altInput);\n }\n });\n panel.appendChild(urlInput);\n }\n\n if (fields.alt) {\n altInput = document.createElement('input');\n altInput.type = 'text';\n altInput.className = 'atri-media-panel-alt';\n this.label(altInput, 'media.altPlaceholder', '替代文字');\n panel.appendChild(altInput);\n }\n\n const fileInput = document.createElement('input');\n fileInput.type = 'file';\n fileInput.className = 'atri-media-panel-file';\n fileInput.hidden = true;\n fileInput.multiple = true;\n const accept = this.runtime.acceptFor(fields.kind);\n if (accept) fileInput.accept = accept;\n fileInput.addEventListener('change', () => {\n this.upload(Array.from(fileInput.files ?? []), fields.kind);\n // 同一个文件连选两次也要还能再触发 change\n fileInput.value = '';\n });\n panel.appendChild(fileInput);\n\n const drop = document.createElement('div');\n drop.className = 'atri-media-panel-drop';\n this.label(drop, 'media.dropHere', '或将文件拖到这里');\n drop.addEventListener('dragover', (event) => event.preventDefault());\n drop.addEventListener('drop', (event) => {\n // 不挡下默认行为,整个页面会跳去那个文件\n event.preventDefault();\n this.upload(filesOf((event as DragEvent).dataTransfer), fields.kind);\n });\n\n const browse = document.createElement('button');\n browse.type = 'button';\n browse.className = 'atri-media-panel-browse';\n this.label(browse, 'media.browse', '选择文件');\n browse.addEventListener('click', () => fileInput.click());\n drop.appendChild(browse);\n panel.appendChild(drop);\n\n let insert: HTMLButtonElement | null = null;\n if (fields.url) {\n insert = document.createElement('button');\n insert.type = 'button';\n insert.className = 'atri-media-panel-insert';\n this.label(insert, 'media.insert', '插入');\n insert.addEventListener('click', () => this.insertFromUrl(urlInput, altInput));\n panel.appendChild(insert);\n }\n\n document.body.appendChild(panel);\n this.element = panel;\n this.detachTheme = followThemeClasses(this.editor.view.dom, panel);\n\n // 焦点落进面板:图片先落在地址栏,附件没有输入框就落在选择文件按钮上\n (urlInput ?? altInput ?? browse).focus();\n }\n\n /**\n * 外链图片当场就插,不进上传队列:地址本来就是可用的\n */\n private insertFromUrl(\n urlInput: HTMLInputElement | null,\n altInput: HTMLInputElement | null\n ): void {\n const src = urlInput?.value.trim();\n if (!src) return;\n\n this.editor\n .chain()\n .focus()\n .setImage({ src, alt: altInput?.value.trim() || undefined })\n .run();\n this.close();\n }\n\n private upload(files: File[], kind: MediaKind): void {\n if (!files.length) return;\n\n void this.runtime.handleFiles(files, { kind });\n this.close();\n }\n\n private position(anchor: HTMLElement): void {\n const panel = this.element;\n if (!panel) return;\n this.floating = positionPanel(anchor, panel, () => this.element === panel);\n }\n\n /**\n * 点外面与 Escape 关闭;锚点按钮交给 toggle,否则关了又立刻被这次点击重开\n */\n private listenForDismiss(anchor: HTMLElement): void {\n const panel = this.element;\n if (!panel) return;\n this.detachDismiss = listenPanelDismiss(anchor, panel, () => this.close());\n }\n}\n","/**\n * StyleOptionPanel - 颜色 / 字号 / 字体的选择面板\n *\n * 这些项不是按钮:它们要的是一组取值,所以是一个挂在 document.body 上的浮层,\n * 定位、外点/Escape 关闭、主题类复制三段全走 floating-panel 公共底盘(与\n * TableGridPanel、InsertPanel 同一套)。\n *\n * 面板只有一份实例,由 ToolbarManager 持有:同时最多开一个,四个入口共用一个实例\n * 也比四个各自留着 DOM 干净。代价是 toggle 要分清\"点的是同一个触发器\"还是\"换了一个\"\n * ——换了就重建,不能因为已经开着就直接关,否则点字号再点字体会得到\"什么都没发生\"。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n followThemeClasses,\n listenPanelDismiss,\n positionPanel,\n type FloatingPanel,\n} from './floating-panel';\nimport { tOrFallback, type I18nManager } from './I18nManager';\nimport type { StyleOption } from './style-presets';\n\nexport interface StylePanelSpec {\n /**\n * 这个入口的标识(如 'textColor')。面板是四个入口共用的一个实例,\n * 而每个触发按钮的按下态只能属于自己——没有这个 key 就会四个一起亮\n */\n key: string;\n /** 面板的无障碍名字,也是列表形状下的提示行 */\n label: string;\n /** grid 是色块方阵,list 是一行一个文字选项 */\n shape: 'grid' | 'list';\n options: StyleOption[];\n /** 选定一个取值。关闭面板由内部负责 */\n onPick: (value: string) => void;\n /** 「清除」行:把该属性 unset 掉而不是设成某个值 */\n clearLabel?: string;\n /** 「其他颜色」行,用原生取色器取任意值 */\n customColorLabel?: string;\n}\n\nexport interface StyleOptionPanelOptions {\n editor: Editor;\n i18n?: I18nManager;\n /** 开合时把控制权交回工具栏:按钮的 active 与 aria-expanded 由它维护 */\n onOpenChange?: (open: boolean) => void;\n}\n\nexport class StyleOptionPanel {\n private editor: Editor;\n private i18n?: I18nManager;\n private onOpenChange?: (open: boolean) => void;\n\n private element: HTMLDivElement | null = null;\n private floating: FloatingPanel | null = null;\n private detachDismiss: (() => void) | null = null;\n private detachTheme: (() => void) | null = null;\n /** 当前锚在哪个触发器上:同一个再点是关,换一个是改开那个 */\n private anchor: HTMLElement | null = null;\n /** 当前开着的是哪个入口的 spec.key,工具栏据此把按下态只给那一个按钮 */\n private currentKey: string | null = null;\n private rows: HTMLButtonElement[] = [];\n\n constructor(options: StyleOptionPanelOptions) {\n this.editor = options.editor;\n this.i18n = options.i18n;\n this.onOpenChange = options.onOpenChange;\n }\n\n get isOpen(): boolean {\n return this.element !== null;\n }\n\n get openKey(): string | null {\n return this.currentKey;\n }\n\n /** 面板是否正开在某个指定触发器上——工具栏据此决定按钮的按下态给谁 */\n isOpenFor(anchor: HTMLElement): boolean {\n return this.isOpen && this.anchor === anchor;\n }\n\n toggle(anchor: HTMLElement, spec: StylePanelSpec): void {\n if (this.isOpen) {\n const switching = this.anchor !== anchor;\n this.close();\n // 换一个入口点进来时不要\"先关再不动\",用户要的是新面板\n if (switching) this.open(anchor, spec);\n return;\n }\n this.open(anchor, spec);\n }\n\n close(): void {\n if (!this.element) return;\n this.detachDismiss?.();\n this.detachDismiss = null;\n this.detachTheme?.();\n this.detachTheme = null;\n this.floating?.stop();\n this.floating = null;\n this.element.remove();\n this.element = null;\n this.anchor = null;\n this.currentKey = null;\n this.rows = [];\n this.onOpenChange?.(false);\n }\n\n destroy(): void {\n this.close();\n }\n\n private open(anchor: HTMLElement, spec: StylePanelSpec): void {\n const panel = this.build(spec);\n document.body.appendChild(panel);\n this.detachTheme = followThemeClasses(this.editor.view.dom, panel);\n this.element = panel;\n this.anchor = anchor;\n this.currentKey = spec.key;\n this.floating = positionPanel(anchor, panel, () => this.element === panel);\n this.detachDismiss = listenPanelDismiss(anchor, panel, () => this.close());\n this.onOpenChange?.(true);\n panel.focus();\n }\n\n private labelOf(option: StyleOption): string {\n if (!option.i18nKey) return option.label;\n return tOrFallback(this.i18n, option.i18nKey, option.label);\n }\n\n private build(spec: StylePanelSpec): HTMLDivElement {\n const panel = document.createElement('div');\n panel.className = `atri-style-panel atri-style-panel--${spec.shape}`;\n panel.setAttribute('role', 'menu');\n panel.setAttribute('aria-label', spec.label);\n // 面板本身可聚焦:方向键与 Escape 才有落点,键盘导航从这一行起步\n panel.tabIndex = -1;\n panel.addEventListener('keydown', (event) => this.onKeyDown(event));\n\n const body = document.createElement('div');\n body.className = 'atri-style-panel-body';\n this.rows = [];\n\n spec.options.forEach((option) => {\n body.appendChild(this.makeRow(option, spec));\n });\n\n if (spec.customColorLabel) {\n body.appendChild(this.makeCustomColorRow(spec));\n }\n\n if (spec.clearLabel) {\n const clear: StyleOption = {\n value: '',\n label: spec.clearLabel,\n isClear: true,\n };\n body.appendChild(this.makeRow(clear, spec));\n }\n\n panel.appendChild(body);\n return panel;\n }\n\n private makeRow(option: StyleOption, spec: StylePanelSpec): HTMLButtonElement {\n const row = document.createElement('button');\n row.type = 'button';\n row.className = 'atri-style-panel-row';\n row.setAttribute('role', 'menuitem');\n row.dataset.styleValue = option.value;\n if (option.isClear) row.classList.add('atri-style-panel-row--clear');\n\n const label = this.labelOf(option);\n row.title = label;\n row.setAttribute('aria-label', label);\n\n if (option.swatch) {\n // 色块是装饰,语义已经挂在 aria-label 上,所以这块本身对读屏隐藏\n const swatch = document.createElement('span');\n swatch.className = 'atri-style-panel-swatch';\n swatch.style.backgroundColor = option.swatch;\n swatch.setAttribute('aria-hidden', 'true');\n row.appendChild(swatch);\n // 色块方阵里不排文字,CSS 值只作为 title 与无障碍名存在\n if (spec.shape === 'grid') row.classList.add('atri-style-panel-row--swatch-only');\n }\n\n if (spec.shape === 'list' || !option.swatch) {\n const text = document.createElement('span');\n text.className = 'atri-style-panel-text';\n text.textContent = label;\n if (option.preview) text.style.fontFamily = option.preview;\n row.appendChild(text);\n }\n\n row.addEventListener('click', () => {\n if (option.isClear) {\n spec.onPick('');\n } else {\n spec.onPick(option.value);\n }\n this.close();\n });\n\n this.rows.push(row);\n return row;\n }\n\n /**\n * 原生取色器:不在预设色板里时用它。\n *\n * 监听 change 而不是 input——input 在拖动取色器的过程中每移一像素就触发一次,\n * 每一次都是一条进撤销历史的事务,用户按一次撤销只能退一格,得按上百次才回到起点。\n */\n private makeCustomColorRow(spec: StylePanelSpec): HTMLElement {\n const row = document.createElement('label');\n row.className = 'atri-style-panel-custom';\n\n const input = document.createElement('input');\n input.type = 'color';\n input.className = 'atri-style-panel-custom-input';\n input.value = '#1890ff';\n input.addEventListener('change', () => {\n spec.onPick(input.value);\n this.close();\n });\n\n const text = document.createElement('span');\n text.textContent = spec.customColorLabel ?? '';\n\n row.appendChild(input);\n row.appendChild(text);\n return row;\n }\n\n private onKeyDown(event: KeyboardEvent): void {\n const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];\n\n if (event.key === 'Enter' || event.key === ' ') return;\n\n if (step === undefined) return;\n event.preventDefault();\n if (this.rows.length === 0) return;\n\n const focused = document.activeElement;\n const current = this.rows.indexOf(focused as HTMLButtonElement);\n // 还没进过列表就从头(或从尾)开始,而不是把焦点丢回面板\n const next =\n current < 0\n ? step > 0\n ? 0\n : this.rows.length - 1\n : (current + step + this.rows.length) % this.rows.length;\n this.rows[next].focus();\n }\n}\n","/**\n * 格式选择面板的内置预设(色板 / 字号 / 字体)\n *\n * 先给固定默认值,不做成配置项:本轮要解决的是\"开箱看不见这些能力\",让宿主换一套品牌\n * 色板是独立需求,向后兼容地加 `formatting.colors` 即可;现在预留一份没人读的配置\n * 只会多一处要同步的文档。\n *\n * 这里的文字一律**不写中文词条**:色块与字号本来就没有名字,用 CSS 值自己当无障碍标签\n * (`rgb(24, 144, 255)` 比\"蓝\"更准确,也不需要同步两份);只有字体族的通用名需要翻译,\n * 那几条走 i18n key 写在 label 字段上,由面板取词。工具栏 40 项文案收成单源那次\n * (见提交 20c6a5a)说的就是\"抄一份兜底字面量等于给漂移留位置\",这里不重犯。\n */\n\n/** 面板的一行 */\nexport interface StyleOption {\n /** 写进 mark 属性的值 */\n value: string;\n /**\n * 行内文字。颜色与字号直接用 CSS 值;\n * 需要翻译的行由面板按 i18nKey 取词,取不到才回落到这里\n */\n label: string;\n /** 填了就用这个 CSS 值画色块,代替文字预览 */\n swatch?: string;\n /** 行文字的 font-family 预览样式 */\n preview?: string;\n /** 需要翻译的行指向的词条 key */\n i18nKey?: string;\n /** 这一行是\"清除该属性\"而不是设置某个值 */\n isClear?: boolean;\n}\n\n/**\n * 文字色:前 9 个对齐包内既有的 Ant 系主题色,后 3 个补常用正文色。\n * 不放纯黑纯白——正文本来就不是\"黑\",而白字在亮色主题下等于隐形。\n */\nexport const TEXT_COLORS: StyleOption[] = [\n 'rgb(31, 31, 31)',\n 'rgb(89, 89, 89)',\n 'rgb(24, 144, 255)',\n 'rgb(82, 196, 26)',\n 'rgb(250, 173, 20)',\n 'rgb(245, 34, 45)',\n 'rgb(114, 46, 209)',\n 'rgb(235, 47, 150)',\n 'rgb(19, 194, 194)',\n 'rgb(47, 84, 235)',\n 'rgb(160, 97, 42)',\n 'rgb(120, 120, 120)',\n].map((value) => ({ value, label: value, swatch: value }));\n\n/**\n * 高亮色一律偏浅:深底配黑字刺眼,深底配白字则把\"高亮\"变成\"遮挡\"。\n * 值写成 rgb() 与 TEXT_COLORS 同形,避免同一份导出里混着 #hex 与 rgb() 两种写法。\n */\nexport const HIGHLIGHT_COLORS: StyleOption[] = [\n 'rgb(255, 241, 184)',\n 'rgb(200, 250, 170)',\n 'rgb(168, 223, 255)',\n 'rgb(255, 192, 203)',\n 'rgb(223, 190, 255)',\n 'rgb(255, 214, 168)',\n 'rgb(178, 235, 242)',\n 'rgb(230, 230, 230)',\n].map((value) => ({ value, label: value, swatch: value }));\n\nexport const FONT_SIZES: StyleOption[] = [\n '12px',\n '14px',\n '16px',\n '18px',\n '20px',\n '24px',\n '28px',\n '32px',\n '40px',\n '48px',\n].map((value) => ({ value, label: value }));\n\n/**\n * 字体族。前三项按用途选,中文栈必须自己列——只写 sans-serif 时中文会落到系统默认宋体上,\n * 与英文部分的观感差一截。后三项按名字选,装了才有用,没装就退回系统默认,不报错。\n */\nexport const FONT_FAMILIES: StyleOption[] = [\n {\n value: '-apple-system, \"Segoe UI\", Roboto, \"PingFang SC\", \"Microsoft YaHei\", sans-serif',\n label: 'Sans',\n i18nKey: 'formatting.fontSans',\n preview: 'sans-serif',\n },\n {\n value: 'Georgia, \"Times New Roman\", \"Songti SC\", SimSun, serif',\n label: 'Serif',\n i18nKey: 'formatting.fontSerif',\n preview: 'serif',\n },\n {\n value: '\"SFMono-Regular\", Consolas, Menlo, monospace',\n label: 'Monospace',\n i18nKey: 'formatting.fontMono',\n preview: 'monospace',\n },\n { value: '\"PingFang SC\", \"Microsoft YaHei\", sans-serif', label: 'PingFang / YaHei' },\n { value: 'SimSun, \"Songti SC\", serif', label: 'SimSun' },\n { value: 'SimHei, \"Heiti SC\", sans-serif', label: 'SimHei' },\n { value: 'KaiTi, \"Kaiti SC\", serif', label: 'KaiTi' },\n];\n","/**\n * TableGridPanel - 工具栏「插入表格」的网格悬停选择器\n *\n * Notion 式交互:悬停高亮左上 (row × col) 区域、点击即插入。\n * 与 InsertPanel 同为挂在 document.body 上的浮层,定位/关闭/主题三段走 floating-panel 公共底盘。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n followThemeClasses,\n listenPanelDismiss,\n positionPanel,\n type FloatingPanel,\n} from './floating-panel';\nimport { tOrFallback, type I18nManager } from './I18nManager';\n\nexport interface TableGridPanelOptions {\n editor: Editor;\n i18n?: I18nManager;\n /** 开合时把控制权交回工具栏:按钮的 active 与 aria-expanded 由它维护 */\n onOpenChange?: (open: boolean) => void;\n}\n\n/** 网格上限:再大就该用数字输入面板了,Notion 也是 8×6 左右 */\nconst MAX_COLS = 8;\nconst MAX_ROWS = 6;\n\nexport class TableGridPanel {\n private editor: Editor;\n private i18n?: I18nManager;\n private onOpenChange?: (open: boolean) => void;\n\n private element: HTMLDivElement | null = null;\n private floating: FloatingPanel | null = null;\n private detachDismiss: (() => void) | null = null;\n private detachTheme: (() => void) | null = null;\n /** 键盘高亮的当前格,-1 表示还没进入键盘导航 */\n private cursorRow = -1;\n private cursorCol = -1;\n /** 单元格按 row * MAX_COLS + col 存放,方便方向键直接换算 */\n private cells: HTMLElement[] = [];\n\n constructor(options: TableGridPanelOptions) {\n this.editor = options.editor;\n this.i18n = options.i18n;\n this.onOpenChange = options.onOpenChange;\n }\n\n get isOpen(): boolean {\n return this.element !== null;\n }\n\n toggle(anchor: HTMLElement): void {\n if (this.isOpen) {\n this.close();\n return;\n }\n\n this.build();\n const panel = this.element;\n if (!panel) return;\n this.floating = positionPanel(anchor, panel, () => this.element === panel);\n this.detachDismiss = listenPanelDismiss(anchor, panel, () => this.close());\n this.onOpenChange?.(true);\n }\n\n close(): void {\n if (!this.element) return;\n this.detachDismiss?.();\n this.detachDismiss = null;\n this.detachTheme?.();\n this.detachTheme = null;\n this.floating?.stop();\n this.floating = null;\n this.element.remove();\n this.element = null;\n this.cells = [];\n this.cursorRow = -1;\n this.cursorCol = -1;\n this.onOpenChange?.(false);\n }\n\n destroy(): void {\n this.close();\n }\n\n private t(key: string, fallback: string): string {\n return tOrFallback(this.i18n, key, fallback);\n }\n\n private build(): void {\n const panel = document.createElement('div');\n panel.className = 'atri-table-panel';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-label', this.t('editor.table', '表格'));\n // 面板本身可聚焦:方向键与 Escape 才有事件落点,jsdom/真实浏览器都靠这个键盘入口\n panel.tabIndex = -1;\n\n const grid = document.createElement('div');\n grid.className = 'atri-table-panel-grid';\n this.cells = [];\n\n for (let row = 0; row < MAX_ROWS; row++) {\n for (let col = 0; col < MAX_COLS; col++) {\n const cell = document.createElement('div');\n cell.className = 'atri-table-panel-cell';\n // 网格用 table 语义 + role=gridcell,读屏能报出行列坐标\n cell.setAttribute('role', 'gridcell');\n cell.dataset.row = String(row);\n cell.dataset.col = String(col);\n cell.addEventListener('mouseenter', () => this.highlight(row + 1, col + 1));\n cell.addEventListener('click', () => this.insert(row + 1, col + 1));\n grid.appendChild(cell);\n this.cells.push(cell);\n }\n }\n\n const hint = document.createElement('div');\n hint.className = 'atri-table-panel-hint';\n hint.textContent = this.t('editor.table', '表格');\n\n panel.addEventListener('keydown', (event) => this.onKeyDown(event));\n panel.appendChild(grid);\n panel.appendChild(hint);\n\n document.body.appendChild(panel);\n this.element = panel;\n this.detachTheme = followThemeClasses(this.editor.view.dom, panel);\n panel.focus();\n }\n\n /**\n * 高亮左上角 (rows × cols) 区域并在提示行显示尺寸;-1 清空\n */\n private highlight(rows: number, cols: number): void {\n this.cursorRow = rows < 0 ? -1 : rows - 1;\n this.cursorCol = cols < 0 ? -1 : cols - 1;\n\n this.cells.forEach((cell) => {\n const r = Number(cell.dataset.row);\n const c = Number(cell.dataset.col);\n const active = rows > 0 && cols > 0 && r < rows && c < cols;\n cell.classList.toggle('atri-table-panel-cell--active', active);\n if (active) {\n cell.setAttribute('aria-selected', 'true');\n } else {\n cell.removeAttribute('aria-selected');\n }\n });\n\n const hint = this.element?.querySelector('.atri-table-panel-hint');\n if (hint) {\n hint.textContent = rows > 0 ? `${rows} × ${cols}` : this.t('editor.table', '表格');\n }\n }\n\n private insert(rows: number, cols: number): void {\n this.editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();\n this.close();\n }\n\n private onKeyDown(event: KeyboardEvent): void {\n const deltas: Record<string, [number, number]> = {\n ArrowLeft: [0, -1],\n ArrowRight: [0, 1],\n ArrowUp: [-1, 0],\n ArrowDown: [1, 0],\n };\n\n if (event.key === 'Enter') {\n event.preventDefault();\n // 还没导航过就没有高亮格,回车不插任何表格\n if (this.cursorRow < 0 || this.cursorCol < 0) return;\n this.insert(this.cursorRow + 1, this.cursorCol + 1);\n return;\n }\n\n const delta = deltas[event.key];\n if (!delta) return;\n event.preventDefault();\n\n const baseRow = this.cursorRow < 0 ? 0 : this.cursorRow;\n const baseCol = this.cursorCol < 0 ? 0 : this.cursorCol;\n const row = Math.min(MAX_ROWS - 1, Math.max(0, baseRow + delta[0]));\n const col = Math.min(MAX_COLS - 1, Math.max(0, baseCol + delta[1]));\n this.highlight(row + 1, col + 1);\n }\n}\n","/**\n * ToolbarMenuPanel - 工具栏项的下拉菜单(ToolbarItem.children 的落地)\n *\n * 与 StyleOptionPanel 是同一类东西的两个特例,所以结构上有八成相似\n * (锚定、外点与 Escape 关闭、主题跟随、方向键走完列表、共用实例时按 key 归属按下态)。\n * 没有在这里合并成一个带 mode 开关的类,是跟本仓库自己的先例走:\n * 浮层公共底盘是在第三份出现时才抽出去的(见提交 2764ef6),两份先并存。\n * 第三份浮层出现时一起抽,比现在为了少 60 行而给两边都加条件分支更划算。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n followThemeClasses,\n listenPanelDismiss,\n positionPanel,\n type FloatingPanel,\n} from './floating-panel';\n\n/** 菜单里的一行。active / disabled 在打开那一刻算好——开着菜单时选区不会变,\n * 因为点进正文的那一下 pointerdown 就已经把菜单关掉了 */\nexport interface ToolbarMenuItemRow {\n id: string;\n label: string;\n active: boolean;\n disabled: boolean;\n run: () => void;\n}\n\nexport interface ToolbarMenuSpec {\n /** 触发这一菜单的工具栏项 id,按下态只归它 */\n key: string;\n /** 面板的无障碍名字 */\n label: string;\n rows: ToolbarMenuItemRow[];\n}\n\nexport interface ToolbarMenuPanelOptions {\n editor: Editor;\n /** 开合时把控制权交回工具栏:按钮的 active 与 aria-expanded 由它维护 */\n onOpenChange?: (open: boolean) => void;\n}\n\nexport class ToolbarMenuPanel {\n private editor: Editor;\n private onOpenChange?: (open: boolean) => void;\n\n private element: HTMLDivElement | null = null;\n private floating: FloatingPanel | null = null;\n private detachDismiss: (() => void) | null = null;\n private detachTheme: (() => void) | null = null;\n private anchor: HTMLElement | null = null;\n private currentKey: string | null = null;\n private rows: HTMLButtonElement[] = [];\n\n constructor(options: ToolbarMenuPanelOptions) {\n this.editor = options.editor;\n this.onOpenChange = options.onOpenChange;\n }\n\n get isOpen(): boolean {\n return this.element !== null;\n }\n\n get openKey(): string | null {\n return this.currentKey;\n }\n\n toggle(anchor: HTMLElement, spec: ToolbarMenuSpec): void {\n if (this.isOpen) {\n const switching = this.anchor !== anchor;\n this.close();\n if (switching) this.open(anchor, spec);\n return;\n }\n this.open(anchor, spec);\n }\n\n close(): void {\n if (!this.element) return;\n this.detachDismiss?.();\n this.detachDismiss = null;\n this.detachTheme?.();\n this.detachTheme = null;\n this.floating?.stop();\n this.floating = null;\n this.element.remove();\n this.element = null;\n this.anchor = null;\n this.currentKey = null;\n this.rows = [];\n this.onOpenChange?.(false);\n }\n\n destroy(): void {\n this.close();\n }\n\n private open(anchor: HTMLElement, spec: ToolbarMenuSpec): void {\n const panel = this.build(spec);\n document.body.appendChild(panel);\n this.detachTheme = followThemeClasses(this.editorViewDom(anchor), panel);\n this.element = panel;\n this.anchor = anchor;\n this.currentKey = spec.key;\n this.floating = positionPanel(anchor, panel, () => this.element === panel);\n this.detachDismiss = listenPanelDismiss(anchor, panel, () => this.close());\n this.onOpenChange?.(true);\n panel.focus();\n }\n\n /**\n * 主题类要从编辑器根上取,而这里只拿得到一个锚在工具栏上的按钮:\n * 沿 DOM 往上找 .atri-editor 比让调用方再多传一个参数省事,且工具栏就在那个根里面\n */\n private editorViewDom(anchor: HTMLElement): Element | null {\n return anchor.closest('.atri-editor');\n }\n\n private build(spec: ToolbarMenuSpec): HTMLDivElement {\n const panel = document.createElement('div');\n panel.className = 'atri-toolbar-menu';\n panel.setAttribute('role', 'menu');\n panel.setAttribute('aria-label', spec.label);\n panel.tabIndex = -1;\n panel.addEventListener('keydown', (event) => this.onKeyDown(event));\n\n this.rows = [];\n spec.rows.forEach((row) => panel.appendChild(this.makeRow(row)));\n return panel;\n }\n\n private makeRow(row: ToolbarMenuItemRow): HTMLButtonElement {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'atri-toolbar-menu-item';\n // 标题级别与对齐方式都是\"多选一\",所以是 menuitemradio + aria-checked,\n // 而不是 menuitem——读屏因此能报出当前选中的是哪一项\n button.setAttribute('role', 'menuitemradio');\n button.setAttribute('aria-checked', String(row.active));\n button.dataset.menuItem = row.id;\n button.textContent = row.label;\n button.disabled = row.disabled;\n if (row.active) button.classList.add('active');\n\n button.addEventListener('click', () => {\n row.run();\n this.close();\n // 焦点要还给编辑器:顶栏普通按钮的路径是在 createButton 里补这一手的,\n // 走菜单不经过那里,而此刻持有焦点的那个行元素刚被 remove 掉——\n // 不还的话用户选完\"标题 2\"接着打字,字就丢了\n this.editor.commands.focus();\n });\n\n this.rows.push(button);\n return button;\n }\n\n private onKeyDown(event: KeyboardEvent): void {\n if (event.key === 'Enter' || event.key === ' ') return;\n\n const step = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];\n if (step === undefined) return;\n event.preventDefault();\n if (this.rows.length === 0) return;\n\n const current = this.rows.indexOf(document.activeElement as HTMLButtonElement);\n const next =\n current < 0\n ? step > 0\n ? 0\n : this.rows.length - 1\n : (current + step + this.rows.length) % this.rows.length;\n this.rows[next].focus();\n }\n}\n","/**\n * 基础格式能力探测:工具栏决定\"这个按钮摆不摆\"的唯一依据\n *\n * 内部使用,不进 utils barrel、不随包出口——判据(mark 的 spec.attrs 上有没有那一项)\n * 是 extension-text-style 的实现细节,公共面只承诺\"这一下真能生效\"。\n *\n * 为什么不看配置而看 schema:CoreEditor 把内置扩展放在用户扩展之前,就是为了让宿主能用\n * 同名扩展顶掉内置 mark。顶掉之后配置里写着 formatting: true,但那四类属性可能一个都不剩,\n * 于是按钮摆出来、点了没反应——正是本包反复在消除的那类\"命令返回成功、什么都没发生\"。\n */\nimport type { Editor } from '@tiptap/core';\n\n/** textStyle 这一个 mark 承载的四个属性,颜色类与字号字体共用同一个 mark */\nexport type TextStyleAttr = 'color' | 'backgroundColor' | 'fontSize' | 'fontFamily';\n\n/**\n * 属性 → 设置它的命令名。\n * 上游这几个是 addGlobalAttributes 挂上去的 Extension,命令名与属性名并不同形\n * (backgroundColor → setBackgroundColor,但首字母大写规则一致,这里仍列死而不做字符串拼接:\n * 拼出来的命令名读不出错,列成表才能一眼看出漏了谁)\n */\nconst SET_COMMAND: Record<TextStyleAttr, string> = {\n color: 'setColor',\n backgroundColor: 'setBackgroundColor',\n fontSize: 'setFontSize',\n fontFamily: 'setFontFamily',\n};\n\n/** 这个属性是否既设得了也存得住 */\nexport function canUseTextStyle(editor: Editor, attr: TextStyleAttr): boolean {\n const command = (editor.commands as Record<string, unknown>)[SET_COMMAND[attr]];\n if (typeof command !== 'function') return false;\n return !!editor.schema.marks.textStyle?.spec.attrs?.[attr];\n}\n\n/** 独立 mark(高亮 / 下标 / 上标)是否在册 */\nexport function hasMark(editor: Editor, name: string): boolean {\n return !!editor.schema.marks[name];\n}\n","/**\n * Lucide Icons - 工具栏图标映射\n * 来源: https://lucide.dev (MIT License)\n */\n\nexport const icons: Record<string, string> = {\n undo: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 7v6h6\"/><path d=\"M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13\"/></svg>',\n\n redo: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 7v6h-6\"/><path d=\"M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13\"/></svg>',\n\n heading1:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12h8\"/><path d=\"M4 18V6\"/><path d=\"M12 18V6\"/><path d=\"M17 12l3-2v8\"/></svg>',\n\n heading2:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12h8\"/><path d=\"M4 18V6\"/><path d=\"M12 18V6\"/><path d=\"M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1\"/></svg>',\n\n heading3:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12h8\"/><path d=\"M4 18V6\"/><path d=\"M12 18V6\"/><path d=\"M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 0 1-2 2\"/><path d=\"M17 17.5c2 1.5 4 .3 4-1.5a2 2 0 0 0-2-2\"/></svg>',\n\n paragraph:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M13 4v16\"/><path d=\"M17 4v16\"/><path d=\"M19 4H9.5a4.5 4.5 0 0 0 0 9H13\"/></svg>',\n\n bold: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8\"/></svg>',\n\n italic:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n underline:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 4v6a6 6 0 0 0 12 0V4\"/><line x1=\"4\" x2=\"20\" y1=\"20\" y2=\"20\"/></svg>',\n\n strikethrough:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n code: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/></svg>',\n\n list: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/><line x1=\"3\" x2=\"3.01\" y1=\"6\" y2=\"6\"/><line x1=\"3\" x2=\"3.01\" y1=\"12\" y2=\"12\"/><line x1=\"3\" x2=\"3.01\" y1=\"18\" y2=\"18\"/></svg>',\n\n listOrdered:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n listChecks:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m3 17 2 2 4-4\"/><path d=\"m3 7 2 2 4-4\"/><path d=\"M13 6h8\"/><path d=\"M13 12h8\"/><path d=\"M13 18h8\"/></svg>',\n\n horizontalRule:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"5\" x2=\"19\" y1=\"12\" y2=\"12\"/></svg>',\n\n quote:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\"/><path d=\"M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\"/></svg>',\n\n codeBlock:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/></svg>',\n\n alignLeft:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n alignCenter:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n alignRight:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n alignJustify:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"21\" x2=\"3\" y1=\"6\" y2=\"6\"/><line x1=\"21\" x2=\"3\" y1=\"12\" y2=\"12\"/><line x1=\"21\" x2=\"3\" y1=\"18\" y2=\"18\"/></svg>',\n\n image:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n paperclip:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m16 6-8.4 8.6a2 2 0 0 0 2.8 2.8l8.4-8.6a4 4 0 1 0-5.6-5.6l-8.4 8.5a6 6 0 1 0 8.5 8.5l8.4-8.4\"/></svg>',\n\n link: '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 17H7A5 5 0 1 1 7 7h2\"/><path d=\"M15 7h2a5 5 0 1 1 0 10h-2\"/><line x1=\"8\" x2=\"16\" y1=\"12\" y2=\"12\"/></svg>',\n\n table:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3v18\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/></svg>',\n\n trash:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/><path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"/><line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\"/><line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\"/></svg>',\n\n // 表格操作菜单:统一用「表格边框 + 方向标记」一套句法——\n // 行操作画横分隔线、列操作画竖分隔线,加号落在哪一格就是插到哪一侧,\n // 减号落在哪一格就是删哪一格(两种删除靠分隔线方向区分)\n tableRowBefore:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/><path d=\"M12 4.5v3\"/><path d=\"M10.5 6h3\"/></svg>',\n\n tableRowAfter:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/><path d=\"M12 16.5v3\"/><path d=\"M10.5 18h3\"/></svg>',\n\n tableRowDelete:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/><path d=\"M10.5 12h3\"/></svg>',\n\n tableColumnBefore:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/><path d=\"M4.5 12h3\"/><path d=\"M6 10.5v3\"/></svg>',\n\n tableColumnAfter:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/><path d=\"M16.5 12h3\"/><path d=\"M18 10.5v3\"/></svg>',\n\n tableColumnDelete:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M9 3v18\"/><path d=\"M15 3v18\"/><path d=\"M10.5 12h3\"/></svg>',\n\n tableMergeCells:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M8 12h8\"/><path d=\"m10.5 9.5-2.5 2.5 2.5 2.5\"/><path d=\"m13.5 9.5 2.5 2.5-2.5 2.5\"/></svg>',\n\n tableSplitCell:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M12 3v18\"/><path d=\"M9 12H6\"/><path d=\"M15 12h3\"/></svg>',\n\n tableHeaderRow:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M7 6h4\"/></svg>',\n\n textColor:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"13.5\" cy=\"6.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"17.5\" cy=\"10.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"8.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"6.5\" cy=\"12.5\" r=\".5\" fill=\"currentColor\"/><path d=\"M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.436-.652-.436-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2Z\"/></svg>',\n\n highlight:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n highlightColor:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m14 7 3-3a2.12 2.12 0 0 1 3 3l-3 3\"/><path d=\"m13 6 5 5\"/><path d=\"M14.7 8.3 8 15v4h4l6.7-6.7a2 2 0 0 0-4-4\"/></svg>',\n\n fontSize:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m2 18 4.5-11L11 18\"/><path d=\"M3.6 14h5.8\"/><path d=\"m14 18 3-7 3 7\"/><path d=\"M14.9 15.6h4.2\"/></svg>',\n\n fontFamily:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><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\"/></svg>',\n\n subscript:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m4 5 6 8\"/><path d=\"m10 5-6 8\"/><path d=\"M14 14.5c.7-1.4 2.8-1.4 3.4 0 .5 1.2-.5 2-1.7 2.6-1.4.7-2.2 1.4-2.2 2.4h4.5\"/></svg>',\n\n superscript:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m4 9 6 8\"/><path d=\"m10 9-6 8\"/><path d=\"M14 5.5c.7-1.4 2.8-1.4 3.4 0 .5 1.2-.5 2-1.7 2.6-1.4.7-2.2 1.4-2.2 2.4h4.5\"/></svg>',\n\n clearFormat:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21\"/><path d=\"M22 21H7\"/><path d=\"m5 11 9 9\"/></svg>',\n\n // 下拉触发器用的通用字形:标题菜单与对齐菜单在\"当前没有选中任何一级/一种对齐\"时用它,\n // 有当前值时触发器换成那一项自己的图标,所见即所得\n heading:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 4v16\"/><path d=\"M18 4v16\"/><path d=\"M6 12h12\"/></svg>',\n\n align:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 6h16\"/><path d=\"M4 10h10\"/><path d=\"M4 14h16\"/><path d=\"M4 18h8\"/></svg>',\n\n chevronDown:\n '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>',\n};\n","/**\n * ToolbarManager - 工具栏管理器\n * 负责创建工具栏 DOM、绑定按钮事件、更新按钮状态\n */\nimport { isNodeSelection, type Editor } from '@tiptap/core';\nimport type { ToolbarConfig, ToolbarItem } from '../types';\nimport { InsertPanel, type InsertPanelMode } from '../media/InsertPanel';\nimport type { MediaRuntime } from '../media/MediaRuntime';\nimport { BUBBLE_NODE_ITEMS, BUBBLE_TEXT_ITEMS } from './bubble-toolbar';\nimport { zhText, type I18nManager } from './I18nManager';\nimport type { LinkPanel } from './LinkPanel';\nimport { StyleOptionPanel, type StylePanelSpec } from './StyleOptionPanel';\nimport {\n FONT_FAMILIES,\n FONT_SIZES,\n HIGHLIGHT_COLORS,\n TEXT_COLORS,\n type StyleOption,\n} from './style-presets';\nimport { TableGridPanel } from './TableGridPanel';\nimport {\n ToolbarMenuPanel,\n type ToolbarMenuItemRow,\n type ToolbarMenuSpec,\n} from './ToolbarMenuPanel';\nimport { canInsertTable } from '../utils/table';\nimport { canUseTextStyle, hasMark, type TextStyleAttr } from '../utils/formatting';\nimport { icons } from './icons';\n\n/**\n * 工具栏项定义\n */\ninterface ToolbarItemDef {\n id: string;\n icon: string;\n /** 没有词条映射时的回退文案;内置项都走 TOOLTIP_KEYS -> 中文词条,不在此抄一份 */\n tooltip?: string;\n /**\n * 会打开浮层。true(或 'dialog')是带输入框的面板,'menu' 是下拉菜单:\n * 两者的 aria-haspopup 不同,读屏报出来的语义也不同。\n * 不管是哪种,都不能把焦点抢回编辑器——抢了浮层里的输入框就根本打不了字\n */\n popup?: boolean | 'menu';\n /**\n * 下拉菜单里的子项 id,走的仍是同一张 itemDefs 表:\n * 子项因此和顶栏项一样受能力门控(比如表格被顶掉时 insertTable 就不会出现在菜单里)\n */\n children?: string[];\n command: (editor: Editor, button: HTMLButtonElement) => void;\n isActive: (editor: Editor) => boolean;\n isDisabled: (editor: Editor) => boolean;\n}\n\n/**\n * 工具栏项与 i18n 词条的映射(这些项的默认文案就是中文词条本身,见 zhText)\n */\nconst TOOLTIP_KEYS: Record<string, string> = {\n undo: 'editor.undo',\n redo: 'editor.redo',\n heading1: 'editor.heading1',\n heading2: 'editor.heading2',\n heading3: 'editor.heading3',\n heading4: 'editor.heading4',\n heading5: 'editor.heading5',\n heading6: 'editor.heading6',\n // 两个下拉触发器。'editor.heading' 这条词条躺了很久、背后没有任何功能,\n // 这批终于有了真正读它的人\n heading: 'editor.heading',\n align: 'editor.align',\n paragraph: 'editor.paragraph',\n bold: 'editor.bold',\n italic: 'editor.italic',\n underline: 'editor.underline',\n strike: 'editor.strikethrough',\n code: 'editor.code',\n bulletList: 'editor.bulletList',\n orderedList: 'editor.orderedList',\n taskList: 'editor.taskList',\n blockquote: 'editor.blockquote',\n codeBlock: 'editor.codeBlock',\n horizontalRule: 'editor.horizontalRule',\n alignLeft: 'editor.alignLeft',\n alignCenter: 'editor.alignCenter',\n alignRight: 'editor.alignRight',\n alignJustify: 'editor.alignJustify',\n insertImage: 'editor.image',\n insertAttachment: 'editor.attachment',\n attachmentDisplay: 'editor.attachmentDisplay',\n insertTable: 'editor.table',\n insertLink: 'editor.link',\n delete: 'editor.delete',\n textColor: 'editor.textColor',\n highlight: 'editor.highlight',\n highlightColor: 'editor.highlightColor',\n fontSize: 'editor.fontSize',\n fontFamily: 'editor.fontFamily',\n subscript: 'editor.subscript',\n superscript: 'editor.superscript',\n clearFormat: 'editor.clearFormat',\n};\n\n/**\n * 工具栏管理器\n */\nexport class ToolbarManager {\n private container: HTMLElement;\n private editor: Editor;\n private i18n?: I18nManager;\n private config?: ToolbarConfig;\n private mediaRuntime: MediaRuntime | null;\n /** 链接浮层由门面持有:编辑态点击链接也要能打开它,跟工具栏渲染不渲染无关 */\n private linkPanel: LinkPanel | null;\n private unsubscribeLanguage?: () => void;\n private unsubscribeEditor?: () => void;\n /** 同一个项可能同时渲染在顶栏与浮动工具栏里,所以一个 id 对应一组按钮 */\n private buttons: Map<string, Set<HTMLButtonElement>> = new Map();\n /** 按 id 记的覆盖文案:顶栏与浮层共用一份,改一处两处一起改,这是有意的 */\n private customTooltips: Map<string, string> = new Map();\n private itemDefs: Map<string, ToolbarItemDef>;\n private createdElements: HTMLElement[] = [];\n private panel: InsertPanel | null = null;\n private tablePanel: TableGridPanel | null = null;\n /** 颜色 / 字号 / 字体四个入口共用一个面板实例:同时只会开一个 */\n private stylePanel: StyleOptionPanel | null = null;\n /** 下拉菜单同理,所有 children 菜单共用一个实例 */\n private menuPanel: ToolbarMenuPanel | null = null;\n\n constructor(\n editor: Editor,\n container: HTMLElement,\n config?: ToolbarConfig,\n i18n?: I18nManager,\n mediaRuntime?: MediaRuntime | null,\n linkPanel?: LinkPanel | null\n ) {\n this.editor = editor;\n this.container = container;\n this.config = config;\n this.i18n = i18n;\n this.mediaRuntime = mediaRuntime ?? null;\n this.linkPanel = linkPanel ?? null;\n this.itemDefs = this.getDefaultItems();\n this.createToolbarDOM();\n this.bindEditorEvents();\n\n this.unsubscribeLanguage = this.i18n?.onLanguageChanged(() => this.applyTooltips());\n }\n\n /**\n * 把浮动工具栏的按钮渲染进给定的元素\n *\n * 元素由门面创建并持有:BubbleMenu 每次隐藏都会把它从文档里摘掉,重建编辑器后\n * 还得拿同一个元素重新 attach,所以它不进 createdElements、也不在这里 remove\n */\n attachBubbleToolbar(element: HTMLElement): void {\n // 重建编辑器后是\"新的管理器 + 同一个元素\"再走一遍这里:先清干净,\n // 否则上一任的按钮还留在盒子里,却再也等不到状态同步\n element.replaceChildren();\n\n // 两组一次都渲染出来,靠 CSS 读容器上的 data-atri-bubble-mode 决定哪组占宽度\n const groups: [string, readonly string[]][] = [\n ['text', BUBBLE_TEXT_ITEMS],\n ['node', BUBBLE_NODE_ITEMS],\n ];\n\n groups.forEach(([groupId, itemIds]) => {\n const group = document.createElement('div');\n group.className = 'atri-editor-bubble-group';\n group.setAttribute('data-atri-bubble-group', groupId);\n\n itemIds.forEach((itemId) => {\n const itemDef = this.itemDefs.get(itemId);\n if (!itemDef) return;\n\n // 带浮层的项不能进这里:面板锚在按钮上,而按钮跟着浮层一起被摘出文档\n if (itemDef.popup) {\n console.warn(\n `[Atri Editor] Toolbar item \"${itemId}\" opens a panel, which bubble toolbar cannot host.`\n );\n return;\n }\n\n group.appendChild(this.createButton(itemId, itemDef));\n });\n\n element.appendChild(group);\n });\n\n this.applyTooltips();\n this.updateButtonStates();\n }\n\n /**\n * 插入浮层按需创建:没点开过就不该有一个实例挂在编辑器上\n */\n private get insertPanel(): InsertPanel | null {\n if (!this.mediaRuntime) return null;\n this.panel ??= new InsertPanel({\n editor: this.editor,\n runtime: this.mediaRuntime,\n i18n: this.i18n,\n // 开合不改文档,等不到 transaction,得主动刷一次按钮状态\n onOpenChange: () => this.updateButtonStates(),\n });\n return this.panel;\n }\n\n private openInsert(mode: InsertPanelMode, button: HTMLButtonElement): void {\n this.insertPanel?.toggle(button, mode);\n }\n\n /**\n * 表格网格面板按需创建:没点开过就不该有一个实例挂在编辑器上\n */\n private get gridPanel(): TableGridPanel | null {\n if (!this.tableExtensionRegistered) return null;\n this.tablePanel ??= new TableGridPanel({\n editor: this.editor,\n i18n: this.i18n,\n // 开合不改文档,等不到 transaction,得主动刷一次按钮状态\n onOpenChange: () => this.updateButtonStates(),\n });\n return this.tablePanel;\n }\n\n /** 内置表格真可用才摆按钮:table:false 未注册、或被用户同名扩展顶掉都不摆(判定见 canInsertTable) */\n private get tableExtensionRegistered(): boolean {\n return canInsertTable(this.editor);\n }\n\n /** 格式面板按需创建:没点开过就不该有一个实例挂在编辑器上 */\n private get styleOptionPanel(): StyleOptionPanel {\n this.stylePanel ??= new StyleOptionPanel({\n editor: this.editor,\n i18n: this.i18n,\n // 开合不改文档,等不到 transaction,得主动刷一次按钮状态\n onOpenChange: () => this.updateButtonStates(),\n });\n return this.stylePanel;\n }\n\n private tOr(key: string, fallback: string): string {\n return this.i18n?.tOr(key, fallback) ?? fallback;\n }\n\n /** 下拉菜单面板按需创建:没点开过就不该有一个实例挂在编辑器上 */\n private get menu(): ToolbarMenuPanel {\n this.menuPanel ??= new ToolbarMenuPanel({\n editor: this.editor,\n // 开合不改文档,等不到 transaction,得主动刷一次按钮状态\n onOpenChange: () => this.updateButtonStates(),\n });\n return this.menuPanel;\n }\n\n /**\n * 把一个项做成下拉触发器。\n * 子项走的仍是同一张 itemDefs 表,所以它们和顶栏项一样受能力门控——\n * 表格被顶掉时 insertTable 就不会出现在菜单里,而不是摆一行按了没反应的\n */\n private menuItem(key: string, icon: string, children: string[]): ToolbarItemDef {\n const def: ToolbarItemDef = {\n id: key,\n icon,\n popup: 'menu',\n children,\n command: (_editor, button) => {\n // 必须现读而不是用上面那个闭包参数:宿主用 { id: 'heading', children: [...] }\n // 覆盖时改的就是 def.children,闭包里那份还是内置默认值,\n // 用闭包会让\"覆盖写在类型上、点在旧列表上\"这种错配静默生效\n const live = this.itemDefs.get(key)?.children ?? children;\n this.menu.toggle(button, this.menuSpec(key, live, button));\n },\n isActive: () => this.menuPanel?.openKey === key,\n isDisabled: (editor) => !editor.isEditable,\n };\n return def;\n }\n\n /** 当前命中的子项:触发器的图标与提示都跟着它走,用户不必点开就知道现在的级别 */\n private activeChild(def: ToolbarItemDef): ToolbarItemDef | null {\n for (const childId of def.children ?? []) {\n const child = this.itemDefs.get(childId);\n if (child && !child.popup && child.isActive(this.editor) && !child.isDisabled(this.editor)) {\n return child;\n }\n }\n return null;\n }\n\n /** 子项词条 key;没有映射的(宿主的自定义项)退回定义里的兜底文案 */\n private rowLabel(childId: string, child: ToolbarItemDef): string {\n const key = TOOLTIP_KEYS[childId];\n return key ? this.tOr(key, zhText(key)) : (child.tooltip ?? childId);\n }\n\n /**\n * 菜单里带浮层的子项一律不收:子项被点中后菜单就关了,\n * 而面板要锚在触发器上——锚一个即将从文档里消失的行没有意义。\n * 这与 attachBubbleToolbar 拒绝浮层项是同一条理由。\n */\n private menuSpec(key: string, children: string[], trigger: HTMLButtonElement): ToolbarMenuSpec {\n const menuKey = TOOLTIP_KEYS[key];\n return {\n key,\n label: menuKey ? this.tOr(menuKey, zhText(menuKey)) : key,\n rows: children.reduce<ToolbarMenuItemRow[]>((rows, childId) => {\n const child = this.itemDefs.get(childId);\n if (!child) {\n console.warn(`[Atri Editor] Menu \"${key}\" declares unknown item \"${childId}\".`);\n return rows;\n }\n if (child.popup) {\n console.warn(\n `[Atri Editor] Menu item \"${childId}\" opens a panel, which menus cannot host.`\n );\n return rows;\n }\n rows.push({\n id: childId,\n label: this.rowLabel(childId, child),\n active: child.isActive(this.editor),\n disabled: child.isDisabled(this.editor),\n // 子项自己不需要触发器元素(只有浮层项会用),传过去是为了共用同一份定义\n run: () => child.command(this.editor, trigger),\n });\n return rows;\n }, []),\n };\n }\n\n /**\n * 面板文案在这里现取,不在建项时算好存下来:\n * 语言切换后重开面板要拿到新词条,而项定义只在建栏时求值一次\n */\n private styleSpec(\n key: string,\n parts: {\n shape: 'grid' | 'list';\n options: StyleOption[];\n swatch: boolean;\n attr: TextStyleAttr | 'highlight';\n }\n ): StylePanelSpec {\n return {\n key,\n label: this.tOr(`editor.${key}`, key),\n shape: parts.shape,\n options: parts.options,\n // 色板才有\"其他颜色\"这一行;字号字体用原生控件选不出任意值\n customColorLabel: parts.swatch ? this.tOr('formatting.customColor', '其他颜色…') : undefined,\n // 清除行的措辞按形状分:颜色去掉是\"无\",字号字体退回的是\"默认\"\n clearLabel: parts.swatch\n ? this.tOr('formatting.noColor', '无')\n : this.tOr('formatting.defaultValue', '默认'),\n onPick: (value) => this.applyStyle(parts.attr, value),\n };\n }\n\n /**\n * 空串是\"清除\"而不是\"设成空值\":设成空串会留下一个属性为空串的 mark,\n * 而 textStyle 的 renderHTML 对空值照样会吐出一个 `<span style=\"\">`,\n * 那种节点既看不见又会在往返里产噪音(见 extensions/formatting.ts 的发出侧)\n */\n private applyStyle(attr: TextStyleAttr | 'highlight', value: string): void {\n const chain = this.editor.chain().focus();\n if (attr === 'highlight') {\n if (value === '') chain.unsetHighlight().run();\n else chain.setHighlight({ color: value }).run();\n return;\n }\n switch (attr) {\n case 'color':\n if (value === '') chain.unsetColor().run();\n else chain.setColor(value).run();\n return;\n case 'backgroundColor':\n if (value === '') chain.unsetBackgroundColor().run();\n else chain.setBackgroundColor(value).run();\n return;\n case 'fontSize':\n if (value === '') chain.unsetFontSize().run();\n else chain.setFontSize(value).run();\n return;\n case 'fontFamily':\n if (value === '') chain.unsetFontFamily().run();\n else chain.setFontFamily(value).run();\n return;\n }\n }\n\n /**\n * 创建工具栏 DOM - 直接将按钮插入 container\n */\n private createToolbarDOM(): void {\n const items = this.config?.items;\n\n if (items) {\n items.forEach((item) => {\n const resolved = this.resolveItem(item);\n if (!resolved) return;\n const button = this.createButton(resolved.id, resolved.def, resolved.overrides);\n this.container.appendChild(button);\n this.createdElements.push(button);\n });\n this.applyTooltips();\n return;\n }\n\n // 默认工具栏布局(分组)\n const layout = [\n ['undo', 'redo'],\n // 标题收成一下拉:一到六级加正文七个取值平铺要占七格,而一次只用得上一个\n ['heading'],\n // 链接跟着行内格式走:它也是一段文字上的标记,只是要填地址所以带浮层\n ['bold', 'italic', 'underline', 'strike', 'code', 'insertLink'],\n // 颜色与字号字体自成一组:都是\"这段文字长什么样\",且除 highlight 外都带浮层\n ['textColor', 'highlight', 'highlightColor', 'fontSize', 'fontFamily'],\n ['bulletList', 'orderedList', 'taskList', 'blockquote', 'codeBlock'],\n // 对齐同样收成一下拉\n ['align'],\n // 分割线与表格是一组:都是\"往正文里插一块\"的动作,没有激活态\n ['horizontalRule', 'insertTable'],\n // 上下标与清除格式:作用于选区已有的格式,而不是加一种新格式\n ['subscript', 'superscript', 'clearFormat'],\n ['insertImage', 'insertAttachment'],\n ];\n\n // 整组一项都不存在就不摆这一组:分隔符是按组序插的,留下一组空的就会多出\n // 一根光杆(formatting:false 时那两组全空,table:false / media:false 同理)\n const groups = layout.filter((group) => group.some((itemId) => this.itemDefs.has(itemId)));\n\n groups.forEach((group, groupIndex) => {\n if (groupIndex > 0) {\n const separator = document.createElement('div');\n separator.className = 'atri-editor-toolbar-separator';\n this.container.appendChild(separator);\n this.createdElements.push(separator);\n }\n\n group.forEach((itemId) => {\n const itemDef = this.itemDefs.get(itemId);\n if (!itemDef) return;\n\n const button = this.createButton(itemId, itemDef);\n this.container.appendChild(button);\n this.createdElements.push(button);\n });\n });\n\n this.applyTooltips();\n }\n\n /**\n * 解析配置项:字符串按 id 取内置定义,ToolbarItem 允许覆盖展示文案\n */\n private resolveItem(\n item: string | ToolbarItem\n ): { id: string; def: ToolbarItemDef; overrides?: ToolbarItem } | null {\n const id = typeof item === 'string' ? item : item.id;\n const def = this.itemDefs.get(id);\n\n if (!def) {\n console.warn(`[Atri Editor] Unknown toolbar item \"${id}\" was skipped.`);\n return null;\n }\n\n if (typeof item === 'string') {\n return { id, def };\n }\n\n if (item.children?.length) {\n // 目前只有内置的下拉项(heading / align)能挂子项:公开注册自定义项还没有入口,\n // 让宿主把 children 写在普通按钮上只会得到一个静默无效的声明——\n // 那正是本包在别处反复消除的\"写了没反应\",所以这里明确告警而不是默默吞掉\n if (def.popup !== 'menu') {\n console.warn(\n `[Atri Editor] Toolbar item \"${id}\" declares children, but it is not a dropdown; ` +\n 'only the built-in dropdown items (heading, align) accept children.'\n );\n } else {\n // 只支持一层:子项再带 children 就变成\"菜单里套菜单\",而菜单面板没有嵌套定位能力。\n // 覆盖写在 def 上而不是只记这一次渲染,因为同一个项可能同时出现在顶栏与浮层,\n // 与 customTooltips 一样是\"一份定义两处共用\"的有意选择\n const nested = item.children.filter((child) => child.children?.length);\n if (nested.length) {\n console.warn(\n `[Atri Editor] Nested children on \"${nested\n .map((child) => child.id)\n .join(', ')}\" are not supported and were ignored.`\n );\n }\n def.children = item.children.map((child) => child.id);\n }\n }\n\n return { id, def, overrides: item };\n }\n\n private createButton(\n itemId: string,\n itemDef: ToolbarItemDef,\n overrides?: ToolbarItem\n ): HTMLButtonElement {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'atri-editor-toolbar-btn';\n button.setAttribute('data-toolbar-item', itemId);\n\n if (overrides?.icon) {\n button.innerHTML = overrides.icon;\n } else if (overrides?.label) {\n button.textContent = overrides.label;\n } else {\n button.innerHTML = itemDef.icon;\n }\n\n if (overrides?.tooltip) {\n this.customTooltips.set(itemId, overrides.tooltip);\n }\n\n if (itemDef.popup) {\n button.setAttribute('aria-haspopup', itemDef.popup === 'menu' ? 'menu' : 'dialog');\n button.setAttribute('aria-expanded', 'false');\n }\n\n if (itemDef.popup === 'menu') {\n // 下拉触发器要带一个下箭头:不带的话它和旁边的普通按钮长得一模一样,\n // 用户得点下去才知道里面还有一层。\n // 基础图标单独存一份:状态刷新要按\"当前命中的子项\"换图标,\n // 换完还得能退回建按钮时那一版(里面可能带着宿主覆盖的图标)\n button.classList.add('atri-editor-toolbar-btn--menu');\n button.dataset.menuIcon = button.innerHTML;\n button.innerHTML += icons.chevronDown;\n }\n\n button.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n itemDef.command(this.editor, button);\n // 浮层项自己管焦点:抢回编辑器会让浮层里的输入框根本打不了字\n if (!itemDef.popup) this.editor.commands.focus();\n });\n\n let group = this.buttons.get(itemId);\n if (!group) {\n group = new Set();\n this.buttons.set(itemId, group);\n }\n group.add(button);\n return button;\n }\n\n /**\n * 按当前语言刷新按钮 tooltip\n */\n private applyTooltips(): void {\n this.buttons.forEach((buttons, itemId) => {\n const itemDef = this.itemDefs.get(itemId);\n if (!itemDef) return;\n\n // 有词条映射的项,中文原文就是词条本身;只有不带词条的自定义项才用定义里的 tooltip。\n // 下拉触发器与宿主覆盖的优先级都收在 itemTitle 里,与状态刷新共用同一份判定\n const title = this.itemTitle(itemId, itemDef);\n\n buttons.forEach((button) => {\n button.title = title;\n });\n });\n }\n\n /**\n * 获取默认工具栏项定义\n */\n private getDefaultItems(): Map<string, ToolbarItemDef> {\n const items = new Map<string, ToolbarItemDef>();\n\n // 媒体节点没注册(media:false)时这两个命令压根不存在,按钮也不提供\n if (this.mediaRuntime) {\n items.set('insertImage', {\n id: 'insertImage',\n icon: icons.image,\n popup: true,\n command: (_editor, button) => this.openInsert('image', button),\n isActive: () => this.panel?.openMode === 'image',\n isDisabled: (editor) => !editor.isEditable,\n });\n\n items.set('insertAttachment', {\n id: 'insertAttachment',\n icon: icons.paperclip,\n popup: true,\n command: (_editor, button) => this.openInsert('attachment', button),\n isActive: () => this.panel?.openMode === 'attachment',\n isDisabled: (editor) => !editor.isEditable,\n });\n\n // 卡片↔行内链接是选中态上的操作:没选中附件就没有作用对象\n items.set('attachmentDisplay', {\n id: 'attachmentDisplay',\n icon: icons.link,\n command: (editor) => editor.chain().focus().toggleAttachmentDisplay().run(),\n isActive: (editor) => editor.isActive('attachment') || editor.isActive('attachmentLink'),\n isDisabled: (editor) =>\n !editor.isActive('attachment') && !editor.isActive('attachmentLink'),\n });\n }\n\n // 内置表格不可用(table:false 或被用户顶掉)时 insertTable 命令跑不通,按钮也不提供\n if (this.tableExtensionRegistered) {\n items.set('insertTable', {\n id: 'insertTable',\n icon: icons.table,\n popup: true,\n command: (_editor, button) => this.gridPanel?.toggle(button),\n isActive: () => this.tablePanel?.isOpen === true,\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n // 链接入口:浮层归门面所有(点链接就地编辑也要用它),这里只摆一个触发按钮\n if (this.linkPanel) {\n items.set('insertLink', {\n id: 'insertLink',\n icon: icons.link,\n popup: true,\n command: (_editor, button) => this.linkPanel?.toggle(button),\n // 浮层开着时按下去是关,所以按钮的\"按下态\"跟的是浮层,不是选区里有没有链接\n isActive: (editor) => this.linkPanel?.isOpen === true || editor.isActive('link'),\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n // 基础格式:按能力摆项。formatting:false 没注册、或宿主用同名扩展顶掉内置 mark 时,\n // 一条都不出——判据是\"命令在不在 + schema 里有没有那个属性\"两条,见 utils/formatting.ts。\n // 这一组全部按能力各自判定,不是一荣俱荣:宿主完全可能只顶掉 textStyle 而留着 highlight。\n const textStyleReady = (attr: TextStyleAttr) => canUseTextStyle(this.editor, attr);\n\n if (textStyleReady('color')) {\n items.set('textColor', {\n id: 'textColor',\n icon: icons.textColor,\n popup: true,\n command: (_editor, button) =>\n this.styleOptionPanel.toggle(\n button,\n this.styleSpec('textColor', {\n shape: 'grid',\n options: TEXT_COLORS,\n swatch: true,\n attr: 'color',\n })\n ),\n // 浮层项的按下态只跟浮层走:选区里有没有颜色是文档事实,不是\"这个按钮正按着\"\n isActive: () => this.stylePanel?.openKey === 'textColor',\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n if (hasMark(this.editor, 'highlight')) {\n // 无色的 ==text== 是个纯开关,与\"挑一个高亮色\"是两件事,所以分成两项\n items.set('highlight', {\n id: 'highlight',\n icon: icons.highlight,\n command: (editor) => editor.chain().focus().toggleHighlight().run(),\n isActive: (editor) => editor.isActive('highlight'),\n isDisabled: (editor) => !editor.isEditable,\n });\n\n items.set('highlightColor', {\n id: 'highlightColor',\n icon: icons.highlightColor,\n popup: true,\n command: (_editor, button) =>\n this.styleOptionPanel.toggle(\n button,\n this.styleSpec('highlightColor', {\n shape: 'grid',\n options: HIGHLIGHT_COLORS,\n swatch: true,\n attr: 'highlight',\n })\n ),\n isActive: () => this.stylePanel?.openKey === 'highlightColor',\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n if (textStyleReady('fontSize')) {\n items.set('fontSize', {\n id: 'fontSize',\n icon: icons.fontSize,\n popup: true,\n command: (_editor, button) =>\n this.styleOptionPanel.toggle(\n button,\n this.styleSpec('fontSize', {\n shape: 'list',\n options: FONT_SIZES,\n swatch: false,\n attr: 'fontSize',\n })\n ),\n isActive: () => this.stylePanel?.openKey === 'fontSize',\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n if (textStyleReady('fontFamily')) {\n items.set('fontFamily', {\n id: 'fontFamily',\n icon: icons.fontFamily,\n popup: true,\n command: (_editor, button) =>\n this.styleOptionPanel.toggle(\n button,\n this.styleSpec('fontFamily', {\n shape: 'list',\n options: FONT_FAMILIES,\n swatch: false,\n attr: 'fontFamily',\n })\n ),\n isActive: () => this.stylePanel?.openKey === 'fontFamily',\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n if (hasMark(this.editor, 'subscript')) {\n items.set('subscript', {\n id: 'subscript',\n icon: icons.subscript,\n command: (editor) => editor.chain().focus().toggleSubscript().run(),\n isActive: (editor) => editor.isActive('subscript'),\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n if (hasMark(this.editor, 'superscript')) {\n items.set('superscript', {\n id: 'superscript',\n icon: icons.superscript,\n command: (editor) => editor.chain().focus().toggleSuperscript().run(),\n isActive: (editor) => editor.isActive('superscript'),\n isDisabled: (editor) => !editor.isEditable,\n });\n }\n\n // 清除格式:把选区上所有 mark 与块级类型一起退回默认。\n // 常驻不禁用——unsetAllMarks 对空格式光标是无操作,而不是报错,\n // 要为\"有没有可清除的东西\"做判定就得遍历选区,不值当\n items.set('clearFormat', {\n id: 'clearFormat',\n icon: icons.clearFormat,\n command: (editor) => editor.chain().focus().unsetAllMarks().clearNodes().run(),\n isActive: () => false,\n isDisabled: (editor) => !editor.isEditable,\n });\n\n // 删除作用于选中的整节点:一段文字该交给 Delete 键,所以它不进顶栏默认布局\n items.set('delete', {\n id: 'delete',\n icon: icons.trash,\n command: (editor) => editor.chain().deleteSelection().run(),\n isActive: () => false,\n isDisabled: (editor) => !isNodeSelection(editor.state.selection),\n });\n\n // 撤销\n items.set('undo', {\n id: 'undo',\n icon: icons.undo,\n command: (editor) => editor.chain().undo().run(),\n isActive: () => false,\n isDisabled: (editor) => !editor.can().undo(),\n });\n\n // 重做\n items.set('redo', {\n id: 'redo',\n icon: icons.redo,\n command: (editor) => editor.chain().redo().run(),\n isActive: () => false,\n isDisabled: (editor) => !editor.can().redo(),\n });\n\n // 标题 1–6。原来只到 3,而 schema 与输入规则一直是 1..6(StarterKit 默认 levels),\n // 缺的只是入口——4/5/6 落在标题菜单里,不用点开顶栏也够得着。\n // 四个新级别用通用 heading 图标:它们只在菜单里以文字标签出现,\n // 而我要是真去手搓三个数字字形,既没法验证好不好看、又和 tooltip 里的\"标题 4\"重复\n [1, 2, 3, 4, 5, 6].forEach((level) => {\n const id = `heading${level}`;\n items.set(id, {\n id,\n icon: level <= 3 ? icons[id] : icons.heading,\n command: (editor) =>\n editor\n .chain()\n .toggleHeading({ level } as never)\n .run(),\n isActive: (editor) => editor.isActive('heading', { level } as never),\n isDisabled: (editor) => !editor.can().toggleHeading({ level } as never),\n });\n });\n\n // 正文\n items.set('paragraph', {\n id: 'paragraph',\n icon: icons.paragraph,\n command: (editor) => editor.chain().setParagraph().run(),\n isActive: (editor) => editor.isActive('paragraph'),\n isDisabled: (editor) => !editor.can().setParagraph(),\n });\n\n // 加粗\n items.set('bold', {\n id: 'bold',\n icon: icons.bold,\n command: (editor) => editor.chain().toggleBold().run(),\n isActive: (editor) => editor.isActive('bold'),\n isDisabled: (editor) => !editor.can().toggleBold(),\n });\n\n // 斜体\n items.set('italic', {\n id: 'italic',\n icon: icons.italic,\n command: (editor) => editor.chain().toggleItalic().run(),\n isActive: (editor) => editor.isActive('italic'),\n isDisabled: (editor) => !editor.can().toggleItalic(),\n });\n\n // 下划线\n items.set('underline', {\n id: 'underline',\n icon: icons.underline,\n command: (editor) => editor.chain().toggleUnderline().run(),\n isActive: (editor) => editor.isActive('underline'),\n isDisabled: (editor) => !editor.can().toggleUnderline(),\n });\n\n // 删除线\n items.set('strike', {\n id: 'strike',\n icon: icons.strikethrough,\n command: (editor) => editor.chain().toggleStrike().run(),\n isActive: (editor) => editor.isActive('strike'),\n isDisabled: (editor) => !editor.can().toggleStrike(),\n });\n\n // 行内代码\n items.set('code', {\n id: 'code',\n icon: icons.code,\n command: (editor) => editor.chain().toggleCode().run(),\n isActive: (editor) => editor.isActive('code'),\n isDisabled: (editor) => !editor.can().toggleCode(),\n });\n\n // 无序列表\n items.set('bulletList', {\n id: 'bulletList',\n icon: icons.list,\n command: (editor) => editor.chain().toggleBulletList().run(),\n isActive: (editor) => editor.isActive('bulletList'),\n isDisabled: (editor) => !editor.can().toggleBulletList(),\n });\n\n // 有序列表\n items.set('orderedList', {\n id: 'orderedList',\n icon: icons.listOrdered,\n command: (editor) => editor.chain().toggleOrderedList().run(),\n isActive: (editor) => editor.isActive('orderedList'),\n isDisabled: (editor) => !editor.can().toggleOrderedList(),\n });\n\n // 任务列表\n items.set('taskList', {\n id: 'taskList',\n icon: icons.listChecks,\n command: (editor) => editor.chain().toggleTaskList().run(),\n isActive: (editor) => editor.isActive('taskList'),\n isDisabled: (editor) => !editor.can().toggleTaskList(),\n });\n\n // 引用\n items.set('blockquote', {\n id: 'blockquote',\n icon: icons.quote,\n command: (editor) => editor.chain().toggleBlockquote().run(),\n isActive: (editor) => editor.isActive('blockquote'),\n isDisabled: (editor) => !editor.can().toggleBlockquote(),\n });\n\n // 代码块。节点不在就不注册这一项:codeBlock:false 且接入方没给替代实现时,\n // toggleCodeBlock 这个命令根本不存在,而 isDisabled 会在每次状态刷新里调它——\n // 不注册的话编辑器直接抛(实测 editor.can(...).toggleCodeBlock is not a function,\n // 每次事务都抛一遍)。与表格、媒体同一套做法:能力不在,项就不在\n if (this.editor.schema.nodes.codeBlock) {\n items.set('codeBlock', {\n id: 'codeBlock',\n icon: icons.codeBlock,\n command: (editor) => editor.chain().toggleCodeBlock().run(),\n isActive: (editor) => editor.isActive('codeBlock'),\n isDisabled: (editor) => !editor.can().toggleCodeBlock(),\n });\n }\n\n // 分割线:插入型操作,没有\"激活\"态可言\n items.set('horizontalRule', {\n id: 'horizontalRule',\n icon: icons.horizontalRule,\n command: (editor) => editor.chain().setHorizontalRule().run(),\n isActive: () => false,\n isDisabled: (editor) => !editor.can().setHorizontalRule(),\n });\n\n // 左对齐\n items.set('alignLeft', {\n id: 'alignLeft',\n icon: icons.alignLeft,\n command: (editor) => editor.chain().setTextAlign('left').run(),\n isActive: (editor) => editor.isActive({ textAlign: 'left' }),\n isDisabled: (editor) => !editor.can().setTextAlign('left'),\n });\n\n // 居中\n items.set('alignCenter', {\n id: 'alignCenter',\n icon: icons.alignCenter,\n command: (editor) => editor.chain().setTextAlign('center').run(),\n isActive: (editor) => editor.isActive({ textAlign: 'center' }),\n isDisabled: (editor) => !editor.can().setTextAlign('center'),\n });\n\n // 右对齐\n items.set('alignRight', {\n id: 'alignRight',\n icon: icons.alignRight,\n command: (editor) => editor.chain().setTextAlign('right').run(),\n isActive: (editor) => editor.isActive({ textAlign: 'right' }),\n isDisabled: (editor) => !editor.can().setTextAlign('right'),\n });\n\n // 两端对齐:命令与词条早就有,缺的只是这个按钮\n items.set('alignJustify', {\n id: 'alignJustify',\n icon: icons.alignJustify,\n command: (editor) => editor.chain().setTextAlign('justify').run(),\n isActive: (editor) => editor.isActive({ textAlign: 'justify' }),\n isDisabled: (editor) => !editor.can().setTextAlign('justify'),\n });\n\n // 两个内置下拉。之所以收成菜单而不是平铺:标题本来就有六级、对齐四种,\n // 平铺在顶栏上要占十个格子,而用户一次只关心\"现在改成哪一级\"。\n // children 走同一张 itemDefs,所以能力门控自动生效(段落在 schema 里没有时不会出现在菜单)\n items.set(\n 'heading',\n this.menuItem('heading', icons.heading, [\n 'heading1',\n 'heading2',\n 'heading3',\n 'heading4',\n 'heading5',\n 'heading6',\n 'paragraph',\n ])\n );\n\n items.set(\n 'align',\n this.menuItem('align', icons.align, [\n 'alignLeft',\n 'alignCenter',\n 'alignRight',\n 'alignJustify',\n ])\n );\n\n return items;\n }\n\n /**\n * 绑定编辑器事件,更新按钮状态\n */\n private bindEditorEvents(): void {\n const refresh = () => this.updateButtonStates();\n\n this.editor.on('transaction', refresh);\n this.editor.on('selectionUpdate', refresh);\n\n // 工具栏是公开导出的类:宿主单独用它、只销毁工具栏而留着编辑器时,\n // 这两条订阅会常驻到编辑器结束,每次事务空跑一遍按钮刷新\n this.unsubscribeEditor = () => {\n this.editor.off('transaction', refresh);\n this.editor.off('selectionUpdate', refresh);\n };\n\n // 初始状态\n this.updateButtonStates();\n }\n\n /**\n * 更新所有按钮状态\n *\n * 公开是因为浮层开合不改文档,等不到 transaction:挂在门面上的浮层(链接)\n * 由门面在开合时叫一次,挂在这里的浮层则在 onOpenChange 里叫\n */\n updateButtonStates(): void {\n this.buttons.forEach((buttons, itemId) => {\n const itemDef = this.itemDefs.get(itemId);\n if (!itemDef) return;\n\n // 更新 active 状态\n const active = itemDef.isActive(this.editor);\n const disabled = itemDef.isDisabled(this.editor);\n\n buttons.forEach((button) => {\n button.classList.toggle('active', active);\n if (itemDef.popup) button.setAttribute('aria-expanded', String(active));\n\n // 更新 disabled 状态\n button.disabled = disabled;\n\n if (itemDef.popup === 'menu') this.refreshMenuTrigger(itemDef, button);\n });\n });\n }\n\n /**\n * 一项当前该显示的文字。下拉触发器跟着命中的子项走(现在是三级标题就写「标题 3」),\n * 宿主显式写的 tooltip 优先级最高。\n * 抽出来是因为图标与提示必须由同一份判定得出:只换其中一个就会出现\n * \"按钮画着 H3、悬停写标题\"这种一半新一半旧的界面\n */\n private itemTitle(itemId: string, def: ToolbarItemDef): string {\n const custom = this.customTooltips.get(itemId);\n if (custom) return custom;\n\n let key = TOOLTIP_KEYS[itemId];\n if (def.popup === 'menu') {\n const activeKey = TOOLTIP_KEYS[this.activeChild(def)?.id ?? ''];\n if (activeKey) key = activeKey;\n }\n\n const fallback = key ? zhText(key) : (def.tooltip ?? '');\n return key ? this.tOr(key, fallback) : fallback;\n }\n\n /**\n * 下拉触发器的图标与提示跟着当前值走:现在是二级标题就显示 H2,\n * 一个都不匹配才退回通用图标。只有菜单项会走到这里,不是每次刷新对所有按钮都做一遍\n */\n private refreshMenuTrigger(def: ToolbarItemDef, button: HTMLButtonElement): void {\n const base = button.dataset.menuIcon ?? def.icon;\n const next = `${this.activeChild(def)?.icon ?? base}${icons.chevronDown}`;\n if (button.innerHTML !== next) button.innerHTML = next;\n\n const title = this.itemTitle(def.id, def);\n if (button.title !== title) button.title = title;\n }\n\n /**\n * 销毁工具栏\n */\n destroy(): void {\n this.unsubscribeLanguage?.();\n this.unsubscribeLanguage = undefined;\n this.unsubscribeEditor?.();\n this.unsubscribeEditor = undefined;\n // 面板挂在 document.body 上,不跟着工具栏一起收就会留在页面上\n this.panel?.destroy();\n this.panel = null;\n this.tablePanel?.destroy();\n this.tablePanel = null;\n this.stylePanel?.destroy();\n this.stylePanel = null;\n this.menuPanel?.destroy();\n this.menuPanel = null;\n this.buttons.clear();\n this.customTooltips.clear();\n // 浮层元素归门面所有,要活过重建编辑器,所以这里既不 remove 也不清空它\n this.createdElements.forEach((el) => el.remove());\n this.createdElements = [];\n }\n}\n","/**\n * LinkPanel - 链接插入 / 编辑浮层\n *\n * 与 InsertPanel 同为挂在 document.body 上的浮层,定位 / 关闭 / 主题三段走 floating-panel 公共底盘。\n *\n * 链接此前只有 schema 与命令、没有任何入口:工具栏没有按钮,插进去也改不了,\n * 而编辑态点击又会按链接扩展的默认行为直接导航,等于把当前页面连同未保存的内容一起交出去。\n * 这个浮层补上入口,同时把\"点链接\"改成\"就地编辑\"。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n followThemeClasses,\n listenPanelDismiss,\n positionPanel,\n type FloatingPanel,\n} from './floating-panel';\nimport { tOrFallback, type I18nManager } from './I18nManager';\nimport type { AtriLinkTarget } from '../types';\n\nexport interface LinkPanelOptions {\n editor: Editor;\n /**\n * 编辑器根容器(.atri-editor)\n *\n * 点击监听与主题类都挂在它身上,不碰 editor.view.dom:Tiptap v3 的视图是迟到且会被销毁的,\n * 构造时未必拿得到、销毁时已经没了,而这个容器由门面创建,生命周期与编辑器实例一致\n */\n root: HTMLElement;\n i18n?: I18nManager;\n /** 与交给链接扩展的 openOnClick 同源:扩展负责导航时浮层不抢点击 */\n openOnClick?: boolean;\n /** 新链接的打开方式,默认 '_blank' */\n target?: AtriLinkTarget;\n /** 开合时把控制权交回工具栏:按钮的 active 与 aria-expanded 由它维护 */\n onOpenChange?: (open: boolean) => void;\n}\n\ninterface LabelledElement {\n el: HTMLElement;\n key: string;\n fallback: string;\n}\n\n/**\n * 补全协议:用户多半只贴 example.com,补成 https:// 浏览器与 Markdown 两边才都认。\n * 已有协议的(https:、mailto:…)与站内绝对路径 / 锚点 / 相对路径原样保留\n */\nexport function normalizeHref(value: string): string {\n const href = value.trim();\n if (!href) return '';\n if (/^[a-z][a-z0-9+.-]*:/i.test(href) || /^[/#.]/.test(href)) return href;\n return `https://${href}`;\n}\n\nexport class LinkPanel {\n private editor: Editor;\n private root: HTMLElement;\n private i18n?: I18nManager;\n private openOnClick: boolean;\n private defaultTarget: AtriLinkTarget;\n private onOpenChange?: (open: boolean) => void;\n private unsubscribeLanguage?: () => void;\n\n private element: HTMLDivElement | null = null;\n private floating: FloatingPanel | null = null;\n private detachDismiss: (() => void) | null = null;\n private detachTheme: (() => void) | null = null;\n private labelled: LabelledElement[] = [];\n private urlInput: HTMLInputElement | null = null;\n private targetInput: HTMLInputElement | null = null;\n /** 打开时选区内是否已有链接:决定\"确定\"是改这一条还是新插一条 */\n private editing = false;\n\n constructor(options: LinkPanelOptions) {\n this.editor = options.editor;\n this.root = options.root;\n this.i18n = options.i18n;\n this.openOnClick = options.openOnClick ?? false;\n this.defaultTarget = options.target ?? '_blank';\n this.onOpenChange = options.onOpenChange;\n\n // 面板是即时创建的,但可能开着的时候宿主切了语言\n this.unsubscribeLanguage = this.i18n?.onLanguageChanged(() => this.applyLabels());\n this.root.addEventListener('click', this.onEditorClick);\n }\n\n get isOpen(): boolean {\n return this.element !== null;\n }\n\n toggle(anchor: HTMLElement): void {\n if (this.isOpen) {\n this.close();\n return;\n }\n this.open(anchor);\n }\n\n open(anchor: HTMLElement): void {\n this.close();\n this.editing = this.editor.isActive('link');\n this.build();\n const panel = this.element;\n if (!panel) return;\n\n this.floating = positionPanel(anchor, panel, () => this.element === panel);\n this.detachDismiss = listenPanelDismiss(anchor, panel, () => this.close());\n this.onOpenChange?.(true);\n }\n\n close(): void {\n const wasOpen = this.element !== null;\n\n this.detachDismiss?.();\n this.detachDismiss = null;\n this.detachTheme?.();\n this.detachTheme = null;\n this.floating?.stop();\n this.floating = null;\n this.element?.remove();\n this.element = null;\n this.urlInput = null;\n this.targetInput = null;\n this.labelled = [];\n this.editing = false;\n\n if (wasOpen) this.onOpenChange?.(false);\n }\n\n destroy(): void {\n this.root.removeEventListener('click', this.onEditorClick);\n this.close();\n this.unsubscribeLanguage?.();\n this.unsubscribeLanguage = undefined;\n }\n\n /**\n * 编辑态点击链接就地编辑(与 wangEditor / AIEditor 一致)\n *\n * 拖选收尾也会派发 click,所以只在选区收拢时接管;openOnClick 开着说明点击归扩展导航\n */\n private onEditorClick = (event: MouseEvent): void => {\n if (this.openOnClick || !this.editor.isEditable) return;\n // 选区不空就是拖选(或双击选词)的收尾,不该弹编辑框\n if (!this.editor.state.selection.empty) return;\n\n const target = event.target;\n if (!(target instanceof Element)) return;\n const anchor = target.closest('a');\n if (!anchor || !this.root.contains(anchor)) return;\n\n // 接管这一下点击,同时挡掉 <a> 的默认导航:真实浏览器里 contenteditable 本来就不导航,\n // 但缺这层语义的环境(jsdom、扩展注入的 DOM)会真的把页面带走\n event.preventDefault();\n this.open(anchor);\n };\n\n private t(key: string, fallback: string): string {\n return tOrFallback(this.i18n, key, fallback);\n }\n\n /** 登记一处随语言重画的文案:input 画到 placeholder,其他画成文字 */\n private label(el: HTMLElement, key: string, fallback: string): void {\n this.labelled.push({ el, key, fallback });\n this.applyLabel({ el, key, fallback });\n }\n\n private applyLabels(): void {\n this.labelled.forEach((entry) => this.applyLabel(entry));\n }\n\n private applyLabel({ el, key, fallback }: LabelledElement): void {\n const text = this.t(key, fallback);\n if (el instanceof HTMLInputElement) el.placeholder = text;\n else el.textContent = text;\n }\n\n private build(): void {\n const panel = document.createElement('div');\n panel.className = 'atri-link-panel';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-label', this.t('link.panelLabel', '链接'));\n\n const url = document.createElement('input');\n url.type = 'url';\n url.className = 'atri-link-panel-url';\n this.label(url, 'link.urlPlaceholder', '链接地址(https://…)');\n // 已有链接带出原地址:编辑的是这一条,不该让用户重抄一遍\n url.value = this.editing ? String(this.editor.getAttributes('link').href ?? '') : '';\n url.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n this.apply();\n }\n });\n panel.appendChild(url);\n this.urlInput = url;\n\n const targetLabel = document.createElement('label');\n targetLabel.className = 'atri-link-panel-target';\n const target = document.createElement('input');\n target.type = 'checkbox';\n target.checked = this.editing\n ? this.editor.getAttributes('link').target !== '_self'\n : this.defaultTarget === '_blank';\n const targetText = document.createElement('span');\n this.label(targetText, 'link.newWindow', '在新窗口打开');\n targetLabel.append(target, targetText);\n panel.appendChild(targetLabel);\n this.targetInput = target;\n\n const actions = document.createElement('div');\n actions.className = 'atri-link-panel-actions';\n\n // 移除只在编辑既有链接时出现:没链接可移除的时候摆个灰按钮没有意义\n if (this.editing) {\n const remove = document.createElement('button');\n remove.type = 'button';\n remove.className = 'atri-link-panel-remove';\n this.label(remove, 'link.remove', '移除链接');\n remove.addEventListener('click', () => this.remove());\n actions.appendChild(remove);\n }\n\n const apply = document.createElement('button');\n apply.type = 'button';\n apply.className = 'atri-link-panel-apply';\n this.label(apply, 'link.apply', '确定');\n apply.addEventListener('click', () => this.apply());\n actions.appendChild(apply);\n\n panel.appendChild(actions);\n document.body.appendChild(panel);\n this.element = panel;\n this.detachTheme = followThemeClasses(this.root, panel);\n\n url.focus();\n url.select();\n }\n\n /** 有链接就整条改(extendMarkRange 把范围摊到整段链接),没链接就看选区:套住它,或者把地址本身当链接文字插进去 */\n private apply(): void {\n const href = normalizeHref(this.urlInput?.value ?? '');\n if (!href) return;\n\n const target: AtriLinkTarget = this.targetInput?.checked ? '_blank' : '_self';\n const { editor } = this;\n const chain = editor.chain().focus();\n\n if (this.editing) {\n chain.extendMarkRange('link').setLink({ href, target }).run();\n } else if (editor.state.selection.empty) {\n chain\n .insertContent({\n type: 'text',\n text: href,\n marks: [{ type: 'link', attrs: { href, target } }],\n })\n .run();\n } else {\n chain.setLink({ href, target }).run();\n }\n\n this.close();\n }\n\n private remove(): void {\n this.editor.chain().focus().extendMarkRange('link').unsetLink().run();\n this.close();\n }\n}\n","/**\n * TableMenu - 光标落在表格里时浮出的上下文操作菜单\n *\n * 与工具栏浮层(InsertPanel / TableGridPanel)同一套外观,但触发方式不同:它没有锚点按钮,\n * 靠选区进入表格自动浮出,所以定位锚是「当前单元格的 DOM 盒子」,量不到再退回光标坐标\n * (AICommandMenu 的虚拟锚点写法)。\n *\n * 行列增删合并这些命令由 prosemirror-tables 提供,此前只有命令没有入口,\n * 表格因此是\"插得出来、改不动\"的半成品。\n */\nimport type { Editor } from '@tiptap/core';\nimport type { EditorState } from '@tiptap/pm/state';\nimport { computePosition } from '@floating-ui/dom';\nimport { followThemeClasses, panelMiddleware, placePanel } from './floating-panel';\nimport { tOrFallback, zhText, type I18nManager } from './I18nManager';\nimport { icons } from './icons';\n\nexport interface TableMenuOptions {\n editor: Editor;\n /**\n * 编辑器根容器(.atri-editor):只用来复制主题类\n *\n * 不取 editor.view.dom:那个视图是迟到且会被销毁的,而这个容器由门面创建,生命周期一致\n */\n root: HTMLElement;\n i18n?: I18nManager;\n}\n\n/** 单元格对齐取值:与单元格 align 属性的取值域一致 */\ntype CellAlignment = 'left' | 'center' | 'right';\n\ninterface TableMenuItemDef {\n id: string;\n icon: string;\n labelKey: string;\n /** 组序号,组与组之间画一条竖线 */\n group: number;\n command: (editor: Editor) => void;\n isDisabled?: (editor: Editor) => boolean;\n isActive?: (editor: Editor) => boolean;\n}\n\n/** 选区所在的单元格(或表头)节点位置;不在表格里返回 null */\nfunction cellPos(state: EditorState): number | null {\n const { $from } = state.selection;\n for (let depth = $from.depth; depth > 0; depth--) {\n const name = $from.node(depth).type.name;\n if (name === 'tableCell' || name === 'tableHeader') return $from.before(depth);\n }\n return null;\n}\n\nfunction cellAlignmentActive(align: CellAlignment) {\n return (editor: Editor): boolean =>\n editor.isActive('tableCell', { align }) || editor.isActive('tableHeader', { align });\n}\n\nfunction buildItems(): TableMenuItemDef[] {\n const items: TableMenuItemDef[] = [\n {\n id: 'addRowBefore',\n icon: icons.tableRowBefore,\n labelKey: 'table.rowBefore',\n group: 0,\n command: (editor) => editor.chain().focus().addRowBefore().run(),\n },\n {\n id: 'addRowAfter',\n icon: icons.tableRowAfter,\n labelKey: 'table.rowAfter',\n group: 0,\n command: (editor) => editor.chain().focus().addRowAfter().run(),\n },\n {\n id: 'deleteRow',\n icon: icons.tableRowDelete,\n labelKey: 'table.deleteRow',\n group: 0,\n command: (editor) => editor.chain().focus().deleteRow().run(),\n },\n {\n id: 'addColumnBefore',\n icon: icons.tableColumnBefore,\n labelKey: 'table.columnBefore',\n group: 1,\n command: (editor) => editor.chain().focus().addColumnBefore().run(),\n },\n {\n id: 'addColumnAfter',\n icon: icons.tableColumnAfter,\n labelKey: 'table.columnAfter',\n group: 1,\n command: (editor) => editor.chain().focus().addColumnAfter().run(),\n },\n {\n id: 'deleteColumn',\n icon: icons.tableColumnDelete,\n labelKey: 'table.deleteColumn',\n group: 1,\n command: (editor) => editor.chain().focus().deleteColumn().run(),\n },\n {\n // 合并要有两个以上单元格被框住,单选一个时按不动——禁用而不是隐藏,位置别跳\n id: 'mergeCells',\n icon: icons.tableMergeCells,\n labelKey: 'table.mergeCells',\n group: 2,\n command: (editor) => editor.chain().focus().mergeCells().run(),\n isDisabled: (editor) => !editor.can().mergeCells(),\n },\n {\n id: 'splitCell',\n icon: icons.tableSplitCell,\n labelKey: 'table.splitCell',\n group: 2,\n command: (editor) => editor.chain().focus().splitCell().run(),\n isDisabled: (editor) => !editor.can().splitCell(),\n },\n {\n id: 'toggleHeaderRow',\n icon: icons.tableHeaderRow,\n labelKey: 'table.toggleHeaderRow',\n group: 2,\n command: (editor) => editor.chain().focus().toggleHeaderRow().run(),\n isActive: (editor) => editor.isActive('tableHeader'),\n },\n ];\n\n // 对齐写在单元格自带的 align 属性上:Markdown 的 pipe table 分隔行(:---)也读它,\n // 于是这一下对齐在导出 Markdown 后仍然成立\n const alignments: [CellAlignment, string][] = [\n ['left', 'table.alignLeft'],\n ['center', 'table.alignCenter'],\n ['right', 'table.alignRight'],\n ];\n alignments.forEach(([align, labelKey]) => {\n const suffix = `${align[0].toUpperCase()}${align.slice(1)}`;\n items.push({\n id: `cellAlign${suffix}`,\n icon: icons[`align${suffix}`],\n labelKey,\n group: 3,\n command: (editor) => editor.chain().focus().setCellAttribute('align', align).run(),\n isActive: cellAlignmentActive(align),\n });\n });\n\n items.push({\n id: 'deleteTable',\n icon: icons.trash,\n labelKey: 'table.deleteTable',\n group: 4,\n command: (editor) => editor.chain().focus().deleteTable().run(),\n });\n\n return items;\n}\n\nexport class TableMenu {\n private editor: Editor;\n private root: HTMLElement;\n private i18n?: I18nManager;\n private items: TableMenuItemDef[];\n private unsubscribeLanguage?: () => void;\n\n private element: HTMLDivElement | null = null;\n private detachEscape: (() => void) | undefined;\n private detachTheme: (() => void) | undefined;\n /** Escape 收起后不再自动浮出,直到光标离开表格(否则下一次状态更新立刻又把它拉出来) */\n private dismissed = false;\n\n constructor(options: TableMenuOptions) {\n this.editor = options.editor;\n this.root = options.root;\n this.i18n = options.i18n;\n this.items = buildItems();\n\n this.editor.on('update', this.sync);\n this.editor.on('selectionUpdate', this.sync);\n this.editor.on('focus', this.sync);\n this.editor.on('blur', this.onBlur);\n // 浮层是 fixed 定位,滚动不会带着它走:滚的是哪一层不确定,捕获阶段挂在 document 上\n document.addEventListener('scroll', this.onScroll, true);\n this.unsubscribeLanguage = this.i18n?.onLanguageChanged(() => this.applyLabels());\n\n this.sync();\n }\n\n get isOpen(): boolean {\n return this.element !== null;\n }\n\n destroy(): void {\n this.editor.off('update', this.sync);\n this.editor.off('selectionUpdate', this.sync);\n this.editor.off('focus', this.sync);\n this.editor.off('blur', this.onBlur);\n document.removeEventListener('scroll', this.onScroll, true);\n this.unsubscribeLanguage?.();\n this.unsubscribeLanguage = undefined;\n this.hide();\n }\n\n private t(key: string): string {\n return tOrFallback(this.i18n, key, zhText(key));\n }\n\n /**\n * 按当前选区决定浮出还是收起。光标在表格里是唯一条件——\n * 空选区、单元格内选一段字、框选多个单元格都算\n *\n * 听的是 update 而不是 transaction:setEditable 只发 update(造一个空事务),\n * 切只读时若不跟着重判,菜单会留在只读的表格上\n */\n private sync = (): void => {\n const inTable = cellPos(this.editor.state) !== null;\n if (!inTable) {\n // 离开表格,Escape 那一次收起到此为止\n this.dismissed = false;\n this.hide();\n return;\n }\n\n if (this.dismissed || !this.editor.isEditable) {\n this.hide();\n return;\n }\n\n this.show();\n };\n\n /** 点到编辑器外面就收起来:菜单没有\"关了还要在\"的理由,焦点回来时 sync 会重新浮出 */\n private onBlur = (): void => {\n this.hide();\n };\n\n private onScroll = (): void => {\n if (this.element) void this.position();\n };\n\n private dismiss = (): void => {\n this.dismissed = true;\n this.hide();\n };\n\n private show(): void {\n if (!this.element) this.build();\n this.updateStates();\n void this.position();\n }\n\n private hide(): void {\n this.detachEscape?.();\n this.detachEscape = undefined;\n this.detachTheme?.();\n this.detachTheme = undefined;\n if (!this.element) return;\n this.element.remove();\n this.element = null;\n }\n\n /**\n * Escape 收起菜单。这里不用 floating-panel 的 listenPanelDismiss:那一套还管外点关闭,\n * 而点外面多半仍落在同一个单元格里,关了立刻被下一次状态更新重新浮出,只会闪\n */\n private listenEscape(): () => void {\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n event.preventDefault();\n event.stopPropagation();\n this.dismiss();\n };\n document.addEventListener('keydown', onKeyDown, true);\n return () => document.removeEventListener('keydown', onKeyDown, true);\n }\n\n private build(): void {\n const panel = document.createElement('div');\n panel.className = 'atri-table-menu';\n panel.setAttribute('role', 'toolbar');\n panel.setAttribute('aria-label', this.t('table.menuLabel'));\n\n // 按钮不抢焦点:抢走会让编辑器 blur,而 blur 正是收起菜单的条件,\n // 于是 mousedown 一按菜单就没了、click 根本派发不到按钮上\n panel.addEventListener('mousedown', (event) => event.preventDefault());\n\n let lastGroup: number | null = null;\n this.items.forEach((item) => {\n if (lastGroup !== null && item.group !== lastGroup) {\n const separator = document.createElement('div');\n separator.className = 'atri-table-menu-separator';\n panel.appendChild(separator);\n }\n lastGroup = item.group;\n\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'atri-editor-toolbar-btn';\n button.setAttribute('data-table-item', item.id);\n button.title = this.t(item.labelKey);\n button.innerHTML = item.icon;\n button.addEventListener('click', (event) => {\n event.preventDefault();\n event.stopPropagation();\n item.command(this.editor);\n });\n panel.appendChild(button);\n });\n\n document.body.appendChild(panel);\n this.element = panel;\n this.detachTheme = followThemeClasses(this.root, panel);\n this.detachEscape = this.listenEscape();\n }\n\n private applyLabels(): void {\n const panel = this.element;\n if (!panel) return;\n panel.setAttribute('aria-label', this.t('table.menuLabel'));\n this.items.forEach((item) => {\n const button = panel.querySelector<HTMLButtonElement>(`[data-table-item=\"${item.id}\"]`);\n if (button) button.title = this.t(item.labelKey);\n });\n }\n\n private updateStates(): void {\n const panel = this.element;\n if (!panel) return;\n\n this.items.forEach((item) => {\n const button = panel.querySelector<HTMLButtonElement>(`[data-table-item=\"${item.id}\"]`);\n if (!button) return;\n button.disabled = item.isDisabled?.(this.editor) ?? false;\n button.classList.toggle('active', item.isActive?.(this.editor) ?? false);\n });\n }\n\n /**\n * 当前单元格的虚拟锚点;量不到返回 null,调用方保持面板可见但不改坐标\n *\n * 优先锚在单元格盒子上(菜单跟着格子走,比跟着光标稳),拿不到盒子才退回光标坐标\n */\n private anchor(): { getBoundingClientRect(): DOMRect } | null {\n const { view, state } = this.editor;\n const pos = cellPos(state);\n\n if (pos !== null) {\n try {\n const dom = view.nodeDOM(pos);\n if (dom instanceof HTMLElement) {\n const rect = dom.getBoundingClientRect();\n // jsdom 里所有盒子都是 0×0,靠这个判据自觉退回光标坐标\n if (rect.width || rect.height) return { getBoundingClientRect: () => rect };\n }\n } catch {\n // 量不到就往下走,别让定位问题变成\"菜单不出现\"\n }\n }\n\n try {\n const coords = view.coordsAtPos(state.selection.from);\n return {\n getBoundingClientRect: () =>\n new DOMRect(coords.left, coords.top, 0, coords.bottom - coords.top),\n };\n } catch {\n return null;\n }\n }\n\n private async position(): Promise<void> {\n const panel = this.element;\n if (!panel) return;\n\n const reference = this.anchor();\n if (!reference) return;\n\n let x: number;\n let y: number;\n try {\n ({ x, y } = await computePosition(reference, panel, {\n // 贴在单元格上沿:菜单跟着当前格走,长表格里也不会跑到视口外\n placement: 'top-start',\n middleware: panelMiddleware(),\n }));\n } catch {\n // 定位失败也要让菜单可见:宁可位置不理想,也不能点了没反应\n return;\n }\n\n // 异步算位置的间隙里菜单可能已经收起或被重建\n if (this.element !== panel) return;\n placePanel(panel, x, y);\n }\n}\n","/**\n * MediaStatusStrip - 上传队列状态条\n *\n * 卡片与图片上没有一处文字状态(那都由样式表靠 data 属性表达),\n * \"上传中 2 个\"\"1 个失败\"\"重试\"这些要读得懂的话都集中在这里,本地化只发生在这一处。\n */\nimport type { I18nManager } from '../core/I18nManager';\nimport type { MediaRuntime, MediaState } from './MediaRuntime';\n\ninterface Labels {\n uploading: (files: number, percent: number) => string;\n failed: (files: number) => string;\n inline: (files: number) => string;\n retry: () => string;\n}\n\nexport class MediaStatusStrip {\n private labels: Labels;\n private element: HTMLDivElement;\n private text: HTMLSpanElement;\n private retry: HTMLButtonElement;\n private unsubscribe?: () => void;\n private unsubscribeLanguage?: () => void;\n\n constructor(runtime: MediaRuntime, container: HTMLElement, i18n?: I18nManager) {\n // t() 在词条缺失时会原样返回 key,此时回退到内置文案\n const t = (key: string, fallback: string, options?: Record<string, unknown>) =>\n i18n?.tOr(key, fallback, options) ?? fallback;\n\n this.labels = {\n uploading: (files, percent) =>\n t('media.uploading', '上传中 {{files}} 个 · {{percent}}%', { files, percent }),\n failed: (files) => t('media.uploadFailed', '{{files}} 个文件上传失败', { files }),\n inline: (files) => t('media.inlined', '{{files}} 张图片未上传(已随文档保存)', { files }),\n retry: () => t('media.retry', '重试'),\n };\n\n this.element = document.createElement('div');\n this.element.className = 'atri-media-status';\n // role=status 已隐含 aria-live=polite,不必再写一遍\n this.element.setAttribute('role', 'status');\n this.element.hidden = true;\n\n this.text = document.createElement('span');\n this.text.className = 'atri-media-status-text';\n\n this.retry = document.createElement('button');\n this.retry.type = 'button';\n this.retry.className = 'atri-media-status-retry';\n this.retry.hidden = true;\n // 重试失败要落地成一条日志:这条 promise 此前被 void 丢在半路,\n // 一直失败时表现为\"点了没反应\"外加一片 unhandled rejection\n this.retry.addEventListener('click', () => {\n runtime.retryFailed().catch((error: unknown) => {\n console.error('[Atri Editor] Retrying the failed uploads did not work:', error);\n });\n });\n\n this.element.append(this.text, this.retry);\n container.appendChild(this.element);\n\n this.unsubscribe = runtime.subscribe((state) => this.render(state));\n // 上传能持续很久,中途切语言时这条读数不能还停在旧语言\n this.unsubscribeLanguage = i18n?.onLanguageChanged(() => this.render(runtime.getState()));\n }\n\n destroy(): void {\n this.unsubscribe?.();\n this.unsubscribeLanguage?.();\n this.element.remove();\n }\n\n private render(state: MediaState): void {\n const { failed, inline } = state;\n const parts: string[] = [];\n if (state.uploading) parts.push(this.labels.uploading(state.uploading, state.percent));\n if (failed) parts.push(this.labels.failed(failed));\n if (inline) parts.push(this.labels.inline(inline));\n // 同一个文件只落进 failed 或 inline 一档,两句同时出现是队列里有不同的文件\n\n const needsAttention = failed > 0 || inline > 0;\n // 失败与已内联共用红色这一档,靠 :has() 找子元素太脆\n this.element.setAttribute('data-atri-media-status', needsAttention ? 'error' : 'uploading');\n this.element.hidden = !parts.length;\n // 每段话各占一个 span,分隔交给样式表:拼接用的标点没有中立写法\n this.text.replaceChildren(\n ...parts.map((part) => {\n const span = document.createElement('span');\n span.textContent = part;\n return span;\n })\n );\n // 已内联的图仍可重试:换到服务端地址之前,这条读数不该只留着当提示\n this.retry.hidden = !needsAttention;\n this.retry.textContent = this.labels.retry();\n }\n}\n","/**\n * AIInterceptor - AI 拦截器链执行\n */\nimport type { AIRequestContext, AIResponse, AtriAIInterceptors } from '../types';\nimport type { MarkdownService } from '../core/MarkdownService';\n\n/**\n * 端点由宿主实现,返回值没有任何编译期保证:\n * 不校验的话 4xx 的 HTML 响应体一路走到 insertCallback,最后什么也不发生\n *\n * 只判形状,不判空:空 content 是合法结果(\"看过之后无需改写\"),\n * 拦它等于把宿主的一个正常业务分支变成报错\n */\nfunction requireResponse(response: unknown, stage: string): AIResponse {\n if (!response || typeof response !== 'object') {\n throw new TypeError(`AI ${stage} must resolve to an AIResponse object.`);\n }\n\n if (typeof (response as AIResponse).content !== 'string') {\n throw new TypeError(`AI ${stage} resolved with a non-string content.`);\n }\n\n return response as AIResponse;\n}\n\nexport class AIInterceptor {\n private interceptors: AtriAIInterceptors;\n private markdownService: MarkdownService | null;\n private autoTranslateMarkdown: boolean;\n\n constructor(\n interceptors: AtriAIInterceptors = {},\n markdownService: MarkdownService | null = null,\n autoTranslateMarkdown: boolean = true\n ) {\n this.interceptors = interceptors;\n this.markdownService = markdownService;\n this.autoTranslateMarkdown = autoTranslateMarkdown;\n }\n\n /**\n * 执行 beforePost 拦截器\n * @returns 处理后的 context,返回 false 表示取消请求\n */\n async runBeforePost(context: AIRequestContext): Promise<AIRequestContext | false> {\n if (!this.interceptors.beforePost) {\n return context;\n }\n const result = this.interceptors.beforePost(context);\n return result;\n }\n\n /**\n * 执行 afterPost 拦截器\n */\n async runAfterPost(response: AIResponse, context: AIRequestContext): Promise<AIResponse> {\n if (!this.interceptors.afterPost) {\n return response;\n }\n return this.interceptors.afterPost(response, context);\n }\n\n /**\n * 执行 beforeRender 拦截器\n */\n async runBeforeRender(content: string, context: AIRequestContext): Promise<string> {\n if (!this.interceptors.beforeRender) {\n return content;\n }\n return this.interceptors.beforeRender(content, context);\n }\n\n /**\n * 执行 autoTranslateMarkdownToHTML\n * 在 beforeRender 之后执行,自动将 Markdown 转为 HTML\n */\n async runAutoTranslateMarkdown(content: string, response: AIResponse): Promise<string> {\n if (!this.autoTranslateMarkdown) {\n return content;\n }\n\n // 仅当 contentType 为 markdown 时自动转换\n if (response.contentType !== 'markdown') {\n return content;\n }\n\n if (this.markdownService) {\n return this.markdownService.markdownToHTML(content);\n }\n\n // 降级:使用简单转换\n const { simpleMarkdownToHtml } = await import('../utils/markdown');\n return simpleMarkdownToHtml(content);\n }\n\n /**\n * 执行 afterRender 回调\n */\n async runAfterRender(content: string, context: AIRequestContext): Promise<void> {\n if (!this.interceptors.afterRender) {\n return;\n }\n this.interceptors.afterRender(content, context);\n }\n\n /**\n * 完整执行拦截器链\n * 流程: beforePost -> requestEndpoint -> afterPost -> beforeRender -> autoTranslateMarkdown -> insert -> afterRender\n */\n async executeChain(\n context: AIRequestContext,\n requestEndpoint: (ctx: AIRequestContext) => Promise<AIResponse>,\n insertCallback: (content: string) => void\n ): Promise<void> {\n // 1. beforePost\n const processedContext = await this.runBeforePost(context);\n if (processedContext === false) {\n return; // 取消请求\n }\n\n // 2. requestEndpoint\n let response = requireResponse(\n await requestEndpoint(processedContext),\n `requestEndpoint(\"${processedContext.functionId}\")`\n );\n\n // 3. afterPost\n response = requireResponse(await this.runAfterPost(response, processedContext), 'afterPost');\n\n // 4. beforeRender\n let content = await this.runBeforeRender(response.content, processedContext);\n\n // 5. autoTranslateMarkdownToHTML\n content = await this.runAutoTranslateMarkdown(content, response);\n\n // 6. 插入编辑器\n insertCallback(content);\n\n // 7. afterRender\n await this.runAfterRender(content, processedContext);\n }\n}\n","/**\n * 选区工具函数\n */\nimport type { Editor } from '@tiptap/core';\nimport { getTextBetween } from '@tiptap/core';\n\n/**\n * 获取选中文本\n */\nexport function getSelectedText(editor: Editor): string {\n const { selection } = editor.state;\n if (selection.empty) return '';\n return getTextBetween(editor.state.doc, {\n from: selection.from,\n to: selection.to,\n });\n}\n\n/**\n * 获取光标前的文本(当前段落)\n */\nexport function getCursorContext(editor: Editor, maxLength: number = 500): string {\n const { selection, doc } = editor.state;\n const pos = selection.from;\n\n // 获取当前段落的起始位置\n const startPos = pos - editor.state.doc.resolve(pos).parentOffset;\n const from = Math.max(0, startPos - maxLength);\n\n return getTextBetween(doc, { from, to: pos });\n}\n\n/**\n * 获取文档纯文本\n */\nexport function getDocumentText(editor: Editor): string {\n return editor.state.doc.textContent;\n}\n","/**\n * AIService - AI 功能调度服务\n */\nimport type { Editor } from '@tiptap/core';\nimport type { AtriAIConfig, AtriAIFunction, AIRequestContext } from '../types';\nimport { AIInterceptor } from './AIInterceptor';\nimport { MarkdownService } from '../core/MarkdownService';\nimport { runSafely } from '../utils/safe-call';\nimport { getSelectedText, getCursorContext, getDocumentText } from '../utils/selection';\n\nexport class AIService {\n private editor: Editor;\n private config: AtriAIConfig;\n private interceptor: AIInterceptor;\n private markdownService: MarkdownService | null = null;\n\n constructor(\n editor: Editor,\n config: AtriAIConfig,\n markdownService: MarkdownService | null = null\n ) {\n this.editor = editor;\n this.config = {\n autoTranslateMarkdownToHTML: true,\n ...config,\n };\n this.markdownService = markdownService;\n this.interceptor = new AIInterceptor(\n config.interceptors || {},\n markdownService,\n this.config.autoTranslateMarkdownToHTML\n );\n }\n\n /**\n * 获取所有 AI 功能\n */\n getFunctions(): AtriAIFunction[] {\n return this.config.functions || [];\n }\n\n /**\n * 根据 ID 获取 AI 功能\n */\n getFunction(id: string): AtriAIFunction | undefined {\n return this.config.functions?.find((f) => f.id === id);\n }\n\n /**\n * 构建请求上下文\n */\n private buildContext(func: AtriAIFunction): AIRequestContext {\n const selection = getSelectedText(this.editor);\n const cursorContext = getCursorContext(this.editor);\n const documentText = getDocumentText(this.editor);\n\n // scope 只决定「主输入」{content},三个上下文字段始终完整提供给端点\n const scopeContent: Record<NonNullable<AtriAIFunction['scope']>, string> = {\n selection,\n cursor: cursorContext,\n document: documentText,\n };\n const values: Record<string, string> = {\n selection,\n cursor: cursorContext,\n document: documentText,\n content: func.scope ? scopeContent[func.scope] : selection || cursorContext,\n };\n\n // 未识别的 {xxx} 原样保留,交给端点自行处理\n const prompt = func.prompt?.replace(/\\{(\\w+)\\}/g, (raw, key: string) => values[key] ?? raw);\n\n return {\n functionId: func.id,\n scope: func.scope,\n selection,\n cursorContext,\n document: documentText,\n prompt,\n extra: {},\n };\n }\n\n /**\n * 执行 AI 功能\n */\n async execute(functionId: string): Promise<void> {\n const func = this.getFunction(functionId);\n if (!func) {\n console.error(`AI function \"${functionId}\" not found.`);\n return;\n }\n\n const context = this.buildContext(func);\n const timeout = this.config.timeout ?? 0;\n // 超时只能\"停止等待\":请求是宿主发的,库无权取消。\n // 晚到的响应靠这个标记挡在插入之前,不能让它覆盖用户之后的光标位置\n let timedOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const chain = this.interceptor.executeChain(context, this.config.requestEndpoint, (content) => {\n if (timedOut) return;\n this.insertContent(content, func.outputMode || 'insert');\n });\n\n try {\n if (timeout > 0) {\n await Promise.race([\n chain,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n timedOut = true;\n reject(new Error(`AI function \"${functionId}\" timed out after ${timeout}ms.`));\n }, timeout);\n }),\n ]);\n } else {\n await chain;\n }\n } catch (error) {\n // 不 rethrow:门面调用 execute 时不 await,抛出只会变成 unhandled rejection。\n // 但没配 onError 也不能就这么静默——端点 500 与\"这次确实没结果\"得区分得开\n const err = error instanceof Error ? error : new Error(String(error));\n if (this.config.onError) {\n runSafely(this.config.onError, 'ai.onError', [err, context]);\n } else {\n console.error(`[Atri Editor] AI function \"${functionId}\" failed:`, err);\n }\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n }\n\n /**\n * 插入内容到编辑器\n */\n private insertContent(content: string, mode: 'replace' | 'insert' | 'append'): void {\n const { selection } = this.editor.state;\n\n switch (mode) {\n case 'replace':\n // 替换选区内容\n if (!selection.empty) {\n this.editor\n .chain()\n .deleteRange({ from: selection.from, to: selection.to })\n .insertContent(content)\n .run();\n } else {\n this.editor.commands.insertContent(content);\n }\n break;\n\n case 'insert':\n // 插入到光标位置\n this.editor.commands.insertContent(content);\n break;\n\n case 'append':\n // 追加到文档末尾\n this.editor.commands.insertContentAt(this.editor.state.doc.content.size, content);\n break;\n }\n }\n\n /**\n * 更新配置\n */\n updateConfig(config: Partial<AtriAIConfig>): void {\n this.config = { ...this.config, ...config };\n this.interceptor = new AIInterceptor(\n config.interceptors || this.config.interceptors || {},\n this.markdownService,\n this.config.autoTranslateMarkdownToHTML ?? true\n );\n }\n\n /**\n * 设置 Markdown 服务\n */\n setMarkdownService(service: MarkdownService): void {\n this.markdownService = service;\n this.interceptor = new AIInterceptor(\n this.config.interceptors || {},\n service,\n this.config.autoTranslateMarkdownToHTML ?? true\n );\n }\n}\n","/**\n * AICommandMenu - AI 命令菜单(使用 Floating UI)\n */\nimport type { Editor } from '@tiptap/core';\nimport type { Transaction } from '@tiptap/pm/state';\nimport { computePosition } from '@floating-ui/dom';\nimport { panelMiddleware, placePanel } from '../core/floating-panel';\nimport type { AtriAIFunction } from '../types';\n\nexport interface AICommandMenuOptions {\n /** AI 功能列表 */\n functions: AtriAIFunction[];\n /** 触发字符(默认 /) */\n triggerChar?: string;\n /** 选择回调 */\n onSelect?: (func: AtriAIFunction) => void;\n}\n\n/**\n * AI 命令菜单管理器\n * 提供 / 触发 AI 命令菜单功能\n */\nexport class AICommandMenuManager {\n private editor: Editor;\n private functions: AtriAIFunction[];\n private menuElement: HTMLDivElement | null = null;\n private isVisible = false;\n private selectedIndex = 0;\n private triggerChar: string;\n private onSelect?: (func: AtriAIFunction) => void;\n private detachKeyListener?: () => void;\n private detachTransactionListener?: () => void;\n\n constructor(\n editor: Editor,\n functions: AtriAIFunction[],\n triggerChar: string = '/',\n onSelect?: (func: AtriAIFunction) => void\n ) {\n this.editor = editor;\n this.functions = functions;\n this.triggerChar = triggerChar;\n this.onSelect = onSelect;\n this.setupKeyboardListener();\n this.setupMenuKeyListener();\n }\n\n /**\n * 让键盘导航可达\n * 监听挂在 document 捕获阶段:ProseMirror 在 view.dom 上已有 keymap,\n * 若等它先处理,Enter 会被用来拆段而不是选中菜单项\n */\n private setupMenuKeyListener(): void {\n const handler = (event: KeyboardEvent) => {\n if (!this.isVisible || !this.editor.isFocused) return;\n if (this.handleKeyDown(event)) {\n event.stopPropagation();\n }\n };\n document.addEventListener('keydown', handler, true);\n this.detachKeyListener = () => document.removeEventListener('keydown', handler, true);\n }\n\n private setupKeyboardListener(): void {\n const handler = ({ transaction }: { editor: Editor; transaction: Transaction }) => {\n // 检测触发字符输入\n if (transaction.docChanged) {\n const lastChar = this.getLastTypedChar();\n if (lastChar === this.triggerChar && !this.isVisible) {\n this.show();\n }\n }\n };\n this.editor.on('transaction', handler);\n // 这条订阅也要能解除:菜单是公开导出的类,宿主单独用它时,\n // destroy 之后编辑器还活着的话,输入触发字符就仍能浮出菜单\n this.detachTransactionListener = () => this.editor.off('transaction', handler);\n }\n\n private getLastTypedChar(): string {\n const { doc, selection } = this.editor.state;\n if (selection.from <= 0) return '';\n return doc.textBetween(selection.from - 1, selection.from);\n }\n\n /**\n * 显示菜单\n */\n show(): void {\n // 没有可选项就没什么可浮出的:空菜单是个看不懂的空盒子,\n // 还会把 Enter/方向键吃掉(selectedIndex 对 0 取模得 NaN)\n if (this.isVisible || !this.functions.length) return;\n this.isVisible = true;\n this.selectedIndex = 0;\n this.createMenuElement();\n this.positionMenu();\n }\n\n /**\n * 隐藏菜单\n */\n hide(): void {\n if (!this.isVisible) return;\n this.isVisible = false;\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n }\n }\n\n private createMenuElement(): void {\n this.menuElement = document.createElement('div');\n this.menuElement.className = 'atri-ai-command-menu';\n this.menuElement.setAttribute('role', 'listbox');\n\n this.functions.forEach((func, index) => {\n const item = document.createElement('div');\n item.className = 'atri-ai-command-menu-item';\n item.setAttribute('role', 'option');\n item.setAttribute('data-index', String(index));\n\n if (index === this.selectedIndex) {\n item.classList.add('active');\n }\n\n if (func.icon) {\n const iconSpan = document.createElement('span');\n iconSpan.className = 'atri-ai-command-menu-icon';\n iconSpan.innerHTML = func.icon;\n item.appendChild(iconSpan);\n }\n\n const nameSpan = document.createElement('span');\n nameSpan.className = 'atri-ai-command-menu-name';\n nameSpan.textContent = func.name;\n item.appendChild(nameSpan);\n\n if (func.description) {\n item.title = func.description;\n }\n\n item.addEventListener('click', () => this.selectItem(index));\n item.addEventListener('mouseenter', () => this.highlightItem(index));\n\n this.menuElement!.appendChild(item);\n });\n\n document.body.appendChild(this.menuElement);\n }\n\n private async positionMenu(): Promise<void> {\n const menuElement = this.menuElement;\n if (!menuElement) return;\n\n const { view } = this.editor;\n const { from } = view.state.selection;\n\n let x: number;\n let y: number;\n try {\n const coords = view.coordsAtPos(from);\n // 用光标坐标合成一个虚拟锚点:VirtualElement 只需要 getBoundingClientRect\n const virtualReference = {\n getBoundingClientRect(): DOMRect {\n return new DOMRect(coords.left, coords.top, 0, coords.bottom - coords.top);\n },\n };\n\n ({ x, y } = await computePosition(virtualReference, menuElement, {\n placement: 'bottom-start',\n middleware: panelMiddleware(),\n }));\n } catch {\n // 测量或定位失败时保持菜单可见,仅跳过定位\n return;\n }\n\n // 异步定位期间菜单可能已关闭或被重建\n if (this.menuElement !== menuElement) return;\n\n placePanel(menuElement, x, y);\n }\n\n private selectItem(index: number): void {\n const func = this.functions[index];\n if (func && this.onSelect) {\n // 删除触发字符\n this.deleteTriggerChar();\n this.onSelect(func);\n }\n this.hide();\n }\n\n private highlightItem(index: number): void {\n this.selectedIndex = index;\n this.updateHighlight();\n }\n\n private updateHighlight(): void {\n if (!this.menuElement) return;\n const items = this.menuElement.querySelectorAll('.atri-ai-command-menu-item');\n items.forEach((item, i) => {\n if (i === this.selectedIndex) {\n item.classList.add('active');\n } else {\n item.classList.remove('active');\n }\n });\n }\n\n private deleteTriggerChar(): void {\n const { state, view } = this.editor;\n const { from } = state.selection;\n if (from <= 0 || state.doc.textBetween(from - 1, from) !== this.triggerChar) {\n return;\n }\n const tr = state.tr.delete(from - 1, from);\n view.dispatch(tr);\n }\n\n /**\n * 处理键盘事件\n */\n handleKeyDown(event: KeyboardEvent): boolean {\n if (!this.isVisible) return false;\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n this.selectedIndex = (this.selectedIndex + 1) % this.functions.length;\n this.updateHighlight();\n return true;\n\n case 'ArrowUp':\n event.preventDefault();\n this.selectedIndex =\n (this.selectedIndex - 1 + this.functions.length) % this.functions.length;\n this.updateHighlight();\n return true;\n\n case 'Enter':\n event.preventDefault();\n this.selectItem(this.selectedIndex);\n return true;\n\n case 'Escape':\n event.preventDefault();\n this.hide();\n return true;\n }\n\n return false;\n }\n\n /**\n * 销毁\n */\n destroy(): void {\n this.detachKeyListener?.();\n this.detachKeyListener = undefined;\n this.detachTransactionListener?.();\n this.detachTransactionListener = undefined;\n this.hide();\n }\n}\n","/**\n * AtriEditor - 编辑器主类\n */\nimport type { Editor, JSONContent } from '@tiptap/core';\nimport type {\n AtriEditorOptions,\n IAtriEditor,\n InsertAttachmentOptions,\n InsertImageOptions,\n InsertTableOptions,\n MarkdownLoss,\n MediaKind,\n SetContentOptions,\n AtriAIConfig,\n AtriNodeViewConfig,\n} from './types';\nimport { CoreEditor } from './core/CoreEditor';\nimport { MarkdownService } from './core/MarkdownService';\nimport { ThemeManager } from './core/ThemeManager';\nimport { I18nManager } from './core/I18nManager';\nimport { ExtensionManager } from './core/ExtensionManager';\nimport type { ThemeType } from './core/ThemeManager';\nimport { ToolbarManager } from './core/ToolbarManager';\nimport { LinkPanel } from './core/LinkPanel';\nimport { TableMenu } from './core/TableMenu';\nimport { MediaRuntime } from './media/MediaRuntime';\nimport { MediaStatusStrip } from './media/MediaStatusStrip';\nimport { AIService } from './ai/AIService';\nimport { AICommandMenuManager } from './ai/AICommandMenu';\nimport { resolveElement, createContainer } from './utils/dom';\nimport { canInsertTable } from './utils/table';\nimport { resolveTableConfig } from './extensions/table';\nimport { getSelectedText as getSelectedTextFromSelection } from './utils/selection';\n\nexport class AtriEditor implements IAtriEditor {\n // 以下两个字段经 createCoreEditor()/setupSubsystems() 在构造函数内建立,\n // 类型上标注「必已赋值」而非可空:门面所有公开方法都假定它们存在\n private coreEditor!: CoreEditor;\n private markdownService!: MarkdownService;\n private themeManager: ThemeManager;\n private i18nManager: I18nManager;\n private extensionManager: ExtensionManager;\n private toolbarManager: ToolbarManager | null = null;\n /** 工具栏容器引用:重建编辑器时复用同一个挂载点,不重新查询 DOM */\n private toolbarContainer: HTMLDivElement | null = null;\n private aiService: AIService | null = null;\n private aiMenuManager: AICommandMenuManager | null = null;\n /** 链接浮层与表格操作菜单归门面所有:两者都跟着\"编辑行为\"走,与工具栏渲染与否无关 */\n private linkPanel: LinkPanel | null = null;\n private tableMenu: TableMenu | null = null;\n private mediaRuntime: MediaRuntime | null;\n private mediaStatus: MediaStatusStrip | null = null;\n /** 重建后待恢复的选区,新视图就绪时应用 */\n private pendingViewState: { from: number; to: number } | null = null;\n /**\n * 浮动工具栏容器:BubbleMenu 显示时把它塞进编辑区、隐藏时直接摘掉,\n * 所以引用必须握在门面手里,重建编辑器后才能拿同一个元素再挂回去\n */\n private bubbleElement: HTMLDivElement | null = null;\n private container: HTMLDivElement;\n private options: AtriEditorOptions;\n /** 销毁后就不再重建:registerNodeView 会往已经 remove() 的容器里复活一个编辑器 */\n private destroyed = false;\n\n constructor(options: AtriEditorOptions) {\n this.options = options;\n\n // 解析挂载元素\n const rootElement = resolveElement(options.element);\n\n // 创建容器\n this.container = createContainer('atri-editor');\n rootElement.appendChild(this.container);\n\n // 初始化主题\n this.themeManager = new ThemeManager(this.container, options.theme || 'light');\n\n // 初始化国际化\n this.i18nManager = new I18nManager(options.lang || 'zh');\n\n // 初始化扩展管理器\n this.extensionManager = new ExtensionManager();\n\n // 注册 NodeView 自定义组件(在创建编辑器之前)\n if (options.nodeViews && options.nodeViews.length > 0) {\n this.extensionManager.registerNodeViews(options.nodeViews);\n }\n\n // 创建工具栏容器(如果 toolbar 不为 false)\n if (options.toolbar !== false) {\n this.toolbarContainer = createContainer('atri-editor-toolbar');\n this.container.appendChild(this.toolbarContainer);\n }\n\n // 创建编辑区域\n const editorElement = createContainer('atri-editor-content-wrapper');\n this.container.appendChild(editorElement);\n\n // 上传运行时只建一次:registerNodeView 会重建编辑器,进行中的上传得跟着这个实例走\n this.mediaRuntime = options.media === false ? null : new MediaRuntime(options.media);\n\n // 状态条也只建一次,挂在根容器上:卡片上没有文字状态,上传反馈全在这一条里\n this.mediaStatus = this.mediaRuntime\n ? new MediaStatusStrip(this.mediaRuntime, this.container, this.i18nManager)\n : null;\n\n this.createCoreEditor({\n element: editorElement,\n content: options.content,\n contentFormat: options.contentFormat,\n });\n this.setupSubsystems();\n }\n\n /** bubble 默认开,显式 false 才不建元素;元素故意不插进文档:挂载与定位全归 BubbleMenu 插件管 */\n private ensureBubbleElement(toolbar: AtriEditorOptions['toolbar']): HTMLDivElement | null {\n if (toolbar === false || toolbar?.bubble === false) return null;\n this.bubbleElement ??= createContainer('atri-editor-bubble-toolbar');\n return this.bubbleElement;\n }\n\n private onEditorCreated(): void {\n // 视图就绪后才能恢复重建前的选区\n if (this.pendingViewState) {\n const { from, to } = this.pendingViewState;\n this.pendingViewState = null;\n\n const limit = this.editor.state.doc.content.size;\n this.editor.commands.setTextSelection({\n from: Math.min(from, limit),\n to: Math.min(to, limit),\n });\n }\n\n // 初始化 AI 命令菜单:空数组也是真值,得判长度,否则挂一个没有选项的菜单管理器\n if (this.options.ai?.functions?.length) {\n this.aiMenuManager = new AICommandMenuManager(\n this.editor,\n this.options.ai.functions,\n this.options.ai.triggerChar ?? '/',\n (func) => {\n this.aiService?.execute(func.id);\n }\n );\n }\n\n this.options.onCreate?.(this);\n }\n\n private initAI(config: AtriAIConfig): void {\n this.aiService = new AIService(this.editor, config, this.markdownService);\n }\n\n /**\n * 创建核心编辑器:构造函数与 recreateEditor 共用的装配点。\n * 只有编辑器壳(挂载元素/初始内容/内容格式)随重建变化,其余全部来自 this.options,\n * 逐项手抄两遍必然漂移——出错的第一现场就是两处不齐\n */\n private createCoreEditor(init: {\n element: HTMLElement;\n content?: string | object;\n contentFormat?: 'html' | 'json' | 'markdown';\n }): void {\n this.coreEditor = new CoreEditor({\n element: init.element,\n content: init.content,\n contentFormat: init.contentFormat,\n editable: this.options.editable,\n placeholder: this.options.placeholder,\n extensions: [...(this.options.extensions || []), ...this.extensionManager.getAll()],\n markdown: this.options.markdown,\n media: this.options.media,\n table: this.options.table,\n link: this.options.link,\n formatting: this.options.formatting,\n codeBlock: this.options.codeBlock,\n mediaRuntime: this.mediaRuntime ?? undefined,\n bubbleElement: this.ensureBubbleElement(this.options.toolbar),\n onCreate: () => {\n this.onEditorCreated();\n },\n onUpdate: () => {\n this.options.onChange?.(this);\n },\n onFocus: () => {\n this.options.onFocus?.(this);\n },\n onBlur: () => {\n this.options.onBlur?.(this);\n },\n onDestroy: () => {\n this.options.onDestroy?.(this);\n },\n });\n }\n\n /** 初始化依赖编辑器实例的子系统:Markdown 服务、链接浮层、工具栏、表格菜单、AI 服务,重建后同样要重新走一遍 */\n private setupSubsystems(): void {\n this.markdownService = new MarkdownService(this.editor, this.options.markdown);\n\n // 链接 mark 不在 schema 里就没得插也没得改,浮层与按钮一并不提供。\n // 这里不走\"能不能改\"的探测:同名 mark 顶掉内置链接时,两边的属性定义仍会按类型名\n // 并到同一个 mark 上,判不出\"被顶掉\",判了也是假判据\n this.linkPanel = this.editor.schema.marks.link\n ? new LinkPanel({\n editor: this.editor,\n root: this.container,\n i18n: this.i18nManager,\n openOnClick: this.options.link?.openOnClick ?? false,\n target: this.options.link?.target ?? '_blank',\n // 开合不改文档,工具栏等不到 transaction,按钮的按下态得主动推一次\n onOpenChange: () => this.toolbarManager?.updateButtonStates(),\n })\n : null;\n\n if (this.toolbarContainer && this.options.toolbar !== false) {\n this.toolbarManager = new ToolbarManager(\n this.editor,\n this.toolbarContainer,\n this.options.toolbar,\n this.i18nManager,\n this.mediaRuntime,\n this.linkPanel\n );\n if (this.bubbleElement) this.toolbarManager.attachBubbleToolbar(this.bubbleElement);\n }\n\n // 表格菜单与表格扩展共用同一份解析后的配置:两处对\"表格开着没开\"必须有一致的判断\n const tableConfig = resolveTableConfig(this.options.table);\n this.tableMenu =\n tableConfig.menu && canInsertTable(this.editor)\n ? new TableMenu({\n editor: this.editor,\n root: this.container,\n i18n: this.i18nManager,\n })\n : null;\n\n if (this.options.ai) {\n this.initAI(this.options.ai);\n }\n }\n\n /**\n * 重新创建编辑器(用于动态注册 NodeView 后更新 schema)\n * 会整体替换编辑器视图,因此内容与选区都要手动恢复\n */\n private recreateEditor(): void {\n // 保存当前内容与光标位置,待新视图就绪后恢复选区\n const previousEditor = this.coreEditor.getEditor();\n const currentContent = this.coreEditor.getHTML();\n const { from, to } = previousEditor.state.selection;\n this.pendingViewState = { from, to };\n\n // 先收 UI 再收编辑器:浮层与菜单挂在 document.body 上,不跟着 coreEditor 一起离开文档,\n // 而它们的事件订阅都指向这个即将销毁的 Editor 实例\n this.toolbarManager?.destroy();\n this.tableMenu?.destroy();\n this.linkPanel?.destroy();\n this.aiMenuManager?.destroy();\n this.coreEditor.destroy();\n\n // 换一个新的编辑区容器:旧容器连同内部视图一起离开文档\n const editorElement = this.container.querySelector(\n '.atri-editor-content-wrapper'\n ) as HTMLElement;\n const newEditorElement = createContainer('atri-editor-content-wrapper');\n if (editorElement) {\n editorElement.replaceWith(newEditorElement);\n } else {\n this.container.appendChild(newEditorElement);\n }\n\n this.createCoreEditor({\n element: newEditorElement,\n content: currentContent,\n // 当前内容取自 getHTML(),重建时按 html 回填,不能沿用用户的 contentFormat\n contentFormat: 'html',\n });\n this.setupSubsystems();\n }\n\n /**\n * 获取 Tiptap Editor 实例\n */\n get editor(): Editor {\n return this.coreEditor.getEditor();\n }\n\n /**\n * 获取 HTML 内容\n */\n getHTML(): string {\n return this.coreEditor.getHTML();\n }\n\n /**\n * 获取 JSON 内容\n */\n getJSON(): object {\n return this.coreEditor.getJSON();\n }\n\n /**\n * 获取 Markdown 内容\n */\n getMarkdown(): string {\n return this.markdownService.getMarkdown();\n }\n\n /**\n * 这一次 getMarkdown() 会丢掉哪些内容\n *\n * 序列化器对没钩子的类型是静默丢弃,所以\"导出完看不出少了什么\"是常态。接入方在保存前\n * 拿它决定要不要提示用户,配 `markdown.inlineStyles: 'drop'` 时尤其需要——那个开关\n * 就是把样式导出成最纯粹的 CommonMark,代价是丢内容。\n */\n getMarkdownLosses(): MarkdownLoss[] {\n return this.markdownService.getLosses();\n }\n\n /**\n * 当前内容里有没有导出 Markdown 时会丢的东西\n */\n hasLossyContent(): boolean {\n return this.markdownService.hasLossyContent();\n }\n\n /**\n * 设置内容\n */\n setContent(content: string | object, options?: SetContentOptions): void {\n const format = options?.format || this.options.contentFormat;\n const emitUpdate = options?.emitUpdate ?? true;\n\n if (format === 'markdown' && typeof content === 'string') {\n this.markdownService.setMarkdown(content, emitUpdate);\n } else {\n this.coreEditor.setContent(content, emitUpdate);\n }\n this.collapseSelectionAfterReplace();\n }\n\n /**\n * 设置 Markdown 内容\n */\n setMarkdown(content: string): void {\n this.markdownService.setMarkdown(content);\n this.collapseSelectionAfterReplace();\n }\n\n /**\n * 整体替换内容后收拢选区:替换前若是全选(Ctrl+A),ProseMirror 会把它映射成\n * 覆盖整篇新文档的 AllSelection——下一个字或下一次插入就把整篇吞掉,\n * 而\"设置内容\"之后用户的预期是安全的光标态,不是\"再输入即全删\"\n */\n private collapseSelectionAfterReplace(): void {\n const editor = this.coreEditor.getEditor();\n if (!editor || editor.state.selection.empty) return;\n editor.commands.setTextSelection(editor.state.doc.content.size);\n }\n\n /**\n * 清空内容\n */\n clearContent(): void {\n this.coreEditor.clearContent();\n }\n\n /**\n * 是否为空\n */\n isEmpty(): boolean {\n return this.coreEditor.isEmpty();\n }\n\n /**\n * Markdown 转 HTML\n */\n markdownToHTML(markdown: string): string {\n return this.markdownService.markdownToHTML(markdown);\n }\n\n /**\n * HTML 转 Markdown\n */\n htmlToMarkdown(html: string): string {\n return this.markdownService.htmlToMarkdown(html);\n }\n\n /**\n * Markdown 转 JSON\n */\n markdownToJSON(markdown: string): JSONContent {\n return this.markdownService.markdownToJSON(markdown);\n }\n\n /**\n * 获取选中文本\n */\n getSelectedText(): string {\n return getSelectedTextFromSelection(this.editor);\n }\n\n /**\n * 插入内容\n */\n insertContent(content: string): void {\n this.coreEditor.insertContent(content);\n }\n\n /**\n * 在选区处插入图片\n */\n insertImage(options: InsertImageOptions): void {\n this.editor.chain().focus().setImage(options).run();\n }\n\n /**\n * 在选区处插入附件(形态由 options.display 或 media.attachment.display 决定)\n */\n insertAttachment(options: InsertAttachmentOptions): void {\n const media = this.options.media;\n const configured = media === false ? undefined : media?.attachment?.display;\n const display = options.display ?? configured ?? 'card';\n const chain = this.editor.chain().focus();\n\n if (display === 'link') {\n chain.setAttachmentLink(options).run();\n } else {\n chain.setAttachment(options).run();\n }\n }\n\n /**\n * 在选区处插入表格(默认 3×3 带表头)\n */\n insertTable(options?: InsertTableOptions): void {\n if (!canInsertTable(this.editor)) {\n console.warn('[Atri Editor] insertTable() ignored: table extensions are disabled.');\n return;\n }\n const { rows = 3, cols = 3, withHeaderRow = true } = options ?? {};\n this.editor.chain().focus().insertTable({ rows, cols, withHeaderRow }).run();\n }\n\n /**\n * 走上传管线插入本地文件\n * kind 缺省时按 MIME 分流;promise 在这批文件全部落定(成功或失败)后 resolve\n */\n async uploadFiles(files: File[] | FileList, kind?: MediaKind): Promise<void> {\n if (!this.mediaRuntime) {\n console.warn('[Atri Editor] uploadFiles() ignored: media extensions are disabled.');\n return;\n }\n\n await this.mediaRuntime.handleFiles(files, { kind });\n }\n\n /**\n * 是否有文件还没落到服务端(上传中与失败都算)\n * 此时保存会把本地预览地址写进内容,接入方应据此提示或阻断\n */\n hasPendingUploads(): boolean {\n return this.mediaRuntime?.hasPendingUploads() ?? false;\n }\n\n /**\n * 重试所有失败的上传,卡片原地回到上传中\n */\n async retryFailedUploads(): Promise<void> {\n await this.mediaRuntime?.retryFailed();\n }\n\n /**\n * 设置占位符\n */\n setPlaceholder(placeholder: string): void {\n // 重建编辑器时占位符取自 options,不同步会退回旧值\n this.options.placeholder = placeholder;\n this.coreEditor.setPlaceholder(placeholder);\n }\n\n /**\n * 设置可编辑状态\n */\n setEditable(editable: boolean): void {\n this.coreEditor.setEditable(editable);\n }\n\n /**\n * 是否可编辑\n */\n isEditable(): boolean {\n return this.coreEditor.isEditable();\n }\n\n /**\n * 聚焦\n */\n focus(): void {\n this.coreEditor.focus();\n }\n\n /**\n * 失焦\n */\n blur(): void {\n this.coreEditor.blur();\n }\n\n /**\n * 获取 AI 服务\n */\n get ai(): AIService | null {\n return this.aiService;\n }\n\n /**\n * 获取主题管理器\n */\n get theme(): ThemeManager {\n return this.themeManager;\n }\n\n /**\n * 获取国际化管理器\n */\n get i18n(): I18nManager {\n return this.i18nManager;\n }\n\n /**\n * 获取扩展管理器\n */\n get extensions(): ExtensionManager {\n return this.extensionManager;\n }\n\n /**\n * 设置主题\n */\n setTheme(theme: ThemeType): void {\n this.themeManager.setTheme(theme);\n }\n\n /**\n * 切换主题\n */\n toggleTheme(): void {\n this.themeManager.toggleTheme();\n }\n\n /**\n * 切换语言\n */\n async setLanguage(lang: string): Promise<void> {\n await this.i18nManager.changeLanguage(lang);\n }\n\n /**\n * 更新 AI 配置\n */\n updateAIConfig(config: Partial<AtriAIConfig>): void {\n if (this.aiService) {\n this.aiService.updateConfig(config);\n }\n }\n\n /**\n * 注册自定义 NodeView 组件\n * 注意:编辑器已创建时,每次调用都会重建一次编辑器以更新 schema;\n * 连续注册多个组件请改用 registerNodeViews,只需重建一次\n */\n registerNodeView(config: AtriNodeViewConfig): void {\n if (this.refuseAfterDestroy('registerNodeView')) return;\n\n this.extensionManager.registerNodeView(config);\n // 如果编辑器已创建,需要重新创建以更新 schema\n if (this.coreEditor) {\n this.recreateEditor();\n }\n }\n\n /**\n * 批量注册 NodeView\n * 注意:如果编辑器已创建,会重新创建编辑器以更新 schema\n */\n registerNodeViews(configs: AtriNodeViewConfig[]): void {\n if (this.refuseAfterDestroy('registerNodeViews')) return;\n\n this.extensionManager.registerNodeViews(configs);\n // 如果编辑器已创建,需要重新创建以更新 schema\n if (this.coreEditor) {\n this.recreateEditor();\n }\n }\n\n /**\n * 销毁后重建类 API 的统一守卫:判在改状态之前,\n * 否则配置进了 extensionManager 却没有对应的编辑器,看起来就像\"注册了但没生效\"\n */\n private refuseAfterDestroy(api: string): boolean {\n if (!this.destroyed) return false;\n console.warn(`[Atri Editor] ${api}() was called on a destroyed editor and did nothing.`);\n return true;\n }\n\n /**\n * 获取所有已注册的 NodeView\n */\n getNodeViews(): Map<string, AtriNodeViewConfig> {\n return this.extensionManager.getNodeViews();\n }\n\n /**\n * 销毁编辑器\n */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n\n this.toolbarManager?.destroy();\n this.tableMenu?.destroy();\n this.linkPanel?.destroy();\n this.aiMenuManager?.destroy();\n this.coreEditor.destroy();\n this.mediaStatus?.destroy();\n // 在途请求随编辑器一起取消,不然回调会打到已销毁的视图上\n this.mediaRuntime?.destroy();\n // 浮层挂在编辑区内部,随 container 一起离开文档;引用断掉,销毁后的门面就再挂不回它\n this.bubbleElement = null;\n this.container.remove();\n }\n}\n","/**\n * AtriEditorElement - Web Component 封装\n */\nimport { AtriEditor } from './AtriEditor';\nimport type {\n AtriEditorOptions,\n InsertAttachmentOptions,\n InsertImageOptions,\n InsertTableOptions,\n MarkdownLoss,\n MediaKind,\n} from './types';\n\nconst ELEMENT_NAME = 'atri-editor';\n\n/**\n * SSR / 纯 Node 安全基类:类声明会在模块加载时求值 extends 后的表达式,\n * 直接 `extends HTMLElement` 会让无 DOM 的环境(Next/Nuxt 服务端、vitest node 池)\n * import 即抛 \"HTMLElement is not defined\"。自定义元素只有在浏览器才真正生效,\n * 这里回退到空基类,注册仍由 registerAtriElement 里的 customElements 守卫把关。\n */\nconst AtriEditorBase: typeof HTMLElement =\n typeof HTMLElement !== 'undefined' ? HTMLElement : (class {} as unknown as typeof HTMLElement);\n\n/**\n * 注册 Web Component\n */\nexport function registerAtriElement(): void {\n if (typeof customElements === 'undefined') {\n console.warn('Custom Elements not supported');\n return;\n }\n\n if (!customElements.get(ELEMENT_NAME)) {\n customElements.define(ELEMENT_NAME, AtriEditorElement);\n }\n}\n\n/**\n * AtriEditor Web Component\n */\nexport class AtriEditorElement extends AtriEditorBase {\n private editor: AtriEditor | null = null;\n private _options: Partial<AtriEditorOptions> = {};\n /** 连接与显式销毁都递增它,用来作废已经排队的拆除任务 */\n private teardownToken = 0;\n /** 断连时留下的内容,重连时接回去 */\n private detachedContent: string | null = null;\n /** 显式销毁过:之后重挂不再自动重建,也不该带任何快照 */\n private destroyed = false;\n\n static get observedAttributes(): string[] {\n return ['theme', 'editable', 'lang', 'placeholder'];\n }\n\n constructor() {\n super();\n }\n\n connectedCallback(): void {\n this.teardownToken += 1;\n if (this.destroyed) return;\n this.initEditor();\n }\n\n disconnectedCallback(): void {\n // 搬动节点(appendChild 重挂、框架 re-parent、虚拟列表滚动)会先同步 disconnect\n // 再同步 connect。拆到微任务边界上,isConnected 已经恢复 true,据此把\"搬家\"\n // 和\"真的卸载\"分开——同步拆的话用户输入连同在途上传一起没了。\n // token 则挡住另一种情形:这一批里被重挂过,就不该再拆后来那个实例\n const token = this.teardownToken;\n queueMicrotask(() => {\n if (token === this.teardownToken && !this.isConnected) this.teardownEditor();\n });\n }\n\n /**\n * 显式销毁:之后重挂不再自动重建,内容快照也一并丢掉\n *\n * 框架里对应 unmount 钩子(Vue 的 unmounted、React effect 的 cleanup)。\n * 断连本身已经会延迟拆除,这个方法的意义在于\"别接回去了\"\n */\n destroy(): void {\n // 顺序要紧:先置 destroyed,teardownEditor 才不会把快照又写回来\n this.destroyed = true;\n this.teardownToken += 1;\n this.detachedContent = null;\n this.teardownEditor();\n }\n\n private teardownEditor(): void {\n if (!this.editor) return;\n // 快照优先于 data-content:后者是用户动手之前的初值,拿它重建等于回滚输入。\n // 但显式 destroy 不留快照——宿主说的是\"结束了\",重挂不该拿回已销毁实例的内容\n if (!this.destroyed) this.detachedContent = this.editor.getHTML();\n this.editor.destroy();\n this.editor = null;\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n switch (name) {\n case 'theme':\n if (this.editor && newValue) {\n this.editor.setTheme(newValue);\n }\n break;\n case 'editable':\n if (this.editor) {\n this.editor.setEditable(newValue !== 'false');\n }\n break;\n case 'lang':\n if (this.editor && newValue) {\n this.editor.setLanguage(newValue);\n }\n break;\n case 'placeholder':\n if (this.editor) {\n this.editor.setPlaceholder(newValue ?? '');\n }\n break;\n }\n }\n\n /**\n * 设置编辑器选项(在 connectedCallback 之前调用)\n */\n setOptions(options: Partial<AtriEditorOptions>): void {\n this._options = { ...this._options, ...options };\n // 宿主显式给了内容就以它为准,断连快照不能压过它\n if ('content' in options) this.detachedContent = null;\n }\n\n private initEditor(): void {\n if (this.editor) return;\n\n const options: AtriEditorOptions = {\n element: this,\n ...this._options,\n };\n\n // 从 HTML 属性读取配置\n const theme = this.getAttribute('theme');\n if (theme) options.theme = theme;\n\n const editable = this.getAttribute('editable');\n if (editable !== null) options.editable = editable !== 'false';\n\n const lang = this.getAttribute('lang');\n if (lang) options.lang = lang;\n\n const placeholder = this.getAttribute('placeholder');\n if (placeholder) options.placeholder = placeholder;\n\n // 从 data 属性读取初始内容\n const content = this.getAttribute('data-content');\n if (content) options.content = content;\n\n const contentFormat = this.getAttribute('data-content-format') as\n | 'html'\n | 'json'\n | 'markdown'\n | undefined;\n if (contentFormat) options.contentFormat = contentFormat;\n\n // 快照排在最后:它是重连前那一刻的 HTML,优先级高于作为\"初值\"的 data-content。\n // contentFormat 也得跟着钉成 html,否则属性写着 markdown 时会拿 Markdown 去解这段 HTML。\n // 只消费一次,第二次重连(宿主自己 destroy 过)就该回到配置的初值\n if (this.detachedContent !== null) {\n options.content = this.detachedContent;\n options.contentFormat = 'html';\n this.detachedContent = null;\n }\n\n this.editor = new AtriEditor(options);\n\n // 派发创建事件\n this.dispatchEvent(\n new CustomEvent('editor-created', {\n detail: { editor: this.editor },\n bubbles: true,\n })\n );\n }\n\n /**\n * 获取编辑器实例\n */\n getEditor(): AtriEditor | null {\n return this.editor;\n }\n\n /**\n * 获取 HTML 内容\n */\n getHTML(): string {\n return this.editor?.getHTML() || '';\n }\n\n /**\n * 获取 JSON 内容\n */\n getJSON(): object | null {\n return this.editor?.getJSON() || null;\n }\n\n /**\n * 获取 Markdown 内容\n */\n getMarkdown(): string {\n return this.editor?.getMarkdown() || '';\n }\n\n /**\n * 这一次 getMarkdown() 会丢掉哪些内容\n *\n * 元素还没初始化时是 null 而不是空数组——\"没什么可丢\"和\"还没建起来\"是两件事,\n * 混在一起会让接入方把没就绪当成安全。\n */\n getMarkdownLosses(): MarkdownLoss[] | null {\n return this.editor?.getMarkdownLosses() ?? null;\n }\n\n /**\n * 当前内容里有没有导出 Markdown 时会丢的东西\n */\n hasLossyContent(): boolean {\n return this.editor?.hasLossyContent() ?? false;\n }\n\n /**\n * 设置内容\n */\n setContent(content: string | object, format?: 'html' | 'json' | 'markdown'): void {\n this.editor?.setContent(content, { format });\n }\n\n /**\n * 清空内容\n */\n clearContent(): void {\n this.editor?.clearContent();\n }\n\n /**\n * 在选区处插入图片\n */\n insertImage(options: InsertImageOptions): void {\n this.editor?.insertImage(options);\n }\n\n /**\n * 在选区处插入附件卡片\n */\n insertAttachment(options: InsertAttachmentOptions): void {\n this.editor?.insertAttachment(options);\n }\n\n /**\n * 在选区处插入表格\n */\n insertTable(options?: InsertTableOptions): void {\n this.editor?.insertTable(options);\n }\n\n /**\n * 走上传管线插入本地文件\n */\n async uploadFiles(files: File[] | FileList, kind?: MediaKind): Promise<void> {\n await this.editor?.uploadFiles(files, kind);\n }\n\n /**\n * 重试所有失败的上传\n */\n async retryFailedUploads(): Promise<void> {\n await this.editor?.retryFailedUploads();\n }\n\n /**\n * 是否有文件还没落到服务端(上传中与失败都算),保存前可据此提示\n */\n hasPendingUploads(): boolean {\n return this.editor?.hasPendingUploads() ?? false;\n }\n\n /**\n * 聚焦\n */\n focus(): void {\n this.editor?.focus();\n }\n}\n\n// 自动注册\nregisterAtriElement();\n"],"names":["url","markdown","ProseMirrorNode","ProseMirrorDOMParser","THEME_PREFIX","_a","simpleMarkdownToHtml","getSelectedTextFromSelection"],"mappings":"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"}