@atri-editor/core 0.1.0 → 0.2.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +5 -5
  3. package/dist/AtriEditor.d.ts +20 -5
  4. package/dist/AtriEditor.d.ts.map +1 -1
  5. package/dist/AtriEditorElement.d.ts +5 -1
  6. package/dist/AtriEditorElement.d.ts.map +1 -1
  7. package/dist/ai/AICommandMenu.d.ts.map +1 -1
  8. package/dist/core/CoreEditor.d.ts +2 -0
  9. package/dist/core/CoreEditor.d.ts.map +1 -1
  10. package/dist/core/ExtensionManager.d.ts +2 -14
  11. package/dist/core/ExtensionManager.d.ts.map +1 -1
  12. package/dist/core/I18nManager.d.ts +9 -0
  13. package/dist/core/I18nManager.d.ts.map +1 -1
  14. package/dist/core/TableGridPanel.d.ts +41 -0
  15. package/dist/core/TableGridPanel.d.ts.map +1 -0
  16. package/dist/core/ThemeManager.d.ts +3 -10
  17. package/dist/core/ThemeManager.d.ts.map +1 -1
  18. package/dist/core/ToolbarManager.d.ts +7 -0
  19. package/dist/core/ToolbarManager.d.ts.map +1 -1
  20. package/dist/core/floating-panel.d.ts +20 -0
  21. package/dist/core/floating-panel.d.ts.map +1 -0
  22. package/dist/core/icons.d.ts.map +1 -1
  23. package/dist/extensions/Attachment.d.ts.map +1 -1
  24. package/dist/extensions/AttachmentLink.d.ts.map +1 -1
  25. package/dist/extensions/MediaFiles.d.ts.map +1 -1
  26. package/dist/extensions/attachment-markdown.d.ts +22 -0
  27. package/dist/extensions/attachment-markdown.d.ts.map +1 -0
  28. package/dist/index.d.ts +1 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +429 -302
  31. package/dist/index.js.map +1 -1
  32. package/dist/media/InsertPanel.d.ts +5 -6
  33. package/dist/media/InsertPanel.d.ts.map +1 -1
  34. package/dist/media/MediaStatusStrip.d.ts.map +1 -1
  35. package/dist/media/Uploader.d.ts.map +1 -1
  36. package/dist/style.css +62 -0
  37. package/dist/types/ai.d.ts +2 -0
  38. package/dist/types/ai.d.ts.map +1 -1
  39. package/dist/types/editor.d.ts +23 -7
  40. package/dist/types/editor.d.ts.map +1 -1
  41. package/dist/types/extension.d.ts +16 -34
  42. package/dist/types/extension.d.ts.map +1 -1
  43. package/dist/types/index.d.ts +0 -1
  44. package/dist/types/index.d.ts.map +1 -1
  45. package/dist/utils/dom.d.ts +3 -2
  46. package/dist/utils/dom.d.ts.map +1 -1
  47. package/dist/utils/table.d.ts +17 -0
  48. package/dist/utils/table.d.ts.map +1 -0
  49. package/package.json +23 -5
  50. package/dist/types/events.d.ts +0 -26
  51. package/dist/types/events.d.ts.map +0 -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.ts","../src/extensions/AttachmentLink.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/media/InsertPanel.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/utils/dom.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 * Attachment - 文件附件节点\n *\n * 不能走 ExtensionManager.registerNodeView:那条路合成的节点声明不了 addCommands 与\n * markdown 三件套(只有 renderMarkdown,导出得回来不去),附件要能往返。\n */\nimport { Node, mergeAttributes, type MarkdownToken } 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, parseFileSize } from '../media/file-policy';\nimport {\n attachmentAttributes,\n attachmentLabel,\n insertAttachmentContent,\n isAttachmentForm,\n} from './attachment-attrs';\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\n/** Markdown 里的附件语法:!file[名字](url \"大小\") */\nconst markdownPattern = /^!file\\[([^\\]]*)\\]\\(\\s*([^\\s)]+)(?:\\s+\"([^\"]*)\")?\\s*\\)(?:\\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 markdownTokenName: 'file',\n\n markdownTokenizer: {\n name: 'file',\n level: 'block',\n start: (src) => src.indexOf('!file['),\n tokenize: (src): MarkdownToken | undefined => {\n const match = markdownPattern.exec(src);\n if (!match) return undefined;\n\n return {\n type: 'file',\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 'attachment',\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 name = node.attrs?.name ?? '';\n const size = node.attrs?.size;\n\n return size ? `!file[${name}](${src} \"${formatFileSize(size)}\")` : `!file[${name}](${src})`;\n },\n});\n","/**\n * AttachmentLink - 行内链接形态的文件附件\n *\n * 与 attachment(块级卡片)是两个节点类型:PM 的 inline/group 在 schema 里静态声明,\n * 同一类型当不了两种形态。属性集、瞬时态补丁与 Markdown 语法家族都走共享模块,\n * 切换命令(Attachment.ts 的 setAttachmentDisplay)只在两者间换节点。\n */\nimport { Node, mergeAttributes, type MarkdownToken } from '@tiptap/core';\nimport type { InsertAttachmentOptions } from '../types';\nimport { formatFileSize, parseFileSize } from '../media/file-policy';\nimport { attachmentAttributes, attachmentLabel, insertAttachmentContent } from './attachment-attrs';\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n attachmentLink: {\n /** 在选区处插入一个行内附件链接 */\n setAttachmentLink: (options: InsertAttachmentOptions) => ReturnType;\n };\n }\n}\n\n/**\n * Markdown 里的行内附件语法:!filelink[名字](url \"大小\")\n * 与块级 !file[ 无碰撞——'!filelink[' 不含子串 '!file['\n */\nconst markdownPattern = /^!filelink\\[([^\\]]*)\\]\\(\\s*([^\\s)]+)(?:\\s+\"([^\"]*)\")?\\s*\\)/;\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 markdownTokenName: 'filelink',\n\n markdownTokenizer: {\n name: 'filelink',\n level: 'inline',\n start: (src) => src.indexOf('!filelink['),\n tokenize: (src): MarkdownToken | undefined => {\n const match = markdownPattern.exec(src);\n if (!match) return undefined;\n\n return {\n type: 'filelink',\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 'attachmentLink',\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 name = node.attrs?.name ?? '';\n const size = node.attrs?.size;\n\n return size\n ? `!filelink[${name}](${src} \"${formatFileSize(size)}\")`\n : `!filelink[${name}](${src})`;\n },\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';\n\nexport interface MediaFilesOptions {\n runtime: MediaRuntime | null;\n}\n\nfunction filesOf(transfer: DataTransfer | null | undefined): File[] {\n if (!transfer) return [];\n // 剪贴板里的截图与文件都在 files 上,text 为空不代表没有可投的东西\n return Array.from(transfer.files);\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, any>;\n const candidates = [record.url, record.location, (record.data as any)?.url];\n const url = candidates.find((value) => typeof value === 'string' && value !== '');\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 { 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 /**\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 // 选区浮动工具栏:默认 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 console.error('[CoreEditor] getHTML() failed:', err);\n // 输出 schema 中所有 node 和 mark 类型,帮助定位缺少 toDOM 的类型\n console.error('[CoreEditor] Schema nodes:', Object.keys(this.editor.schema.nodes));\n console.error('[CoreEditor] Schema marks:', Object.keys(this.editor.schema.marks));\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 // 图片:必须先于链接,否则 ![alt](src) 会被下面的链接规则从 ! 之后开始匹配,吞成一个 <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}](${src})` : alt;\n });\n\n // 代码\n md = md.replace(/<code[^>]*>(.+?)<\\/code>/gi, '`$1`');\n md = md.replace(/<pre[^>]*><code[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi, '```\\n$1\\n```\\n\\n');\n\n // 段落和换行\n md = md.replace(/<br\\s*\\/?>/gi, '\\n');\n md = md.replace(/<p[^>]*>([\\s\\S]*?)<\\/p>/gi, '$1\\n\\n');\n\n // 移除剩余标签\n md = md.replace(/<[^>]+>/g, '');\n\n // 解码 HTML 实体\n md = md.replace(/&amp;/g, '&');\n md = md.replace(/&lt;/g, '<');\n md = md.replace(/&gt;/g, '>');\n md = md.replace(/&quot;/g, '\"');\n md = md.replace(/&#39;/g, \"'\");\n\n // 清理多余空行\n md = md.replace(/\\n{3,}/g, '\\n\\n');\n\n return md.trim();\n}\n","/**\n * MarkdownService - Markdown 解析/序列化服务\n */\nimport {\n elementFromString,\n getHTMLFromFragment,\n type Editor,\n type JSONContent,\n} from '@tiptap/core';\nimport { DOMParser as ProseMirrorDOMParser, Node as ProseMirrorNode } from '@tiptap/pm/model';\nimport type { MarkdownManager } from '@tiptap/markdown';\nimport type { AtriMarkdownConfig } from '../types';\nimport { simpleMarkdownToHtml, simpleHtmlToMarkdown } from '../utils/markdown';\n\nexport class MarkdownService {\n private editor: Editor;\n private config: AtriMarkdownConfig;\n\n constructor(editor: Editor, config: AtriMarkdownConfig = {}) {\n this.editor = editor;\n this.config = {\n enabled: true,\n ...config,\n };\n }\n\n /**\n * Tiptap Markdown 序列化器,未注册 Markdown 扩展时为 null\n */\n private get manager(): MarkdownManager | null {\n return this.editor.markdown ?? null;\n }\n\n /**\n * 获取编辑器内容为 Markdown\n * 自定义节点的输出由其 renderMarkdown 钩子负责(见 ExtensionManager.registerNodeView),\n * 因此这里始终走 Tiptap 序列化器,不对整篇文档做额外替换。\n */\n getMarkdown(): string {\n const manager = this.manager;\n if (!manager) {\n return simpleHtmlToMarkdown(this.editor.getHTML());\n }\n return manager.serialize(this.editor.getJSON());\n }\n\n /**\n * 设置 Markdown 内容\n */\n setMarkdown(content: string, emitUpdate = true): void {\n if (this.manager) {\n this.editor.commands.setContent(content, { contentType: 'markdown', emitUpdate });\n return;\n }\n this.editor.commands.setContent(simpleMarkdownToHtml(content), { emitUpdate });\n }\n\n /**\n * Markdown 转 HTML\n * 与 htmlToMarkdown 方向对称:解析为 JSON 后再按当前 schema 序列化为 HTML\n */\n markdownToHTML(markdown: string): string {\n const manager = this.manager;\n if (!manager) {\n return simpleMarkdownToHtml(markdown);\n }\n const { schema } = this.editor;\n return getHTMLFromFragment(\n ProseMirrorNode.fromJSON(schema, manager.parse(markdown)).content,\n schema\n );\n }\n\n /**\n * HTML 转 Markdown\n */\n htmlToMarkdown(html: string): string {\n const manager = this.manager;\n if (!manager) {\n return simpleHtmlToMarkdown(html);\n }\n const parsed = ProseMirrorDOMParser.fromSchema(this.editor.schema).parse(\n elementFromString(html)\n );\n return manager.serialize(parsed.toJSON());\n }\n\n /**\n * Markdown 转 JSON\n */\n markdownToJSON(markdown: string): JSONContent {\n const manager = this.manager;\n if (manager) {\n return manager.parse(markdown);\n }\n // 降级:返回简单结构\n return {\n type: 'doc',\n content: [\n {\n type: 'paragraph',\n content: [{ type: 'text', text: markdown }],\n },\n ],\n };\n }\n\n /**\n * 是否启用\n */\n isEnabled(): boolean {\n return this.config.enabled !== false;\n }\n}\n","/**\n * ThemeManager - 主题管理\n */\n\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 * 应用当前主题\n */\n private applyTheme(): void {\n // 移除旧主题类\n this.container.classList.remove('atri-theme-light', 'atri-theme-dark');\n\n // 添加新主题类\n if (this.currentTheme === 'light' || this.currentTheme === 'dark') {\n this.container.classList.add(`atri-theme-${this.currentTheme}`);\n } else {\n // 自定义主题\n this.container.classList.add(`atri-theme-${this.currentTheme}`);\n }\n\n // 设置 data 属性\n this.container.setAttribute('data-atri-theme', this.currentTheme);\n }\n\n /**\n * 获取当前主题\n */\n getTheme(): ThemeType {\n return 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 * 是否为暗色主题\n */\n isDark(): boolean {\n return this.currentTheme === 'dark';\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 * 切换语言\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 * ExtensionManager - 扩展注册管理\n */\nimport { Node, mergeAttributes } from '@tiptap/core';\nimport type { AnyExtension } from '@tiptap/core';\nimport type { AtriExtensionMeta, 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, _meta?: AtriExtensionMeta): 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 * 批量注册扩展\n */\n registerAll(extensions: AnyExtension[]): void {\n extensions.forEach((ext) => this.register(ext));\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 // 注册到扩展管理器\n this.register(nodeExtension, {\n name: config.name,\n version: '1.0.0',\n category: 'custom',\n description: config.description,\n });\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 unregister(name: string): boolean {\n this.nodeViews.delete(name);\n return this.extensions.delete(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 * 清空所有扩展\n */\n clear(): void {\n this.extensions.clear();\n this.nodeViews.clear();\n }\n}\n","/**\n * InsertPanel - 工具栏下方的插入浮层\n *\n * 挂在 document.body 上并用 fixed 定位:工具栏那一层链路上任何一处 overflow 都会把浮层裁掉。\n */\nimport type { Editor } from '@tiptap/core';\nimport { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';\nimport type { I18nManager } from '../core/I18nManager';\nimport type { MediaKind } from '../types';\nimport type { MediaRuntime } from './MediaRuntime';\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\nfunction filesOf(transfer: DataTransfer | null | undefined): File[] {\n if (!transfer) return [];\n return Array.from(transfer.files);\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 stopAutoUpdate: (() => void) | 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.stopAutoUpdate?.();\n this.stopAutoUpdate = 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 const translated = this.i18n?.t(key);\n // 未注入 i18n 或词条缺失时回退到内置文案,与工具栏 tooltip 同一套规矩\n return translated && translated !== key ? translated : fallback;\n }\n\n /**\n * 登记一处随语言重画的文案:input 画到 placeholder,其他画成文字\n */\n private label(el: HTMLElement, key: string, fallback: string): void {\n this.labelled.push({ el, key, fallback });\n this.applyLabel({ el, key, fallback });\n }\n\n private applyLabels(): void {\n this.labelled.forEach((entry) => this.applyLabel(entry));\n }\n\n private applyLabel({ el, key, fallback }: LabelledElement): void {\n const text = this.t(key, fallback);\n if (el instanceof HTMLInputElement) el.placeholder = text;\n else el.textContent = text;\n }\n\n private build(mode: InsertPanelMode): void {\n const fields = FIELDS[mode];\n const panel = document.createElement('div');\n panel.className = 'atri-media-panel';\n panel.setAttribute('role', 'dialog');\n panel.setAttribute(\n 'aria-label',\n this.t(`editor.${mode === 'image' ? 'image' : 'attachment'}`, '插入')\n );\n\n let urlInput: HTMLInputElement | null = null;\n let altInput: HTMLInputElement | null = null;\n\n if (fields.url) {\n urlInput = document.createElement('input');\n urlInput.type = 'url';\n urlInput.className = 'atri-media-panel-url';\n this.label(urlInput, 'media.urlPlaceholder', '图片地址(https://…)');\n urlInput.addEventListener('keydown', (event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n this.insertFromUrl(urlInput, altInput);\n }\n });\n panel.appendChild(urlInput);\n }\n\n if (fields.alt) {\n altInput = document.createElement('input');\n altInput.type = 'text';\n altInput.className = 'atri-media-panel-alt';\n this.label(altInput, 'media.altPlaceholder', '替代文字');\n panel.appendChild(altInput);\n }\n\n const fileInput = document.createElement('input');\n fileInput.type = 'file';\n fileInput.className = 'atri-media-panel-file';\n fileInput.hidden = true;\n fileInput.multiple = true;\n const accept = this.runtime.acceptFor(fields.kind);\n if (accept) fileInput.accept = accept;\n fileInput.addEventListener('change', () => {\n this.upload(Array.from(fileInput.files ?? []), fields.kind);\n // 同一个文件连选两次也要还能再触发 change\n fileInput.value = '';\n });\n panel.appendChild(fileInput);\n\n const drop = document.createElement('div');\n drop.className = 'atri-media-panel-drop';\n this.label(drop, 'media.dropHere', '或将文件拖到这里');\n drop.addEventListener('dragover', (event) => event.preventDefault());\n drop.addEventListener('drop', (event) => {\n // 不挡下默认行为,整个页面会跳去那个文件\n event.preventDefault();\n this.upload(filesOf((event as DragEvent).dataTransfer), fields.kind);\n });\n\n const browse = document.createElement('button');\n browse.type = 'button';\n browse.className = 'atri-media-panel-browse';\n this.label(browse, 'media.browse', '选择文件');\n browse.addEventListener('click', () => fileInput.click());\n drop.appendChild(browse);\n panel.appendChild(drop);\n\n let insert: HTMLButtonElement | null = null;\n if (fields.url) {\n insert = document.createElement('button');\n insert.type = 'button';\n insert.className = 'atri-media-panel-insert';\n this.label(insert, 'media.insert', '插入');\n insert.addEventListener('click', () => this.insertFromUrl(urlInput, altInput));\n panel.appendChild(insert);\n }\n\n document.body.appendChild(panel);\n this.element = panel;\n this.applyTheme(panel);\n\n // 焦点落进面板:图片先落在地址栏,附件没有输入框就落在选择文件按钮上\n (urlInput ?? altInput ?? browse).focus();\n }\n\n /**\n * 主题变量写在 .atri-editor 上,浮层挂在 body 上够不着,只能把主题类复制一份\n */\n private applyTheme(panel: HTMLElement): void {\n const root = this.editor.view.dom.closest('.atri-editor');\n const themes = Array.from(root?.classList ?? []).filter((name) =>\n name.startsWith('atri-theme-')\n );\n panel.classList.add(...themes);\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 async position(anchor: HTMLElement): Promise<void> {\n const panel = this.element;\n if (!panel) return;\n\n const update = async () => {\n if (this.element !== panel) return;\n try {\n const { x, y } = await computePosition(anchor, panel, {\n placement: 'bottom-start',\n middleware: [offset(6), flip(), shift({ padding: 8 })],\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 } catch {\n // 量不到也要让面板看得见:宁可位置不理想,也不能点了没反应\n }\n };\n\n await update();\n if (this.element === panel) {\n this.stopAutoUpdate = autoUpdate(anchor, panel, () => void update());\n }\n }\n\n /**\n * 点外面与 Escape 关闭;锚点按钮交给 toggle,否则关了又立刻被这次点击重开\n */\n private listenForDismiss(anchor: HTMLElement): void {\n const onPointerDown = (event: Event) => {\n const target = event.target as Node | null;\n if (!target || this.element?.contains(target) || anchor.contains(target)) return;\n this.close();\n };\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n event.preventDefault();\n event.stopPropagation();\n this.close();\n };\n\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('keydown', onKeyDown, true);\n this.detachDismiss = () => {\n document.removeEventListener('pointerdown', onPointerDown, true);\n document.removeEventListener('keydown', onKeyDown, true);\n };\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 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 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 { 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 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\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 * 创建工具栏 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('insertImage')\n ? [['insertImage', 'insertAttachment', 'attachmentDisplay']]\n : []),\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 // 确保 SVG 图标有正确的尺寸\n const svg = button.querySelector('svg');\n if (svg) {\n svg.setAttribute('width', '18');\n svg.setAttribute('height', '18');\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 const translated = key ? this.i18n?.t(key) : undefined;\n // 未注入 i18n 或词条缺失时回退到定义里的文案\n const title = custom ?? (translated && translated !== key ? translated : 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 // 删除作用于选中的整节点:一段文字该交给 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.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 const translated = i18n?.t(key, options);\n return translated && translated !== key ? translated : fallback;\n };\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 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 as any, 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 * 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 */\nexport function isBrowser(): boolean {\n return typeof window !== 'undefined' && typeof document !== 'undefined';\n}\n","/**\n * AtriEditor - 编辑器主类\n */\nimport type { Editor } from '@tiptap/core';\nimport { getTextBetween } from '@tiptap/core';\nimport type {\n AtriEditorOptions,\n IAtriEditor,\n InsertAttachmentOptions,\n InsertImageOptions,\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 { 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';\n\nexport class AtriEditor implements IAtriEditor {\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 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 let toolbarContainer: HTMLDivElement | null = null;\n if (options.toolbar !== false) {\n toolbarContainer = createContainer('atri-editor-toolbar');\n this.container.appendChild(toolbarContainer);\n }\n const bubbleElement = this.ensureBubbleElement(options.toolbar);\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 // 初始化核心编辑器\n this.coreEditor = new CoreEditor({\n element: editorElement,\n content: options.content,\n contentFormat: options.contentFormat,\n editable: options.editable,\n placeholder: options.placeholder,\n extensions: [...(options.extensions || []), ...this.extensionManager.getAll()],\n markdown: options.markdown,\n media: options.media,\n mediaRuntime: this.mediaRuntime ?? undefined,\n bubbleElement,\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 // 初始化 Markdown 服务\n this.markdownService = new MarkdownService(this.editor, options.markdown);\n\n // 初始化工具栏\n if (toolbarContainer && options.toolbar !== false) {\n this.toolbarManager = new ToolbarManager(\n this.editor,\n toolbarContainer,\n options.toolbar,\n this.i18nManager,\n this.mediaRuntime\n );\n if (bubbleElement) this.toolbarManager.attachBubbleToolbar(bubbleElement);\n }\n\n // 初始化 AI 服务\n if (options.ai) {\n this.initAI(options.ai);\n }\n }\n\n /** 开了 bubble 才建元素,且故意不插进文档:挂载与定位全归 BubbleMenu 插件管 */\n private ensureBubbleElement(toolbar: AtriEditorOptions['toolbar']): HTMLDivElement | null {\n if (toolbar === false || !toolbar?.bubble) 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 '/',\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 * 重新创建编辑器(用于动态注册 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 toolbarContainer = this.container.querySelector('.atri-editor-toolbar') as HTMLElement;\n\n // 创建新的编辑区域\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 const bubbleElement = this.ensureBubbleElement(this.options.toolbar);\n\n // 重新创建核心编辑器\n this.coreEditor = new CoreEditor({\n element: newEditorElement,\n content: currentContent,\n // 当前内容取自 getHTML(),重建时按 html 回填,不能沿用用户的 contentFormat\n contentFormat: 'html',\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 mediaRuntime: this.mediaRuntime ?? undefined,\n bubbleElement,\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 // 重新初始化 Markdown 服务\n this.markdownService = new MarkdownService(this.editor, this.options.markdown);\n\n // 重新初始化工具栏\n if (toolbarContainer && this.options.toolbar !== false) {\n this.toolbarManager = new ToolbarManager(\n this.editor,\n toolbarContainer,\n this.options.toolbar,\n this.i18nManager,\n this.mediaRuntime\n );\n if (bubbleElement) this.toolbarManager.attachBubbleToolbar(bubbleElement);\n }\n\n // 重新初始化 AI 服务\n if (this.options.ai) {\n this.initAI(this.options.ai);\n }\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): object {\n return this.markdownService.markdownToJSON(markdown);\n }\n\n /**\n * 获取选中文本\n */\n getSelectedText(): string {\n const { selection } = this.editor.state;\n if (selection.empty) return '';\n return getTextBetween(this.editor.state.doc, {\n from: selection.from,\n to: selection.to,\n });\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 * 走上传管线插入本地文件\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: 'light' | 'dark' | string): 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 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 as 'light' | 'dark');\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 as 'light' | 'dark';\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 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":["markdownPattern","filesOf","_b","_a","markdown","ProseMirrorNode","ProseMirrorDOMParser","simpleMarkdownToHtml"],"mappings":";;;;;;;;;;;AASO,SAAS,kBAAkB,WAA6D;AAC7F,MACE,qBAAqB,kBACpB,UAAU,KAAK,KAAK,SAAS,WAC5B,UAAU,KAAK,KAAK,SAAS,gBAC7B,UAAU,KAAK,KAAK,SAAS,mBAC/B;AACA,WAAO,UAAU;AAAA,EACnB;AAEA,SAAO,EAAE,MAAM,UAAU,MAAM,IAAI,UAAU,GAAA;AAC/C;ACRO,MAAM,YAAY,MAAM,OAAO;AAAA,EACpC,gBAAgB;;AACd,WAAO;AAAA,MACL,IAAG,UAAK,WAAL;AAAA;AAAA,MAEH,QAAQ;AAAA,QACN,SAAS;AAAA,QACT,WAAW,MAAM;AAAA,QACjB,YAAY,CAAC,eACX,WAAW,SAAS,EAAE,2BAA2B,WAAW,WAAW,CAAA;AAAA,MAAC;AAAA,MAE5E,UAAU;AAAA,QACR,SAAS;AAAA,QACT,WAAW,MAAM;AAAA,QACjB,UAAU;AAAA,MAAA;AAAA,IACZ;AAAA,EAEJ;AAAA,EAEA,cAAc;;AACZ,WAAO;AAAA,MACL,IAAG,UAAK,WAAL;AAAA;AAAA;AAAA,MAGH,UACE,CAAC,YACD,CAAC,EAAE,OAAO,SAAA,MACR,SAAS,gBAAgB,kBAAkB,MAAM,SAAS,GAAG;AAAA,QAC3D,MAAM,KAAK;AAAA,QACX,OAAO;AAAA,MAAA,CACR;AAAA,IAAA;AAAA,EAET;AACF,CAAC;ACzCD,MAAM,QAAQ,CAAC,KAAK,MAAM,MAAM,MAAM,IAAI;AAC1C,MAAM,cAAc;AAMb,SAAS,eAAe,OAAuB;AACpD,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,EAAG,QAAO;AACjD,MAAI,QAAQ,YAAa,QAAO,GAAG,KAAK,MAAM,KAAK,CAAC;AAEpD,MAAI,QAAQ;AACZ,MAAI,OAAO;AACX,SAAO,SAAS,eAAe,OAAO,MAAM,SAAS,GAAG;AACtD,aAAS;AACT,YAAQ;AAAA,EACV;AAEA,QAAM,QAAQ,OAAO,UAAU,KAAK,IAAI,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC;AACvE,SAAO,GAAG,KAAK,IAAI,MAAM,IAAI,CAAC;AAChC;AAMO,SAAS,cAAc,OAA2C;AACvE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,QAAQ,sCAAsC,KAAK,KAAK;AAC9D,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,QAAQ,OAAO,MAAM,CAAC,CAAC;AAC7B,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AAEpC,QAAM,OAAO,MAAM,QAAQ,MAAM,CAAC,EAAE,aAAuC;AAC3E,SAAO,KAAK,MAAM,QAAQ,eAAe,IAAI;AAC/C;AAGO,MAAM,wBAAwB,KAAK,eAAe;AAGlD,MAAM,oBAAoB;AAG1B,SAAS,cAAc,MAAY,QAAqC;AAC7E,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,YAAY,MAAM,QAAQ,MAAM,IAAI,SAAS,OAAO,MAAM,GAAG,GAChE,IAAI,CAAC,YAAY,QAAQ,KAAA,EAAO,aAAa,EAC7C,OAAO,OAAO;AAEjB,MAAI,CAAC,SAAS,OAAQ,QAAO;AAE7B,QAAM,OAAO,KAAK,KAAK,YAAA;AACvB,QAAM,QAAQ,KAAK,QAAQ,IAAI,YAAA;AAE/B,SAAO,SAAS,KAAK,CAAC,YAAY;AAChC,QAAI,QAAQ,WAAW,GAAG,EAAG,QAAO,KAAK,SAAS,OAAO;AACzD,QAAI,QAAQ,SAAS,IAAI,EAAG,QAAO,KAAK,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;AACvE,WAAO,SAAS;AAAA,EAClB,CAAC;AACH;AAGO,SAAS,gBAAgB,QAAgD;AAC9E,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,YAAY,MAAM,QAAQ,MAAM,IAAI,SAAS,OAAO,MAAM,GAAG,GAChE,IAAI,CAAC,YAAY,QAAQ,MAAM,EAC/B,OAAO,OAAO;AAEjB,SAAO,SAAS,SAAS,SAAS,KAAK,GAAG,IAAI;AAChD;AAIO,SAAS,aACd,MACA,SACgB;AAChB,QAAM,cAAc,QAAQ,eAAe;AAE3C,MAAI,KAAK,OAAO,YAAa,QAAO,EAAE,IAAI,OAAO,QAAQ,YAAA;AACzD,MAAI,CAAC,cAAc,MAAM,QAAQ,MAAM,EAAG,QAAO,EAAE,IAAI,OAAO,QAAQ,mBAAA;AAEtE,SAAO,EAAE,IAAI,KAAA;AACf;AAGO,SAAS,YAAY,MAAqB;AAC/C,UAAQ,KAAK,QAAQ,IAAI,YAAA,EAAc,WAAW,QAAQ;AAC5D;ACnFO,SAAS,iBAAiB,MAAuB;AACtD,SAAO,SAAS,gBAAgB,SAAS;AAC3C;AAIO,SAAS,kBAAkB,IAAiB,MAAc,UAAwB;AACvF,MAAI,OAAO;AACX,MAAI,WAAW,OAAO;AACtB,KAAG,IAAI,YAAY,CAAC,MAAM,QAAQ;AAChC,QAAI,KAAK,KAAK,SAAS,SAAU;AACjC,UAAM,OAAO,KAAK,IAAI,MAAM,IAAI;AAChC,QAAI,OAAO,UAAU;AACnB,iBAAW;AACX,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACD,MAAI,OAAO,EAAG;AACd,MAAI;AACF,OAAG,aAAa,cAAc,OAAO,GAAG,KAAK,IAAI,CAAC;AAAA,EACpD,QAAQ;AAAA,EAER;AACF;AAQO,SAAS,wBACd,OACA,UACA,SACS;AACT,QAAM,EAAE,OAAO,UAAU,IAAI,aAAa;AAC1C,QAAM,SAAS,kBAAkB,MAAM,SAAS;AAChD,QAAM,SAAS,OAAO,WAAW,WAAW,SAAS,OAAO;AAE5D,MACE,CAAC,SAAS,gBAAgB,QAAQ;AAAA,IAChC,MAAM;AAAA,IACN,OAAO;AAAA,MACL,KAAK,QAAQ;AAAA,MACb,MAAM,QAAQ,QAAQ;AAAA,MACtB,MAAM,QAAQ,QAAQ;AAAA,MACtB,MAAM,QAAQ,QAAQ;AAAA,IAAA;AAAA,EACxB,CACD,GACD;AACA,WAAO;AAAA,EACT;AAEA,MAAI,SAAU,mBAAkB,IAAI,QAAQ,QAAQ;AACpD,SAAO;AACT;AAGO,SAAS,SACd,MACA,OACwB;AACxB,SAAO,UAAU,QAAQ,UAAU,UAAa,UAAU,KAAK,CAAA,IAAK,EAAE,CAAC,IAAI,GAAG,OAAO,KAAK,EAAA;AAC5F;AAGO,SAAS,cAAc,UAA6D;AACzF,MAAI,OAAO,aAAa,YAAY,CAAC,OAAO,SAAS,QAAQ,EAAG,QAAO,CAAA;AACvE,QAAM,UAAU,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,CAAC,CAAC;AAC/D,SAAO;AAAA,IACL,6BAA6B,OAAO,OAAO;AAAA,IAC3C,OAAO,2BAA2B,OAAO;AAAA,EAAA;AAE7C;AAGO,SAAS,gBAAgB,MAAe,KAAsB;AACnE,SACG,QACD,OAAO,OAAO,EAAE,EACb,MAAM,GAAG,EACT,IAAA,KACH;AAEJ;AAEO,SAAS,uBAAkD;AAChE,SAAO;AAAA,IACL,KAAK;AAAA,MACH,SAAS;AAAA,MACT,WAAW,CAAC,YAAY,QAAQ,aAAa,UAAU;AAAA,MACvD,YAAY,CAAC,eAAe,SAAS,YAAY,WAAW,GAAG;AAAA,IAAA;AAAA,IAEjE,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,WAAW,CAAC,YAAY,QAAQ,aAAa,WAAW;AAAA,MACxD,YAAY,CAAC,eAAe,SAAS,aAAa,WAAW,IAAI;AAAA,IAAA;AAAA,IAEnE,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,WAAW,CAAC,YAAY;AACtB,cAAM,MAAM,QAAQ,aAAa,WAAW;AAC5C,cAAM,SAAS,QAAQ,OAAO,OAAO,MAAM,OAAO,GAAG;AACrD,eAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAAA,MAC5C;AAAA,MACA,YAAY,CAAC,eAAe,SAAS,aAAa,WAAW,IAAI;AAAA,IAAA;AAAA,IAEnE,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,WAAW,CAAC,YAAY,QAAQ,aAAa,WAAW;AAAA,MACxD,YAAY,CAAC,eAAe,SAAS,aAAa,WAAW,IAAI;AAAA,IAAA;AAAA;AAAA;AAAA,IAInE,QAAQ;AAAA,MACN,SAAS;AAAA,MACT,WAAW,MAAM;AAAA,MACjB,YAAY,CAAC,eAAe,SAAS,2BAA2B,WAAW,MAAM;AAAA,IAAA;AAAA;AAAA;AAAA,IAInF,UAAU;AAAA,MACR,SAAS;AAAA,MACT,WAAW,MAAM;AAAA,MACjB,YAAY,CAAC,eAAe,cAAc,WAAW,QAAQ;AAAA,IAAA;AAAA,IAE/D,UAAU;AAAA,MACR,SAAS;AAAA,MACT,WAAW,MAAM;AAAA,MACjB,UAAU;AAAA,IAAA;AAAA,EACZ;AAEJ;AC3GA,MAAMA,oBAAkB;AAExB,MAAM,aAA6E;AAAA,EACjF,MAAM;AAAA,EACN,MAAM;AACR;AAGA,SAAS,iBAAiB,OAA0D;AAClF,QAAM,SAAS,CAAC,SAAmB,iBAAiB,KAAK,IAAI;AAE7D,QAAM,EAAE,cAAc;AACtB,MAAI,qBAAqB,eAAe;AACtC,WAAO,OAAO,UAAU,KAAK,IAAI,IAAI,EAAE,MAAM,UAAU,MAAM,KAAK,UAAU,KAAA,IAAS;AAAA,EACvF;AACA,MAAI,EAAE,WAAW,WAAY,QAAO;AAEpC,QAAM,QAAQ,UAAU,MAAM;AAC9B,MAAI,SAAS,OAAO,MAAM,IAAI,EAAG,QAAO,EAAE,MAAM,OAAO,KAAK,UAAU,KAAA;AACtE,QAAM,SAAS,UAAU,MAAM;AAC/B,MAAI,UAAU,OAAO,OAAO,IAAI,EAAG,QAAO,EAAE,MAAM,QAAQ,KAAK,UAAU,OAAO,OAAO,SAAA;AACvF,SAAO;AACT;AAEA,SAAS,wBACP,OACA,UACA,SACS;;AACT,QAAM,UAAU,iBAAiB,KAAK;AACtC,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,WAAW,WAAW,OAAO;AACnC,MAAI,QAAQ,KAAK,KAAK,SAAS,SAAU,QAAO;AAEhD,QAAM,SAAS,MAAM,OAAO,MAAM,QAAQ;AAC1C,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,CAAC,SAAU,QAAO;AAItB,QAAM,OAAO,OAAO,OAAO,QAAQ,KAAK,KAAK;AAC7C,QAAM,KAAK,MAAM,GAAG,iBAAiB,QAAQ,KAAK,QAAQ,MAAM,QAAQ,KAAK,UAAU,IAAI;AAI3F,MAAI,SAAS,GAAG,QAAQ,IAAI,QAAQ,KAAK,CAAC;AAC1C,QAAI,QAAG,IAAI,OAAO,MAAM,MAApB,mBAAuB,KAAK,UAAS,UAAU;AACjD,aAAS;AACT,OAAG,IAAI,YAAY,CAAC,GAAG,QAAQ;AAC7B,UACE,SAAS,KACT,EAAE,KAAK,SAAS,YAChB,EAAE,MAAM,QAAQ,QAAQ,KAAK,MAAM,OACnC,EAAE,MAAM,aAAa,QAAQ,KAAK,MAAM,UACxC;AACA,iBAAS;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,UAAU,KAAK,OAAO,KAAK,eAAe,OAAO;AACnD,UAAM,gBAAgB,cAAc,OAAO,GAAG,KAAK,MAAM;AACzD,OAAG,aAAa,aAAa;AAAA,EAC/B;AAEA,WAAS,EAAE;AACX,SAAO;AACT;AAEO,MAAM,aAAa,KAAK,OAAO;AAAA,EACpC,MAAM;AAAA,EAEN,OAAO;AAAA,EAEP,MAAM;AAAA,EAEN,YAAY;AAAA,EAEZ,WAAW;AAAA,EAEX,gBAAgB;AACd,WAAO,qBAAA;AAAA,EACT;AAAA,EAEA,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,6BAA6B;AAAA,EAC9C;AAAA,EAEA,WAAW,EAAE,MAAM,kBAAkB;AACnC,UAAM,EAAE,KAAK,MAAM,MAAM,OAAA,IAAW,KAAK;AACzC,UAAM,QAAQ,gBAAgB,MAAM,GAAG;AAEvC,UAAM,WAA4B;AAAA,MAChC,CAAC,QAAQ,EAAE,OAAO,wBAAwB,eAAe,QAAQ;AAAA,IAAA;AAGnE,UAAM,OAAwB;AAAA,MAC5B,CAAC,KAAK,EAAE,OAAO,wBAAwB,MAAM,KAAK,KAAK,sBAAA,GAAyB,KAAK;AAAA,IAAA;AAEvF,QAAI,MAAM;AACR,WAAK,KAAK,CAAC,QAAQ,EAAE,OAAO,0BAA0B,eAAe,IAAI,CAAC,CAAC;AAAA,IAC7E;AAEA,aAAS,KAAK,CAAC,OAAO,EAAE,OAAO,uBAAA,GAA0B,GAAG,IAAI,CAAC;AAIjE,QAAI,QAAQ;AACV,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,EAAE,OAAO,2BAAA;AAAA,QACT,CAAC,QAAQ,EAAE,OAAO,gCAAgC;AAAA,MAAA,CACnD;AAAA,IACH;AAEA,WAAO;AAAA,MACL;AAAA,MACA,gBAAgB,gBAAgB,EAAE,wBAAwB,IAAI,OAAO,mBAAmB;AAAA,MACxF,GAAG;AAAA,IAAA;AAAA,EAEP;AAAA,EAEA,cAAc;AACZ,WAAO;AAAA,MACL,eAAe,CAAC,YAAY,CAAC,UAAU,wBAAwB,OAAO,KAAK,MAAM,OAAO;AAAA,MAExF,sBACE,CAAC,YACD,CAAC,EAAE,OAAO,eACR,wBAAwB,OAAO,UAAU,QAAQ,OAAO;AAAA,MAE5D,yBACE,MACA,CAAC,EAAE,OAAO,eAAe;AACvB,cAAM,UAAU,iBAAiB,KAAK;AACtC,YAAI,CAAC,QAAS,QAAO;AACrB,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,QAAQ,KAAK,KAAK,SAAS,eAAe,SAAS;AAAA,QAAA;AAAA,MAEvD;AAAA,IAAA;AAAA,EAEN;AAAA,EAEA,wBAAwB;AACtB,UAAM,WAAW,CAAC,UAAiB;AACjC,YAAM,SAAS,MAAM,kBAAkB,UAAU,MAAM,SAAS;AAChE,YAAM,MAAM,iCAAQ,QAAQ;AAC5B,UAAI,CAAC,IAAK,QAAO;AAEjB,YAAM,OAAO,KAAK,OAAO;AACzB,YAAM,MAAM,KAAK,SAAS,KAAoB,CAAC;AAC/C,YAAM,OAAO,KAAK,MAAM,IAAI,OAAO,GAAG;AACtC,aAAO,QAAQ,iBAAiB,KAAK,KAAK,IAAI,IAAI,EAAE,KAAK,KAAK,KAAA,IAAS;AAAA,IACzE;AAIA,QAAI,eAA0D;AAE9D,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,QACT,KAAK,IAAI,UAAU,yBAAyB;AAAA,QAC5C,OAAO;AAAA,UACL,iBAAiB;AAAA;AAAA;AAAA,YAGf,WAAW,CAAC,MAAM,UAAU;AAC1B,oBAAM,MAAM,SAAS,KAAK;AAC1B,kBAAI,CAAC,KAAK;AACR,+BAAe;AACf,uBAAO;AAAA,cACT;AAEA,oBAAM,MAAM,KAAK,MAAM;AACvB,kBAAI,eAAe,iBAAiB,IAAI,SAAS,IAAI,KAAK;AAGxD,+BAAe,EAAE,KAAK,IAAI,KAAK,UAAU,KAAA;AACzC,uBAAO;AAAA,cACT;AAEA,oBAAM,eAAA;AACN,6BAAe,EAAE,KAAK,IAAI,KAAK,UAAU,MAAA;AACzC,mBAAK;AAAA,gBACH,KAAK,MAAM,GAAG,aAAa,cAAc,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC;AAAA,cAAA;AAE1E,qBAAO;AAAA,YACT;AAAA;AAAA;AAAA;AAAA;AAAA,YAKA,OAAO,CAAC,OAAO,UAAU;AACvB,oBAAM,MAAM,SAAS,KAAK;AAC1B,oBAAM,UAAU;AAChB,6BAAe;AACf,kBAAI,CAAC,OAAO,CAAC,WAAW,QAAQ,QAAQ,IAAI,IAAK,QAAO;AAExD,oBAAM,WAAW,MAAM,kBAAkB,WAAW,CAAC,CAAC,MAAM,OAAO,QAAQ,SAAS;AACpF,kBAAI,QAAQ,aAAa,YAAY,IAAI,KAAK,KAAK,SAAS,mBAAmB;AAC7E,uBAAO;AAAA,cACT;AAEA,oBAAM,eAAA;AACN,qBAAO;AAAA,YACT;AAAA,UAAA;AAAA,QACF;AAAA,MACF,CACD;AAAA,IAAA;AAAA,EAEL;AAAA,EAEA,mBAAmB;AAAA,EAEnB,mBAAmB;AAAA,IACjB,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO,CAAC,QAAQ,IAAI,QAAQ,QAAQ;AAAA,IACpC,UAAU,CAAC,QAAmC;AAC5C,YAAM,QAAQA,kBAAgB,KAAK,GAAG;AACtC,UAAI,CAAC,MAAO,QAAO;AAEnB,aAAO;AAAA,QACL,MAAM;AAAA,QACN,KAAK,MAAM,CAAC;AAAA,QACZ,MAAM,MAAM,CAAC,KAAK;AAAA,QAClB,MAAM,MAAM,CAAC,KAAK;AAAA,QAClB,OAAO,MAAM,CAAC;AAAA,MAAA;AAAA,IAElB;AAAA,EAAA;AAAA,EAGF,eAAe,CAAC,OAAO,YACrB,QAAQ;AAAA,IACN;AAAA,IACA;AAAA,MACE,KAAK,MAAM;AAAA,MACX,MAAM,MAAM,QAAQ;AAAA,MACpB,MAAM,cAAc,MAAM,KAAK,KAAK;AAAA,MACpC,MAAM;AAAA,IAAA;AAAA,IAER,CAAA;AAAA,EAAC;AAAA,EAGL,gBAAgB,CAAC,SAAS;;AACxB,UAAM,QAAM,UAAK,UAAL,mBAAY,QAAO;AAC/B,UAAM,SAAO,UAAK,UAAL,mBAAY,SAAQ;AACjC,UAAM,QAAO,UAAK,UAAL,mBAAY;AAEzB,WAAO,OAAO,SAAS,IAAI,KAAK,GAAG,KAAK,eAAe,IAAI,CAAC,OAAO,SAAS,IAAI,KAAK,GAAG;AAAA,EAC1F;AACF,CAAC;AC1QD,MAAM,kBAAkB;AAEjB,MAAM,iBAAiB,KAAK,OAAO;AAAA,EACxC,MAAM;AAAA,EAEN,QAAQ;AAAA,EAER,OAAO;AAAA,EAEP,MAAM;AAAA,EAEN,YAAY;AAAA,EAEZ,WAAW;AAAA,EAEX,gBAAgB;AACd,WAAO,qBAAA;AAAA,EACT;AAAA,EAEA,YAAY;AAGV,WAAO;AAAA,MACL;AAAA,QACE,KAAK;AAAA,QACL,UAAU;AAAA;AAAA,QAEV,UAAU,CAAC,YACT,QAAQ,aAAa,UAAU,IAAI,KAAK,EAAE,KAAK,QAAQ,aAAa,MAAM,KAAK,KAAA;AAAA,MAAK;AAAA,IACxF;AAAA,EAEJ;AAAA,EAEA,WAAW,EAAE,MAAM,kBAAkB;AACnC,UAAM,EAAE,KAAK,KAAA,IAAS,KAAK;AAE3B,WAAO;AAAA,MACL;AAAA,MACA,gBAAgB,gBAAgB;AAAA,QAC9B,6BAA6B;AAAA,QAC7B,OAAO;AAAA,QACP,MAAM;AAAA;AAAA;AAAA,QAGN,UAAU;AAAA,QACV,KAAK;AAAA,MAAA,CACN;AAAA,MACD,CAAC,QAAQ,EAAE,OAAO,wBAAwB,eAAe,QAAQ;AAAA,MACjE,gBAAgB,MAAM,GAAG;AAAA,IAAA;AAAA,EAE7B;AAAA,EAEA,cAAc;AACZ,WAAO;AAAA,MACL,mBAAmB,CAAC,YAAY,CAAC,UAAU,wBAAwB,OAAO,KAAK,MAAM,OAAO;AAAA,IAAA;AAAA,EAEhG;AAAA,EAEA,mBAAmB;AAAA,EAEnB,mBAAmB;AAAA,IACjB,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO,CAAC,QAAQ,IAAI,QAAQ,YAAY;AAAA,IACxC,UAAU,CAAC,QAAmC;AAC5C,YAAM,QAAQ,gBAAgB,KAAK,GAAG;AACtC,UAAI,CAAC,MAAO,QAAO;AAEnB,aAAO;AAAA,QACL,MAAM;AAAA,QACN,KAAK,MAAM,CAAC;AAAA,QACZ,MAAM,MAAM,CAAC,KAAK;AAAA,QAClB,MAAM,MAAM,CAAC,KAAK;AAAA,QAClB,OAAO,MAAM,CAAC;AAAA,MAAA;AAAA,IAElB;AAAA,EAAA;AAAA,EAGF,eAAe,CAAC,OAAO,YACrB,QAAQ;AAAA,IACN;AAAA,IACA;AAAA,MACE,KAAK,MAAM;AAAA,MACX,MAAM,MAAM,QAAQ;AAAA,MACpB,MAAM,cAAc,MAAM,KAAK,KAAK;AAAA,MACpC,MAAM;AAAA,IAAA;AAAA,IAER,CAAA;AAAA,EAAC;AAAA,EAGL,gBAAgB,CAAC,SAAS;;AACxB,UAAM,QAAM,UAAK,UAAL,mBAAY,QAAO;AAC/B,UAAM,SAAO,UAAK,UAAL,mBAAY,SAAQ;AACjC,UAAM,QAAO,UAAK,UAAL,mBAAY;AAEzB,WAAO,OACH,aAAa,IAAI,KAAK,GAAG,KAAK,eAAe,IAAI,CAAC,OAClD,aAAa,IAAI,KAAK,GAAG;AAAA,EAC/B;AACF,CAAC;AC7GD,SAASC,UAAQ,UAAmD;AAClE,MAAI,CAAC,SAAU,QAAO,CAAA;AAEtB,SAAO,MAAM,KAAK,SAAS,KAAK;AAClC;AAOA,SAAS,QAAQ,MAAkB,OAAsC;AACvE,QAAM,SAAS,KAAK,YAAY,EAAE,MAAM,MAAM,SAAS,KAAK,MAAM,SAAS;AAC3E,MAAI,CAAC,OAAQ,QAAO;AAGpB,SAAO,OAAO,SAAS,KAAK,OAAO,SAAS,OAAO;AACrD;AAEO,MAAM,aAAa,UAAU,OAA0B;AAAA,EAC5D,MAAM;AAAA,EAEN,aAAa;AACX,WAAO,EAAE,SAAS,KAAA;AAAA,EACpB;AAAA,EAEA,wBAAwB;AACtB,UAAM,UAAU,KAAK,QAAQ;AAC7B,QAAI,CAAC,QAAS,QAAO,CAAA;AAErB,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,QACT,OAAO;AAAA;AAAA,UAEL,aAAa,CAAC,MAAM,UAAU;AAC5B,kBAAM,QAAQA,UAAQ,MAAM,aAAa;AACzC,gBAAI,CAAC,MAAM,OAAQ,QAAO;AAE1B,kBAAM,eAAA;AACN,gBAAI,KAAK,SAAU,MAAK,QAAQ,YAAY,KAAK;AACjD,mBAAO;AAAA,UACT;AAAA,UAEA,YAAY,CAAC,MAAM,UAAU;AAC3B,kBAAM,YAAY;AAClB,kBAAM,QAAQA,UAAQ,UAAU,YAAY;AAC5C,gBAAI,CAAC,MAAM,OAAQ,QAAO;AAG1B,sBAAU,eAAA;AACV,gBAAI,CAAC,KAAK,SAAU,QAAO;AAE3B,iBAAK,QAAQ,YAAY,OAAO,EAAE,KAAK,QAAQ,MAAM,SAAS,GAAG;AACjE,mBAAO;AAAA,UACT;AAAA,QAAA;AAAA,MACF,CACD;AAAA,IAAA;AAAA,EAEL;AACF,CAAC;AC5DD,MAAM,kBAAkB;AAEjB,SAAS,sBACd,OACA,SAC0C;AAC1C,MAAI,UAAU,MAAO,QAAO,CAAA;AAE5B,QAAM,QAAQ,+BAAO;AACrB,QAAM,aAAY,+BAAO,WAAU;AAInC,SAAO;AAAA,IACL,UAAU,UAAU;AAAA,MAClB,SAAQ,+BAAO,WAAU;AAAA;AAAA;AAAA;AAAA,MAIzB,eAAc,+BAAO,gBAAe,YAAW,+BAAO,qBAAoB;AAAA,MAC1E,QAAQ,YACJ;AAAA,QACE,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAW;AAAA,QACX,2BAA2B;AAAA,MAAA,IAE7B;AAAA,IAAA,CACL;AAAA,IACD;AAAA;AAAA,IAEA;AAAA;AAAA,IAEA,WAAW,UAAU,EAAE,SAAS,WAAW,MAAM;AAAA,EAAA;AAErD;ACpCA,SAAS,QAAQ,MAAmC;;AAClD,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAE9C,QAAM,SAAS;AACf,QAAM,aAAa,CAAC,OAAO,KAAK,OAAO,WAAW,YAAO,SAAP,mBAAqB,GAAG;AAC1E,QAAM,MAAM,WAAW,KAAK,CAAC,UAAU,OAAO,UAAU,YAAY,UAAU,EAAE;AAEhF,SAAO;AACT;AAEA,SAAS,kBACP,QACA,MACA,SACuB;AACvB,SAAO,IAAI,QAAsB,CAAC,SAAS,WAAW;;AACpD,UAAM,MAAM,IAAI,eAAA;AAEhB,QAAI,KAAK,QAAQ,OAAO,UAAU,IAAI;AACtC,QAAI,kBAAkB,OAAO,mBAAmB;AAEhD,WAAO,QAAQ,OAAO,WAAW,CAAA,CAAE,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC7D,UAAI,iBAAiB,KAAK,KAAK;AAAA,IACjC,CAAC;AAED,QAAI,OAAO,aAAa,CAAC,UAAU;AACjC,UAAI,CAAC,MAAM,oBAAoB,CAAC,MAAM,MAAO;AAC7C,cAAQ,WAAW;AAAA,QACjB,SAAS,KAAK,MAAO,MAAM,SAAS,MAAM,QAAS,GAAG;AAAA,QACtD,QAAQ,MAAM;AAAA,QACd,OAAO,MAAM;AAAA,MAAA,CACd;AAAA,IACH;AAEA,QAAI,SAAS,MAAM;AACjB,UAAI,IAAI,SAAS,OAAO,IAAI,UAAU,KAAK;AACzC,eAAO,IAAI,MAAM,aAAa,IAAI,MAAM,EAAE,CAAC;AAC3C;AAAA,MACF;AAEA,UAAI,OAAgB;AACpB,UAAI;AACF,eAAO,IAAI,eAAe,KAAK,MAAM,IAAI,YAAY,IAAI;AAAA,MAC3D,QAAQ;AAEN,eAAO,IAAI;AAAA,MACb;AAEA,UAAI,OAAO,iBAAiB;AAC1B,gBAAQ,OAAO,gBAAgB,MAAM,IAAI,CAAC;AAC1C;AAAA,MACF;AAGA,YAAM,MAAM,OAAO,SAAS,WAAW,OAAO,QAAQ,IAAI;AAE1D,UAAI,CAAC,KAAK;AACR,eAAO,IAAI,MAAM,8CAA8C,CAAC;AAChE;AAAA,MACF;AAEA,cAAQ,EAAE,KAAK;AAAA,IACjB;AAEA,QAAI,UAAU,MAAM,OAAO,IAAI,MAAM,WAAW,CAAC;AACjD,QAAI,UAAU,MAAM,OAAO,IAAI,aAAa,WAAW,YAAY,CAAC;AAEpE,QAAI,QAAQ,OAAO,SAAS;AAC1B,UAAI,MAAA;AACJ;AAAA,IACF;AACA,YAAQ,OAAO,iBAAiB,SAAS,MAAM,IAAI,SAAS,EAAE,MAAM,MAAM;AAE1E,UAAM,OAAO,IAAI,SAAA;AACjB,SAAK,OAAO,OAAO,aAAa,QAAQ,QAAM,YAAO,gBAAP,gCAAqB,UAAS,KAAK,IAAI;AACrF,QAAI,KAAK,IAAI;AAAA,EACf,CAAC;AACH;AAEO,SAAS,eAAe,QAA4D;AACzF,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI,OAAO,WAAW,YAAY;AAChC,WAAO,EAAE,QAAQ,CAAC,MAAM,YAAY,OAAO,MAAM,OAAO,EAAA;AAAA,EAC1D;AAEA,MAAI,CAAC,OAAO,SAAU,QAAO;AAE7B,SAAO,EAAE,QAAQ,CAAC,MAAM,YAAY,kBAAkB,QAAQ,MAAM,OAAO,EAAA;AAC7E;ACzCA,SAAS,kBAAkB,MAA6B;AACtD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAS,IAAI,WAAA;AACnB,WAAO,SAAS,MAAM,QAAQ,OAAO,OAAO,MAAM,CAAC;AACnD,WAAO,UAAU,MAAM,OAAO,OAAO,SAAS,IAAI,MAAM,QAAQ,CAAC;AACjE,WAAO,cAAc,IAAI;AAAA,EAC3B,CAAC;AACH;AAEO,MAAM,aAAa;AAAA,EAQxB,YAAY,SAA0B,IAAI;AAL1C,SAAQ,SAAwB;AAChC,SAAQ,4BAAoC,IAAA;AAC5C,SAAQ,gCAAkD,IAAA;AAC1D,SAAQ,WAAW;AAGjB,SAAK,SAAS;AACd,SAAK,WAAW,eAAe,OAAO,MAAM;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,QAAsB;AACzB,SAAK,SAAS;AACd,SAAK,sBAAA;AAIL,WAAO,GAAG,eAAe,CAAC,EAAE,kBAAkB;AAC5C,UAAI,YAAY,cAAc,KAAK,MAAM,WAAW,MAAA;AAAA,IACtD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,OAA0B,UAA8B,IAAmB;;AAC3F,QAAI,CAAC,KAAK,OAAQ;AAElB,UAAM,EAAE,MAAM,IAAA,IAAQ;AACtB,UAAM,WAAW,KAAK,OAAO,YAAY;AACzC,UAAM,QAAsB,CAAA;AAE5B,eAAW,QAAQ,MAAM,KAAK,KAAK,GAAG;AACpC,YAAM,YAAY,SAAS,YAAY,IAAI,IAAI,UAAU;AACzD,YAAM,SACJ,cAAc,WAAU,UAAK,OAAO,UAAZ,mBAAmB,UAAS,UAAK,OAAO,eAAZ,mBAAwB;AAC9E,YAAM,aAAa,aAAa,MAAM,EAAE,QAAQ,aAAa,KAAK,OAAO,aAAa;AAEtF,UAAI,CAAC,WAAW,IAAI;AAClB,aAAK,OAAO,MAAM,WAAW,MAAM;AACnC;AAAA,MACF;AAGA,UAAI,MAAM,UAAU,UAAU;AAC5B,aAAK,OAAO,MAAM,UAAU;AAC5B;AAAA,MACF;AAEA,YAAM,KAAK,EAAE,MAAM,MAAM,WAAW;AAAA,IACtC;AAEA,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,KAAK,aAAa,OAAO,GAAG;AAClC;AAAA,IACF;AAEA,UAAM,QAAQ,MAAM,IAAI,CAAC,EAAE,MAAM,MAAM,UAAA,MAAgB,KAAK,WAAW,MAAM,SAAS,CAAC;AACvF,SAAK;AAAA,MACH,MAAM,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC;AAAA,MACvC;AAAA,IAAA;AAEF,UAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,SAAS,KAAK,YAAY,IAAI,CAAC,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,MAAqC;;AAC7C,WAAO;AAAA,MACL,SAAS,WAAU,UAAK,OAAO,UAAZ,mBAAmB,UAAS,UAAK,OAAO,eAAZ,mBAAwB;AAAA,IAAA;AAAA,EAE3E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAA6B;AAC3B,UAAM,UAAU,MAAM,KAAK,KAAK,MAAM,OAAA,CAAQ,EAC3C,OAAO,CAAC,SAAS,KAAK,WAAW,OAAO,EACxC,IAAI,CAAC,SAAS,KAAK,YAAY,IAAI,CAAC;AAEvC,WAAO,QAAQ,IAAI,OAAO,EAAE,KAAK,MAAM,MAAS;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,oBAA6B;AAC3B,WAAO,MAAM,KAAK,KAAK,MAAM,OAAA,CAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,KAAK,OAAO;AAAA,EACrE;AAAA,EAEA,WAAuB;AACrB,UAAM,QAAQ,MAAM,KAAK,KAAK,MAAM,QAAQ;AAC5C,UAAM,YAAY,MAAM,OAAO,CAAC,SAAS,KAAK,WAAW,WAAW;AACpE,UAAM,QAAQ,UAAU,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,KAAK,MAAM,CAAC;AACrE,UAAM,SAAS,UAAU;AAAA,MACvB,CAAC,KAAK,SAAS,MAAO,KAAK,KAAK,OAAO,aAAa,KAAK,WAAW,CAAC,IAAK;AAAA,MAC1E;AAAA,IAAA;AAGF,WAAO;AAAA,MACL,WAAW,UAAU;AAAA,MACrB,QAAQ,MAAM,OAAO,CAAC,SAAS,KAAK,WAAW,WAAW,CAAC,KAAK,OAAO,EAAE;AAAA,MACzE,QAAQ,MAAM,OAAO,CAAC,SAAS,KAAK,OAAO,EAAE;AAAA,MAC7C,SAAS,QAAQ,KAAK,MAAO,SAAS,QAAS,GAAG,IAAI;AAAA,IAAA;AAAA,EAE1D;AAAA,EAEA,UAAU,UAAmD;AAC3D,SAAK,UAAU,IAAI,QAAQ;AAC3B,aAAS,KAAK,UAAU;AACxB,WAAO,MAAM;AACX,WAAK,UAAU,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,SAAK,MAAM,QAAQ,CAAC,SAAS;AAC3B,WAAK,WAAW,MAAA;AAChB,uBAAiB,IAAI;AAAA,IACvB,CAAC;AACD,SAAK,MAAM,MAAA;AACX,SAAK,UAAU,MAAA;AACf,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,aAAa,SAAuB,KAA6B;;AAC7E,UAAM,WAAsB,CAAA;AAE5B,eAAW,EAAE,MAAM,KAAA,KAAU,SAAS;AACpC,UAAI,SAAS,WAAW,GAAC,UAAK,OAAO,UAAZ,mBAAmB,cAAa;AACvD,aAAK,OAAO,MAAM,WAAW;AAC7B;AAAA,MACF;AAEA,UAAI;AACF,cAAM,MAAM,MAAM,kBAAkB,IAAI;AACxC,iBAAS,KAAK,EAAE,MAAM,SAAS,OAAO,EAAE,KAAK,KAAK,KAAK,KAAA,EAAK,CAAG;AAAA,MACjE,SAAS,OAAO;AACd,aAAK,OAAO,MAAM,iBAAiB,KAAK;AAAA,MAC1C;AAAA,IACF;AAEA,SAAK,eAAe,UAAU,GAAG;AAAA,EACnC;AAAA,EAEQ,WAAW,MAAY,MAA4B;AACzD,UAAM,OAAkB;AAAA,MACtB,IAAI,eAAgB,KAAK,YAAY,CAAE;AAAA,MACvC;AAAA,MACA;AAAA,MACA,YAAY,iBAAiB,IAAI;AAAA,MACjC,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,YAAY,IAAI,gBAAA;AAAA,IAAgB;AAElC,SAAK,MAAM,IAAI,KAAK,IAAI,IAAI;AAC5B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAS,MAA0B;;AACzC,UAAM,EAAE,MAAM,WAAA,IAAe;AAC7B,UAAM,QAAiC;AAAA,MACrC,KAAK,cAAc,KAAK;AAAA,MACxB,QAAQ,KAAK;AAAA,MACb,UAAU,KAAK;AAAA,IAAA;AAGjB,QAAI,KAAK,SAAS,cAAc;AAC9B,YAAM,OAAO,KAAK;AAClB,YAAM,OAAO,KAAK;AAClB,YAAM,OAAO,KAAK,QAAQ;AAAA,IAC5B,OAAO;AACL,YAAM,MAAM,KAAK;AAAA,IACnB;AAGA,UAAM,OACJ,KAAK,SAAS,kBAAgB,UAAK,OAAO,eAAZ,mBAAwB,aAAY,SAC9D,mBACA,KAAK;AAEX,WAAO,EAAE,MAAM,MAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,eAAe,UAAqB,KAAoB;AAC9D,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,UAAU,CAAC,SAAS,OAAQ;AAEjC,WACG,MAAA,EACA,gBAAgB,OAAO,kBAAkB,OAAO,MAAM,SAAS,GAAG,QAAQ,EAC1E,IAAA;AACH,SAAK,KAAA;AAAA,EACP;AAAA,EAEA,MAAc,YAAY,MAAgC;;AACxD,QAAI,CAAC,KAAK,SAAU;AAEpB,SAAK,SAAS;AACd,SAAK,UAAU;AAGf,SAAK,UAAU;AACf,SAAK,aAAa,IAAI,gBAAA;AACtB,SAAK,MAAM,MAAM,KAAK,WAAW,MAAM,WAAW,CAAC;AACnD,SAAK,KAAA;AAEL,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,SAAS,OAAO,KAAK,MAAM;AAAA,QACnD,YAAY,CAAC,aAAa,KAAK,eAAe,MAAM,QAAQ;AAAA,QAC5D,QAAQ,KAAK,WAAW;AAAA,MAAA,CACzB;AAGD,UAAI,CAAC,KAAK,MAAM,IAAI,KAAK,EAAE,EAAG;AAC9B,WAAK,YAAY,MAAM,MAAM;AAAA,IAC/B,SAAS,OAAO;AACd,UAAI,KAAK,WAAW,OAAO,SAAS;AAClC,aAAK,SAAS,IAAI;AAClB;AAAA,MACF;AAEA,WAAK,SAAS;AACd,WAAK,UAAU;AAGf,YAAM,YAAY,KAAK,SAAS,aAAW,UAAK,OAAO,UAAZ,mBAAmB,sBAAqB;AACnF,YAAM,UAAU,aAAc,MAAM,KAAK,mBAAmB,IAAI;AAEhE,UAAI,CAAC,SAAS;AACZ,aAAK,MAAM,MAAM,KAAK,WAAW,MAAM,OAAO,CAAC;AAE/C,aAAK,KAAA;AAAA,MACP;AAEA,WAAK,OAAO,KAAK,MAAM,iBAAiB,KAAK;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,mBAAmB,MAAmC;AAElE,QAAI,CAAC,KAAK,MAAM,IAAI,KAAK,EAAE,EAAG,QAAO;AAErC,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,kBAAkB,KAAK,IAAI;AAAA,IACzC,QAAQ;AACN,aAAO;AAAA,IACT;AAEA,SAAK,MAAM,MAAM,EAAE,KAAK,GAAG,KAAK,WAAW,MAAM,OAAO,GAAG;AAE3D,QAAI,CAAC,KAAK,MAAM,IAAI,KAAK,EAAE,EAAG,QAAO;AAErC,SAAK,UAAU;AAEf,qBAAiB,IAAI;AACrB,SAAK,KAAA;AACL,WAAO;AAAA,EACT;AAAA,EAEQ,YAAY,MAAiB,QAA4B;AAC/D,UAAM,EAAE,SAAS;AACjB,UAAM,QAAiC;AAAA,MACrC,KAAK,OAAO;AAAA,MACZ,QAAQ;AAAA,MACR,UAAU;AAAA,IAAA;AAGZ,QAAI,KAAK,SAAS,cAAc;AAC9B,YAAM,WAAW;AACjB,YAAM,OAAO,OAAO,QAAQ,KAAK;AACjC,YAAM,OAAO,OAAO,QAAQ,KAAK;AACjC,YAAM,OAAO,OAAO,SAAS,KAAK,QAAQ;AAAA,IAC5C,OAAO;AACL,YAAM,MAAM,OAAO,QAAQ,KAAK;AAAA,IAClC;AAEA,SAAK,MAAM,MAAM,KAAK;AACtB,SAAK,MAAM,OAAO,KAAK,EAAE;AACzB,qBAAiB,IAAI;AACrB,SAAK,KAAA;AAAA,EACP;AAAA,EAEQ,eAAe,MAAiB,UAAgC;AACtE,QAAI,KAAK,WAAW,YAAa;AAEjC,UAAM,UAAU,OAAO,SAAS,SAAS,OAAO,IAAI,aAAa,SAAS,OAAO,IAAI;AACrF,SAAK,UAAU;AAIf,QAAI,KAAK,SAAS,cAAc;AAC9B,WAAK,MAAM,MAAM,EAAE,UAAU,WAAW,EAAE,QAAQ,MAAM;AAAA,IAC1D;AACA,SAAK,KAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKQ,WACN,MACA,QACA,WAA0B,MACD;AACzB,WAAO,KAAK,SAAS,eAAe,EAAE,QAAQ,SAAA,IAAa,EAAE,OAAA;AAAA,EAC/D;AAAA,EAEQ,MACN,MACA,OACA,UAAgC,CAAA,GAC1B;AACN,UAAM,SAAS,KAAK;AAEpB,QAAI,CAAC,UAAU,OAAO,YAAa;AAEnC,UAAM,WAAW,KAAK,aAAa,IAAI;AACvC,QAAI,aAAa,MAAM;AAErB,WAAK,WAAW,MAAA;AAChB,WAAK,SAAS,IAAI;AAClB;AAAA,IACF;AAEA,UAAM,OAAO,OAAO,MAAM,IAAI,OAAO,QAAQ;AAC7C,QAAI,CAAC,KAAM;AAEX,UAAM,KAAK,OAAO,MAAM,GAAG,cAAc,UAAU,QAAW;AAAA,MAC5D,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,IAAA,CACJ;AAID,OAAG,QAAQ,gBAAgB,KAAK;AAChC,QAAI,QAAQ,OAAQ,IAAG,QAAQ,iBAAiB,IAAI;AAEpD,WAAO,KAAK,SAAS,EAAE;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,wBAA8B;AACpC,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,UAAU,CAAC,KAAK,MAAM,KAAM;AAEjC,UAAM,UAAiE,CAAA;AACvE,WAAO,MAAM,IAAI,YAAY,CAAC,MAAM,aAAa;AAC/C,YAAM,OAAO,KAAK,iBAAiB,KAAK,MAAM,GAAG;AACjD,UAAI,QAAQ,KAAK,MAAM,YAAY,KAAM,SAAQ,KAAK,EAAE,UAAU,MAAM,KAAA,CAAM;AAAA,IAChF,CAAC;AACD,QAAI,CAAC,QAAQ,OAAQ;AAErB,UAAM,KAAK,OAAO,MAAM;AAExB,YACG,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EACtC,QAAQ,CAAC,EAAE,UAAU,MAAM,WAAW;AACrC,SAAG,cAAc,UAAU,QAAW;AAAA,QACpC,GAAG,KAAK;AAAA,QACR,UAAU,KAAK;AAAA,QACf,GAAG,KAAK,WAAW,MAAM,KAAK,QAAQ,KAAK,OAAO;AAAA,MAAA,CACnD;AAAA,IACH,CAAC;AACH,OAAG,QAAQ,gBAAgB,KAAK;AAChC,OAAG,QAAQ,iBAAiB,IAAI;AAChC,WAAO,KAAK,SAAS,EAAE;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,QAAc;AACpB,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,UAAU,OAAO,YAAa;AAEnC,UAAM,4BAAY,IAAA;AAClB,WAAO,MAAM,IAAI,YAAY,CAAC,SAAS;AACrC,UAAI,OAAO,KAAK,MAAM,aAAa,SAAU,OAAM,IAAI,KAAK,MAAM,QAAQ;AAC1E,UAAI,OAAO,KAAK,MAAM,QAAQ,SAAU,OAAM,IAAI,KAAK,MAAM,GAAG;AAAA,IAClE,CAAC;AAED,SAAK,MAAM,QAAQ,CAAC,SAAS;AAC3B,YAAM,SAAS,MAAM,IAAI,KAAK,EAAE,KAAM,KAAK,eAAe,QAAQ,MAAM,IAAI,KAAK,UAAU;AAC3F,UAAI,OAAQ;AAEZ,WAAK,WAAW,MAAA;AAChB,WAAK,SAAS,IAAI;AAAA,IACpB,CAAC;AAAA,EACH;AAAA,EAEQ,aAAa,MAAgC;AACnD,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,OAAQ,QAAO;AAEpB,QAAI,QAAuB;AAC3B,WAAO,MAAM,IAAI,YAAY,CAAC,MAAM,aAAa;AAC/C,UAAI,UAAU,KAAM;AACpB,UAAI,KAAK,MAAM,aAAa,KAAK,MAAM,KAAK,MAAM,QAAQ,KAAK,YAAY;AACzE,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAiB,KAAgC;AACvD,QAAI,OAAO,QAAQ,YAAY,CAAC,IAAK,QAAO;AAC5C,eAAW,QAAQ,KAAK,MAAM,OAAA,GAAU;AACtC,UAAI,KAAK,eAAe,IAAK,QAAO;AAAA,IACtC;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAS,MAAuB;AACtC,QAAI,KAAK,MAAM,IAAI,KAAK,EAAE,MAAM,KAAM;AACtC,SAAK,MAAM,OAAO,KAAK,EAAE;AACzB,qBAAiB,IAAI;AACrB,SAAK,KAAA;AAAA,EACP;AAAA,EAEQ,OAAO,MAAY,QAA2B,OAAuB;AAC3E,QAAI,KAAK,OAAO,SAAS;AACvB,WAAK,OAAO,QAAQ,EAAE,MAAM,QAAQ,OAAO;AAC3C;AAAA,IACF;AAGA,YAAQ,KAAK,uBAAuB,KAAK,IAAI,mBAAmB,MAAM,IAAI;AAAA,EAC5E;AAAA,EAEQ,OAAa;AACnB,QAAI,CAAC,KAAK,UAAU,KAAM;AAC1B,UAAM,QAAQ,KAAK,SAAA;AACnB,SAAK,UAAU,QAAQ,CAAC,aAAa,SAAS,KAAK,CAAC;AAAA,EACtD;AACF;AAEA,SAAS,aAAa,SAAyB;AAC7C,SAAO,KAAK,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;AACvD;AAKA,SAAS,iBAAiB,MAA2B;AACnD,MAAI,OAAO,QAAQ,eAAe,OAAO,IAAI,oBAAoB,WAAY,QAAO;AACpF,MAAI;AACF,WAAO,IAAI,gBAAgB,IAAI;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,iBAAiB,MAAuB;AAC/C,QAAM,EAAE,eAAe;AACvB,MAAI,CAAC,WAAY;AACjB,MAAI,OAAO,QAAQ,eAAe,OAAO,IAAI,oBAAoB,WAAY;AAE7E,MAAI,gBAAgB,UAAU;AAC9B,OAAK,aAAa;AACpB;AC5iBO,MAAM,oBAAoB,CAAC,QAAQ,UAAU,aAAa,UAAU,MAAM;AAG1E,MAAM,oBAAoB,CAAC,qBAAqB,QAAQ;AAGxD,MAAM,0BAA0B,CAAC,SAAS,cAAc,gBAAgB;AAKxE,SAAS,kBAAkB,OAAmC;AACnE,MAAI,CAAC,gBAAgB,MAAM,SAAS,EAAG,QAAO;AAC9C,QAAM,EAAE,KAAA,IAAS,MAAM,UAAU,KAAK;AACtC,SAAQ,wBAA8C,SAAS,IAAI,IAAI,OAAO;AAChF;AAKO,SAAS,eAAe,OAAwC;AACrE,MAAI,gBAAgB,MAAM,SAAS,UAAU,kBAAkB,KAAK,IAAI,SAAS;AACjF,SAAO;AACT;AAQA,SAAS,iBAAiB,OAAoB,MAAuB;AACnE,QAAM,SAAS,MAAM,IAAI,QAAQ,IAAI,EAAE,OAAO;AAE9C,SAAO,kBAAkB,KAAK,CAAC,aAAa;AAC1C,UAAM,OAAO,MAAM,OAAO,MAAM,QAAQ;AACxC,WAAO,CAAC,CAAC,QAAQ,OAAO,eAAe,IAAI;AAAA,EAC7C,CAAC;AACH;AAQO,SAAS,qBAAqB,OAMzB;AACV,QAAM,EAAE,QAAQ,SAAS,OAAO,MAAM,OAAO;AAE7C,MAAI,QAAQ,OAAO,aAAa,eAAe,KAAK,IAAI;AACxD,QAAM,WAAW,UAAU,SAAS,kBAAkB,KAAK,IAAI;AAG/D,MAAI,UAAU,UAAU,CAAC,MAAM,IAAI,YAAY,MAAM,EAAE,EAAE,KAAA,EAAQ,SAAQ;AAEzE,MAAI,UAAU,UAAU,CAAC,iBAAiB,OAAO,IAAI,EAAG,SAAQ;AAIhE,UAAQ,QAAQ,iBAAiB,SAAS;AAC1C,UAAQ,QAAQ,iBAAiB,YAAY;AAE7C,MAAI,CAAC,MAAO,QAAO;AAGnB,SAAO,CAAC,CAAC,QAAQ,cAAc,4BAA4B,KAAK,wBAAwB;AAC1F;AC5CO,MAAM,WAAW;AAAA,EAOtB,YAAY,QAA0B;AACpC,SAAK,SAAS;AACd,SAAK,kBAAkB,OAAO,eAAe;AAC7C,SAAK,eACH,OAAO,UAAU,QAAQ,OAAQ,OAAO,gBAAgB,IAAI,aAAa,OAAO,KAAK;AACvF,SAAK,SAAS,KAAK,aAAA;AAAA,EACrB;AAAA,EAEQ,eAAuB;;AAC7B,UAAM,EAAE,SAAS,SAAS,eAAe,UAAU,WAAA,IAAe,KAAK;AAEvE,UAAM,mBAAgD;AAAA,MACpD,WAAW,UAAU;AAAA;AAAA,MAAA,CAEpB;AAAA,IAAA;AAMH,qBAAiB;AAAA,MACf,YAAY,UAAU;AAAA,QACpB,aAAa,MAAM,KAAK;AAAA,MAAA,CACzB;AAAA,IAAA;AAIH,qBAAiB;AAAA,MACf,UAAU,UAAU;AAAA,QAClB,OAAO,CAAC,WAAW,WAAW;AAAA,MAAA,CAC/B;AAAA,IAAA;AAIH,UAAM,oBAAkB,UAAK,OAAO,aAAZ,mBAAsB,aAAY;AAC1D,QAAI,iBAAiB;AACnB,uBAAiB;AAAA,QACf,SAAS,UAAU;AAAA,UACjB,cAAa,UAAK,OAAO,aAAZ,mBAAsB;AAAA,UACnC,gBAAe,UAAK,OAAO,aAAZ,mBAAsB;AAAA,QAAA,CACtC;AAAA,MAAA;AAAA,IAEL;AAGA,UAAM,cACJ,kBAAkB,cAAc,CAAC,kBAAkB,SAAY;AAIjE,qBAAiB,KAAK,GAAG,sBAAsB,KAAK,OAAO,OAAO,KAAK,YAAY,CAAC;AAIpF,QAAI,KAAK,OAAO,eAAe;AAC7B,uBAAiB;AAAA,QACf,WAAW,UAAU;AAAA,UACnB,SAAS,KAAK,OAAO;AAAA;AAAA,UAErB,aAAa;AAAA,UACb,YAAY;AAAA,UACZ,SAAS;AAAA;AAAA,YAEP,cAAc;AAAA;AAAA,YAEd,MAAM,EAAE,SAAS,EAAA;AAAA,YACjB,OAAO,EAAE,SAAS,EAAA;AAAA,UAAE;AAAA,QACtB,CACD;AAAA,MAAA;AAAA,IAEL;AAGA,UAAM,qBAAmB,UAAK,OAAO,aAAZ,mBAAsB,eAAc;AAG7D,QAAI,YAAY;AACd,uBAAiB,KAAK,GAAG,UAAU;AAAA,IACrC;AAEA,UAAM,SAAS,IAAI,OAAO;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,YAAY;AAAA,MACtB,YAAY;AAAA,MACZ,aAAa;AAAA,QACX,YAAY;AAAA,UACV,OAAO;AAAA,QAAA;AAAA,MACT;AAAA,MAEF,UAAU,MAAM;;AACd,SAAAC,OAAAC,MAAA,KAAK,QAAO,aAAZ,gBAAAD,IAAA,KAAAC;AAAA,MACF;AAAA,MACA,UAAU,MAAM;;AACd,SAAAD,OAAAC,MAAA,KAAK,QAAO,aAAZ,gBAAAD,IAAA,KAAAC;AAAA,MACF;AAAA,MACA,SAAS,MAAM;;AACb,SAAAD,OAAAC,MAAA,KAAK,QAAO,YAAZ,gBAAAD,IAAA,KAAAC;AAAA,MACF;AAAA,MACA,QAAQ,MAAM;;AACZ,SAAAD,OAAAC,MAAA,KAAK,QAAO,WAAZ,gBAAAD,IAAA,KAAAC;AAAA,MACF;AAAA,MACA,WAAW,MAAM;;AACf,SAAAD,OAAAC,MAAA,KAAK,QAAO,cAAZ,gBAAAD,IAAA,KAAAC;AAAA,MACF;AAAA,IAAA,CACD;AAGD,eAAK,iBAAL,mBAAmB,KAAK;AAExB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAuC;AACrC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,YAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAkB;AAChB,QAAI;AACF,aAAO,KAAK,OAAO,QAAA;AAAA,IACrB,SAAS,KAAK;AACZ,cAAQ,MAAM,kCAAkC,GAAG;AAEnD,cAAQ,MAAM,8BAA8B,OAAO,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC;AACjF,cAAQ,MAAM,8BAA8B,OAAO,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC;AACjF,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAkB;AAChB,WAAO,KAAK,OAAO,QAAA;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAA0B,aAAa,MAAY;AAC5D,SAAK,OAAO,SAAS,WAAW,SAAoB,EAAE,YAAY;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,SAAK,OAAO,SAAS,aAAa,IAAI;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AACjB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAyB;AACnC,SAAK,OAAO,YAAY,QAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACpB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,OAAO,SAAS,MAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,SAAK,OAAO,SAAS,KAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,SAAuB;AACnC,SAAK,OAAO,SAAS,cAAc,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,aAA2B;AACxC,SAAK,kBAAkB;AACvB,SAAK,OAAO,KAAK,SAAS,KAAK,OAAO,MAAM,EAAE;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,OAAO,QAAA;AAAA,EACd;AACF;ACtQO,SAAS,qBAAqBC,WAA0B;AAC7D,MAAI,OAAOA;AAGX,SAAO,KAAK,QAAQ,gBAAgB,aAAa;AACjD,SAAO,KAAK,QAAQ,eAAe,aAAa;AAChD,SAAO,KAAK,QAAQ,cAAc,aAAa;AAG/C,SAAO,KAAK,QAAQ,sBAAsB,8BAA8B;AACxE,SAAO,KAAK,QAAQ,kBAAkB,qBAAqB;AAC3D,SAAO,KAAK,QAAQ,cAAc,aAAa;AAG/C,SAAO,KAAK,QAAQ,4BAA4B,gDAAgD;AAChG,SAAO,KAAK,QAAQ,YAAY,iBAAiB;AAGjD,SAAO,KAAK;AAAA,IACV;AAAA,IACA,CAAC,MAAM,KAAK,KAAK,UAAU,aAAa,GAAG,UAAU,GAAG,IAAI,QAAQ,WAAW,KAAK,MAAM,EAAE;AAAA,EAAA;AAI9F,SAAO,KAAK,QAAQ,uBAAuB,qBAAqB;AAGhE,SAAO,KAAK,QAAQ,eAAe,aAAa;AAChD,SAAO,KAAK,QAAQ,cAAc,aAAa;AAC/C,SAAO,KAAK,QAAQ,oBAAoB,aAAa;AAGrD,SAAO,KAAK,QAAQ,SAAS,SAAS;AACtC,SAAO,MAAM,IAAI;AAGjB,SAAO,KAAK,QAAQ,aAAa,EAAE;AACnC,SAAO,KAAK,QAAQ,kBAAkB,IAAI;AAC1C,SAAO,KAAK,QAAQ,sBAAsB,IAAI;AAC9C,SAAO,KAAK,QAAQ,eAAe,IAAI;AACvC,SAAO,KAAK,QAAQ,mBAAmB,IAAI;AAC3C,SAAO,KAAK,QAAQ,cAAc,IAAI;AACtC,SAAO,KAAK,QAAQ,kBAAkB,IAAI;AAE1C,SAAO;AACT;AAKO,SAAS,qBAAqB,MAAsB;AACzD,MAAI,KAAK;AAGT,OAAK,GAAG,QAAQ,0BAA0B,UAAU;AACpD,OAAK,GAAG,QAAQ,0BAA0B,WAAW;AACrD,OAAK,GAAG,QAAQ,0BAA0B,YAAY;AAGtD,OAAK,GAAG,QAAQ,kCAAkC,QAAQ;AAC1D,OAAK,GAAG,QAAQ,wBAAwB,QAAQ;AAChD,OAAK,GAAG,QAAQ,0BAA0B,MAAM;AAChD,OAAK,GAAG,QAAQ,wBAAwB,MAAM;AAG9C,OAAK,GAAG,QAAQ,2CAA2C,UAAU;AAGrE,OAAK,GAAG,QAAQ,kBAAkB,CAAC,QAAQ;;AACzC,UAAM,QAAM,sBAAiB,KAAK,GAAG,MAAzB,mBAA6B,OAAM;AAC/C,UAAM,QAAM,sBAAiB,KAAK,GAAG,MAAzB,mBAA6B,OAAM;AAC/C,WAAO,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM;AAAA,EACrC,CAAC;AAGD,OAAK,GAAG,QAAQ,8BAA8B,MAAM;AACpD,OAAK,GAAG,QAAQ,oDAAoD,kBAAkB;AAGtF,OAAK,GAAG,QAAQ,gBAAgB,IAAI;AACpC,OAAK,GAAG,QAAQ,6BAA6B,QAAQ;AAGrD,OAAK,GAAG,QAAQ,YAAY,EAAE;AAG9B,OAAK,GAAG,QAAQ,UAAU,GAAG;AAC7B,OAAK,GAAG,QAAQ,SAAS,GAAG;AAC5B,OAAK,GAAG,QAAQ,SAAS,GAAG;AAC5B,OAAK,GAAG,QAAQ,WAAW,GAAG;AAC9B,OAAK,GAAG,QAAQ,UAAU,GAAG;AAG7B,OAAK,GAAG,QAAQ,WAAW,MAAM;AAEjC,SAAO,GAAG,KAAA;AACZ;;;;;;AC3FO,MAAM,gBAAgB;AAAA,EAI3B,YAAY,QAAgB,SAA6B,IAAI;AAC3D,SAAK,SAAS;AACd,SAAK,SAAS;AAAA,MACZ,SAAS;AAAA,MACT,GAAG;AAAA,IAAA;AAAA,EAEP;AAAA;AAAA;AAAA;AAAA,EAKA,IAAY,UAAkC;AAC5C,WAAO,KAAK,OAAO,YAAY;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAsB;AACpB,UAAM,UAAU,KAAK;AACrB,QAAI,CAAC,SAAS;AACZ,aAAO,qBAAqB,KAAK,OAAO,QAAA,CAAS;AAAA,IACnD;AACA,WAAO,QAAQ,UAAU,KAAK,OAAO,SAAS;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAiB,aAAa,MAAY;AACpD,QAAI,KAAK,SAAS;AAChB,WAAK,OAAO,SAAS,WAAW,SAAS,EAAE,aAAa,YAAY,YAAY;AAChF;AAAA,IACF;AACA,SAAK,OAAO,SAAS,WAAW,qBAAqB,OAAO,GAAG,EAAE,YAAY;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAeA,WAA0B;AACvC,UAAM,UAAU,KAAK;AACrB,QAAI,CAAC,SAAS;AACZ,aAAO,qBAAqBA,SAAQ;AAAA,IACtC;AACA,UAAM,EAAE,WAAW,KAAK;AACxB,WAAO;AAAA,MACLC,OAAgB,SAAS,QAAQ,QAAQ,MAAMD,SAAQ,CAAC,EAAE;AAAA,MAC1D;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,MAAsB;AACnC,UAAM,UAAU,KAAK;AACrB,QAAI,CAAC,SAAS;AACZ,aAAO,qBAAqB,IAAI;AAAA,IAClC;AACA,UAAM,SAASE,UAAqB,WAAW,KAAK,OAAO,MAAM,EAAE;AAAA,MACjE,kBAAkB,IAAI;AAAA,IAAA;AAExB,WAAO,QAAQ,UAAU,OAAO,OAAA,CAAQ;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,eAAeF,WAA+B;AAC5C,UAAM,UAAU,KAAK;AACrB,QAAI,SAAS;AACX,aAAO,QAAQ,MAAMA,SAAQ;AAAA,IAC/B;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAMA,WAAU;AAAA,QAAA;AAAA,MAC5C;AAAA,IACF;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACnB,WAAO,KAAK,OAAO,YAAY;AAAA,EACjC;AACF;AC3GO,MAAM,aAAa;AAAA,EAIxB,YAAY,WAAwB,QAAmB,SAAS;AAC9D,SAAK,YAAY;AACjB,SAAK,eAAe;AACpB,SAAK,WAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAmB;AAEzB,SAAK,UAAU,UAAU,OAAO,oBAAoB,iBAAiB;AAGrE,QAAI,KAAK,iBAAiB,WAAW,KAAK,iBAAiB,QAAQ;AACjE,WAAK,UAAU,UAAU,IAAI,cAAc,KAAK,YAAY,EAAE;AAAA,IAChE,OAAO;AAEL,WAAK,UAAU,UAAU,IAAI,cAAc,KAAK,YAAY,EAAE;AAAA,IAChE;AAGA,SAAK,UAAU,aAAa,mBAAmB,KAAK,YAAY;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA,EAKA,WAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAAwB;AAC/B,SAAK,eAAe;AACpB,SAAK,WAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,cAAoB;AAClB,QAAI,KAAK,iBAAiB,SAAS;AACjC,WAAK,SAAS,MAAM;AAAA,IACtB,WAAW,KAAK,iBAAiB,QAAQ;AACvC,WAAK,SAAS,OAAO;AAAA,IACvB,OAAO;AAEL,WAAK,SAAS,OAAO;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAkB;AAChB,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AACF;AChEA,MAAM,cAAc;AAAA,EAClB,QAAQ;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,eAAe;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,UAAU;AAAA,IACV,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,mBAAmB;AAAA,IACnB,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,MAAM;AAAA,IACN,WAAW;AAAA,IACX,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,cAAc;AAAA,EAAA;AAAA,EAEhB,OAAO;AAAA,IACL,WAAW;AAAA,IACX,cAAc;AAAA,IACd,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,QAAQ;AAAA,EAAA;AAAA,EAEV,IAAI;AAAA,IACF,MAAM;AAAA,IACN,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAEX;AAGA,MAAM,cAAc;AAAA,EAClB,QAAQ;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,eAAe;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,UAAU;AAAA,IACV,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,mBAAmB;AAAA,IACnB,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,MAAM;AAAA,IACN,WAAW;AAAA,IACX,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,cAAc;AAAA,EAAA;AAAA,EAEhB,OAAO;AAAA,IACL,WAAW;AAAA,IACX,cAAc;AAAA,IACd,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,QAAQ;AAAA,EAAA;AAAA,EAEV,IAAI;AAAA,IACF,MAAM;AAAA,IACN,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAEX;AAEO,MAAM,YAAY;AAAA,EAGvB,YAAY,OAAe,MAAM;AAE/B,SAAK,WAAW,QAAQ,eAAA;AAExB,SAAK,SAAS,KAAK;AAAA,MACjB,KAAK;AAAA,MACL,aAAa;AAAA,MACb,eAAe;AAAA,MACf,WAAW;AAAA,QACT,IAAI,EAAE,aAAa,YAAA;AAAA,QACnB,IAAI,EAAE,aAAa,YAAA;AAAA,MAAY;AAAA,IACjC,CACD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,EAAE,KAAa,SAA2C;AACxD,WAAO,KAAK,SAAS,EAAE,KAAK,OAAO;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,MAA6B;AAChD,UAAM,KAAK,SAAS,eAAe,IAAI;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,SAA6C;AAC7D,SAAK,SAAS,GAAG,mBAAmB,OAAO;AAC3C,WAAO,MAAM,KAAK,SAAS,IAAI,mBAAmB,OAAO;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,cAAsB;AACpB,WAAO,KAAK,SAAS,YAAY;AAAA,EACnC;AACF;AC1JO,MAAM,iBAAiB;AAAA,EAAvB,cAAA;AACL,SAAQ,iCAA4C,IAAA;AACpD,SAAQ,gCAAiD,IAAA;AAAA,EAAI;AAAA;AAAA;AAAA;AAAA,EAK7D,SAAS,WAAyB,OAAiC;AACjE,UAAM,OAAO,UAAU;AACvB,QAAI,KAAK,WAAW,IAAI,IAAI,GAAG;AAC7B,cAAQ,KAAK,cAAc,IAAI,oCAAoC;AAAA,IACrE;AACA,SAAK,WAAW,IAAI,MAAM,SAAS;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,YAAkC;AAC5C,eAAW,QAAQ,CAAC,QAAQ,KAAK,SAAS,GAAG,CAAC;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,QAAkC;AACjD,QAAI,KAAK,UAAU,IAAI,OAAO,IAAI,GAAG;AACnC,cAAQ,KAAK,aAAa,OAAO,IAAI,oCAAoC;AAAA,IAC3E;AAEA,SAAK,UAAU,IAAI,OAAO,MAAM,MAAM;AAGtC,UAAM,gBAAgB,KAAK,OAAO;AAAA,MAChC,MAAM,OAAO;AAAA,MACb,OAAO,OAAO,UAAU,OAAO,SAAS,WAAW;AAAA,MACnD,MAAM,OAAO,QAAQ;AAAA,MAErB,gBAAgB;AACd,eAAO,OAAO,cAAc,CAAA;AAAA,MAC9B;AAAA,MAEA,YAAY;;AACV,iBAAO,YAAO,cAAP,oCAAwB,CAAC,EAAE,KAAK,OAAO,MAAM;AAAA,MACtD;AAAA,MAEA,WAAW,EAAE,gBAAgB,QAAQ;AACnC,YAAI,OAAO,YAAY;AACrB,iBAAO,OAAO,WAAW,EAAE,gBAAgB,MAAM;AAAA,QACnD;AACA,eAAO,CAAC,OAAO,MAAM,gBAAgB,cAAc,CAAC;AAAA,MACtD;AAAA,MAEA,cAAc;AACZ,eAAO,OAAO;AAAA,MAChB;AAAA;AAAA,MAGA,gBAAgB,OAAO,oBACnB,CAAC,SAAS,OAAO,kBAAmB,IAAI,IACxC;AAAA,IAAA,CACL;AAGD,SAAK,SAAS,eAAe;AAAA,MAC3B,MAAM,OAAO;AAAA,MACb,SAAS;AAAA,MACT,UAAU;AAAA,MACV,aAAa,OAAO;AAAA,IAAA,CACrB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,SAAqC;AACrD,YAAQ,QAAQ,CAAC,WAAW,KAAK,iBAAiB,MAAM,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,MAAwC;AAC1C,WAAO,KAAK,WAAW,IAAI,IAAI;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAuB;AAChC,SAAK,UAAU,OAAO,IAAI;AAC1B,WAAO,KAAK,WAAW,OAAO,IAAI;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAyB;AACvB,WAAO,MAAM,KAAK,KAAK,WAAW,QAAQ;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,eAAgD;AAC9C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,MAAuB;AACzB,WAAO,KAAK,WAAW,IAAI,IAAI;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,WAAW,MAAA;AAChB,SAAK,UAAU,MAAA;AAAA,EACjB;AACF;AC3GA,MAAM,SAAmF;AAAA,EACvF,OAAO,EAAE,KAAK,MAAM,KAAK,MAAM,MAAM,QAAA;AAAA,EACrC,YAAY,EAAE,KAAK,OAAO,KAAK,OAAO,MAAM,aAAA;AAC9C;AAEA,SAAS,QAAQ,UAAmD;AAClE,MAAI,CAAC,SAAU,QAAO,CAAA;AACtB,SAAO,MAAM,KAAK,SAAS,KAAK;AAClC;AAQO,MAAM,YAAY;AAAA,EAavB,YAAY,SAA6B;;AANzC,SAAQ,UAAiC;AACzC,SAAQ,OAA+B;AACvC,SAAQ,iBAAsC;AAC9C,SAAQ,gBAAqC;AAC7C,SAAQ,WAA8B,CAAA;AAGpC,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ;AACvB,SAAK,OAAO,QAAQ;AACpB,SAAK,eAAe,QAAQ;AAE5B,SAAK,uBAAsB,UAAK,SAAL,mBAAW,kBAAkB,MAAM,KAAK;EACrE;AAAA,EAEA,IAAI,WAAmC;AACrC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,QAAqB,MAA6B;;AACvD,QAAI,KAAK,SAAS,MAAM;AACtB,WAAK,MAAA;AACL;AAAA,IACF;AAEA,SAAK,MAAA;AACL,SAAK,OAAO;AACZ,SAAK,MAAM,IAAI;AACf,SAAK,SAAS,MAAM;AACpB,SAAK,iBAAiB,MAAM;AAC5B,eAAK,iBAAL,8BAAoB;AAAA,EACtB;AAAA,EAEA,QAAc;;AACZ,eAAK,kBAAL;AACA,SAAK,gBAAgB;AACrB,eAAK,mBAAL;AACA,SAAK,iBAAiB;AACtB,eAAK,YAAL,mBAAc;AACd,SAAK,UAAU;AACf,SAAK,WAAW,CAAA;AAEhB,QAAI,KAAK,SAAS,KAAM;AACxB,SAAK,OAAO;AACZ,eAAK,iBAAL,8BAAoB;AAAA,EACtB;AAAA,EAEA,UAAgB;;AACd,SAAK,MAAA;AACL,eAAK,wBAAL;AACA,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAEQ,EAAE,KAAa,UAA0B;;AAC/C,UAAM,cAAa,UAAK,SAAL,mBAAW,EAAE;AAEhC,WAAO,cAAc,eAAe,MAAM,aAAa;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKQ,MAAM,IAAiB,KAAa,UAAwB;AAClE,SAAK,SAAS,KAAK,EAAE,IAAI,KAAK,UAAU;AACxC,SAAK,WAAW,EAAE,IAAI,KAAK,UAAU;AAAA,EACvC;AAAA,EAEQ,cAAoB;AAC1B,SAAK,SAAS,QAAQ,CAAC,UAAU,KAAK,WAAW,KAAK,CAAC;AAAA,EACzD;AAAA,EAEQ,WAAW,EAAE,IAAI,KAAK,YAAmC;AAC/D,UAAM,OAAO,KAAK,EAAE,KAAK,QAAQ;AACjC,QAAI,cAAc,iBAAkB,IAAG,cAAc;AAAA,YAC7C,cAAc;AAAA,EACxB;AAAA,EAEQ,MAAM,MAA6B;AACzC,UAAM,SAAS,OAAO,IAAI;AAC1B,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY;AAClB,UAAM,aAAa,QAAQ,QAAQ;AACnC,UAAM;AAAA,MACJ;AAAA,MACA,KAAK,EAAE,UAAU,SAAS,UAAU,UAAU,YAAY,IAAI,IAAI;AAAA,IAAA;AAGpE,QAAI,WAAoC;AACxC,QAAI,WAAoC;AAExC,QAAI,OAAO,KAAK;AACd,iBAAW,SAAS,cAAc,OAAO;AACzC,eAAS,OAAO;AAChB,eAAS,YAAY;AACrB,WAAK,MAAM,UAAU,wBAAwB,iBAAiB;AAC9D,eAAS,iBAAiB,WAAW,CAAC,UAAU;AAC9C,YAAI,MAAM,QAAQ,SAAS;AACzB,gBAAM,eAAA;AACN,eAAK,cAAc,UAAU,QAAQ;AAAA,QACvC;AAAA,MACF,CAAC;AACD,YAAM,YAAY,QAAQ;AAAA,IAC5B;AAEA,QAAI,OAAO,KAAK;AACd,iBAAW,SAAS,cAAc,OAAO;AACzC,eAAS,OAAO;AAChB,eAAS,YAAY;AACrB,WAAK,MAAM,UAAU,wBAAwB,MAAM;AACnD,YAAM,YAAY,QAAQ;AAAA,IAC5B;AAEA,UAAM,YAAY,SAAS,cAAc,OAAO;AAChD,cAAU,OAAO;AACjB,cAAU,YAAY;AACtB,cAAU,SAAS;AACnB,cAAU,WAAW;AACrB,UAAM,SAAS,KAAK,QAAQ,UAAU,OAAO,IAAI;AACjD,QAAI,kBAAkB,SAAS;AAC/B,cAAU,iBAAiB,UAAU,MAAM;AACzC,WAAK,OAAO,MAAM,KAAK,UAAU,SAAS,CAAA,CAAE,GAAG,OAAO,IAAI;AAE1D,gBAAU,QAAQ;AAAA,IACpB,CAAC;AACD,UAAM,YAAY,SAAS;AAE3B,UAAM,OAAO,SAAS,cAAc,KAAK;AACzC,SAAK,YAAY;AACjB,SAAK,MAAM,MAAM,kBAAkB,UAAU;AAC7C,SAAK,iBAAiB,YAAY,CAAC,UAAU,MAAM,gBAAgB;AACnE,SAAK,iBAAiB,QAAQ,CAAC,UAAU;AAEvC,YAAM,eAAA;AACN,WAAK,OAAO,QAAS,MAAoB,YAAY,GAAG,OAAO,IAAI;AAAA,IACrE,CAAC;AAED,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,OAAO;AACd,WAAO,YAAY;AACnB,SAAK,MAAM,QAAQ,gBAAgB,MAAM;AACzC,WAAO,iBAAiB,SAAS,MAAM,UAAU,OAAO;AACxD,SAAK,YAAY,MAAM;AACvB,UAAM,YAAY,IAAI;AAEtB,QAAI,SAAmC;AACvC,QAAI,OAAO,KAAK;AACd,eAAS,SAAS,cAAc,QAAQ;AACxC,aAAO,OAAO;AACd,aAAO,YAAY;AACnB,WAAK,MAAM,QAAQ,gBAAgB,IAAI;AACvC,aAAO,iBAAiB,SAAS,MAAM,KAAK,cAAc,UAAU,QAAQ,CAAC;AAC7E,YAAM,YAAY,MAAM;AAAA,IAC1B;AAEA,aAAS,KAAK,YAAY,KAAK;AAC/B,SAAK,UAAU;AACf,SAAK,WAAW,KAAK;AAGrB,KAAC,YAAY,YAAY,QAAQ,MAAA;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,OAA0B;AAC3C,UAAM,OAAO,KAAK,OAAO,KAAK,IAAI,QAAQ,cAAc;AACxD,UAAM,SAAS,MAAM,MAAK,6BAAM,cAAa,CAAA,CAAE,EAAE;AAAA,MAAO,CAAC,SACvD,KAAK,WAAW,aAAa;AAAA,IAAA;AAE/B,UAAM,UAAU,IAAI,GAAG,MAAM;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKQ,cACN,UACA,UACM;AACN,UAAM,MAAM,qCAAU,MAAM;AAC5B,QAAI,CAAC,IAAK;AAEV,SAAK,OACF,MAAA,EACA,MAAA,EACA,SAAS,EAAE,KAAK,MAAK,qCAAU,MAAM,WAAU,OAAA,CAAW,EAC1D,IAAA;AACH,SAAK,MAAA;AAAA,EACP;AAAA,EAEQ,OAAO,OAAe,MAAuB;AACnD,QAAI,CAAC,MAAM,OAAQ;AAEnB,SAAK,KAAK,QAAQ,YAAY,OAAO,EAAE,MAAM;AAC7C,SAAK,MAAA;AAAA,EACP;AAAA,EAEA,MAAc,SAAS,QAAoC;AACzD,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,MAAO;AAEZ,UAAM,SAAS,YAAY;AACzB,UAAI,KAAK,YAAY,MAAO;AAC5B,UAAI;AACF,cAAM,EAAE,GAAG,EAAA,IAAM,MAAM,gBAAgB,QAAQ,OAAO;AAAA,UACpD,WAAW;AAAA,UACX,YAAY,CAAC,OAAO,CAAC,GAAG,KAAA,GAAQ,MAAM,EAAE,SAAS,GAAG,CAAC;AAAA,QAAA,CACtD;AAED,YAAI,KAAK,YAAY,MAAO;AAC5B,cAAM,MAAM,WAAW;AACvB,cAAM,MAAM,OAAO,GAAG,CAAC;AACvB,cAAM,MAAM,MAAM,GAAG,CAAC;AAAA,MACxB,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,OAAA;AACN,QAAI,KAAK,YAAY,OAAO;AAC1B,WAAK,iBAAiB,WAAW,QAAQ,OAAO,MAAM,KAAK,QAAQ;AAAA,IACrE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,QAA2B;AAClD,UAAM,gBAAgB,CAAC,UAAiB;;AACtC,YAAM,SAAS,MAAM;AACrB,UAAI,CAAC,YAAU,UAAK,YAAL,mBAAc,SAAS,YAAW,OAAO,SAAS,MAAM,EAAG;AAC1E,WAAK,MAAA;AAAA,IACP;AAEA,UAAM,YAAY,CAAC,UAAyB;AAC1C,UAAI,MAAM,QAAQ,SAAU;AAC5B,YAAM,eAAA;AACN,YAAM,gBAAA;AACN,WAAK,MAAA;AAAA,IACP;AAEA,aAAS,iBAAiB,eAAe,eAAe,IAAI;AAC5D,aAAS,iBAAiB,WAAW,WAAW,IAAI;AACpD,SAAK,gBAAgB,MAAM;AACzB,eAAS,oBAAoB,eAAe,eAAe,IAAI;AAC/D,eAAS,oBAAoB,WAAW,WAAW,IAAI;AAAA,IACzD;AAAA,EACF;AACF;ACpSO,MAAM,QAAgC;AAAA,EAC3C,MAAM;AAAA,EAEN,MAAM;AAAA,EAEN,UACE;AAAA,EAEF,UACE;AAAA,EAEF,UACE;AAAA,EAEF,WACE;AAAA,EAEF,MAAM;AAAA,EAEN,QACE;AAAA,EAEF,WACE;AAAA,EAEF,eACE;AAAA,EAEF,MAAM;AAAA,EAEN,MAAM;AAAA,EAEN,aACE;AAAA,EAEF,OACE;AAAA,EAEF,WACE;AAAA,EAEF,WACE;AAAA,EAEF,aACE;AAAA,EAEF,YACE;AAAA,EAEF,OACE;AAAA,EAEF,WACE;AAAA,EAEF,MAAM;AAAA,EAEN,OACE;AACJ;ACpCA,MAAM,eAAuC;AAAA,EAC3C,MAAM;AAAA,EACN,MAAM;AAAA,EACN,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,WAAW;AAAA,EACX,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,QAAQ;AACV;AAKO,MAAM,eAAe;AAAA,EAe1B,YACE,QACA,WACA,QACA,MACA,cACA;;AAbF,SAAQ,8BAAmD,IAAA;AAE3D,SAAQ,qCAA0C,IAAA;AAElD,SAAQ,kBAAiC,CAAA;AACzC,SAAQ,QAA4B;AASlC,SAAK,SAAS;AACd,SAAK,YAAY;AACjB,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,SAAK,eAAe,gBAAgB;AACpC,SAAK,WAAW,KAAK,gBAAA;AACrB,SAAK,iBAAA;AACL,SAAK,iBAAA;AAEL,SAAK,uBAAsB,UAAK,SAAL,mBAAW,kBAAkB,MAAM,KAAK;EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,oBAAoB,SAA4B;AAG9C,YAAQ,gBAAA;AAGR,UAAM,SAAwC;AAAA,MAC5C,CAAC,QAAQ,iBAAiB;AAAA,MAC1B,CAAC,QAAQ,iBAAiB;AAAA,IAAA;AAG5B,WAAO,QAAQ,CAAC,CAAC,SAAS,OAAO,MAAM;AACrC,YAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,YAAM,YAAY;AAClB,YAAM,aAAa,0BAA0B,OAAO;AAEpD,cAAQ,QAAQ,CAAC,WAAW;AAC1B,cAAM,UAAU,KAAK,SAAS,IAAI,MAAM;AACxC,YAAI,CAAC,QAAS;AAGd,YAAI,QAAQ,OAAO;AACjB,kBAAQ;AAAA,YACN,+BAA+B,MAAM;AAAA,UAAA;AAEvC;AAAA,QACF;AAEA,cAAM,YAAY,KAAK,aAAa,QAAQ,OAAO,CAAC;AAAA,MACtD,CAAC;AAED,cAAQ,YAAY,KAAK;AAAA,IAC3B,CAAC;AAED,SAAK,cAAA;AACL,SAAK,mBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,IAAY,cAAkC;AAC5C,QAAI,CAAC,KAAK,aAAc,QAAO;AAC/B,SAAK,UAAL,KAAK,QAAU,IAAI,YAAY;AAAA,MAC7B,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA;AAAA,MAEX,cAAc,MAAM,KAAK,mBAAA;AAAA,IAAmB,CAC7C;AACD,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,WAAW,MAAuB,QAAiC;;AACzE,eAAK,gBAAL,mBAAkB,OAAO,QAAQ;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAyB;;AAC/B,UAAM,SAAQ,UAAK,WAAL,mBAAa;AAE3B,QAAI,OAAO;AACT,YAAM,QAAQ,CAAC,SAAS;AACtB,cAAM,WAAW,KAAK,YAAY,IAAI;AACtC,YAAI,CAAC,SAAU;AACf,cAAM,SAAS,KAAK,aAAa,SAAS,IAAI,SAAS,KAAK,SAAS,SAAS;AAC9E,aAAK,UAAU,YAAY,MAAM;AACjC,aAAK,gBAAgB,KAAK,MAAM;AAAA,MAClC,CAAC;AACD,WAAK,cAAA;AACL;AAAA,IACF;AAGA,UAAM,SAAS;AAAA,MACb,CAAC,QAAQ,MAAM;AAAA,MACf,CAAC,YAAY,YAAY,YAAY,WAAW;AAAA,MAChD,CAAC,QAAQ,UAAU,aAAa,UAAU,MAAM;AAAA,MAChD,CAAC,cAAc,eAAe,cAAc,WAAW;AAAA,MACvD,CAAC,aAAa,eAAe,YAAY;AAAA;AAAA,MAEzC,GAAI,KAAK,SAAS,IAAI,aAAa,IAC/B,CAAC,CAAC,eAAe,oBAAoB,mBAAmB,CAAC,IACzD,CAAA;AAAA,IAAC;AAGP,WAAO,QAAQ,CAAC,OAAO,eAAe;AACpC,UAAI,aAAa,GAAG;AAClB,cAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,kBAAU,YAAY;AACtB,aAAK,UAAU,YAAY,SAAS;AACpC,aAAK,gBAAgB,KAAK,SAAS;AAAA,MACrC;AAEA,YAAM,QAAQ,CAAC,WAAW;AACxB,cAAM,UAAU,KAAK,SAAS,IAAI,MAAM;AACxC,YAAI,CAAC,QAAS;AAEd,cAAM,SAAS,KAAK,aAAa,QAAQ,OAAO;AAChD,aAAK,UAAU,YAAY,MAAM;AACjC,aAAK,gBAAgB,KAAK,MAAM;AAAA,MAClC,CAAC;AAAA,IACH,CAAC;AAED,SAAK,cAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKQ,YACN,MACqE;;AACrE,UAAM,KAAK,OAAO,SAAS,WAAW,OAAO,KAAK;AAClD,UAAM,MAAM,KAAK,SAAS,IAAI,EAAE;AAEhC,QAAI,CAAC,KAAK;AACR,cAAQ,KAAK,uCAAuC,EAAE,gBAAgB;AACtE,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,SAAS,UAAU;AAC5B,aAAO,EAAE,IAAI,IAAA;AAAA,IACf;AAEA,SAAI,UAAK,aAAL,mBAAe,QAAQ;AACzB,cAAQ,KAAK,+BAA+B,EAAE,8CAA8C;AAAA,IAC9F;AAEA,WAAO,EAAE,IAAI,KAAK,WAAW,KAAA;AAAA,EAC/B;AAAA,EAEQ,aACN,QACA,SACA,WACmB;AACnB,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,OAAO;AACd,WAAO,YAAY;AACnB,WAAO,aAAa,qBAAqB,MAAM;AAE/C,QAAI,uCAAW,MAAM;AACnB,aAAO,YAAY,UAAU;AAAA,IAC/B,WAAW,uCAAW,OAAO;AAC3B,aAAO,cAAc,UAAU;AAAA,IACjC,OAAO;AACL,aAAO,YAAY,QAAQ;AAAA,IAC7B;AAGA,UAAM,MAAM,OAAO,cAAc,KAAK;AACtC,QAAI,KAAK;AACP,UAAI,aAAa,SAAS,IAAI;AAC9B,UAAI,aAAa,UAAU,IAAI;AAAA,IACjC;AAEA,QAAI,uCAAW,SAAS;AACtB,WAAK,eAAe,IAAI,QAAQ,UAAU,OAAO;AAAA,IACnD;AAEA,QAAI,QAAQ,OAAO;AACjB,aAAO,aAAa,iBAAiB,QAAQ;AAC7C,aAAO,aAAa,iBAAiB,OAAO;AAAA,IAC9C;AAEA,WAAO,iBAAiB,SAAS,CAAC,MAAM;AACtC,QAAE,eAAA;AACF,QAAE,gBAAA;AACF,cAAQ,QAAQ,KAAK,QAAQ,MAAM;AAEnC,UAAI,CAAC,QAAQ,MAAO,MAAK,OAAO,SAAS,MAAA;AAAA,IAC3C,CAAC;AAED,QAAI,QAAQ,KAAK,QAAQ,IAAI,MAAM;AACnC,QAAI,CAAC,OAAO;AACV,kCAAY,IAAA;AACZ,WAAK,QAAQ,IAAI,QAAQ,KAAK;AAAA,IAChC;AACA,UAAM,IAAI,MAAM;AAChB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAsB;AAC5B,SAAK,QAAQ,QAAQ,CAAC,SAAS,WAAW;;AACxC,YAAM,UAAU,KAAK,SAAS,IAAI,MAAM;AACxC,UAAI,CAAC,QAAS;AAEd,YAAM,SAAS,KAAK,eAAe,IAAI,MAAM;AAC7C,YAAM,MAAM,aAAa,MAAM;AAC/B,YAAM,aAAa,OAAM,UAAK,SAAL,mBAAW,EAAE,OAAO;AAE7C,YAAM,QAAQ,WAAW,cAAc,eAAe,MAAM,aAAa,QAAQ;AAEjF,cAAQ,QAAQ,CAAC,WAAW;AAC1B,eAAO,QAAQ;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAA+C;AACrD,UAAM,4BAAY,IAAA;AAGlB,QAAI,KAAK,cAAc;AACrB,YAAM,IAAI,eAAe;AAAA,QACvB,IAAI;AAAA,QACJ,MAAM,MAAM;AAAA,QACZ,SAAS;AAAA,QACT,OAAO;AAAA,QACP,SAAS,CAAC,SAAS,WAAW,KAAK,WAAW,SAAS,MAAM;AAAA,QAC7D,UAAU,MAAA;;AAAM,6BAAK,UAAL,mBAAY,cAAa;AAAA;AAAA,QACzC,YAAY,CAAC,WAAW,CAAC,OAAO;AAAA,MAAA,CACjC;AAED,YAAM,IAAI,oBAAoB;AAAA,QAC5B,IAAI;AAAA,QACJ,MAAM,MAAM;AAAA,QACZ,SAAS;AAAA,QACT,OAAO;AAAA,QACP,SAAS,CAAC,SAAS,WAAW,KAAK,WAAW,cAAc,MAAM;AAAA,QAClE,UAAU,MAAA;;AAAM,6BAAK,UAAL,mBAAY,cAAa;AAAA;AAAA,QACzC,YAAY,CAAC,WAAW,CAAC,OAAO;AAAA,MAAA,CACjC;AAGD,YAAM,IAAI,qBAAqB;AAAA,QAC7B,IAAI;AAAA,QACJ,MAAM,MAAM;AAAA,QACZ,SAAS;AAAA,QACT,SAAS,CAAC,WAAW,OAAO,MAAA,EAAQ,MAAA,EAAQ,wBAAA,EAA0B,IAAA;AAAA,QACtE,UAAU,CAAC,WAAW,OAAO,SAAS,YAAY,KAAK,OAAO,SAAS,gBAAgB;AAAA,QACvF,YAAY,CAAC,WACX,CAAC,OAAO,SAAS,YAAY,KAAK,CAAC,OAAO,SAAS,gBAAgB;AAAA,MAAA,CACtE;AAAA,IACH;AAGA,UAAM,IAAI,UAAU;AAAA,MAClB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,gBAAA,EAAkB,IAAA;AAAA,MACtD,UAAU,MAAM;AAAA,MAChB,YAAY,CAAC,WAAW,CAAC,gBAAgB,OAAO,MAAM,SAAS;AAAA,IAAA,CAChE;AAGD,UAAM,IAAI,QAAQ;AAAA,MAChB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,KAAA,EAAO,IAAA;AAAA,MAC3C,UAAU,MAAM;AAAA,MAChB,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,KAAA;AAAA,IAAK,CAC5C;AAGD,UAAM,IAAI,QAAQ;AAAA,MAChB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,KAAA,EAAO,IAAA;AAAA,MAC3C,UAAU,MAAM;AAAA,MAChB,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,KAAA;AAAA,IAAK,CAC5C;AAGD,UAAM,IAAI,YAAY;AAAA,MACpB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,MAAA,EAAQ,cAAc,EAAE,OAAO,GAAG,EAAE,IAAA;AAAA,MAChE,UAAU,CAAC,WAAW,OAAO,SAAS,WAAW,EAAE,OAAO,GAAG;AAAA,MAC7D,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,cAAc,EAAE,OAAO,EAAA,CAAG;AAAA,IAAA,CACjE;AAGD,UAAM,IAAI,YAAY;AAAA,MACpB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,MAAA,EAAQ,cAAc,EAAE,OAAO,GAAG,EAAE,IAAA;AAAA,MAChE,UAAU,CAAC,WAAW,OAAO,SAAS,WAAW,EAAE,OAAO,GAAG;AAAA,MAC7D,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,cAAc,EAAE,OAAO,EAAA,CAAG;AAAA,IAAA,CACjE;AAGD,UAAM,IAAI,YAAY;AAAA,MACpB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,MAAA,EAAQ,cAAc,EAAE,OAAO,GAAG,EAAE,IAAA;AAAA,MAChE,UAAU,CAAC,WAAW,OAAO,SAAS,WAAW,EAAE,OAAO,GAAG;AAAA,MAC7D,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,cAAc,EAAE,OAAO,EAAA,CAAG;AAAA,IAAA,CACjE;AAGD,UAAM,IAAI,aAAa;AAAA,MACrB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,aAAA,EAAe,IAAA;AAAA,MACnD,UAAU,CAAC,WAAW,OAAO,SAAS,WAAW;AAAA,MACjD,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,aAAA;AAAA,IAAa,CACpD;AAGD,UAAM,IAAI,QAAQ;AAAA,MAChB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,WAAA,EAAa,IAAA;AAAA,MACjD,UAAU,CAAC,WAAW,OAAO,SAAS,MAAM;AAAA,MAC5C,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,WAAA;AAAA,IAAW,CAClD;AAGD,UAAM,IAAI,UAAU;AAAA,MAClB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,aAAA,EAAe,IAAA;AAAA,MACnD,UAAU,CAAC,WAAW,OAAO,SAAS,QAAQ;AAAA,MAC9C,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,aAAA;AAAA,IAAa,CACpD;AAGD,UAAM,IAAI,aAAa;AAAA,MACrB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,gBAAA,EAAkB,IAAA;AAAA,MACtD,UAAU,CAAC,WAAW,OAAO,SAAS,WAAW;AAAA,MACjD,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,gBAAA;AAAA,IAAgB,CACvD;AAGD,UAAM,IAAI,UAAU;AAAA,MAClB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,aAAA,EAAe,IAAA;AAAA,MACnD,UAAU,CAAC,WAAW,OAAO,SAAS,QAAQ;AAAA,MAC9C,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,aAAA;AAAA,IAAa,CACpD;AAGD,UAAM,IAAI,QAAQ;AAAA,MAChB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,WAAA,EAAa,IAAA;AAAA,MACjD,UAAU,CAAC,WAAW,OAAO,SAAS,MAAM;AAAA,MAC5C,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,WAAA;AAAA,IAAW,CAClD;AAGD,UAAM,IAAI,cAAc;AAAA,MACtB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,iBAAA,EAAmB,IAAA;AAAA,MACvD,UAAU,CAAC,WAAW,OAAO,SAAS,YAAY;AAAA,MAClD,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,iBAAA;AAAA,IAAiB,CACxD;AAGD,UAAM,IAAI,eAAe;AAAA,MACvB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,kBAAA,EAAoB,IAAA;AAAA,MACxD,UAAU,CAAC,WAAW,OAAO,SAAS,aAAa;AAAA,MACnD,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,kBAAA;AAAA,IAAkB,CACzD;AAGD,UAAM,IAAI,cAAc;AAAA,MACtB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,iBAAA,EAAmB,IAAA;AAAA,MACvD,UAAU,CAAC,WAAW,OAAO,SAAS,YAAY;AAAA,MAClD,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,iBAAA;AAAA,IAAiB,CACxD;AAGD,UAAM,IAAI,aAAa;AAAA,MACrB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,gBAAA,EAAkB,IAAA;AAAA,MACtD,UAAU,CAAC,WAAW,OAAO,SAAS,WAAW;AAAA,MACjD,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,gBAAA;AAAA,IAAgB,CACvD;AAGD,UAAM,IAAI,aAAa;AAAA,MACrB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,aAAa,MAAM,EAAE,IAAA;AAAA,MACzD,UAAU,CAAC,WAAW,OAAO,SAAS,EAAE,WAAW,QAAQ;AAAA,MAC3D,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,aAAa,MAAM;AAAA,IAAA,CAC1D;AAGD,UAAM,IAAI,eAAe;AAAA,MACvB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,aAAa,QAAQ,EAAE,IAAA;AAAA,MAC3D,UAAU,CAAC,WAAW,OAAO,SAAS,EAAE,WAAW,UAAU;AAAA,MAC7D,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,aAAa,QAAQ;AAAA,IAAA,CAC5D;AAGD,UAAM,IAAI,cAAc;AAAA,MACtB,IAAI;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,SAAS;AAAA,MACT,SAAS,CAAC,WAAW,OAAO,QAAQ,aAAa,OAAO,EAAE,IAAA;AAAA,MAC1D,UAAU,CAAC,WAAW,OAAO,SAAS,EAAE,WAAW,SAAS;AAAA,MAC5D,YAAY,CAAC,WAAW,CAAC,OAAO,IAAA,EAAM,aAAa,OAAO;AAAA,IAAA,CAC3D;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAyB;AAC/B,SAAK,OAAO,GAAG,eAAe,MAAM;AAClC,WAAK,mBAAA;AAAA,IACP,CAAC;AAED,SAAK,OAAO,GAAG,mBAAmB,MAAM;AACtC,WAAK,mBAAA;AAAA,IACP,CAAC;AAGD,SAAK,mBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAA2B;AACjC,SAAK,QAAQ,QAAQ,CAAC,SAAS,WAAW;AACxC,YAAM,UAAU,KAAK,SAAS,IAAI,MAAM;AACxC,UAAI,CAAC,QAAS;AAGd,YAAM,SAAS,QAAQ,SAAS,KAAK,MAAM;AAC3C,YAAM,WAAW,QAAQ,WAAW,KAAK,MAAM;AAE/C,cAAQ,QAAQ,CAAC,WAAW;AAC1B,eAAO,UAAU,OAAO,UAAU,MAAM;AACxC,YAAI,QAAQ,MAAO,QAAO,aAAa,iBAAiB,OAAO,MAAM,CAAC;AAGtE,eAAO,WAAW;AAAA,MACpB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;;AACd,eAAK,wBAAL;AACA,SAAK,sBAAsB;AAE3B,eAAK,UAAL,mBAAY;AACZ,SAAK,QAAQ;AACb,SAAK,QAAQ,MAAA;AACb,SAAK,eAAe,MAAA;AAEpB,SAAK,gBAAgB,QAAQ,CAAC,OAAO,GAAG,QAAQ;AAChD,SAAK,kBAAkB,CAAA;AAAA,EACzB;AACF;AC5jBO,MAAM,iBAAiB;AAAA,EAQ5B,YAAY,SAAuB,WAAwB,MAAoB;AAE7E,UAAM,IAAI,CAAC,KAAa,UAAkB,YAAsC;AAC9E,YAAM,aAAa,6BAAM,EAAE,KAAK;AAChC,aAAO,cAAc,eAAe,MAAM,aAAa;AAAA,IACzD;AAEA,SAAK,SAAS;AAAA,MACZ,WAAW,CAAC,OAAO,YACjB,EAAE,mBAAmB,kCAAkC,EAAE,OAAO,SAAS;AAAA,MAC3E,QAAQ,CAAC,UAAU,EAAE,sBAAsB,qBAAqB,EAAE,OAAO;AAAA,MACzE,QAAQ,CAAC,UAAU,EAAE,iBAAiB,4BAA4B,EAAE,OAAO;AAAA,MAC3E,OAAO,MAAM,EAAE,eAAe,IAAI;AAAA,IAAA;AAGpC,SAAK,UAAU,SAAS,cAAc,KAAK;AAC3C,SAAK,QAAQ,YAAY;AAEzB,SAAK,QAAQ,aAAa,QAAQ,QAAQ;AAC1C,SAAK,QAAQ,SAAS;AAEtB,SAAK,OAAO,SAAS,cAAc,MAAM;AACzC,SAAK,KAAK,YAAY;AAEtB,SAAK,QAAQ,SAAS,cAAc,QAAQ;AAC5C,SAAK,MAAM,OAAO;AAClB,SAAK,MAAM,YAAY;AACvB,SAAK,MAAM,SAAS;AACpB,SAAK,MAAM,iBAAiB,SAAS,MAAM,KAAK,QAAQ,aAAa;AAErE,SAAK,QAAQ,OAAO,KAAK,MAAM,KAAK,KAAK;AACzC,cAAU,YAAY,KAAK,OAAO;AAElC,SAAK,cAAc,QAAQ,UAAU,CAAC,UAAU,KAAK,OAAO,KAAK,CAAC;AAElE,SAAK,sBAAsB,6BAAM,kBAAkB,MAAM,KAAK,OAAO,QAAQ,SAAA,CAAU;AAAA,EACzF;AAAA,EAEA,UAAgB;;AACd,eAAK,gBAAL;AACA,eAAK,wBAAL;AACA,SAAK,QAAQ,OAAA;AAAA,EACf;AAAA,EAEQ,OAAO,OAAyB;AACtC,UAAM,EAAE,QAAQ,OAAA,IAAW;AAC3B,UAAM,QAAkB,CAAA;AACxB,QAAI,MAAM,UAAW,OAAM,KAAK,KAAK,OAAO,UAAU,MAAM,WAAW,MAAM,OAAO,CAAC;AACrF,QAAI,OAAQ,OAAM,KAAK,KAAK,OAAO,OAAO,MAAM,CAAC;AACjD,QAAI,OAAQ,OAAM,KAAK,KAAK,OAAO,OAAO,MAAM,CAAC;AAGjD,UAAM,iBAAiB,SAAS,KAAK,SAAS;AAE9C,SAAK,QAAQ,aAAa,0BAA0B,iBAAiB,UAAU,WAAW;AAC1F,SAAK,QAAQ,SAAS,CAAC,MAAM;AAE7B,SAAK,KAAK;AAAA,MACR,GAAG,MAAM,IAAI,CAAC,SAAS;AACrB,cAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,aAAK,cAAc;AACnB,eAAO;AAAA,MACT,CAAC;AAAA,IAAA;AAGH,SAAK,MAAM,SAAS,CAAC;AACrB,SAAK,MAAM,cAAc,KAAK,OAAO,MAAA;AAAA,EACvC;AACF;ACtFO,MAAM,cAAc;AAAA,EAKzB,YACE,eAAmC,CAAA,GACnC,kBAA0C,MAC1C,wBAAiC,MACjC;AACA,SAAK,eAAe;AACpB,SAAK,kBAAkB;AACvB,SAAK,wBAAwB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,SAA8D;AAChF,QAAI,CAAC,KAAK,aAAa,YAAY;AACjC,aAAO;AAAA,IACT;AACA,UAAM,SAAS,KAAK,aAAa,WAAW,OAAO;AACnD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,UAAsB,SAAgD;AACvF,QAAI,CAAC,KAAK,aAAa,WAAW;AAChC,aAAO;AAAA,IACT;AACA,WAAO,KAAK,aAAa,UAAU,UAAU,OAAO;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,SAAiB,SAA4C;AACjF,QAAI,CAAC,KAAK,aAAa,cAAc;AACnC,aAAO;AAAA,IACT;AACA,WAAO,KAAK,aAAa,aAAa,SAAS,OAAO;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,yBAAyB,SAAiB,UAAuC;AACrF,QAAI,CAAC,KAAK,uBAAuB;AAC/B,aAAO;AAAA,IACT;AAGA,QAAI,SAAS,gBAAgB,YAAY;AACvC,aAAO;AAAA,IACT;AAEA,QAAI,KAAK,iBAAiB;AACxB,aAAO,KAAK,gBAAgB,eAAe,OAAO;AAAA,IACpD;AAGA,UAAM,EAAE,sBAAAG,sBAAA,IAAyB,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,QAAA;AACvC,WAAOA,sBAAqB,OAAO;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,SAAiB,SAA0C;AAC9E,QAAI,CAAC,KAAK,aAAa,aAAa;AAClC;AAAA,IACF;AACA,SAAK,aAAa,YAAY,SAAS,OAAO;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aACJ,SACA,iBACA,gBACe;AAEf,UAAM,mBAAmB,MAAM,KAAK,cAAc,OAAO;AACzD,QAAI,qBAAqB,OAAO;AAC9B;AAAA,IACF;AAGA,QAAI,WAAW,MAAM,gBAAgB,gBAAgB;AAGrD,eAAW,MAAM,KAAK,aAAa,UAAU,gBAAgB;AAG7D,QAAI,UAAU,MAAM,KAAK,gBAAgB,SAAS,SAAS,gBAAgB;AAG3E,cAAU,MAAM,KAAK,yBAAyB,SAAS,QAAQ;AAG/D,mBAAe,OAAO;AAGtB,UAAM,KAAK,eAAe,SAAS,gBAAgB;AAAA,EACrD;AACF;AC9GO,SAAS,gBAAgB,QAAwB;AACtD,QAAM,EAAE,cAAc,OAAO;AAC7B,MAAI,UAAU,MAAO,QAAO;AAC5B,SAAO,eAAe,OAAO,MAAM,KAAK;AAAA,IACtC,MAAM,UAAU;AAAA,IAChB,IAAI,UAAU;AAAA,EAAA,CACf;AACH;AAKO,SAAS,iBAAiB,QAAgB,YAAoB,KAAa;AAChF,QAAM,EAAE,WAAW,IAAA,IAAQ,OAAO;AAClC,QAAM,MAAM,UAAU;AAGtB,QAAM,WAAW,MAAM,OAAO,MAAM,IAAI,QAAQ,GAAG,EAAE;AACrD,QAAM,OAAO,KAAK,IAAI,GAAG,WAAW,SAAS;AAE7C,SAAO,eAAe,KAAK,EAAE,MAAM,IAAI,KAAK;AAC9C;AAKO,SAAS,gBAAgB,QAAwB;AACtD,SAAO,OAAO,MAAM,IAAI;AAC1B;AC5BO,MAAM,UAAU;AAAA,EAMrB,YACE,QACA,QACA,kBAA0C,MAC1C;AANF,SAAQ,kBAA0C;AAOhD,SAAK,SAAS;AACd,SAAK,SAAS;AAAA,MACZ,6BAA6B;AAAA,MAC7B,GAAG;AAAA,IAAA;AAEL,SAAK,kBAAkB;AACvB,SAAK,cAAc,IAAI;AAAA,MACrB,OAAO,gBAAgB,CAAA;AAAA,MACvB;AAAA,MACA,KAAK,OAAO;AAAA,IAAA;AAAA,EAEhB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAiC;AAC/B,WAAO,KAAK,OAAO,aAAa,CAAA;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,IAAwC;;AAClD,YAAO,UAAK,OAAO,cAAZ,mBAAuB,KAAK,CAAC,MAAM,EAAE,OAAO;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa,MAAwC;;AAC3D,UAAM,YAAY,gBAAgB,KAAK,MAAM;AAC7C,UAAM,gBAAgB,iBAAiB,KAAK,MAAM;AAClD,UAAM,eAAe,gBAAgB,KAAK,MAAM;AAGhD,UAAM,eAAqE;AAAA,MACzE;AAAA,MACA,QAAQ;AAAA,MACR,UAAU;AAAA,IAAA;AAEZ,UAAM,SAAiC;AAAA,MACrC;AAAA,MACA,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS,KAAK,QAAQ,aAAa,KAAK,KAAK,IAAI,aAAa;AAAA,IAAA;AAIhE,UAAM,UAAS,UAAK,WAAL,mBAAa,QAAQ,cAAc,CAAC,KAAK,QAAgB,OAAO,GAAG,KAAK;AAEvF,WAAO;AAAA,MACL,YAAY,KAAK;AAAA,MACjB,OAAO,KAAK;AAAA,MACZ;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA,OAAO,CAAA;AAAA,IAAC;AAAA,EAEZ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,YAAmC;;AAC/C,UAAM,OAAO,KAAK,YAAY,UAAU;AACxC,QAAI,CAAC,MAAM;AACT,cAAQ,MAAM,gBAAgB,UAAU,cAAc;AACtD;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,aAAa,IAAI;AAEtC,QAAI;AACF,YAAM,KAAK,YAAY;AAAA,QAAa;AAAA,QAAS,KAAK,OAAO;AAAA,QAAiB,CAAC,YACzE,KAAK,cAAc,SAAS,KAAK,cAAc,QAAQ;AAAA,MAAA;AAAA,IAE3D,SAAS,OAAO;AACd,YAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACpE,uBAAK,QAAO,YAAZ,4BAAsB,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,SAAiB,MAA6C;AAClF,UAAM,EAAE,UAAA,IAAc,KAAK,OAAO;AAElC,YAAQ,MAAA;AAAA,MACN,KAAK;AAEH,YAAI,CAAC,UAAU,OAAO;AACpB,eAAK,OACF,MAAA,EACA,YAAY,EAAE,MAAM,UAAU,MAAM,IAAI,UAAU,IAAI,EACtD,cAAc,OAAO,EACrB,IAAA;AAAA,QACL,OAAO;AACL,eAAK,OAAO,SAAS,cAAc,OAAO;AAAA,QAC5C;AACA;AAAA,MAEF,KAAK;AAEH,aAAK,OAAO,SAAS,cAAc,OAAO;AAC1C;AAAA,MAEF,KAAK;AAEH,aAAK,OAAO,SAAS,gBAAgB,KAAK,OAAO,MAAM,IAAI,QAAQ,MAAM,OAAO;AAChF;AAAA,IAAA;AAAA,EAEN;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,QAAqC;AAChD,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,OAAA;AACnC,SAAK,cAAc,IAAI;AAAA,MACrB,OAAO,gBAAgB,KAAK,OAAO,gBAAgB,CAAA;AAAA,MACnD,KAAK;AAAA,MACL,KAAK,OAAO,+BAA+B;AAAA,IAAA;AAAA,EAE/C;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,SAAgC;AACjD,SAAK,kBAAkB;AACvB,SAAK,cAAc,IAAI;AAAA,MACrB,KAAK,OAAO,gBAAgB,CAAA;AAAA,MAC5B;AAAA,MACA,KAAK,OAAO,+BAA+B;AAAA,IAAA;AAAA,EAE/C;AACF;AC3IO,MAAM,qBAAqB;AAAA,EAUhC,YACE,QACA,WACA,cAAsB,KACtB,UACA;AAZF,SAAQ,cAAqC;AAC7C,SAAQ,YAAY;AACpB,SAAQ,gBAAgB;AAWtB,SAAK,SAAS;AACd,SAAK,YAAY;AACjB,SAAK,cAAc;AACnB,SAAK,WAAW;AAChB,SAAK,sBAAA;AACL,SAAK,qBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,uBAA6B;AACnC,UAAM,UAAU,CAAC,UAAyB;AACxC,UAAI,CAAC,KAAK,aAAa,CAAC,KAAK,OAAO,UAAW;AAC/C,UAAI,KAAK,cAAc,KAAK,GAAG;AAC7B,cAAM,gBAAA;AAAA,MACR;AAAA,IACF;AACA,aAAS,iBAAiB,WAAW,SAAS,IAAI;AAClD,SAAK,oBAAoB,MAAM,SAAS,oBAAoB,WAAW,SAAS,IAAI;AAAA,EACtF;AAAA,EAEQ,wBAA8B;AACpC,SAAK,OAAO,GAAG,eAAe,CAAC,EAAE,kBAAkB;AAEjD,UAAI,YAAY,YAAY;AAC1B,cAAM,WAAW,KAAK,iBAAA;AACtB,YAAI,aAAa,KAAK,eAAe,CAAC,KAAK,WAAW;AACpD,eAAK,KAAA;AAAA,QACP;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,mBAA2B;AACjC,UAAM,EAAE,KAAK,UAAA,IAAc,KAAK,OAAO;AACvC,QAAI,UAAU,QAAQ,EAAG,QAAO;AAChC,WAAO,IAAI,YAAY,UAAU,OAAO,GAAG,UAAU,IAAI;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,SAAK,gBAAgB;AACrB,SAAK,kBAAA;AACL,SAAK,aAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,QAAI,CAAC,KAAK,UAAW;AACrB,SAAK,YAAY;AACjB,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,OAAA;AACjB,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA,EAEQ,oBAA0B;AAChC,SAAK,cAAc,SAAS,cAAc,KAAK;AAC/C,SAAK,YAAY,YAAY;AAC7B,SAAK,YAAY,aAAa,QAAQ,SAAS;AAE/C,SAAK,UAAU,QAAQ,CAAC,MAAM,UAAU;AACtC,YAAM,OAAO,SAAS,cAAc,KAAK;AACzC,WAAK,YAAY;AACjB,WAAK,aAAa,QAAQ,QAAQ;AAClC,WAAK,aAAa,cAAc,OAAO,KAAK,CAAC;AAE7C,UAAI,UAAU,KAAK,eAAe;AAChC,aAAK,UAAU,IAAI,QAAQ;AAAA,MAC7B;AAEA,UAAI,KAAK,MAAM;AACb,cAAM,WAAW,SAAS,cAAc,MAAM;AAC9C,iBAAS,YAAY;AACrB,iBAAS,YAAY,KAAK;AAC1B,aAAK,YAAY,QAAQ;AAAA,MAC3B;AAEA,YAAM,WAAW,SAAS,cAAc,MAAM;AAC9C,eAAS,YAAY;AACrB,eAAS,cAAc,KAAK;AAC5B,WAAK,YAAY,QAAQ;AAEzB,UAAI,KAAK,aAAa;AACpB,aAAK,QAAQ,KAAK;AAAA,MACpB;AAEA,WAAK,iBAAiB,SAAS,MAAM,KAAK,WAAW,KAAK,CAAC;AAC3D,WAAK,iBAAiB,cAAc,MAAM,KAAK,cAAc,KAAK,CAAC;AAEnE,WAAK,YAAa,YAAY,IAAI;AAAA,IACpC,CAAC;AAED,aAAS,KAAK,YAAY,KAAK,WAAW;AAAA,EAC5C;AAAA,EAEA,MAAc,eAA8B;AAC1C,UAAM,cAAc,KAAK;AACzB,QAAI,CAAC,YAAa;AAElB,UAAM,EAAE,SAAS,KAAK;AACtB,UAAM,EAAE,KAAA,IAAS,KAAK,MAAM;AAE5B,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,YAAM,SAAS,KAAK,YAAY,IAAI;AACpC,YAAM,mBAAmB;AAAA,QACvB,wBAAiC;AAC/B,iBAAO,IAAI,QAAQ,OAAO,MAAM,OAAO,KAAK,GAAG,OAAO,SAAS,OAAO,GAAG;AAAA,QAC3E;AAAA,MAAA;AAGF,OAAC,EAAE,GAAG,EAAA,IAAM,MAAM,gBAAgB,kBAAyB,aAAa;AAAA,QACtE,WAAW;AAAA,QACX,YAAY,CAAC,OAAO,CAAC,GAAG,KAAA,GAAQ,OAAO;AAAA,MAAA,CACxC;AAAA,IACH,QAAQ;AAEN;AAAA,IACF;AAGA,QAAI,KAAK,gBAAgB,YAAa;AAEtC,gBAAY,MAAM,WAAW;AAC7B,gBAAY,MAAM,OAAO,GAAG,CAAC;AAC7B,gBAAY,MAAM,MAAM,GAAG,CAAC;AAAA,EAC9B;AAAA,EAEQ,WAAW,OAAqB;AACtC,UAAM,OAAO,KAAK,UAAU,KAAK;AACjC,QAAI,QAAQ,KAAK,UAAU;AAEzB,WAAK,kBAAA;AACL,WAAK,SAAS,IAAI;AAAA,IACpB;AACA,SAAK,KAAA;AAAA,EACP;AAAA,EAEQ,cAAc,OAAqB;AACzC,SAAK,gBAAgB;AACrB,SAAK,gBAAA;AAAA,EACP;AAAA,EAEQ,kBAAwB;AAC9B,QAAI,CAAC,KAAK,YAAa;AACvB,UAAM,QAAQ,KAAK,YAAY,iBAAiB,4BAA4B;AAC5E,UAAM,QAAQ,CAAC,MAAM,MAAM;AACzB,UAAI,MAAM,KAAK,eAAe;AAC5B,aAAK,UAAU,IAAI,QAAQ;AAAA,MAC7B,OAAO;AACL,aAAK,UAAU,OAAO,QAAQ;AAAA,MAChC;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,oBAA0B;AAChC,UAAM,EAAE,OAAO,KAAA,IAAS,KAAK;AAC7B,UAAM,EAAE,SAAS,MAAM;AACvB,QAAI,QAAQ,KAAK,MAAM,IAAI,YAAY,OAAO,GAAG,IAAI,MAAM,KAAK,aAAa;AAC3E;AAAA,IACF;AACA,UAAM,KAAK,MAAM,GAAG,OAAO,OAAO,GAAG,IAAI;AACzC,SAAK,SAAS,EAAE;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,OAA+B;AAC3C,QAAI,CAAC,KAAK,UAAW,QAAO;AAE5B,YAAQ,MAAM,KAAA;AAAA,MACZ,KAAK;AACH,cAAM,eAAA;AACN,aAAK,iBAAiB,KAAK,gBAAgB,KAAK,KAAK,UAAU;AAC/D,aAAK,gBAAA;AACL,eAAO;AAAA,MAET,KAAK;AACH,cAAM,eAAA;AACN,aAAK,iBACF,KAAK,gBAAgB,IAAI,KAAK,UAAU,UAAU,KAAK,UAAU;AACpE,aAAK,gBAAA;AACL,eAAO;AAAA,MAET,KAAK;AACH,cAAM,eAAA;AACN,aAAK,WAAW,KAAK,aAAa;AAClC,eAAO;AAAA,MAET,KAAK;AACH,cAAM,eAAA;AACN,aAAK,KAAA;AACL,eAAO;AAAA,IAAA;AAGX,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;;AACd,eAAK,sBAAL;AACA,SAAK,oBAAoB;AACzB,SAAK,KAAA;AAAA,EACP;AACF;ACvPO,SAAS,eAAe,SAA4C;AACzE,MAAI,OAAO,YAAY,UAAU;AAC/B,UAAM,KAAK,SAAS,cAAc,OAAO;AACzC,QAAI,CAAC,IAAI;AACP,YAAM,IAAI,MAAM,sBAAsB,OAAO,EAAE;AAAA,IACjD;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAKO,SAAS,gBAAgB,WAAoC;AAClE,QAAM,MAAM,SAAS,cAAc,KAAK;AACxC,MAAI,WAAW;AACb,QAAI,YAAY;AAAA,EAClB;AACA,SAAO;AACT;AAKO,SAAS,YAAqB;AACnC,SAAO,OAAO,WAAW,eAAe,OAAO,aAAa;AAC9D;ACPO,MAAM,WAAkC;AAAA,EAqB7C,YAAY,SAA4B;AAfxC,SAAQ,iBAAwC;AAChD,SAAQ,YAA8B;AACtC,SAAQ,gBAA6C;AAErD,SAAQ,cAAuC;AAE/C,SAAQ,mBAAwD;AAKhE,SAAQ,gBAAuC;AAK7C,SAAK,UAAU;AAGf,UAAM,cAAc,eAAe,QAAQ,OAAO;AAGlD,SAAK,YAAY,gBAAgB,aAAa;AAC9C,gBAAY,YAAY,KAAK,SAAS;AAGtC,SAAK,eAAe,IAAI,aAAa,KAAK,WAAW,QAAQ,SAAS,OAAO;AAG7E,SAAK,cAAc,IAAI,YAAY,QAAQ,QAAQ,IAAI;AAGvD,SAAK,mBAAmB,IAAI,iBAAA;AAG5B,QAAI,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;AACrD,WAAK,iBAAiB,kBAAkB,QAAQ,SAAS;AAAA,IAC3D;AAGA,QAAI,mBAA0C;AAC9C,QAAI,QAAQ,YAAY,OAAO;AAC7B,yBAAmB,gBAAgB,qBAAqB;AACxD,WAAK,UAAU,YAAY,gBAAgB;AAAA,IAC7C;AACA,UAAM,gBAAgB,KAAK,oBAAoB,QAAQ,OAAO;AAG9D,UAAM,gBAAgB,gBAAgB,6BAA6B;AACnE,SAAK,UAAU,YAAY,aAAa;AAGxC,SAAK,eAAe,QAAQ,UAAU,QAAQ,OAAO,IAAI,aAAa,QAAQ,KAAK;AAGnF,SAAK,cAAc,KAAK,eACpB,IAAI,iBAAiB,KAAK,cAAc,KAAK,WAAW,KAAK,WAAW,IACxE;AAGJ,SAAK,aAAa,IAAI,WAAW;AAAA,MAC/B,SAAS;AAAA,MACT,SAAS,QAAQ;AAAA,MACjB,eAAe,QAAQ;AAAA,MACvB,UAAU,QAAQ;AAAA,MAClB,aAAa,QAAQ;AAAA,MACrB,YAAY,CAAC,GAAI,QAAQ,cAAc,CAAA,GAAK,GAAG,KAAK,iBAAiB,QAAQ;AAAA,MAC7E,UAAU,QAAQ;AAAA,MAClB,OAAO,QAAQ;AAAA,MACf,cAAc,KAAK,gBAAgB;AAAA,MACnC;AAAA,MACA,UAAU,MAAM;AACd,aAAK,gBAAA;AAAA,MACP;AAAA,MACA,UAAU,MAAM;;AACd,yBAAK,SAAQ,aAAb,4BAAwB;AAAA,MAC1B;AAAA,MACA,SAAS,MAAM;;AACb,yBAAK,SAAQ,YAAb,4BAAuB;AAAA,MACzB;AAAA,MACA,QAAQ,MAAM;;AACZ,yBAAK,SAAQ,WAAb,4BAAsB;AAAA,MACxB;AAAA,MACA,WAAW,MAAM;;AACf,yBAAK,SAAQ,cAAb,4BAAyB;AAAA,MAC3B;AAAA,IAAA,CACD;AAGD,SAAK,kBAAkB,IAAI,gBAAgB,KAAK,QAAQ,QAAQ,QAAQ;AAGxE,QAAI,oBAAoB,QAAQ,YAAY,OAAO;AACjD,WAAK,iBAAiB,IAAI;AAAA,QACxB,KAAK;AAAA,QACL;AAAA,QACA,QAAQ;AAAA,QACR,KAAK;AAAA,QACL,KAAK;AAAA,MAAA;AAEP,UAAI,cAAe,MAAK,eAAe,oBAAoB,aAAa;AAAA,IAC1E;AAGA,QAAI,QAAQ,IAAI;AACd,WAAK,OAAO,QAAQ,EAAE;AAAA,IACxB;AAAA,EACF;AAAA;AAAA,EAGQ,oBAAoB,SAA8D;AACxF,QAAI,YAAY,SAAS,EAAC,mCAAS,QAAQ,QAAO;AAClD,SAAK,kBAAL,KAAK,gBAAkB,gBAAgB,4BAA4B;AACnE,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,kBAAwB;;AAE9B,QAAI,KAAK,kBAAkB;AACzB,YAAM,EAAE,MAAM,GAAA,IAAO,KAAK;AAC1B,WAAK,mBAAmB;AAExB,YAAM,QAAQ,KAAK,OAAO,MAAM,IAAI,QAAQ;AAC5C,WAAK,OAAO,SAAS,iBAAiB;AAAA,QACpC,MAAM,KAAK,IAAI,MAAM,KAAK;AAAA,QAC1B,IAAI,KAAK,IAAI,IAAI,KAAK;AAAA,MAAA,CACvB;AAAA,IACH;AAGA,SAAI,UAAK,QAAQ,OAAb,mBAAiB,WAAW;AAC9B,WAAK,gBAAgB,IAAI;AAAA,QACvB,KAAK;AAAA,QACL,KAAK,QAAQ,GAAG;AAAA,QAChB;AAAA,QACA,CAAC,SAAS;;AACR,WAAAJ,MAAA,KAAK,cAAL,gBAAAA,IAAgB,QAAQ,KAAK;AAAA,QAC/B;AAAA,MAAA;AAAA,IAEJ;AAEA,qBAAK,SAAQ,aAAb,4BAAwB;AAAA,EAC1B;AAAA,EAEQ,OAAO,QAA4B;AACzC,SAAK,YAAY,IAAI,UAAU,KAAK,QAAQ,QAAQ,KAAK,eAAe;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAAuB;;AAE7B,UAAM,iBAAiB,KAAK,WAAW,UAAA;AACvC,UAAM,iBAAiB,KAAK,WAAW,QAAA;AACvC,UAAM,EAAE,MAAM,GAAA,IAAO,eAAe,MAAM;AAC1C,SAAK,mBAAmB,EAAE,MAAM,GAAA;AAGhC,SAAK,WAAW,QAAA;AAChB,eAAK,mBAAL,mBAAqB;AACrB,eAAK,kBAAL,mBAAoB;AAGpB,UAAM,gBAAgB,KAAK,UAAU;AAAA,MACnC;AAAA,IAAA;AAEF,UAAM,mBAAmB,KAAK,UAAU,cAAc,sBAAsB;AAG5E,UAAM,mBAAmB,gBAAgB,6BAA6B;AACtE,QAAI,eAAe;AACjB,oBAAc,YAAY,gBAAgB;AAAA,IAC5C,OAAO;AACL,WAAK,UAAU,YAAY,gBAAgB;AAAA,IAC7C;AAEA,UAAM,gBAAgB,KAAK,oBAAoB,KAAK,QAAQ,OAAO;AAGnE,SAAK,aAAa,IAAI,WAAW;AAAA,MAC/B,SAAS;AAAA,MACT,SAAS;AAAA;AAAA,MAET,eAAe;AAAA,MACf,UAAU,KAAK,QAAQ;AAAA,MACvB,aAAa,KAAK,QAAQ;AAAA,MAC1B,YAAY,CAAC,GAAI,KAAK,QAAQ,cAAc,CAAA,GAAK,GAAG,KAAK,iBAAiB,QAAQ;AAAA,MAClF,UAAU,KAAK,QAAQ;AAAA,MACvB,OAAO,KAAK,QAAQ;AAAA,MACpB,cAAc,KAAK,gBAAgB;AAAA,MACnC;AAAA,MACA,UAAU,MAAM;AACd,aAAK,gBAAA;AAAA,MACP;AAAA,MACA,UAAU,MAAM;;AACd,SAAAD,OAAAC,MAAA,KAAK,SAAQ,aAAb,gBAAAD,IAAA,KAAAC,KAAwB;AAAA,MAC1B;AAAA,MACA,SAAS,MAAM;;AACb,SAAAD,OAAAC,MAAA,KAAK,SAAQ,YAAb,gBAAAD,IAAA,KAAAC,KAAuB;AAAA,MACzB;AAAA,MACA,QAAQ,MAAM;;AACZ,SAAAD,OAAAC,MAAA,KAAK,SAAQ,WAAb,gBAAAD,IAAA,KAAAC,KAAsB;AAAA,MACxB;AAAA,MACA,WAAW,MAAM;;AACf,SAAAD,OAAAC,MAAA,KAAK,SAAQ,cAAb,gBAAAD,IAAA,KAAAC,KAAyB;AAAA,MAC3B;AAAA,IAAA,CACD;AAGD,SAAK,kBAAkB,IAAI,gBAAgB,KAAK,QAAQ,KAAK,QAAQ,QAAQ;AAG7E,QAAI,oBAAoB,KAAK,QAAQ,YAAY,OAAO;AACtD,WAAK,iBAAiB,IAAI;AAAA,QACxB,KAAK;AAAA,QACL;AAAA,QACA,KAAK,QAAQ;AAAA,QACb,KAAK;AAAA,QACL,KAAK;AAAA,MAAA;AAEP,UAAI,cAAe,MAAK,eAAe,oBAAoB,aAAa;AAAA,IAC1E;AAGA,QAAI,KAAK,QAAQ,IAAI;AACnB,WAAK,OAAO,KAAK,QAAQ,EAAE;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAAiB;AACnB,WAAO,KAAK,WAAW,UAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAkB;AAChB,WAAO,KAAK,WAAW,QAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAkB;AAChB,WAAO,KAAK,WAAW,QAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,cAAsB;AACpB,WAAO,KAAK,gBAAgB,YAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAA0B,SAAmC;AACtE,UAAM,UAAS,mCAAS,WAAU,KAAK,QAAQ;AAC/C,UAAM,cAAa,mCAAS,eAAc;AAE1C,QAAI,WAAW,cAAc,OAAO,YAAY,UAAU;AACxD,WAAK,gBAAgB,YAAY,SAAS,UAAU;AAAA,IACtD,OAAO;AACL,WAAK,WAAW,WAAW,SAAS,UAAU;AAAA,IAChD;AACA,SAAK,8BAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAuB;AACjC,SAAK,gBAAgB,YAAY,OAAO;AACxC,SAAK,8BAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,gCAAsC;AAC5C,UAAM,SAAS,KAAK,WAAW,UAAA;AAC/B,QAAI,CAAC,UAAU,OAAO,MAAM,UAAU,MAAO;AAC7C,WAAO,SAAS,iBAAiB,OAAO,MAAM,IAAI,QAAQ,IAAI;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,SAAK,WAAW,aAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAmB;AACjB,WAAO,KAAK,WAAW,QAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAeC,WAA0B;AACvC,WAAO,KAAK,gBAAgB,eAAeA,SAAQ;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,MAAsB;AACnC,WAAO,KAAK,gBAAgB,eAAe,IAAI;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,eAAeA,WAA0B;AACvC,WAAO,KAAK,gBAAgB,eAAeA,SAAQ;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA0B;AACxB,UAAM,EAAE,UAAA,IAAc,KAAK,OAAO;AAClC,QAAI,UAAU,MAAO,QAAO;AAC5B,WAAO,eAAe,KAAK,OAAO,MAAM,KAAK;AAAA,MAC3C,MAAM,UAAU;AAAA,MAChB,IAAI,UAAU;AAAA,IAAA,CACf;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,SAAuB;AACnC,SAAK,WAAW,cAAc,OAAO;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAmC;AAC7C,SAAK,OAAO,QAAQ,MAAA,EAAQ,SAAS,OAAO,EAAE,IAAA;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,SAAwC;;AACvD,UAAM,QAAQ,KAAK,QAAQ;AAC3B,UAAM,aAAa,UAAU,QAAQ,UAAY,oCAAO,eAAP,mBAAmB;AACpE,UAAM,UAAU,QAAQ,WAAW,cAAc;AACjD,UAAM,QAAQ,KAAK,OAAO,MAAA,EAAQ,MAAA;AAElC,QAAI,YAAY,QAAQ;AACtB,YAAM,kBAAkB,OAAO,EAAE,IAAA;AAAA,IACnC,OAAO;AACL,YAAM,cAAc,OAAO,EAAE,IAAA;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,YAAY,OAA0B,MAAiC;AAC3E,QAAI,CAAC,KAAK,cAAc;AACtB,cAAQ,KAAK,qEAAqE;AAClF;AAAA,IACF;AAEA,UAAM,KAAK,aAAa,YAAY,OAAO,EAAE,MAAM;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAA6B;;AAC3B,aAAO,UAAK,iBAAL,mBAAmB,wBAAuB;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAoC;;AACxC,YAAM,UAAK,iBAAL,mBAAmB;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,aAA2B;AAExC,SAAK,QAAQ,cAAc;AAC3B,SAAK,WAAW,eAAe,WAAW;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAyB;AACnC,SAAK,WAAW,YAAY,QAAQ;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACpB,WAAO,KAAK,WAAW,WAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,WAAW,MAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,SAAK,WAAW,KAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,KAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,QAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,aAA+B;AACjC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAAwC;AAC/C,SAAK,aAAa,SAAS,KAAK;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,cAAoB;AAClB,SAAK,aAAa,YAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,MAA6B;AAC7C,UAAM,KAAK,YAAY,eAAe,IAAI;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,QAAqC;AAClD,QAAI,KAAK,WAAW;AAClB,WAAK,UAAU,aAAa,MAAM;AAAA,IACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,QAAkC;AACjD,SAAK,iBAAiB,iBAAiB,MAAM;AAE7C,QAAI,KAAK,YAAY;AACnB,WAAK,eAAA;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB,SAAqC;AACrD,SAAK,iBAAiB,kBAAkB,OAAO;AAE/C,QAAI,KAAK,YAAY;AACnB,WAAK,eAAA;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,eAAgD;AAC9C,WAAO,KAAK,iBAAiB,aAAA;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;;AACd,eAAK,mBAAL,mBAAqB;AACrB,eAAK,kBAAL,mBAAoB;AACpB,SAAK,WAAW,QAAA;AAChB,eAAK,gBAAL,mBAAkB;AAElB,eAAK,iBAAL,mBAAmB;AAEnB,SAAK,gBAAgB;AACrB,SAAK,UAAU,OAAA;AAAA,EACjB;AACF;ACjjBA,MAAM,eAAe;AAQrB,MAAM,iBACJ,OAAO,gBAAgB,cAAc,cAAe,MAAM;AAAC;AAKtD,SAAS,sBAA4B;AAC1C,MAAI,OAAO,mBAAmB,aAAa;AACzC,YAAQ,KAAK,+BAA+B;AAC5C;AAAA,EACF;AAEA,MAAI,CAAC,eAAe,IAAI,YAAY,GAAG;AACrC,mBAAe,OAAO,cAAc,iBAAiB;AAAA,EACvD;AACF;AAKO,MAAM,0BAA0B,eAAe;AAAA,EAQpD,cAAc;AACZ,UAAA;AARF,SAAQ,SAA4B;AACpC,SAAQ,WAAuC,CAAA;AAAA,EAQ/C;AAAA,EANA,WAAW,qBAA+B;AACxC,WAAO,CAAC,SAAS,YAAY,QAAQ,aAAa;AAAA,EACpD;AAAA,EAMA,oBAA0B;AACxB,SAAK,WAAA;AAAA,EACP;AAAA,EAEA,uBAA6B;AAC3B,SAAK,cAAA;AAAA,EACP;AAAA,EAEA,yBAAyB,MAAc,UAAyB,UAA+B;AAC7F,QAAI,aAAa,SAAU;AAE3B,YAAQ,MAAA;AAAA,MACN,KAAK;AACH,YAAI,KAAK,UAAU,UAAU;AAC3B,eAAK,OAAO,SAAS,QAA4B;AAAA,QACnD;AACA;AAAA,MACF,KAAK;AACH,YAAI,KAAK,QAAQ;AACf,eAAK,OAAO,YAAY,aAAa,OAAO;AAAA,QAC9C;AACA;AAAA,MACF,KAAK;AACH,YAAI,KAAK,UAAU,UAAU;AAC3B,eAAK,OAAO,YAAY,QAAQ;AAAA,QAClC;AACA;AAAA,MACF,KAAK;AACH,YAAI,KAAK,QAAQ;AACf,eAAK,OAAO,eAAe,YAAY,EAAE;AAAA,QAC3C;AACA;AAAA,IAAA;AAAA,EAEN;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAA2C;AACpD,SAAK,WAAW,EAAE,GAAG,KAAK,UAAU,GAAG,QAAA;AAAA,EACzC;AAAA,EAEQ,aAAmB;AACzB,QAAI,KAAK,OAAQ;AAEjB,UAAM,UAA6B;AAAA,MACjC,SAAS;AAAA,MACT,GAAG,KAAK;AAAA,IAAA;AAIV,UAAM,QAAQ,KAAK,aAAa,OAAO;AACvC,QAAI,eAAe,QAAQ;AAE3B,UAAM,WAAW,KAAK,aAAa,UAAU;AAC7C,QAAI,aAAa,KAAM,SAAQ,WAAW,aAAa;AAEvD,UAAM,OAAO,KAAK,aAAa,MAAM;AACrC,QAAI,cAAc,OAAO;AAEzB,UAAM,cAAc,KAAK,aAAa,aAAa;AACnD,QAAI,qBAAqB,cAAc;AAGvC,UAAM,UAAU,KAAK,aAAa,cAAc;AAChD,QAAI,iBAAiB,UAAU;AAE/B,UAAM,gBAAgB,KAAK,aAAa,qBAAqB;AAK7D,QAAI,uBAAuB,gBAAgB;AAE3C,SAAK,SAAS,IAAI,WAAW,OAAO;AAGpC,SAAK;AAAA,MACH,IAAI,YAAY,kBAAkB;AAAA,QAChC,QAAQ,EAAE,QAAQ,KAAK,OAAA;AAAA,QACvB,SAAS;AAAA,MAAA,CACV;AAAA,IAAA;AAAA,EAEL;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,KAAK,QAAQ;AACf,WAAK,OAAO,QAAA;AACZ,WAAK,SAAS;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,YAA+B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAkB;;AAChB,aAAO,UAAK,WAAL,mBAAa,cAAa;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAyB;;AACvB,aAAO,UAAK,WAAL,mBAAa,cAAa;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,cAAsB;;AACpB,aAAO,UAAK,WAAL,mBAAa,kBAAiB;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAA0B,QAA6C;;AAChF,eAAK,WAAL,mBAAa,WAAW,SAAS,EAAE;EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;;AACnB,eAAK,WAAL,mBAAa;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAmC;;AAC7C,eAAK,WAAL,mBAAa,YAAY;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,SAAwC;;AACvD,eAAK,WAAL,mBAAa,iBAAiB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,OAA0B,MAAiC;;AAC3E,YAAM,UAAK,WAAL,mBAAa,YAAY,OAAO;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAoC;;AACxC,YAAM,UAAK,WAAL,mBAAa;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA6B;;AAC3B,aAAO,UAAK,WAAL,mBAAa,wBAAuB;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;;AACZ,eAAK,WAAL,mBAAa;AAAA,EACf;AACF;AAGA,oBAAA;"}
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 // 图片:必须先于链接,否则 ![alt](src) 会被下面的链接规则从 ! 之后开始匹配,吞成一个 <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}](${src})` : alt;\n });\n\n // 代码\n md = md.replace(/<code[^>]*>(.+?)<\\/code>/gi, '`$1`');\n md = md.replace(/<pre[^>]*><code[^>]*>([\\s\\S]*?)<\\/code><\\/pre>/gi, '```\\n$1\\n```\\n\\n');\n\n // 段落和换行\n md = md.replace(/<br\\s*\\/?>/gi, '\\n');\n md = md.replace(/<p[^>]*>([\\s\\S]*?)<\\/p>/gi, '$1\\n\\n');\n\n // 移除剩余标签\n md = md.replace(/<[^>]+>/g, '');\n\n // 解码 HTML 实体\n md = md.replace(/&amp;/g, '&');\n md = md.replace(/&lt;/g, '<');\n md = md.replace(/&gt;/g, '>');\n md = md.replace(/&quot;/g, '\"');\n md = md.replace(/&#39;/g, \"'\");\n\n // 清理多余空行\n md = md.replace(/\\n{3,}/g, '\\n\\n');\n\n return md.trim();\n}\n","/**\n * MarkdownService - Markdown 解析/序列化服务\n */\nimport {\n elementFromString,\n getHTMLFromFragment,\n type Editor,\n type JSONContent,\n} from '@tiptap/core';\nimport { DOMParser as ProseMirrorDOMParser, Node as ProseMirrorNode } from '@tiptap/pm/model';\nimport type { MarkdownManager } from '@tiptap/markdown';\nimport type { AtriMarkdownConfig } from '../types';\nimport { simpleMarkdownToHtml, simpleHtmlToMarkdown } from '../utils/markdown';\n\nexport class MarkdownService {\n private editor: Editor;\n private config: AtriMarkdownConfig;\n\n constructor(editor: Editor, config: AtriMarkdownConfig = {}) {\n this.editor = editor;\n this.config = {\n enabled: true,\n ...config,\n };\n }\n\n /**\n * Tiptap Markdown 序列化器,未注册 Markdown 扩展时为 null\n */\n private get manager(): MarkdownManager | null {\n return this.editor.markdown ?? null;\n }\n\n /**\n * 获取编辑器内容为 Markdown\n * 自定义节点的输出由其 renderMarkdown 钩子负责(见 ExtensionManager.registerNodeView),\n * 因此这里始终走 Tiptap 序列化器,不对整篇文档做额外替换。\n */\n getMarkdown(): string {\n const manager = this.manager;\n if (!manager) {\n return simpleHtmlToMarkdown(this.editor.getHTML());\n }\n return manager.serialize(this.editor.getJSON());\n }\n\n /**\n * 设置 Markdown 内容\n */\n setMarkdown(content: string, emitUpdate = true): void {\n if (this.manager) {\n this.editor.commands.setContent(content, { contentType: 'markdown', emitUpdate });\n return;\n }\n this.editor.commands.setContent(simpleMarkdownToHtml(content), { emitUpdate });\n }\n\n /**\n * Markdown 转 HTML\n * 与 htmlToMarkdown 方向对称:解析为 JSON 后再按当前 schema 序列化为 HTML\n */\n markdownToHTML(markdown: string): string {\n const manager = this.manager;\n if (!manager) {\n return simpleMarkdownToHtml(markdown);\n }\n const { schema } = this.editor;\n return getHTMLFromFragment(\n ProseMirrorNode.fromJSON(schema, manager.parse(markdown)).content,\n schema\n );\n }\n\n /**\n * HTML 转 Markdown\n */\n htmlToMarkdown(html: string): string {\n const manager = this.manager;\n if (!manager) {\n return simpleHtmlToMarkdown(html);\n }\n const parsed = ProseMirrorDOMParser.fromSchema(this.editor.schema).parse(\n elementFromString(html)\n );\n return manager.serialize(parsed.toJSON());\n }\n\n /**\n * Markdown 转 JSON\n */\n markdownToJSON(markdown: string): JSONContent {\n const manager = this.manager;\n if (manager) {\n return manager.parse(markdown);\n }\n // 降级:返回简单结构\n return {\n type: 'doc',\n content: [\n {\n type: 'paragraph',\n content: [{ type: 'text', text: markdown }],\n },\n ],\n };\n }\n\n /**\n * 是否启用\n */\n isEnabled(): boolean {\n return this.config.enabled !== false;\n }\n}\n","/**\n * ThemeManager - 主题管理\n */\n\n/** 内置亮/暗两套,带字面量提示;仍需允许任意自定义主题名 */\nexport type ThemeType = 'light' | 'dark' | (string & {});\n\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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"}