@atri-editor/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +1 -1
- package/dist/AtriEditor.d.ts +4 -1
- package/dist/AtriEditor.d.ts.map +1 -1
- package/dist/core/CoreEditor.d.ts +5 -3
- package/dist/core/CoreEditor.d.ts.map +1 -1
- package/dist/core/I18nManager.d.ts.map +1 -1
- package/dist/core/LinkPanel.d.ts +73 -0
- package/dist/core/LinkPanel.d.ts.map +1 -0
- package/dist/core/TableMenu.d.ts +67 -0
- package/dist/core/TableMenu.d.ts.map +1 -0
- package/dist/core/ToolbarManager.d.ts +8 -2
- package/dist/core/ToolbarManager.d.ts.map +1 -1
- package/dist/core/icons.d.ts.map +1 -1
- package/dist/extensions/table.d.ts +23 -0
- package/dist/extensions/table.d.ts.map +1 -0
- package/dist/index.js +685 -31
- package/dist/index.js.map +1 -1
- package/dist/style.css +125 -4
- package/dist/types/editor.d.ts +31 -2
- package/dist/types/editor.d.ts.map +1 -1
- package/dist/utils/table.d.ts +11 -4
- package/dist/utils/table.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/media/insert-position.ts","../src/extensions/AtriImage.ts","../src/media/file-policy.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/media/Uploader.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/TableGridPanel.ts","../src/utils/table.ts","../src/core/icons.ts","../src/core/ToolbarManager.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 * 媒体节点的插入落点\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 * 附件两种形态(块级卡片 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';\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 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 parseHTML: (element) => 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 {\n attachmentAttributes,\n attachmentLabel,\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 ['a', { class: 'atri-attachment-name', href: src, rel: 'noopener noreferrer' }, label],\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 { attachmentAttributes, attachmentLabel, insertAttachmentContent } 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 getAttrs: (element) =>\n element.getAttribute('data-src') ? {} : { src: element.getAttribute('href') ?? null },\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: src,\n // download 让同源地址点击即下载(跨域浏览器会退化成导航);\n // mergeAttributes 会丢掉 null,没名字就只剩普通链接\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 * Uploader - 上传通道\n *\n * 两条路:用户给 handler 就完全接管;给 endpoint 就走内置 XHR。\n * 选 XHR 而不是 fetch,是因为 fetch 没有可移植的上传进度事件(xhr.upload.onprogress)。\n */\nimport type { AtriUploadConfig, UploadContext, UploadHandler, UploadResult } from '../types';\n\nexport interface Uploader {\n upload(file: File, context: UploadContext): Promise<UploadResult>;\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 resolve(config.transformResult(body, file));\n return;\n }\n\n // 响应本身就是地址字符串时也认,省掉一个必填配置\n const url = typeof body === 'string' ? body : pickUrl(body);\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\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 * 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 { 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 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 let found: number | null = null;\n editor.state.doc.descendants((node, position) => {\n if (found !== null) return;\n if (node.attrs.uploadId === task.id || node.attrs.src === task.previewUrl) {\n found = position;\n }\n });\n return found;\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 this.config.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 */\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/** 文字组只放行内格式:块级与列表留在顶栏,浮层短到不遮住所选的字 */\nexport const BUBBLE_TEXT_ITEMS = ['bold', 'italic', 'underline', 'strike', 'code'] 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 { Table, TableHeader, TableCell, TableRow } from '@tiptap/extension-table';\nimport { createMediaExtensions } from '../extensions/media';\nimport { MediaRuntime } from '../media/MediaRuntime';\nimport { shouldShowBubbleMenu } from './bubble-toolbar';\nimport type { AtriMarkdownConfig, AtriMediaConfig } from '../types';\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;\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\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.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 }),\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(Table, TableRow, TableHeader, TableCell);\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 各扩展的 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 this.config.onCreate?.();\n },\n onUpdate: () => {\n this.config.onUpdate?.();\n },\n onFocus: () => {\n this.config.onFocus?.();\n },\n onBlur: () => {\n this.config.onBlur?.();\n },\n onDestroy: () => {\n this.config.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 }\n}\n","/**\n * Markdown 工具函数\n */\n\n/**\n * 简单的 Markdown 转 HTML(用于 AI 响应处理)\n * 注意:完整的 Markdown 解析由 @tiptap/markdown 处理\n * 这里仅提供基础的 Markdown 语法转换\n */\nexport function simpleMarkdownToHtml(markdown: string): string {\n let html = markdown;\n\n // 标题\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 // 图片:必须先于链接,否则  会被下面的链接规则从 ! 之后开始匹配,吞成一个 <a>\n html = html.replace(\n /!\\[([^\\]]*)\\]\\(\\s*([^\\s)]+)(?:\\s+\"([^\"]*)\")?\\s*\\)/g,\n (_all, alt, src, title) => `<img src=\"${src}\" alt=\"${alt}\"${title ? ` title=\"${title}\"` : ''}>`\n );\n\n // 链接\n html = html.replace(/\\[(.+?)\\]\\((.+?)\\)/g, '<a href=\"$2\">$1</a>');\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\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;\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(/&/g, '&');\n md = md.replace(/</g, '<');\n md = md.replace(/>/g, '>');\n md = md.replace(/"/g, '\"');\n md = md.replace(/'/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\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 // 移除旧主题类后添加新主题类\n this.container.classList.remove('atri-theme-light', 'atri-theme-dark');\n this.container.classList.add(`atri-theme-${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 paragraph: '正文',\n bulletList: '无序列表',\n orderedList: '有序列表',\n taskList: '任务列表',\n codeBlock: '代码块',\n blockquote: '引用',\n link: '链接',\n image: '图片',\n attachment: '附件',\n attachmentDisplay: '附件样式',\n delete: '删除',\n video: '视频',\n table: '表格',\n undo: '撤销',\n redo: '重做',\n alignLeft: '左对齐',\n alignCenter: '居中',\n alignRight: '右对齐',\n alignJustify: '两端对齐',\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 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 paragraph: 'Paragraph',\n bulletList: 'Bullet List',\n orderedList: 'Ordered List',\n taskList: 'Task List',\n codeBlock: 'Code Block',\n blockquote: 'Blockquote',\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 alignLeft: 'Align Left',\n alignCenter: 'Align Center',\n alignRight: 'Align Right',\n alignJustify: 'Justify',\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 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\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: zhResources },\n en: { translation: 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 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 * 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 共用这里的定位、外点/Escape 关闭与主题类复制三段。\n */\nimport { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';\n\nexport interface FloatingPanel {\n /** 停止位置跟随;重复调用无副作用 */\n stop: () => void;\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: [offset(6), flip(), shift({ padding: 8 })],\n });\n // 算位置的这几微秒里面板可能已经关了\n if (!isCurrent()) return;\n panel.style.position = 'fixed';\n panel.style.left = `${x}px`;\n panel.style.top = `${y}px`;\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\n/**\n * 主题变量写在 .atri-editor 上,浮层挂在 body 上够不着,只能把主题类复制一份。\n * 面板此刻往往还没入 DOM,closest 搜不到编辑器根,所以源元素必须由编辑器侧给(如 view.dom)\n */\nexport function copyThemeClasses(source: Element | null, panel: HTMLElement): void {\n const root = source?.closest('.atri-editor');\n const themes = Array.from(root?.classList ?? []).filter((name) => name.startsWith('atri-theme-'));\n panel.classList.add(...themes);\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 copyThemeClasses,\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 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.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 copyThemeClasses(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 * TableGridPanel - 工具栏「插入表格」的网格悬停选择器\n *\n * Notion 式交互:悬停高亮左上 (row × col) 区域、点击即插入。\n * 与 InsertPanel 同为挂在 document.body 上的浮层,定位/关闭/主题三段走 floating-panel 公共底盘。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n copyThemeClasses,\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 /** 键盘高亮的当前格,-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.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 copyThemeClasses(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 * 表格能力探测:工具栏按钮与门面 insertTable 共用的 gate\n *\n * 内部使用,不进 utils barrel、不随包出口——检测口径(schema spec 上的 tableRole)\n * 是 extension-table 的实现细节,公共面只承诺\"能不能插\"这件事本身\n */\nimport type { Editor } from '@tiptap/core';\n\n/**\n * 内置表格是否真的可用:table:false 不注册、用户用同名扩展顶掉,两种情况都该判 false\n *\n * 不能扫 extensionManager 里的名字:tiptap 对重名扩展只告警不去重,顶掉场景下\n * 内置 Table 仍在列表里、赢的却是用户的 schema 节点,而内置 insertTable 命令\n * 按 spec.tableRole 找型,找不到就在点击当场抛错。命令存在性 + schema 角色\n * 两条一起看,判定的才是\"这一下真能插出来\"\n */\nexport function canInsertTable(editor: Editor): boolean {\n if (typeof editor.commands.insertTable !== 'function') return false;\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 * 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 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 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 * 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 type { I18nManager } from './I18nManager';\nimport { TableGridPanel } from './TableGridPanel';\nimport { canInsertTable } from '../utils/table';\nimport { icons } from './icons';\n\n/**\n * 工具栏项定义\n */\ninterface ToolbarItemDef {\n id: string;\n icon: string;\n tooltip: string;\n /** 打开浮层的项不能把焦点抢回编辑器,否则浮层里的输入框永远打不了字 */\n popup?: boolean;\n command: (editor: Editor, button: HTMLButtonElement) => void;\n isActive: (editor: Editor) => boolean;\n isDisabled: (editor: Editor) => boolean;\n}\n\n/**\n * 工具栏项与 i18n 词条的映射(缺词条时回退到 tooltip 文案)\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 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 blockquote: 'editor.blockquote',\n codeBlock: 'editor.codeBlock',\n alignLeft: 'editor.alignLeft',\n alignCenter: 'editor.alignCenter',\n alignRight: 'editor.alignRight',\n insertImage: 'editor.image',\n insertAttachment: 'editor.attachment',\n attachmentDisplay: 'editor.attachmentDisplay',\n insertTable: 'editor.table',\n delete: 'editor.delete',\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 private unsubscribeLanguage?: () => 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 constructor(\n editor: Editor,\n container: HTMLElement,\n config?: ToolbarConfig,\n i18n?: I18nManager,\n mediaRuntime?: MediaRuntime | 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.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 * 创建工具栏 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 ['heading1', 'heading2', 'heading3', 'paragraph'],\n ['bold', 'italic', 'underline', 'strike', 'code'],\n ['bulletList', 'orderedList', 'blockquote', 'codeBlock'],\n ['alignLeft', 'alignCenter', 'alignRight'],\n // 同媒体组的道理:表格节点没注册时这项不存在,默认布局里也不摆空位\n ...(this.itemDefs.has('insertTable') ? [['insertTable']] : []),\n // 「附件样式」不再常驻:切换形态改由选中附件时的浮动工具栏承接(bubble 默认开)\n ...(this.itemDefs.has('insertImage') ? [['insertImage', 'insertAttachment']] : []),\n ];\n\n layout.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 console.warn(`[Atri Editor] Toolbar item \"${id}\" declares children, which is not supported.`);\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', 'dialog');\n button.setAttribute('aria-expanded', 'false');\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 const custom = this.customTooltips.get(itemId);\n const key = TOOLTIP_KEYS[itemId];\n // 未注入 i18n 或词条缺失时回退到定义里的文案\n const title =\n custom ??\n (key ? (this.i18n?.tOr(key, itemDef.tooltip) ?? itemDef.tooltip) : itemDef.tooltip);\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 tooltip: '图片',\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 tooltip: '附件',\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 tooltip: '附件样式',\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 tooltip: '表格',\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 // 删除作用于选中的整节点:一段文字该交给 Delete 键,所以它不进顶栏默认布局\n items.set('delete', {\n id: 'delete',\n icon: icons.trash,\n tooltip: '删除',\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 tooltip: '撤销',\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 tooltip: '重做',\n command: (editor) => editor.chain().redo().run(),\n isActive: () => false,\n isDisabled: (editor) => !editor.can().redo(),\n });\n\n // 标题1\n items.set('heading1', {\n id: 'heading1',\n icon: icons.heading1,\n tooltip: '标题 1',\n command: (editor) => editor.chain().toggleHeading({ level: 1 }).run(),\n isActive: (editor) => editor.isActive('heading', { level: 1 }),\n isDisabled: (editor) => !editor.can().toggleHeading({ level: 1 }),\n });\n\n // 标题2\n items.set('heading2', {\n id: 'heading2',\n icon: icons.heading2,\n tooltip: '标题 2',\n command: (editor) => editor.chain().toggleHeading({ level: 2 }).run(),\n isActive: (editor) => editor.isActive('heading', { level: 2 }),\n isDisabled: (editor) => !editor.can().toggleHeading({ level: 2 }),\n });\n\n // 标题3\n items.set('heading3', {\n id: 'heading3',\n icon: icons.heading3,\n tooltip: '标题 3',\n command: (editor) => editor.chain().toggleHeading({ level: 3 }).run(),\n isActive: (editor) => editor.isActive('heading', { level: 3 }),\n isDisabled: (editor) => !editor.can().toggleHeading({ level: 3 }),\n });\n\n // 正文\n items.set('paragraph', {\n id: 'paragraph',\n icon: icons.paragraph,\n tooltip: '正文',\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 tooltip: '加粗',\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 tooltip: '斜体',\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 tooltip: '下划线',\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 tooltip: '删除线',\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 tooltip: '行内代码',\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 tooltip: '无序列表',\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 tooltip: '有序列表',\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('blockquote', {\n id: 'blockquote',\n icon: icons.quote,\n tooltip: '引用',\n command: (editor) => editor.chain().toggleBlockquote().run(),\n isActive: (editor) => editor.isActive('blockquote'),\n isDisabled: (editor) => !editor.can().toggleBlockquote(),\n });\n\n // 代码块\n items.set('codeBlock', {\n id: 'codeBlock',\n icon: icons.codeBlock,\n tooltip: '代码块',\n command: (editor) => editor.chain().toggleCodeBlock().run(),\n isActive: (editor) => editor.isActive('codeBlock'),\n isDisabled: (editor) => !editor.can().toggleCodeBlock(),\n });\n\n // 左对齐\n items.set('alignLeft', {\n id: 'alignLeft',\n icon: icons.alignLeft,\n tooltip: '左对齐',\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 tooltip: '居中',\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 tooltip: '右对齐',\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 return items;\n }\n\n /**\n * 绑定编辑器事件,更新按钮状态\n */\n private bindEditorEvents(): void {\n this.editor.on('transaction', () => {\n this.updateButtonStates();\n });\n\n this.editor.on('selectionUpdate', () => {\n this.updateButtonStates();\n });\n\n // 初始状态\n this.updateButtonStates();\n }\n\n /**\n * 更新所有按钮状态\n */\n private 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 });\n }\n\n /**\n * 销毁工具栏\n */\n destroy(): void {\n this.unsubscribeLanguage?.();\n this.unsubscribeLanguage = undefined;\n // 面板挂在 document.body 上,不跟着工具栏一起收就会留在页面上\n this.panel?.destroy();\n this.panel = null;\n this.tablePanel?.destroy();\n this.tablePanel = 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 * 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 this.retry.addEventListener('click', () => void runtime.retryFailed());\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\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 = await requestEndpoint(processedContext);\n\n // 3. afterPost\n response = await this.runAfterPost(response, processedContext);\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 { 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\n try {\n await this.interceptor.executeChain(context, this.config.requestEndpoint, (content) =>\n this.insertContent(content, func.outputMode || 'insert')\n );\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n this.config.onError?.(err, context);\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 { computePosition, flip, offset, shift } from '@floating-ui/dom';\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\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 this.editor.on('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 }\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 if (this.isVisible) 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: [offset(8), flip(), shift()],\n }));\n } catch {\n // 测量或定位失败时保持菜单可见,仅跳过定位\n return;\n }\n\n // 异步定位期间菜单可能已关闭或被重建\n if (this.menuElement !== menuElement) return;\n\n menuElement.style.position = 'fixed';\n menuElement.style.left = `${x}px`;\n menuElement.style.top = `${y}px`;\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.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 { 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 { 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 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\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) {\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 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 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 );\n if (this.bubbleElement) this.toolbarManager.attachBubbleToolbar(this.bubbleElement);\n }\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 // 销毁旧编辑器\n this.coreEditor.destroy();\n this.toolbarManager?.destroy();\n this.aiMenuManager?.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 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 this.extensionManager.registerNodeViews(configs);\n // 如果编辑器已创建,需要重新创建以更新 schema\n if (this.coreEditor) {\n this.recreateEditor();\n }\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 this.toolbarManager?.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 static get observedAttributes(): string[] {\n return ['theme', 'editable', 'lang', 'placeholder'];\n }\n\n constructor() {\n super();\n }\n\n connectedCallback(): void {\n this.initEditor();\n }\n\n disconnectedCallback(): void {\n this.destroyEditor();\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\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 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 private destroyEditor(): void {\n if (this.editor) {\n this.editor.destroy();\n this.editor = null;\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":["_b","_a","markdown","ProseMirrorNode","ProseMirrorDOMParser","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/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/media/Uploader.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/TableGridPanel.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 * 附件两种形态(块级卡片 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';\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 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 parseHTML: (element) => 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 {\n attachmentAttributes,\n attachmentLabel,\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 ['a', { class: 'atri-attachment-name', href: src, rel: 'noopener noreferrer' }, label],\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 { attachmentAttributes, attachmentLabel, insertAttachmentContent } 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 getAttrs: (element) =>\n element.getAttribute('data-src') ? {} : { src: element.getAttribute('href') ?? null },\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: src,\n // download 让同源地址点击即下载(跨域浏览器会退化成导航);\n // mergeAttributes 会丢掉 null,没名字就只剩普通链接\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 * Uploader - 上传通道\n *\n * 两条路:用户给 handler 就完全接管;给 endpoint 就走内置 XHR。\n * 选 XHR 而不是 fetch,是因为 fetch 没有可移植的上传进度事件(xhr.upload.onprogress)。\n */\nimport type { AtriUploadConfig, UploadContext, UploadHandler, UploadResult } from '../types';\n\nexport interface Uploader {\n upload(file: File, context: UploadContext): Promise<UploadResult>;\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 resolve(config.transformResult(body, file));\n return;\n }\n\n // 响应本身就是地址字符串时也认,省掉一个必填配置\n const url = typeof body === 'string' ? body : pickUrl(body);\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\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 * 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 { 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 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 let found: number | null = null;\n editor.state.doc.descendants((node, position) => {\n if (found !== null) return;\n if (node.attrs.uploadId === task.id || node.attrs.src === task.previewUrl) {\n found = position;\n }\n });\n return found;\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 this.config.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 */\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/** 文字组只放行内格式:块级与列表留在顶栏,浮层短到不遮住所选的字 */\nexport const BUBBLE_TEXT_ITEMS = ['bold', 'italic', 'underline', 'strike', 'code'] 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 { MediaRuntime } from '../media/MediaRuntime';\nimport { shouldShowBubbleMenu } from './bubble-toolbar';\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 * 复用外部的上传运行时\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\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.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 }),\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 // 输入实时转换由 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 this.config.onCreate?.();\n },\n onUpdate: () => {\n this.config.onUpdate?.();\n },\n onFocus: () => {\n this.config.onFocus?.();\n },\n onBlur: () => {\n this.config.onBlur?.();\n },\n onDestroy: () => {\n this.config.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 }\n}\n","/**\n * Markdown 工具函数\n */\n\n/**\n * 简单的 Markdown 转 HTML(用于 AI 响应处理)\n * 注意:完整的 Markdown 解析由 @tiptap/markdown 处理\n * 这里仅提供基础的 Markdown 语法转换\n */\nexport function simpleMarkdownToHtml(markdown: string): string {\n let html = markdown;\n\n // 标题\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 // 图片:必须先于链接,否则  会被下面的链接规则从 ! 之后开始匹配,吞成一个 <a>\n html = html.replace(\n /!\\[([^\\]]*)\\]\\(\\s*([^\\s)]+)(?:\\s+\"([^\"]*)\")?\\s*\\)/g,\n (_all, alt, src, title) => `<img src=\"${src}\" alt=\"${alt}\"${title ? ` title=\"${title}\"` : ''}>`\n );\n\n // 链接\n html = html.replace(/\\[(.+?)\\]\\((.+?)\\)/g, '<a href=\"$2\">$1</a>');\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\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;\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(/&/g, '&');\n md = md.replace(/</g, '<');\n md = md.replace(/>/g, '>');\n md = md.replace(/"/g, '\"');\n md = md.replace(/'/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\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 // 移除旧主题类后添加新主题类\n this.container.classList.remove('atri-theme-light', 'atri-theme-dark');\n this.container.classList.add(`atri-theme-${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 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 alignLeft: '左对齐',\n alignCenter: '居中',\n alignRight: '右对齐',\n alignJustify: '两端对齐',\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 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 alignLeft: 'Align Left',\n alignCenter: 'Align Center',\n alignRight: 'Align Right',\n alignJustify: 'Justify',\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\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: zhResources },\n en: { translation: 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 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 * 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 共用这里的定位、外点/Escape 关闭与主题类复制三段。\n */\nimport { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';\n\nexport interface FloatingPanel {\n /** 停止位置跟随;重复调用无副作用 */\n stop: () => void;\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: [offset(6), flip(), shift({ padding: 8 })],\n });\n // 算位置的这几微秒里面板可能已经关了\n if (!isCurrent()) return;\n panel.style.position = 'fixed';\n panel.style.left = `${x}px`;\n panel.style.top = `${y}px`;\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\n/**\n * 主题变量写在 .atri-editor 上,浮层挂在 body 上够不着,只能把主题类复制一份。\n * 面板此刻往往还没入 DOM,closest 搜不到编辑器根,所以源元素必须由编辑器侧给(如 view.dom)\n */\nexport function copyThemeClasses(source: Element | null, panel: HTMLElement): void {\n const root = source?.closest('.atri-editor');\n const themes = Array.from(root?.classList ?? []).filter((name) => name.startsWith('atri-theme-'));\n panel.classList.add(...themes);\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 copyThemeClasses,\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 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.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 copyThemeClasses(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 * TableGridPanel - 工具栏「插入表格」的网格悬停选择器\n *\n * Notion 式交互:悬停高亮左上 (row × col) 区域、点击即插入。\n * 与 InsertPanel 同为挂在 document.body 上的浮层,定位/关闭/主题三段走 floating-panel 公共底盘。\n */\nimport type { Editor } from '@tiptap/core';\nimport {\n copyThemeClasses,\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 /** 键盘高亮的当前格,-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.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 copyThemeClasses(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 * 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","/**\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 type { I18nManager } from './I18nManager';\nimport type { LinkPanel } from './LinkPanel';\nimport { TableGridPanel } from './TableGridPanel';\nimport { canInsertTable } from '../utils/table';\nimport { icons } from './icons';\n\n/**\n * 工具栏项定义\n */\ninterface ToolbarItemDef {\n id: string;\n icon: string;\n tooltip: string;\n /** 打开浮层的项不能把焦点抢回编辑器,否则浮层里的输入框永远打不了字 */\n popup?: boolean;\n command: (editor: Editor, button: HTMLButtonElement) => void;\n isActive: (editor: Editor) => boolean;\n isDisabled: (editor: Editor) => boolean;\n}\n\n/**\n * 工具栏项与 i18n 词条的映射(缺词条时回退到 tooltip 文案)\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 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};\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 /** 同一个项可能同时渲染在顶栏与浮动工具栏里,所以一个 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 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 * 创建工具栏 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 ['heading1', 'heading2', 'heading3', 'paragraph'],\n // 链接跟着行内格式走:它也是一段文字上的标记,只是要填地址所以带浮层\n ['bold', 'italic', 'underline', 'strike', 'code', 'insertLink'],\n ['bulletList', 'orderedList', 'taskList', 'blockquote', 'codeBlock'],\n ['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'],\n // 分割线与表格自成一组:都是\"往正文里插一块\"的动作,没有激活态。\n // 表格节点没注册时(table:false 或被顶掉)这项不存在,组里只剩分割线,不会吊出空组\n ['horizontalRule', ...(this.itemDefs.has('insertTable') ? ['insertTable'] : [])],\n // 「附件样式」不再常驻:切换形态改由选中附件时的浮动工具栏承接(bubble 默认开)\n ...(this.itemDefs.has('insertImage') ? [['insertImage', 'insertAttachment']] : []),\n ];\n\n layout.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 console.warn(`[Atri Editor] Toolbar item \"${id}\" declares children, which is not supported.`);\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', 'dialog');\n button.setAttribute('aria-expanded', 'false');\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 const custom = this.customTooltips.get(itemId);\n const key = TOOLTIP_KEYS[itemId];\n // 未注入 i18n 或词条缺失时回退到定义里的文案\n const title =\n custom ??\n (key ? (this.i18n?.tOr(key, itemDef.tooltip) ?? itemDef.tooltip) : itemDef.tooltip);\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 tooltip: '图片',\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 tooltip: '附件',\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 tooltip: '附件样式',\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 tooltip: '表格',\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 tooltip: '链接',\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 // 删除作用于选中的整节点:一段文字该交给 Delete 键,所以它不进顶栏默认布局\n items.set('delete', {\n id: 'delete',\n icon: icons.trash,\n tooltip: '删除',\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 tooltip: '撤销',\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 tooltip: '重做',\n command: (editor) => editor.chain().redo().run(),\n isActive: () => false,\n isDisabled: (editor) => !editor.can().redo(),\n });\n\n // 标题1\n items.set('heading1', {\n id: 'heading1',\n icon: icons.heading1,\n tooltip: '标题 1',\n command: (editor) => editor.chain().toggleHeading({ level: 1 }).run(),\n isActive: (editor) => editor.isActive('heading', { level: 1 }),\n isDisabled: (editor) => !editor.can().toggleHeading({ level: 1 }),\n });\n\n // 标题2\n items.set('heading2', {\n id: 'heading2',\n icon: icons.heading2,\n tooltip: '标题 2',\n command: (editor) => editor.chain().toggleHeading({ level: 2 }).run(),\n isActive: (editor) => editor.isActive('heading', { level: 2 }),\n isDisabled: (editor) => !editor.can().toggleHeading({ level: 2 }),\n });\n\n // 标题3\n items.set('heading3', {\n id: 'heading3',\n icon: icons.heading3,\n tooltip: '标题 3',\n command: (editor) => editor.chain().toggleHeading({ level: 3 }).run(),\n isActive: (editor) => editor.isActive('heading', { level: 3 }),\n isDisabled: (editor) => !editor.can().toggleHeading({ level: 3 }),\n });\n\n // 正文\n items.set('paragraph', {\n id: 'paragraph',\n icon: icons.paragraph,\n tooltip: '正文',\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 tooltip: '加粗',\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 tooltip: '斜体',\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 tooltip: '下划线',\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 tooltip: '删除线',\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 tooltip: '行内代码',\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 tooltip: '无序列表',\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 tooltip: '有序列表',\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 tooltip: '任务列表',\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 tooltip: '引用',\n command: (editor) => editor.chain().toggleBlockquote().run(),\n isActive: (editor) => editor.isActive('blockquote'),\n isDisabled: (editor) => !editor.can().toggleBlockquote(),\n });\n\n // 代码块\n items.set('codeBlock', {\n id: 'codeBlock',\n icon: icons.codeBlock,\n tooltip: '代码块',\n command: (editor) => editor.chain().toggleCodeBlock().run(),\n isActive: (editor) => editor.isActive('codeBlock'),\n isDisabled: (editor) => !editor.can().toggleCodeBlock(),\n });\n\n // 分割线:插入型操作,没有\"激活\"态可言\n items.set('horizontalRule', {\n id: 'horizontalRule',\n icon: icons.horizontalRule,\n tooltip: '分割线',\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 tooltip: '左对齐',\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 tooltip: '居中',\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 tooltip: '右对齐',\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 tooltip: '两端对齐',\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 return items;\n }\n\n /**\n * 绑定编辑器事件,更新按钮状态\n */\n private bindEditorEvents(): void {\n this.editor.on('transaction', () => {\n this.updateButtonStates();\n });\n\n this.editor.on('selectionUpdate', () => {\n this.updateButtonStates();\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 });\n }\n\n /**\n * 销毁工具栏\n */\n destroy(): void {\n this.unsubscribeLanguage?.();\n this.unsubscribeLanguage = undefined;\n // 面板挂在 document.body 上,不跟着工具栏一起收就会留在页面上\n this.panel?.destroy();\n this.panel = null;\n this.tablePanel?.destroy();\n this.tablePanel = 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 copyThemeClasses,\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 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.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 copyThemeClasses(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, flip, offset, shift } from '@floating-ui/dom';\nimport { copyThemeClasses } from './floating-panel';\nimport { tOrFallback, 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 /** i18n 缺失时的中文兜底 */\n label: 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 label: '上方插入行',\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 label: '下方插入行',\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 label: '删除行',\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 label: '左侧插入列',\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 label: '右侧插入列',\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 label: '删除列',\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 label: '合并单元格',\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 label: '拆分单元格',\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 label: '切换表头行',\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, string][] = [\n ['left', 'table.alignLeft', '单元格左对齐'],\n ['center', 'table.alignCenter', '单元格居中'],\n ['right', 'table.alignRight', '单元格右对齐'],\n ];\n alignments.forEach(([align, labelKey, label]) => {\n const suffix = `${align[0].toUpperCase()}${align.slice(1)}`;\n items.push({\n id: `cellAlign${suffix}`,\n icon: icons[`align${suffix}`],\n labelKey,\n label,\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 label: '删除表格',\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 /** 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, fallback: string): string {\n return tOrFallback(this.i18n, key, fallback);\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 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, item.label);\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 copyThemeClasses(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, item.label);\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: [offset(6), flip(), shift({ padding: 8 })],\n }));\n } catch {\n // 定位失败也要让菜单可见:宁可位置不理想,也不能点了没反应\n return;\n }\n\n // 异步算位置的间隙里菜单可能已经收起或被重建\n if (this.element !== panel) return;\n panel.style.position = 'fixed';\n panel.style.left = `${x}px`;\n panel.style.top = `${y}px`;\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 this.retry.addEventListener('click', () => void runtime.retryFailed());\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\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 = await requestEndpoint(processedContext);\n\n // 3. afterPost\n response = await this.runAfterPost(response, processedContext);\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 { 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\n try {\n await this.interceptor.executeChain(context, this.config.requestEndpoint, (content) =>\n this.insertContent(content, func.outputMode || 'insert')\n );\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n this.config.onError?.(err, context);\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 { computePosition, flip, offset, shift } from '@floating-ui/dom';\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\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 this.editor.on('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 }\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 if (this.isVisible) 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: [offset(8), flip(), shift()],\n }));\n } catch {\n // 测量或定位失败时保持菜单可见,仅跳过定位\n return;\n }\n\n // 异步定位期间菜单可能已关闭或被重建\n if (this.menuElement !== menuElement) return;\n\n menuElement.style.position = 'fixed';\n menuElement.style.left = `${x}px`;\n menuElement.style.top = `${y}px`;\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.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\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) {\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 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 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 this.extensionManager.registerNodeViews(configs);\n // 如果编辑器已创建,需要重新创建以更新 schema\n if (this.coreEditor) {\n this.recreateEditor();\n }\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 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 static get observedAttributes(): string[] {\n return ['theme', 'editable', 'lang', 'placeholder'];\n }\n\n constructor() {\n super();\n }\n\n connectedCallback(): void {\n this.initEditor();\n }\n\n disconnectedCallback(): void {\n this.destroyEditor();\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\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 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 private destroyEditor(): void {\n if (this.editor) {\n this.editor.destroy();\n this.editor = null;\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":["_b","_a","markdown","ProseMirrorNode","ProseMirrorDOMParser","simpleMarkdownToHtml","getSelectedTextFromSelection"],"mappings":"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"}
|