@pixerate/editor 0.25.0 → 0.26.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 (112) hide show
  1. package/README.md +30 -0
  2. package/THIRD_PARTY_NOTICES.md +11 -0
  3. package/dist/agent/index.cjs +174 -11
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +18 -4
  6. package/dist/agent/index.d.ts +18 -4
  7. package/dist/agent/index.js +167 -4
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/{arrangeShortcuts-B8O3f8-P.d.cts → arrange-BICutYPk.d.cts} +243 -29
  10. package/dist/{arrangeShortcuts-sLlD2rSX.d.ts → arrange-BICutYPk.d.ts} +243 -29
  11. package/dist/builtins-CdPcLwwF.d.cts +176 -0
  12. package/dist/builtins-Chj6zv_O.d.ts +176 -0
  13. package/dist/canvas/index.cjs +3917 -19
  14. package/dist/canvas/index.cjs.map +1 -1
  15. package/dist/canvas/index.d.cts +1232 -7
  16. package/dist/canvas/index.d.ts +1232 -7
  17. package/dist/canvas/index.js +3916 -18
  18. package/dist/canvas/index.js.map +1 -1
  19. package/dist/chunk-3CC7OPUQ.cjs +831 -0
  20. package/dist/chunk-3CC7OPUQ.cjs.map +1 -0
  21. package/dist/{chunk-AL6JMGWW.cjs → chunk-CS6NZWWA.cjs} +24 -24
  22. package/dist/{chunk-AL6JMGWW.cjs.map → chunk-CS6NZWWA.cjs.map} +1 -1
  23. package/dist/{chunk-C7P22VIP.cjs → chunk-EWCOFSCQ.cjs} +17 -6
  24. package/dist/chunk-EWCOFSCQ.cjs.map +1 -0
  25. package/dist/chunk-HDYMLS3X.cjs +409 -0
  26. package/dist/chunk-HDYMLS3X.cjs.map +1 -0
  27. package/dist/{chunk-X4N4WUKB.cjs → chunk-IFHKKVXH.cjs} +150 -38
  28. package/dist/chunk-IFHKKVXH.cjs.map +1 -0
  29. package/dist/{chunk-ECCCYKGL.js → chunk-JOIREKL7.js} +63 -39
  30. package/dist/chunk-JOIREKL7.js.map +1 -0
  31. package/dist/chunk-KRGXUQ2Y.js +1696 -0
  32. package/dist/chunk-KRGXUQ2Y.js.map +1 -0
  33. package/dist/{chunk-QGYDWPRM.js → chunk-MHEPLBFB.js} +2 -2
  34. package/dist/chunk-MR2764X2.js +831 -0
  35. package/dist/chunk-MR2764X2.js.map +1 -0
  36. package/dist/{chunk-7WISYZFO.cjs → chunk-NFVBT3UZ.cjs} +67 -43
  37. package/dist/chunk-NFVBT3UZ.cjs.map +1 -0
  38. package/dist/{chunk-WSQKHXW7.cjs → chunk-NQF32I6Z.cjs} +1209 -457
  39. package/dist/chunk-NQF32I6Z.cjs.map +1 -0
  40. package/dist/{chunk-IXHSYOVY.js → chunk-Q2OHWD2A.js} +638 -866
  41. package/dist/chunk-Q2OHWD2A.js.map +1 -0
  42. package/dist/{chunk-NAXVVS5I.js → chunk-QJSOZFCZ.js} +2 -2
  43. package/dist/{chunk-V3CHHWOE.cjs → chunk-RRREAOVM.cjs} +4 -4
  44. package/dist/{chunk-V3CHHWOE.cjs.map → chunk-RRREAOVM.cjs.map} +1 -1
  45. package/dist/{chunk-UAKDG62Z.js → chunk-SWE3NPOU.js} +116 -4
  46. package/dist/chunk-SWE3NPOU.js.map +1 -0
  47. package/dist/chunk-TXXNBDQX.cjs +1696 -0
  48. package/dist/chunk-TXXNBDQX.cjs.map +1 -0
  49. package/dist/chunk-UBCFIABU.js +409 -0
  50. package/dist/chunk-UBCFIABU.js.map +1 -0
  51. package/dist/{chunk-RR7F5GUM.js → chunk-X7HXL2SB.js} +941 -189
  52. package/dist/chunk-X7HXL2SB.js.map +1 -0
  53. package/dist/{chunk-PO6A6QQH.cjs → chunk-YOMYSFJU.cjs} +742 -970
  54. package/dist/chunk-YOMYSFJU.cjs.map +1 -0
  55. package/dist/{chunk-FH6F67DN.js → chunk-ZRNHGAQT.js} +17 -6
  56. package/dist/{chunk-FH6F67DN.js.map → chunk-ZRNHGAQT.js.map} +1 -1
  57. package/dist/commands/index.cjs +352 -4
  58. package/dist/commands/index.cjs.map +1 -1
  59. package/dist/commands/index.d.cts +37 -108
  60. package/dist/commands/index.d.ts +37 -108
  61. package/dist/commands/index.js +351 -3
  62. package/dist/commands/index.js.map +1 -1
  63. package/dist/compare/index.cjs +3 -3
  64. package/dist/compare/index.js +2 -2
  65. package/dist/{controller-BIJiNoVs.d.cts → controller-CxXGOEmy.d.cts} +209 -13
  66. package/dist/{controller-BIJiNoVs.d.ts → controller-CxXGOEmy.d.ts} +209 -13
  67. package/dist/export/index.cjs +36 -0
  68. package/dist/export/index.cjs.map +1 -0
  69. package/dist/export/index.d.cts +243 -0
  70. package/dist/export/index.d.ts +243 -0
  71. package/dist/export/index.js +36 -0
  72. package/dist/export/index.js.map +1 -0
  73. package/dist/extensions/index.cjs +54 -6
  74. package/dist/extensions/index.cjs.map +1 -1
  75. package/dist/extensions/index.d.cts +7 -2
  76. package/dist/extensions/index.d.ts +7 -2
  77. package/dist/extensions/index.js +59 -11
  78. package/dist/{index-7a6bHTLy.d.ts → index-B273Zllk.d.ts} +211 -207
  79. package/dist/{index-Csd5qI8u.d.cts → index-jcJwmBYt.d.cts} +211 -207
  80. package/dist/index.cjs +144 -32
  81. package/dist/index.cjs.map +1 -1
  82. package/dist/index.d.cts +49 -13
  83. package/dist/index.d.ts +49 -13
  84. package/dist/index.js +136 -24
  85. package/dist/index.js.map +1 -1
  86. package/dist/registry-CED5C0lA.d.cts +204 -0
  87. package/dist/registry-CED5C0lA.d.ts +204 -0
  88. package/dist/{registry-D32LpIc4.d.cts → registry-D0GADKc7.d.cts} +1 -1
  89. package/dist/{registry-evKTP4ls.d.ts → registry-DhVUZioj.d.ts} +1 -1
  90. package/dist/serializers/index.cjs +4 -4
  91. package/dist/serializers/index.d.cts +2 -1
  92. package/dist/serializers/index.d.ts +2 -1
  93. package/dist/serializers/index.js +3 -3
  94. package/dist/spreadsheet/index.cjs +33 -3
  95. package/dist/spreadsheet/index.cjs.map +1 -1
  96. package/dist/spreadsheet/index.d.cts +106 -3
  97. package/dist/spreadsheet/index.d.ts +106 -3
  98. package/dist/spreadsheet/index.js +32 -2
  99. package/package.json +10 -1
  100. package/dist/chunk-7WISYZFO.cjs.map +0 -1
  101. package/dist/chunk-C7P22VIP.cjs.map +0 -1
  102. package/dist/chunk-ECCCYKGL.js.map +0 -1
  103. package/dist/chunk-IXHSYOVY.js.map +0 -1
  104. package/dist/chunk-PO6A6QQH.cjs.map +0 -1
  105. package/dist/chunk-RR7F5GUM.js.map +0 -1
  106. package/dist/chunk-UAKDG62Z.js.map +0 -1
  107. package/dist/chunk-WSQKHXW7.cjs.map +0 -1
  108. package/dist/chunk-X4N4WUKB.cjs.map +0 -1
  109. package/dist/types-DX4Mh4SL.d.cts +0 -217
  110. package/dist/types-DX4Mh4SL.d.ts +0 -217
  111. /package/dist/{chunk-QGYDWPRM.js.map → chunk-MHEPLBFB.js.map} +0 -0
  112. /package/dist/{chunk-NAXVVS5I.js.map → chunk-QJSOZFCZ.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-IFHKKVXH.cjs","../src/extensions/comment-store.ts","../src/extensions/track-changes.ts"],"names":[],"mappings":"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T;AACE;AACA;AACA;AAGK;AACP;AACE;AACQ;AACR;AACA;AAMK;AACP;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEK;AAIM;AACA;AAEA;AAEA;AAMA;AAOP;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAMY;AA6KA;AACH,EAAA;AACA,kBAAA;AAER,EAAA;AACO,IAAA;AACP,EAAA;AAEA,kBAAA;AACO,IAAA;AACL,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AACF,EAAA;AAEA,kBAAA;AAA+C;AAG3C,EAAA;AACG,IAAA;AACA,IAAA;AACC,MAAA;AACF,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACD,IAAA;AACH,EAAA;AACF;AAea;AA4BJ;AACA,EAAA;AACT;AAES;AACF,EAAA;AACD,EAAA;AACF,IAAA;AACM,EAAA;AACN,IAAA;AACF,EAAA;AACF;AAEM;AACJ,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AACA,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AACA,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AACA,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AAEF,EAAA;AACA,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AACF;AAEM;AACH,EAAA;AACC,IAAA;AACA,IAAA;AACA,IAAA;AAEF,EAAA;AACF;AASa;AAEA;AAOA;AACL,EAAA;AACN,EAAA;AACA,EAAA;AACE,IAAA;AACE,MAAA;AACE,QAAA;AAEF,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AACA,EAAA;AAEE,IAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACE,IAAA;AACF,EAAA;AACA,EAAA;AACQ,IAAA;AAGN,IAAA;AACE,MAAA;AACA,MAAA;AACE,QAAA;AACD,MAAA;AACD,MAAA;AACF,IAAA;AACF,EAAA;AACD;AAOY;AACL,EAAA;AACN,EAAA;AACA,EAAA;AACE,IAAA;A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G3E,YAAA;AACF,UAAA;AACA,UAAA;AAA2D,YAAA;AACzD,YAAA;AACa,YAAA;AACO,YAAA;AACgC,YAAA;AACF,YAAA;AAI5C,YAAA;AAC+B,YAAA;AACrC,YAAA;AACA,YAAA;AAEF,UAAA;AACA,UAAA;AACA,UAAA;AACF,QAAA;AACA,QAAA;AACE,UAAA;AACA,UAAA;AAAiB,YAAA;AAEb,cAAA;AACA,cAAA;AAAO,YAAA;AAEX,UAAA;AACF,QAAA;AACA,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AAAO,YAAA;AAEH,cAAA;AAME,gBAAA;AAAmD,cAAA;AAErD,cAAA;AAA8C,YAAA;AAChD,YAAA;AAEE,cAAA;AACA,cAAA;AACA,cAAA;AAAuC,YAAA;AAE3C,UAAA;AACF,QAAA;AACD,MAAA;AACH,IAAA;AACF,EAAA;AACD;AF7lBS;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-IFHKKVXH.cjs","sourcesContent":[null,"/**\n * Who made a comment, reply or tracked change.\n */\nexport interface ReviewAuthor {\n id: string;\n name: string;\n color?: string;\n avatarUrl?: string;\n}\n\n/**\n * Where a comment thread is attached in the document. `from`/`to` are\n * ProseMirror positions and `quote` is the commented text at the time the\n * anchor was last synced. Positions are only meaningful for the current\n * document; the `comment` mark in the document is the source of truth.\n */\nexport interface CommentAnchor {\n from: number;\n to: number;\n quote: string;\n}\n\nexport interface CommentReply {\n id: string;\n author: ReviewAuthor;\n body: string;\n createdAt: number;\n editedAt?: number;\n}\n\nexport interface CommentThread {\n id: string;\n /** `null` while the thread has no text in the document (see `orphaned`). */\n anchor: CommentAnchor | null;\n author: ReviewAuthor;\n createdAt: number;\n /** An empty body marks a draft that has not been posted yet. */\n body: string;\n editedAt?: number;\n replies: CommentReply[];\n resolved: boolean;\n resolvedAt?: number;\n resolvedBy?: ReviewAuthor;\n /**\n * True when the commented text was deleted from the document. Undoing the\n * deletion re-attaches the thread.\n */\n orphaned: boolean;\n /** The quote the thread had before it was orphaned. */\n lastQuote?: string;\n}\n\nexport interface CommentStoreSnapshot {\n /** Threads in document order (anchored first), then orphaned threads. */\n threads: readonly CommentThread[];\n activeThreadId: string | null;\n}\n\n/** JSON shape produced by `CommentStore.toJSON()`. */\nexport interface SerializedComments {\n version: 1;\n threads: CommentThread[];\n}\n\nexport interface CommentStoreOptions {\n threads?: CommentThread[] | SerializedComments;\n generateId?: () => string;\n now?: () => number;\n}\n\nexport interface CreateThreadInput {\n id?: string;\n body: string;\n author: ReviewAuthor;\n anchor?: CommentAnchor | null;\n createdAt?: number;\n}\n\nexport type CommentStoreListener = (snapshot: CommentStoreSnapshot) => void;\n\nlet idCounter = 0;\n\n/**\n * Generates a reasonably unique id without depending on `crypto`.\n */\nexport function createReviewId(prefix = \"c\"): string {\n idCounter = (idCounter + 1) % 1e6;\n return `${prefix}-${Date.now().toString(36)}-${idCounter.toString(36)}-${Math.random()\n .toString(36)\n .slice(2, 8)}`;\n}\n\nfunction sameAnchor(a: CommentAnchor | null, b: CommentAnchor | null): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n return a.from === b.from && a.to === b.to && a.quote === b.quote;\n}\n\nfunction normalizeThread(raw: any): CommentThread {\n return {\n id: String(raw.id),\n anchor: raw.anchor ?? null,\n author: raw.author ?? { id: \"unknown\", name: \"Unknown\" },\n createdAt: Number(raw.createdAt ?? 0),\n body: String(raw.body ?? \"\"),\n ...(raw.editedAt !== undefined ? { editedAt: raw.editedAt } : {}),\n replies: Array.isArray(raw.replies) ? raw.replies.map((r: any) => ({ ...r })) : [],\n resolved: !!raw.resolved,\n ...(raw.resolvedAt !== undefined ? { resolvedAt: raw.resolvedAt } : {}),\n ...(raw.resolvedBy !== undefined ? { resolvedBy: raw.resolvedBy } : {}),\n orphaned: !!raw.orphaned,\n ...(raw.lastQuote !== undefined ? { lastQuote: String(raw.lastQuote) } : {}),\n };\n}\n\n/**\n * Headless, framework-agnostic store of comment threads.\n *\n * The store never talks to a server: persist `toJSON()` yourself and pass it\n * back via `new CommentStore({ threads })` or `load()`. Snapshots are\n * immutable and only replaced when something changes, so `getSnapshot` is safe\n * to use with React's `useSyncExternalStore`. `subscribe` calls the listener\n * immediately (Svelte store contract).\n */\nexport class CommentStore {\n private threads: CommentThread[] = [];\n private activeThreadId: string | null = null;\n private snapshot: CommentStoreSnapshot;\n private listeners = new Set<CommentStoreListener>();\n /** Deleted threads, kept so undoing a \"Delete Comment\" can restore them. */\n private deleted = new Map<string, CommentThread>();\n private generateId: () => string;\n private now: () => number;\n\n constructor(options: CommentStoreOptions = {}) {\n this.generateId = options.generateId ?? (() => createReviewId(\"comment\"));\n this.now = options.now ?? (() => Date.now());\n if (options.threads) {\n this.threads = CommentStore.parse(options.threads);\n }\n this.snapshot = this.computeSnapshot();\n }\n\n /** Parses `toJSON()` output (or a plain thread array). */\n static parse(json: string | CommentThread[] | SerializedComments): CommentThread[] {\n const value = typeof json === \"string\" ? JSON.parse(json) : json;\n const list = Array.isArray(value) ? value : value?.threads;\n if (!Array.isArray(list)) return [];\n return list.filter((t: any) => t && t.id != null).map(normalizeThread);\n }\n\n subscribe = (listener: CommentStoreListener): (() => void) => {\n this.listeners.add(listener);\n listener(this.snapshot);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getSnapshot = (): CommentStoreSnapshot => this.snapshot;\n\n getThread(id: string): CommentThread | undefined {\n return this.threads.find((t) => t.id === id);\n }\n\n createThread(input: CreateThreadInput): CommentThread {\n const thread: CommentThread = {\n id: input.id ?? this.generateId(),\n anchor: input.anchor ?? null,\n author: input.author,\n createdAt: input.createdAt ?? this.now(),\n body: input.body,\n replies: [],\n resolved: false,\n orphaned: false,\n };\n this.threads = [...this.threads.filter((t) => t.id !== thread.id), thread];\n this.deleted.delete(thread.id);\n this.emit();\n return thread;\n }\n\n /** Edits the thread's body, or one of its replies when `replyId` is given. */\n editComment(threadId: string, body: string, replyId?: string): boolean {\n const editedAt = this.now();\n return this.updateThread(threadId, (t) => {\n if (!replyId) return { ...t, body, editedAt };\n if (!t.replies.some((r) => r.id === replyId)) return t;\n return {\n ...t,\n replies: t.replies.map((r) => (r.id === replyId ? { ...r, body, editedAt } : r)),\n };\n });\n }\n\n addReply(\n threadId: string,\n input: { body: string; author: ReviewAuthor; id?: string },\n ): CommentReply | null {\n const reply: CommentReply = {\n id: input.id ?? this.generateId(),\n author: input.author,\n body: input.body,\n createdAt: this.now(),\n };\n const ok = this.updateThread(threadId, (t) => ({\n ...t,\n replies: [...t.replies, reply],\n // Replying to a resolved thread re-opens it, as in Word and Pages.\n resolved: false,\n resolvedAt: undefined,\n resolvedBy: undefined,\n }));\n return ok ? reply : null;\n }\n\n deleteReply(threadId: string, replyId: string): boolean {\n return this.updateThread(threadId, (t) =>\n t.replies.some((r) => r.id === replyId)\n ? { ...t, replies: t.replies.filter((r) => r.id !== replyId) }\n : t,\n );\n }\n\n resolveThread(id: string, resolved = true, by?: ReviewAuthor): boolean {\n return this.updateThread(id, (t) =>\n t.resolved === resolved\n ? t\n : resolved\n ? { ...t, resolved, resolvedAt: this.now(), ...(by ? { resolvedBy: by } : {}) }\n : { ...t, resolved, resolvedAt: undefined, resolvedBy: undefined },\n );\n }\n\n deleteThread(id: string): boolean {\n const thread = this.getThread(id);\n if (!thread) return false;\n this.deleted.set(id, thread);\n this.threads = this.threads.filter((t) => t.id !== id);\n if (this.activeThreadId === id) this.activeThreadId = null;\n this.emit();\n return true;\n }\n\n /** Deletes every thread whose text is no longer in the document. */\n removeOrphanedThreads(): string[] {\n const ids = this.threads.filter((t) => t.orphaned).map((t) => t.id);\n if (ids.length === 0) return ids;\n ids.forEach((id) => this.deleted.set(id, this.getThread(id)!));\n this.threads = this.threads.filter((t) => !t.orphaned);\n if (this.activeThreadId && ids.includes(this.activeThreadId)) {\n this.activeThreadId = null;\n }\n this.emit();\n return ids;\n }\n\n setActiveThread(id: string | null): void {\n const next = id && this.getThread(id) ? id : null;\n if (next === this.activeThreadId) return;\n this.activeThreadId = next;\n this.emit();\n }\n\n /**\n * Updates anchors from the document. Threads missing from `anchors` become\n * orphaned. Threads that were deleted but whose mark re-appeared (e.g. after\n * undoing \"Delete Comment\") are restored. Returns true if anything changed.\n */\n syncAnchors(anchors: Map<string, CommentAnchor>): boolean {\n let changed = false;\n const restored: CommentThread[] = [];\n anchors.forEach((_anchor, id) => {\n if (!this.getThread(id) && this.deleted.has(id)) {\n restored.push(this.deleted.get(id)!);\n this.deleted.delete(id);\n }\n });\n const next = [...this.threads, ...restored].map((t) => {\n const anchor = anchors.get(t.id) ?? null;\n const orphaned = anchor === null;\n if (sameAnchor(t.anchor, anchor) && t.orphaned === orphaned) return t;\n changed = true;\n // Keep the last known anchor quote for orphaned threads so the UI can\n // still show what was commented on.\n return orphaned\n ? { ...t, anchor: null, orphaned, ...(t.anchor ? { lastQuote: t.anchor.quote } : {}) }\n : { ...t, anchor, orphaned };\n });\n if (!changed && restored.length === 0) return false;\n this.threads = next;\n this.emit();\n return true;\n }\n\n /** Replaces all threads, e.g. with data loaded from a server. */\n load(json: string | CommentThread[] | SerializedComments): void {\n this.threads = CommentStore.parse(json);\n this.deleted.clear();\n if (this.activeThreadId && !this.getThread(this.activeThreadId)) {\n this.activeThreadId = null;\n }\n this.emit();\n }\n\n clear(): void {\n this.threads = [];\n this.deleted.clear();\n this.activeThreadId = null;\n this.emit();\n }\n\n toJSON(): SerializedComments {\n return {\n version: 1,\n threads: this.threads.map((t) => ({ ...t, replies: t.replies.map((r) => ({ ...r })) })),\n };\n }\n\n private updateThread(id: string, fn: (t: CommentThread) => CommentThread): boolean {\n const index = this.threads.findIndex((t) => t.id === id);\n if (index === -1) return false;\n const updated = fn(this.threads[index]);\n if (updated === this.threads[index]) return true;\n this.threads = this.threads.map((t, i) => (i === index ? updated : t));\n this.emit();\n return true;\n }\n\n private computeSnapshot(): CommentStoreSnapshot {\n const threads = [...this.threads].sort((a, b) => {\n if (a.anchor && b.anchor) {\n return a.anchor.from - b.anchor.from || a.createdAt - b.createdAt;\n }\n if (a.anchor) return -1;\n if (b.anchor) return 1;\n return a.createdAt - b.createdAt;\n });\n return { threads, activeThreadId: this.activeThreadId };\n }\n\n private emit(): void {\n this.snapshot = this.computeSnapshot();\n const snapshot = this.snapshot;\n this.listeners.forEach((listener) => {\n try {\n listener(snapshot);\n } catch (err) {\n console.error(\"[CommentStore] Error in listener:\", err);\n }\n });\n }\n}\n\nexport function createCommentStore(options?: CommentStoreOptions): CommentStore {\n return new CommentStore(options);\n}\n\n/**\n * Short, human-readable timestamp for review UIs (\"just now\", \"5m ago\",\n * \"3h ago\", then a locale date). Shared by the React and Svelte components.\n */\nexport function formatReviewTime(timestamp: number, now: number = Date.now()): string {\n const seconds = Math.round((now - timestamp) / 1000);\n if (seconds < 45) return \"just now\";\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.round(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n return new Date(timestamp).toLocaleDateString();\n}\n","// Portions ported from wordcraft/crates/engine/src/cmd/moves.rs and wordcraft/crates/engine/src/cmd/review.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { Extension, Mark, mergeAttributes, type Editor } from \"@tiptap/core\";\nimport {\n Plugin,\n PluginKey,\n TextSelection,\n type EditorState,\n type Transaction,\n} from \"@tiptap/pm/state\";\nimport {\n Fragment,\n Mark as ProsemirrorMark,\n NodeRange,\n Slice,\n type ResolvedPos,\n type MarkType,\n type Node as ProsemirrorNode,\n type NodeType,\n type Schema,\n} from \"@tiptap/pm/model\";\nimport {\n AddMarkStep,\n AttrStep,\n Mapping,\n RemoveMarkStep,\n ReplaceAroundStep,\n ReplaceStep,\n Transform,\n canJoin,\n liftTarget,\n type StepMap,\n} from \"@tiptap/pm/transform\";\nimport type { EditorView } from \"@tiptap/pm/view\";\nimport { createReviewId, type ReviewAuthor } from \"./comment-store\";\n\nexport const INSERTION_MARK_NAME = \"insertion\";\nexport const DELETION_MARK_NAME = \"deletion\";\n/** Mark recording a tracked formatting change (mark added/removed on existing text). */\nexport const FORMAT_CHANGE_MARK_NAME = \"formatChange\";\n/** Node attribute recording a tracked block change (type, attributes or wrapping). */\nexport const BLOCK_CHANGE_ATTRIBUTE = \"blockChange\";\n/**\n * Attribute of the insertion and deletion marks linking the two ends of a\n * tracked move: the deletion is where the text moved from, the insertion\n * where it moved to. Serialized as `data-move-id`.\n */\nexport const MOVE_ID_ATTRIBUTE = \"moveId\";\n\n/**\n * Marks that are never treated as formatting: the review marks themselves,\n * comments (`comment`) and bookmarks (`bookmark`). Names are used instead of\n * imports to avoid module cycles.\n */\nconst REVIEW_MARK_NAMES = new Set([\n INSERTION_MARK_NAME,\n DELETION_MARK_NAME,\n FORMAT_CHANGE_MARK_NAME,\n \"comment\",\n \"bookmark\",\n]);\n\n/**\n * Set this transaction meta to `true` to apply a change without tracking it,\n * e.g. `editor.chain().command(({ tr }) => { tr.setMeta(TRACK_CHANGES_SKIP_META, true); return true; }).setContent(…).run()`.\n */\nexport const TRACK_CHANGES_SKIP_META = \"trackChangesSkip\";\n\nexport type TrackedChangeType = \"insertion\" | \"deletion\" | \"format\" | \"block\" | \"move\";\n\n/**\n * How tracked content is rendered by serializers:\n * - `accepted` (default): as if every change were accepted (deleted text is\n * dropped, inserted text kept, new formatting and block types kept).\n * - `original`: as if every change were rejected.\n * - `all`: both inserted and deleted text, current formatting and blocks.\n */\nexport type TrackedChangesView = \"accepted\" | \"original\" | \"all\";\n\nexport interface TrackedChangeAttributes {\n changeId: string;\n authorId: string;\n authorName: string;\n authorColor: string | null;\n createdAt: number;\n /**\n * Insertion/deletion marks only: links a deletion (moved from) and an\n * insertion (moved to) into one tracked move.\n */\n moveId?: string | null;\n}\n\n/** A serializable mark (`mark.toJSON()`). */\nexport interface TrackedMark {\n type: string;\n attrs?: Record<string, any>;\n}\n\n/** Attributes of the `formatChange` mark. */\nexport interface TrackedFormatChangeAttributes extends TrackedChangeAttributes {\n /** Formatting marks before the change. */\n before: TrackedMark[];\n /** Formatting marks after the change. */\n after: TrackedMark[];\n}\n\n/** A textblock's type, attributes and enclosing wrappers (lists, quotes). */\nexport interface TrackedBlockSnapshot {\n type: string;\n attrs: Record<string, any>;\n /** Ancestors from the outermost (below the document) to the parent. */\n wrappers: { type: string; attrs: Record<string, any> }[];\n}\n\n/**\n * Whole-block operation of a `blockChange` record: the block was inserted\n * (rejecting removes it) or deleted (accepting removes it). Produced by\n * `compareDocuments`; absent for type/attribute/wrapping changes.\n */\nexport type TrackedBlockOperation = \"insert\" | \"delete\";\n\n/** Value of the `blockChange` node attribute. */\nexport interface TrackedBlockChangeAttributes extends TrackedChangeAttributes {\n previous: TrackedBlockSnapshot;\n operation?: TrackedBlockOperation;\n}\n\nexport interface TrackedFormatSummary {\n /** Labels of added formatting, e.g. `[\"Bold\"]`. */\n added: string[];\n /** Labels of removed formatting. */\n removed: string[];\n /** Labels of changed formatting (same mark, different attributes), e.g. `[\"Font size\"]`. */\n changed: string[];\n}\n\nexport interface TrackedChange {\n id: string;\n type: TrackedChangeType;\n author: ReviewAuthor;\n createdAt: number;\n /** Start of the first range. */\n from: number;\n /** End of the last range. */\n to: number;\n /**\n * A change can be split into several ranges by other marks or blocks.\n * Block changes cover whole textblock nodes.\n */\n ranges: { from: number; to: number }[];\n text: string;\n /** Human-readable summary, e.g. `Formatted: Bold` or `Changed Paragraph to Heading 2`. */\n description: string;\n /** Formatting changes (`type: \"format\"`). */\n format?: TrackedFormatSummary;\n /**\n * Block changes (`type: \"block\"`): the first block's previous and current\n * state, and the whole-block operation for inserted/deleted blocks.\n */\n block?: {\n previous: TrackedBlockSnapshot;\n current: TrackedBlockSnapshot;\n operation?: TrackedBlockOperation;\n };\n /**\n * Moves (`type: \"move\"`): where the text was moved from (struck through\n * until accepted) and where it was moved to. `ranges` holds both.\n */\n move?: {\n from: { from: number; to: number }[];\n to: { from: number; to: number }[];\n /** The moved text at its source. */\n fromText: string;\n /** The moved text at its destination. */\n toText: string;\n };\n}\n\nexport interface TrackChangesSnapshot {\n enabled: boolean;\n author: ReviewAuthor;\n /** Changes in document order. */\n changes: readonly TrackedChange[];\n /** The change at the selection, if any. */\n currentChangeId: string | null;\n}\n\nexport interface TrackChangesOptions {\n /** Whether tracking is on when the editor is created. */\n enabled: boolean;\n author: ReviewAuthor;\n now: () => number;\n generateId: () => string;\n insertionHTMLAttributes: Record<string, any>;\n deletionHTMLAttributes: Record<string, any>;\n /** HTML attributes (e.g. `class`) of tracked formatting. Replaces the default dotted underline. */\n formatChangeHTMLAttributes: Record<string, any>;\n /** Class added to blocks with a tracked block change (margin bar by default). */\n blockChangeClass: string;\n /** Extra class of wholly inserted blocks (`blockChange.operation: \"insert\"`, from comparisons). */\n blockInsertedClass: string;\n /** Extra class of wholly deleted blocks (`blockChange.operation: \"delete\"`, from comparisons). */\n blockDeletedClass: string;\n /** Track mark changes (bold, links, font size, …) on existing text. Default `true`. */\n trackFormatting: boolean;\n /** Track textblock type/attribute and wrapping (list, quote) changes. Default `true`. */\n trackBlocks: boolean;\n /**\n * Track insertions and deletions inside code blocks by allowing the\n * insertion/deletion marks there. Default `true`.\n */\n trackCodeBlocks: boolean;\n /** Textblock node types that can record block changes. */\n blockTypes: string[];\n /** Extra mark names not tracked as formatting. */\n ignoredMarks: string[];\n /** Node attributes ignored when comparing blocks (e.g. generated ids). */\n ignoredBlockAttributes: string[];\n /**\n * Record a cut and paste (or a drag) of the same text as one tracked move\n * instead of a deletion and an insertion. Default `true`.\n */\n trackMoves: boolean;\n /**\n * Decides whether moved text is big enough to be a move. Default\n * `isMoveSized` (a paragraph, or at least a sentence of two words, as in\n * Word); pass `() => true` to record every cut/paste or drag as a move.\n */\n isMove: (text: string) => boolean;\n /** HTML attributes (e.g. `class`) of moved text, at both ends. Replaces the default green double line. */\n moveHTMLAttributes: Record<string, any>;\n}\n\n/**\n * External store over the editor's tracked-change state. Snapshots only\n * change when the document, selection or tracking state changes, so they can\n * be used with `useSyncExternalStore`. `subscribe` calls the listener\n * immediately (Svelte store contract).\n */\nexport class TrackChangesStore {\n private snapshot: TrackChangesSnapshot;\n private listeners = new Set<(snapshot: TrackChangesSnapshot) => void>();\n\n constructor(initial: TrackChangesSnapshot) {\n this.snapshot = initial;\n }\n\n subscribe = (listener: (snapshot: TrackChangesSnapshot) => void): (() => void) => {\n this.listeners.add(listener);\n listener(this.snapshot);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getSnapshot = (): TrackChangesSnapshot => this.snapshot;\n\n /** @internal Called by the extension's plugin view. */\n set(snapshot: TrackChangesSnapshot): void {\n this.snapshot = snapshot;\n this.listeners.forEach((listener) => {\n try {\n listener(snapshot);\n } catch (err) {\n console.error(\"[TrackChangesStore] Error in listener:\", err);\n }\n });\n }\n}\n\nexport interface TrackChangesStorage {\n store: TrackChangesStore;\n}\n\ninterface TrackChangesPluginState {\n enabled: boolean;\n author: ReviewAuthor;\n}\n\ntype TrackChangesMeta =\n | { type: \"setEnabled\"; enabled: boolean }\n | { type: \"setAuthor\"; author: ReviewAuthor };\n\nexport const trackChangesPluginKey = new PluginKey<TrackChangesPluginState>(\"trackChanges\");\n\ndeclare module \"@tiptap/core\" {\n interface Commands<ReturnType> {\n trackChanges: {\n setTrackChanges: (enabled: boolean) => ReturnType;\n toggleTrackChanges: () => ReturnType;\n setTrackChangesAuthor: (author: ReviewAuthor) => ReturnType;\n /** Accepts one change (single undo step). */\n acceptChange: (id: string) => ReturnType;\n /** Rejects one change (single undo step). */\n rejectChange: (id: string) => ReturnType;\n acceptAllChanges: () => ReturnType;\n /** Accepts a tracked move: removes the source, keeps the destination. */\n acceptMove: (id: string) => ReturnType;\n /** Rejects a tracked move: restores the source, removes the destination. */\n rejectMove: (id: string) => ReturnType;\n rejectAllChanges: () => ReturnType;\n /** Selects the next change after the selection, wrapping around. */\n selectNextChange: () => ReturnType;\n /** Selects the previous change before the selection, wrapping around. */\n selectPreviousChange: () => ReturnType;\n /** Selects a change (a move's destination, a block change's content). */\n selectChange: (id: string) => ReturnType;\n };\n }\n}\n\nfunction changeAttributes(mark: { attrs: Record<string, any> }): TrackedChangeAttributes {\n return mark.attrs as TrackedChangeAttributes;\n}\n\nfunction parseJSON<T>(value: string | null, fallback: T): T {\n if (!value) return fallback;\n try {\n return JSON.parse(value) as T;\n } catch {\n return fallback;\n }\n}\n\nconst sharedAttributes = () => ({\n changeId: {\n default: null,\n parseHTML: (el: HTMLElement) => el.getAttribute(\"data-change-id\"),\n renderHTML: (attrs: any) => ({ \"data-change-id\": attrs.changeId }),\n },\n authorId: {\n default: \"\",\n parseHTML: (el: HTMLElement) => el.getAttribute(\"data-author-id\") ?? \"\",\n renderHTML: (attrs: any) => ({ \"data-author-id\": attrs.authorId }),\n },\n authorName: {\n default: \"\",\n parseHTML: (el: HTMLElement) => el.getAttribute(\"data-author-name\") ?? \"\",\n renderHTML: (attrs: any) => ({ \"data-author-name\": attrs.authorName }),\n },\n authorColor: {\n default: null,\n parseHTML: (el: HTMLElement) => el.getAttribute(\"data-author-color\"),\n renderHTML: (attrs: any) =>\n attrs.authorColor ? { \"data-author-color\": attrs.authorColor } : {},\n },\n createdAt: {\n default: 0,\n parseHTML: (el: HTMLElement) => Number(el.getAttribute(\"data-created-at\") ?? 0),\n renderHTML: (attrs: any) => ({ \"data-created-at\": String(attrs.createdAt) }),\n },\n});\n\nconst moveAttribute = () => ({\n [MOVE_ID_ATTRIBUTE]: {\n default: null,\n parseHTML: (el: HTMLElement) => el.getAttribute(\"data-move-id\"),\n renderHTML: (attrs: any) =>\n attrs[MOVE_ID_ATTRIBUTE] ? { \"data-move-id\": attrs[MOVE_ID_ATTRIBUTE] } : {},\n },\n});\n\ninterface TrackedMarkOptions {\n HTMLAttributes: Record<string, any>;\n /** Attributes used instead of `HTMLAttributes` when the mark is one end of a move. */\n moveHTMLAttributes: Record<string, any>;\n}\n\n/** Default classes of moved text: green double underline (destination) / strike (source), as in Word. */\nexport const MOVE_TO_CLASS =\n \"pixerate-move-to underline decoration-double decoration-emerald-600 text-emerald-700 dark:text-emerald-400\";\nexport const MOVE_FROM_CLASS =\n \"pixerate-move-from line-through decoration-double decoration-emerald-600 text-emerald-700 dark:text-emerald-400\";\n\n/**\n * Text inserted while tracking was on. Rendered as `<ins>`. With a `moveId`\n * it is the destination of a tracked move.\n */\nexport const TrackedInsertion = Mark.create<TrackedMarkOptions>({\n name: INSERTION_MARK_NAME,\n inclusive: false,\n addOptions() {\n return {\n HTMLAttributes: {\n class:\n \"pixerate-insertion underline decoration-emerald-500 text-emerald-700 dark:text-emerald-400\",\n },\n moveHTMLAttributes: { class: MOVE_TO_CLASS },\n };\n },\n addStorage() {\n // tiptap-markdown: inserted text is exported as plain text.\n return { markdown: { serialize: { open: \"\", close: \"\" }, parse: {} } };\n },\n addAttributes: () => ({ ...sharedAttributes(), ...moveAttribute() }),\n parseHTML() {\n return [{ tag: 'ins[data-change-type=\"insertion\"]', priority: 100 }];\n },\n renderHTML({ mark, HTMLAttributes }) {\n const base = mark.attrs[MOVE_ID_ATTRIBUTE]\n ? this.options.moveHTMLAttributes\n : this.options.HTMLAttributes;\n return [\n \"ins\",\n mergeAttributes(base, HTMLAttributes, {\n \"data-change-type\": \"insertion\",\n }),\n 0,\n ];\n },\n});\n\n/**\n * Text deleted while tracking was on. The text stays in the document until\n * the change is accepted. Rendered as `<del>` (struck through). With a\n * `moveId` it is the source of a tracked move.\n */\nexport const TrackedDeletion = Mark.create<TrackedMarkOptions>({\n name: DELETION_MARK_NAME,\n inclusive: false,\n addOptions() {\n return {\n HTMLAttributes: {\n class:\n \"pixerate-deletion line-through decoration-red-500 text-red-600 dark:text-red-400\",\n },\n moveHTMLAttributes: { class: MOVE_FROM_CLASS },\n };\n },\n addStorage() {\n // Only reached when serializing a document that still has deletions\n // (`getEditorMarkdown(editor, { trackedChanges: \"all\" })`).\n return {\n markdown: {\n serialize: { open: \"~~\", close: \"~~\", mixable: true, expelEnclosingWhitespace: true },\n parse: {},\n },\n };\n },\n addAttributes: () => ({ ...sharedAttributes(), ...moveAttribute() }),\n parseHTML() {\n // Higher priority than Strike, which also parses `<del>`.\n return [{ tag: 'del[data-change-type=\"deletion\"]', priority: 100 }];\n },\n renderHTML({ mark, HTMLAttributes }) {\n const base = mark.attrs[MOVE_ID_ATTRIBUTE]\n ? this.options.moveHTMLAttributes\n : this.options.HTMLAttributes;\n return [\n \"del\",\n mergeAttributes(base, HTMLAttributes, {\n \"data-change-type\": \"deletion\",\n }),\n 0,\n ];\n },\n});\n\n/**\n * Existing text whose formatting changed while tracking was on. Stores the\n * formatting before and after the change, rendered as\n * `<span data-change-type=\"format\">` (dotted underline by default).\n */\nexport const TrackedFormatChange = Mark.create<{ HTMLAttributes: Record<string, any> }>({\n name: FORMAT_CHANGE_MARK_NAME,\n inclusive: false,\n addOptions() {\n return {\n HTMLAttributes: {\n class:\n \"pixerate-format-change underline decoration-dotted decoration-2 decoration-sky-500 underline-offset-4\",\n },\n };\n },\n addStorage() {\n return { markdown: { serialize: { open: \"\", close: \"\" }, parse: {} } };\n },\n addAttributes() {\n return {\n ...sharedAttributes(),\n before: {\n default: [],\n parseHTML: (el: HTMLElement) => parseJSON(el.getAttribute(\"data-format-before\"), []),\n renderHTML: (attrs: any) => ({ \"data-format-before\": JSON.stringify(attrs.before ?? []) }),\n },\n after: {\n default: [],\n parseHTML: (el: HTMLElement) => parseJSON(el.getAttribute(\"data-format-after\"), []),\n renderHTML: (attrs: any) => ({ \"data-format-after\": JSON.stringify(attrs.after ?? []) }),\n },\n };\n },\n parseHTML() {\n return [{ tag: 'span[data-change-type=\"format\"]', priority: 100 }];\n },\n renderHTML({ HTMLAttributes }) {\n return [\n \"span\",\n mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {\n \"data-change-type\": \"format\",\n }),\n 0,\n ];\n },\n});\n\n// ---------------------------------------------------------------------------\n// Descriptions\n// ---------------------------------------------------------------------------\n\nconst MARK_LABELS: Record<string, string> = {\n bold: \"Bold\",\n italic: \"Italic\",\n underline: \"Underline\",\n strike: \"Strikethrough\",\n code: \"Code\",\n link: \"Link\",\n highlight: \"Highlight\",\n subscript: \"Subscript\",\n superscript: \"Superscript\",\n textStyle: \"Text style\",\n};\n\nconst TEXT_STYLE_LABELS: Record<string, string> = {\n fontSize: \"Font size\",\n fontFamily: \"Font\",\n color: \"Color\",\n backgroundColor: \"Background color\",\n lineHeight: \"Line height\",\n};\n\nconst BLOCK_LABELS: Record<string, string> = {\n paragraph: \"Paragraph\",\n codeBlock: \"Code block\",\n blockquote: \"Quote\",\n bulletList: \"Bulleted list\",\n orderedList: \"Numbered list\",\n taskList: \"Task list\",\n listItem: \"List item\",\n taskItem: \"Task item\",\n};\n\nfunction humanize(name: string): string {\n const words = name\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/[-_]+/g, \" \")\n .toLowerCase();\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === undefined) return \"null\";\n if (value === null || typeof value !== \"object\") return JSON.stringify(value);\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n const obj = value as Record<string, unknown>;\n return `{${Object.keys(obj)\n .filter((k) => obj[k] !== undefined)\n .sort()\n .map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)\n .join(\",\")}}`;\n}\n\nconst sameValue = (a: unknown, b: unknown) => stableStringify(a) === stableStringify(b);\n\nfunction markLabels(mark: TrackedMark, other?: TrackedMark): string[] {\n if (mark.type === \"textStyle\") {\n const attrs = mark.attrs ?? {};\n const otherAttrs = other?.attrs ?? {};\n const keys = Array.from(new Set([...Object.keys(attrs), ...Object.keys(otherAttrs)])).filter(\n (key) => (other ? !sameValue(attrs[key], otherAttrs[key]) : attrs[key] != null),\n );\n return keys.length ? keys.map((key) => TEXT_STYLE_LABELS[key] ?? humanize(key)) : [\"Text style\"];\n }\n return [MARK_LABELS[mark.type] ?? humanize(mark.type)];\n}\n\nfunction diffFormatting(before: TrackedMark[], after: TrackedMark[]): TrackedFormatSummary {\n const summary: TrackedFormatSummary = { added: [], removed: [], changed: [] };\n const push = (list: string[], labels: string[]) =>\n labels.forEach((label) => {\n if (!list.includes(label)) list.push(label);\n });\n after.forEach((mark) => {\n const old = before.find((m) => m.type === mark.type);\n if (!old) push(summary.added, markLabels(mark));\n else if (!sameValue(old.attrs ?? {}, mark.attrs ?? {})) push(summary.changed, markLabels(mark, old));\n });\n before.forEach((mark) => {\n if (!after.some((m) => m.type === mark.type)) push(summary.removed, markLabels(mark));\n });\n return summary;\n}\n\nfunction mergeSummary(target: TrackedFormatSummary, source: TrackedFormatSummary): void {\n ([\"added\", \"removed\", \"changed\"] as const).forEach((key) =>\n source[key].forEach((label) => {\n if (!target[key].includes(label)) target[key].push(label);\n }),\n );\n}\n\n/** Describes a formatting change, e.g. `Formatted: Bold; Removed formatting: Italic`. */\nexport function describeFormatChange(summary: TrackedFormatSummary): string {\n const parts: string[] = [];\n if (summary.added.length) parts.push(`Formatted: ${summary.added.join(\", \")}`);\n if (summary.removed.length) parts.push(`Removed formatting: ${summary.removed.join(\", \")}`);\n if (summary.changed.length) parts.push(`Changed formatting: ${summary.changed.join(\", \")}`);\n return parts.join(\"; \") || \"Formatted\";\n}\n\nfunction blockLabel(type: string, attrs?: Record<string, any>): string {\n if (type === \"heading\" && attrs?.level) return `Heading ${attrs.level}`;\n return BLOCK_LABELS[type] ?? humanize(type);\n}\n\nconst containerLabels = (wrappers: TrackedBlockSnapshot[\"wrappers\"]) =>\n wrappers\n .filter((w) => w.type !== \"listItem\" && w.type !== \"taskItem\")\n .map((w) => blockLabel(w.type, w.attrs));\n\nconst isPrefix = (a: string[], b: string[]) => a.every((label, i) => b[i] === label);\n\n/** Describes a block change, e.g. `Changed Paragraph to Heading 2`. */\nexport function describeBlockChange(\n previous: TrackedBlockSnapshot,\n current: TrackedBlockSnapshot,\n ignoredAttributes: readonly string[] = [\"id\"],\n): string {\n const parts: string[] = [];\n const before = blockLabel(previous.type, previous.attrs);\n const after = blockLabel(current.type, current.attrs);\n if (before !== after) {\n parts.push(`Changed ${before} to ${after}`);\n } else {\n const keys = Array.from(\n new Set([...Object.keys(previous.attrs ?? {}), ...Object.keys(current.attrs ?? {})]),\n ).filter(\n (key) =>\n key !== BLOCK_CHANGE_ATTRIBUTE &&\n !ignoredAttributes.includes(key) &&\n !(current.type === \"heading\" && key === \"level\") &&\n !sameValue(previous.attrs?.[key], current.attrs?.[key]),\n );\n keys.forEach((key) => {\n if (key === \"textAlign\") {\n parts.push(`Changed alignment to ${humanize(String(current.attrs?.textAlign ?? \"default\"))}`);\n } else if (key === \"styleId\") {\n // Paragraph styles (`ParagraphStyles`): no id means the block's default style.\n parts.push(`Changed style to ${current.attrs?.styleId ?? \"default\"}`);\n } else {\n parts.push(`Changed ${humanize(key).toLowerCase()} of ${after}`);\n }\n });\n }\n const oldTypes = previous.wrappers.map((w) => w.type).join(\"/\");\n const newTypes = current.wrappers.map((w) => w.type).join(\"/\");\n if (oldTypes !== newTypes) {\n const was = containerLabels(previous.wrappers);\n const now = containerLabels(current.wrappers);\n const last = (list: string[]) => list[list.length - 1];\n if (was.length === 0 && now.length > 0) parts.push(`Wrapped in ${last(now)}`);\n else if (now.length === 0 && was.length > 0) parts.push(`Removed from ${last(was)}`);\n else if (now.length > was.length && isPrefix(was, now)) parts.push(`Indented in ${last(now)}`);\n else if (now.length < was.length && isPrefix(now, was)) parts.push(`Outdented from ${last(was)}`);\n else if (was.length && now.length) parts.push(`Changed ${last(was)} to ${last(now)}`);\n else parts.push(\"Changed nesting\");\n }\n return parts.join(\"; \") || `Changed ${after}`;\n}\n\n/** Short label of a change's type for review UIs (`Moved` for a move). */\nexport function trackedChangeLabel(change: Pick<TrackedChange, \"type\">): string {\n switch (change.type) {\n case \"insertion\":\n return \"Inserted\";\n case \"deletion\":\n return \"Deleted\";\n case \"format\":\n return \"Formatted\";\n case \"move\":\n return \"Moved\";\n default:\n return \"Changed\";\n }\n}\n\nfunction truncate(text: string, max = 40): string {\n return text.length > max ? `${text.slice(0, max - 1)}…` : text;\n}\n\n// ---------------------------------------------------------------------------\n// Reading changes\n// ---------------------------------------------------------------------------\n\nfunction toDoc(source: any): ProsemirrorNode | null {\n if (!source) return null;\n if (source.state?.doc) return source.state.doc;\n if (source.doc && typeof source.doc.descendants === \"function\") return source.doc;\n if (typeof source.descendants === \"function\") return source;\n return null;\n}\n\nfunction stripBlockChange(attrs: Record<string, any>): Record<string, any> {\n const copy = { ...attrs };\n delete copy[BLOCK_CHANGE_ATTRIBUTE];\n return copy;\n}\n\n/** Snapshot of the textblock at `pos` (position before the node). */\nfunction blockSnapshot(doc: ProsemirrorNode, pos: number): TrackedBlockSnapshot {\n const $pos = doc.resolve(pos);\n const node = $pos.nodeAfter!;\n const wrappers: TrackedBlockSnapshot[\"wrappers\"] = [];\n for (let depth = 1; depth <= $pos.depth; depth++) {\n const wrapper = $pos.node(depth);\n wrappers.push({ type: wrapper.type.name, attrs: stripBlockChange(wrapper.attrs) });\n }\n return { type: node.type.name, attrs: stripBlockChange(node.attrs), wrappers };\n}\n\nfunction sameBlock(\n a: TrackedBlockSnapshot,\n b: TrackedBlockSnapshot,\n ignored: ReadonlySet<string>,\n): boolean {\n if (a.type !== b.type) return false;\n const filter = (attrs: Record<string, any>) =>\n Object.fromEntries(\n Object.entries(attrs ?? {}).filter(\n ([key]) => key !== BLOCK_CHANGE_ATTRIBUTE && !ignored.has(key),\n ),\n );\n if (!sameValue(filter(a.attrs), filter(b.attrs))) return false;\n if (a.wrappers.length !== b.wrappers.length) return false;\n return a.wrappers.every((w, i) => w.type === b.wrappers[i].type);\n}\n\nconst isMarkChange = (type: string) =>\n type === INSERTION_MARK_NAME || type === DELETION_MARK_NAME || type === FORMAT_CHANGE_MARK_NAME;\n\n/**\n * Lists tracked changes in document order. Accepts an editor, an editor\n * state or a ProseMirror document.\n */\nexport function getTrackedChanges(\n source: Editor | EditorState | ProsemirrorNode | null | undefined,\n): TrackedChange[] {\n const doc = toDoc(source);\n if (!doc) return [];\n const byKey = new Map<string, TrackedChange>();\n\n const add = (\n key: string,\n type: TrackedChangeType,\n attrs: TrackedChangeAttributes,\n from: number,\n to: number,\n text: string,\n ): TrackedChange => {\n let change = byKey.get(key);\n if (!change) {\n change = {\n id: attrs.changeId,\n type,\n author: {\n id: attrs.authorId,\n name: attrs.authorName,\n ...(attrs.authorColor ? { color: attrs.authorColor } : {}),\n },\n createdAt: attrs.createdAt,\n from,\n to,\n ranges: [],\n text: \"\",\n description: \"\",\n };\n byKey.set(key, change);\n }\n const last = change.ranges[change.ranges.length - 1];\n if (last && last.to === from) {\n last.to = to;\n } else {\n change.ranges.push({ from, to });\n }\n change.to = to;\n change.text += text;\n return change;\n };\n\n const blockCounts = new Map<string, number>();\n // Table cells holding each whole-block change (null once a block is outside a table).\n const blockCells = new Map<string, Set<number> | null>();\n doc.descendants((node, pos) => {\n const block = node.attrs?.[BLOCK_CHANGE_ATTRIBUTE] as TrackedBlockChangeAttributes | null;\n if (block?.changeId && node.isTextblock) {\n blockCounts.set(block.changeId, (blockCounts.get(block.changeId) ?? 0) + 1);\n if (block.operation && blockCells.get(block.changeId) !== null) {\n const $pos = doc.resolve(pos);\n let cell = -1;\n for (let depth = $pos.depth; depth > 0 && cell < 0; depth--) {\n const role = $pos.node(depth).type.spec.tableRole;\n if (role === \"cell\" || role === \"header_cell\") cell = $pos.before(depth);\n }\n const cells = blockCells.get(block.changeId) ?? new Set<number>();\n if (cell < 0) blockCells.set(block.changeId, null);\n else blockCells.set(block.changeId, cells.add(cell));\n }\n const change = add(\n `block:${block.changeId}`,\n \"block\",\n block,\n pos,\n pos + node.nodeSize,\n node.textContent,\n );\n if (!change.block) {\n change.block = {\n previous: block.previous,\n current: blockSnapshot(doc, pos),\n ...(block.operation ? { operation: block.operation } : {}),\n };\n }\n }\n if (!node.isInline) return;\n node.marks.forEach((mark) => {\n const markType = mark.type.name;\n if (!isMarkChange(markType)) return;\n const attrs = changeAttributes(mark);\n if (!attrs.changeId) return;\n const text = node.isText ? node.text ?? \"\" : \"\";\n const moveId = markType !== FORMAT_CHANGE_MARK_NAME ? attrs[MOVE_ID_ATTRIBUTE] : null;\n if (moveId) {\n const change = add(`move:${moveId}`, \"move\", { ...attrs, changeId: moveId }, pos, pos + node.nodeSize, text);\n if (!change.move) change.move = { from: [], to: [], fromText: \"\", toText: \"\" };\n const source = markType === DELETION_MARK_NAME;\n const list = source ? change.move.from : change.move.to;\n const last = list[list.length - 1];\n if (last && last.to === pos) last.to = pos + node.nodeSize;\n else list.push({ from: pos, to: pos + node.nodeSize });\n if (source) change.move.fromText += text;\n else change.move.toText += text;\n return;\n }\n const type: TrackedChangeType = markType === FORMAT_CHANGE_MARK_NAME ? \"format\" : (markType as TrackedChangeType);\n const change = add(\n `${type}:${attrs.changeId}`,\n type,\n attrs,\n pos,\n pos + node.nodeSize,\n text,\n );\n if (type === \"format\") {\n const summary = diffFormatting(mark.attrs.before ?? [], mark.attrs.after ?? []);\n if (!change.format) change.format = { added: [], removed: [], changed: [] };\n mergeSummary(change.format, summary);\n }\n });\n });\n\n const changes = Array.from(byKey.values());\n changes.forEach((change) => {\n switch (change.type) {\n case \"insertion\":\n change.description = change.text ? `Inserted \"${truncate(change.text)}\"` : \"Inserted content\";\n break;\n case \"deletion\":\n change.description = change.text ? `Deleted \"${truncate(change.text)}\"` : \"Deleted content\";\n break;\n case \"format\":\n change.description = describeFormatChange(change.format!);\n break;\n case \"move\": {\n const move = change.move!;\n change.text = move.toText || move.fromText;\n change.description = change.text ? `Moved \"${truncate(change.text)}\"` : \"Moved content\";\n break;\n }\n case \"block\": {\n const { previous, current, operation } = change.block!;\n if (operation) {\n const count = blockCounts.get(change.id) ?? 1;\n const verb = operation === \"insert\" ? \"Inserted\" : \"Deleted\";\n const cells = blockCells.get(change.id)?.size;\n change.description = cells\n ? `${verb} ${cells === 1 ? \"a table cell\" : `${cells} table cells`}`\n : count > 1\n ? `${verb} ${count} blocks`\n : `${verb} ${blockLabel(current.type, current.attrs)}`;\n } else {\n change.description = describeBlockChange(previous, current);\n }\n break;\n }\n }\n });\n return changes.sort((a, b) => a.from - b.from || a.to - b.to);\n}\n\n/** The change containing `pos` (defaults to the selection start). Inline changes win over block changes. */\nexport function getTrackedChangeAt(\n state: EditorState,\n pos: number = state.selection.from,\n changes: readonly TrackedChange[] = getTrackedChanges(state.doc),\n): TrackedChange | null {\n const { from, to } = state.selection;\n // A selection exactly covering a change (e.g. after selectNextChange).\n const exact = changes.find((c) => {\n const [start, end] = selectionRange(c);\n return start === from && end === to;\n });\n if (exact) return exact;\n const inline = changes.filter((c) => c.type !== \"block\");\n const blocks = changes.filter((c) => c.type === \"block\");\n return (\n inline.find((c) => c.ranges.some((r) => r.from < pos && pos < r.to)) ??\n inline.find((c) => c.ranges.some((r) => r.from <= pos && pos <= r.to)) ??\n blocks.find((c) => c.ranges.some((r) => r.from < pos && pos < r.to)) ??\n null\n );\n}\n\n/**\n * Text positions selected for a change (block changes select their content,\n * moves their destination).\n */\nfunction selectionRange(change: TrackedChange): [number, number] {\n if (change.type === \"block\") return [change.from + 1, change.to - 1];\n if (change.type === \"move\" && change.move) {\n const ranges = change.move.to.length ? change.move.to : change.move.from;\n if (ranges.length) return [ranges[0].from, ranges[ranges.length - 1].to];\n }\n return [change.from, change.to];\n}\n\nexport function isTrackChangesEnabled(source: Editor | EditorState | null | undefined): boolean {\n const state: EditorState | undefined = (source as any)?.state ?? (source as any);\n if (!state || typeof (state as any).plugins === \"undefined\") return false;\n return trackChangesPluginKey.getState(state)?.enabled ?? false;\n}\n\n/** Returns the tracked-changes store of an editor using `TrackChanges`. */\nexport function getTrackChangesStore(editor: Editor | null | undefined): TrackChangesStore | null {\n return (editor?.storage as any)?.trackChanges?.store ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Accept / reject\n// ---------------------------------------------------------------------------\n\nfunction marksFromJSON(schema: Schema, marks: TrackedMark[] | undefined): ProsemirrorMark[] {\n const result: ProsemirrorMark[] = [];\n (marks ?? []).forEach((json) => {\n try {\n if (schema.marks[json.type]) result.push(schema.markFromJSON(json));\n } catch {\n // Unknown or invalid mark: skip.\n }\n });\n return result;\n}\n\nfunction decideFormat(\n tr: Transform,\n changes: readonly TrackedChange[],\n decision: \"accept\" | \"reject\",\n): void {\n const schema = tr.doc.type.schema;\n const formatType = schema.marks[FORMAT_CHANGE_MARK_NAME];\n if (!formatType) return;\n const ids = new Set(changes.map((c) => c.id));\n const segments: { from: number; to: number; node: ProsemirrorNode; mark: ProsemirrorMark }[] = [];\n changes.forEach((change) =>\n change.ranges.forEach(({ from, to }) =>\n tr.doc.nodesBetween(from, to, (node, pos) => {\n if (!node.isInline) return true;\n const mark = formatType.isInSet(node.marks);\n if (mark && ids.has(mark.attrs.changeId)) {\n segments.push({ from: Math.max(pos, from), to: Math.min(pos + node.nodeSize, to), node, mark });\n }\n return false;\n }),\n ),\n );\n segments.forEach(({ from, to, node, mark }) => {\n tr.removeMark(from, to, formatType);\n if (decision === \"accept\") return;\n const before: TrackedMark[] = mark.attrs.before ?? [];\n const after: TrackedMark[] = mark.attrs.after ?? [];\n const involved = new Set([...before, ...after].map((m) => m.type));\n node.marks.forEach((current) => {\n if (involved.has(current.type.name)) tr.removeMark(from, to, current);\n });\n marksFromJSON(schema, before).forEach((restored) => tr.addMark(from, to, restored));\n });\n}\n\nconst LIST_ITEM_TYPES = new Set([\"listItem\", \"taskItem\"]);\n\n/**\n * The textblock at `contentPos` when it is the first child of a list item:\n * its resolved position and the item's depth.\n */\nfunction listItemAt(\n tr: Transform,\n contentPos: number,\n): { $pos: ResolvedPos; itemDepth: number } | null {\n const $pos = tr.doc.resolve(contentPos);\n const itemDepth = $pos.depth - 1;\n if (itemDepth < 2) return null;\n if (!LIST_ITEM_TYPES.has($pos.node(itemDepth).type.name) || $pos.index(itemDepth) !== 0) {\n return null;\n }\n return { $pos, itemDepth };\n}\n\n/** Nests a list item into its previous sibling (prosemirror-schema-list `sinkListItem`). */\nfunction sinkItem(tr: Transform, contentPos: number): boolean {\n const info = listItemAt(tr, contentPos);\n if (!info) return false;\n const { $pos, itemDepth } = info;\n const list = $pos.node(itemDepth - 1);\n const index = $pos.index(itemDepth - 1);\n const itemType = $pos.node(itemDepth).type;\n if (index === 0 || list.child(index - 1).type !== itemType) return false;\n const nodeBefore = list.child(index - 1);\n const nestedBefore = nodeBefore.lastChild?.type === list.type;\n const inner = Fragment.from(nestedBefore ? itemType.create() : null);\n const slice = new Slice(\n Fragment.from(itemType.create(null, Fragment.from(list.type.create(null, inner)))),\n nestedBefore ? 3 : 1,\n 0,\n );\n const before = $pos.before(itemDepth);\n const after = $pos.after(itemDepth);\n tr.step(\n new ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after, before, after, slice, 1, true),\n );\n return true;\n}\n\n/**\n * Moves a nested list item out to the enclosing list (prosemirror-schema-list\n * `liftToOuterList`); following siblings become its children.\n */\nfunction outdentItem(tr: Transform, contentPos: number): boolean {\n const info = listItemAt(tr, contentPos);\n if (!info) return false;\n const { $pos, itemDepth } = info;\n const listDepth = itemDepth - 1;\n if (listDepth < 2 || !LIST_ITEM_TYPES.has($pos.node(listDepth - 1).type.name)) return false;\n const itemType = $pos.node(itemDepth).type;\n let range = new NodeRange(\n tr.doc.resolve($pos.before(itemDepth)),\n tr.doc.resolve($pos.after(itemDepth)),\n listDepth,\n );\n const end = range.end;\n const endOfList = range.$to.end(range.depth);\n if (end < endOfList) {\n tr.step(\n new ReplaceAroundStep(\n end - 1,\n endOfList,\n end,\n endOfList,\n new Slice(Fragment.from(itemType.create(null, range.parent.copy())), 1, 0),\n 1,\n true,\n ),\n );\n range = new NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);\n }\n const target = liftTarget(range);\n if (target == null) return false;\n tr.lift(range, target);\n return true;\n}\n\n/** Lifts/wraps the textblock whose content starts at `contentPos` to match `wrappers`. */\nfunction restoreWrappers(\n tr: Transform,\n contentPos: number,\n wrappers: TrackedBlockSnapshot[\"wrappers\"],\n): void {\n const schema = tr.doc.type.schema;\n /** Runs `fn`, maps `contentPos` and reports whether it added steps. */\n const step = (fn: () => unknown): boolean => {\n const index = tr.steps.length;\n const result = fn();\n contentPos = tr.mapping.slice(index).map(contentPos);\n return result !== false && tr.steps.length > index;\n };\n const chain = () => {\n const $pos = tr.doc.resolve(contentPos);\n const types: string[] = [];\n for (let depth = 1; depth < $pos.depth; depth++) types.push($pos.node(depth).type.name);\n return types;\n };\n const target = wrappers.map((w) => w.type);\n\n for (let guard = 0; guard < 24; guard++) {\n const current = chain();\n if (current.join(\"/\") === target.join(\"/\")) return;\n\n // Same nesting, different wrapper types (e.g. bulleted → numbered list).\n if (current.length === target.length) {\n const index = current.findIndex((type, i) => type !== target[i]);\n const type = schema.nodes[target[index]];\n if (type && LIST_ITEM_TYPES.has(type.name) === LIST_ITEM_TYPES.has(current[index])) {\n const $pos = tr.doc.resolve(contentPos);\n if (step(() => tr.setNodeMarkup($pos.before(index + 1), type, wrappers[index].attrs))) {\n continue;\n }\n }\n }\n\n let common = 0;\n while (common < current.length && common < target.length && current[common] === target[common]) {\n common++;\n }\n\n if (current.length > common) {\n // Leave wrappers that were not there before: list items move to the\n // outer list, anything else is lifted.\n if (step(() => outdentItem(tr, contentPos))) continue;\n const $from = tr.doc.resolve(contentPos);\n const range = $from.blockRange(tr.doc.resolve($from.end()));\n const lift = range ? liftTarget(range) : null;\n if (!range || lift == null || !step(() => tr.lift(range, lift))) return;\n continue;\n }\n\n // Enter the wrappers that were removed: a list item nests into its\n // previous sibling, anything else is wrapped.\n const missing = wrappers.slice(current.length);\n if (\n missing.length >= 2 &&\n LIST_ITEM_TYPES.has(missing[1].type) &&\n step(() => sinkItem(tr, contentPos))\n ) {\n continue;\n }\n const types = missing.map((w) => ({ type: schema.nodes[w.type], attrs: w.attrs }));\n if (types.some((t) => !t.type)) return;\n const $from = tr.doc.resolve(contentPos);\n const range = $from.blockRange(tr.doc.resolve($from.end()));\n if (!range) return;\n const start = range.start;\n step(() => tr.wrap(range, types as { type: NodeType; attrs: Record<string, any> }[]));\n // Re-join with neighbouring wrappers of the same type (restoring one list).\n const wrapper = tr.doc.nodeAt(start);\n if (!wrapper) return;\n const end = start + wrapper.nodeSize;\n if (tr.doc.resolve(end).nodeAfter?.type === wrapper.type && canJoin(tr.doc, end)) {\n step(() => tr.join(end));\n }\n if (tr.doc.resolve(start).nodeBefore?.type === wrapper.type && canJoin(tr.doc, start)) {\n step(() => tr.join(start));\n }\n return;\n }\n}\n\n/**\n * Removes the blocks of whole-block operations that the decision drops\n * (accepted deletions, rejected insertions). Containers whose textblocks are\n * all removed (list items, lists, quotes, table rows) are removed with them.\n */\nfunction removeBlocks(tr: Transform, ids: ReadonlySet<string>, decision: \"accept\" | \"reject\"): void {\n const removed: TrackedBlockOperation = decision === \"accept\" ? \"delete\" : \"insert\";\n const memo = new Map<ProsemirrorNode, boolean>();\n const removable = (node: ProsemirrorNode): boolean => {\n const cached = memo.get(node);\n if (cached !== undefined) return cached;\n let result: boolean;\n if (node.isTextblock) {\n const record = node.attrs?.[BLOCK_CHANGE_ATTRIBUTE] as TrackedBlockChangeAttributes | null;\n result = !!record?.changeId && record.operation === removed && ids.has(record.changeId);\n } else if (node.isLeaf || node.childCount === 0) {\n result = false;\n } else {\n result = true;\n for (let i = 0; i < node.childCount && result; i++) result = removable(node.child(i));\n }\n memo.set(node, result);\n return result;\n };\n\n const doc = tr.doc;\n if (removable(doc)) {\n const filler = doc.type.contentMatch.defaultType?.createAndFill();\n if (filler) tr.replaceWith(0, doc.content.size, filler);\n return;\n }\n const ranges: { from: number; to: number }[] = [];\n const visit = (node: ProsemirrorNode, contentStart: number) => {\n node.forEach((child, offset) => {\n const from = contentStart + offset;\n if (removable(child)) {\n ranges.push({ from, to: from + child.nodeSize });\n } else if (!child.isTextblock && !child.isLeaf) {\n visit(child, from + 1);\n }\n });\n };\n visit(doc, 0);\n if (ranges.length === 0) return;\n const startIndex = tr.steps.length;\n ranges.reverse().forEach(({ from, to }) => {\n const mapping = tr.mapping.slice(startIndex);\n try {\n tr.delete(mapping.map(from, 1), mapping.map(to, -1));\n } catch {\n // The parent cannot lose this block (e.g. a list item's first paragraph): keep it.\n }\n });\n}\n\nfunction decideBlocks(\n tr: Transform,\n changes: readonly TrackedChange[],\n decision: \"accept\" | \"reject\",\n): void {\n const ids = new Set(changes.map((c) => c.id));\n removeBlocks(tr, ids, decision);\n const items: { contentPos: number; record: TrackedBlockChangeAttributes }[] = [];\n tr.doc.descendants((node, pos) => {\n const record = node.attrs?.[BLOCK_CHANGE_ATTRIBUTE] as TrackedBlockChangeAttributes | null;\n if (node.isTextblock && record?.changeId && ids.has(record.changeId)) {\n items.push({ contentPos: pos + 1, record });\n }\n return !node.isTextblock;\n });\n const schema = tr.doc.type.schema;\n const startIndex = tr.steps.length;\n // Last block first, so structural changes do not shift earlier blocks.\n items.reverse().forEach(({ contentPos: original, record }) => {\n const contentPos = tr.mapping.slice(startIndex).map(original);\n const $pos = tr.doc.resolve(contentPos);\n if (!$pos.parent.isTextblock || $pos.parentOffset !== 0) return;\n const pos = $pos.before();\n if (decision === \"accept\" || record.operation) {\n tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, null);\n return;\n }\n const previous = record.previous;\n const type = previous && schema.nodes[previous.type];\n if (!type) {\n tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, null);\n return;\n }\n const attrs = { ...previous.attrs, [BLOCK_CHANGE_ATTRIBUTE]: null };\n try {\n tr.setNodeMarkup(pos, type, attrs);\n } catch {\n // The content does not fit the previous type (e.g. marks in a code\n // block): convert while dropping incompatible content.\n try {\n tr.setBlockType(pos + 1, pos + 1, type, attrs);\n } catch {\n tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, null);\n }\n }\n try {\n restoreWrappers(tr, tr.mapping.slice(startIndex).map(original), previous.wrappers ?? []);\n } catch {\n // Best effort: the block keeps its current wrappers.\n }\n });\n}\n\n/**\n * Moved text that fills a whole top-level textblock takes the block with it\n * when it is removed, so moving a paragraph and accepting (or rejecting) the\n * move leaves no empty paragraph behind. Returns `false` when the range is\n * not such a block or the block cannot be removed.\n */\nfunction deleteWholeBlock(tr: Transform, from: number, to: number): boolean {\n const $from = tr.doc.resolve(from);\n const block = $from.parent;\n if (\n $from.depth !== 1 ||\n !block.isTextblock ||\n from !== $from.start() ||\n to !== $from.end() ||\n tr.doc.childCount < 2\n ) {\n return false;\n }\n try {\n tr.delete($from.before(), $from.after());\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Accepts or rejects changes on a transform. Formatting is resolved first,\n * then inline marks are removed and text is deleted from the end of the\n * document backwards so positions stay valid; block changes come last.\n */\nfunction applyDecision(\n tr: Transform,\n changes: readonly TrackedChange[],\n decision: \"accept\" | \"reject\",\n): void {\n const schema = tr.doc.type.schema;\n const format = changes.filter((c) => c.type === \"format\");\n if (format.length) decideFormat(tr, format, decision);\n\n const deletions: { from: number; to: number; wholeBlocks?: boolean }[] = [];\n changes.forEach((change) => {\n if (change.type === \"move\" && change.move) {\n // Accepting keeps the destination and removes the source; rejecting\n // restores the source and removes the destination.\n const accept = decision === \"accept\";\n const keepType = schema.marks[accept ? INSERTION_MARK_NAME : DELETION_MARK_NAME];\n (accept ? change.move.to : change.move.from).forEach(({ from, to }) => {\n if (keepType) tr.removeMark(from, to, keepType);\n });\n (accept ? change.move.from : change.move.to).forEach(({ from, to }) =>\n deletions.push({ from, to, wholeBlocks: true }),\n );\n return;\n }\n if (change.type !== \"insertion\" && change.type !== \"deletion\") return;\n const markType = schema.marks[change.type];\n if (!markType) return;\n const keep = (change.type === \"insertion\") === (decision === \"accept\");\n change.ranges.forEach(({ from, to }) => {\n if (keep) {\n tr.removeMark(from, to, markType);\n } else {\n deletions.push({ from, to });\n }\n });\n });\n deletions\n .sort((a, b) => b.from - a.from)\n .forEach(({ from, to, wholeBlocks }) => {\n const start = tr.mapping.map(from);\n const end = tr.mapping.map(to);\n if (wholeBlocks && deleteWholeBlock(tr, start, end)) return;\n tr.delete(start, end);\n });\n\n const blocks = changes.filter((c) => c.type === \"block\");\n if (blocks.length) decideBlocks(tr, blocks, decision);\n}\n\n/**\n * Returns a copy of `doc` with tracked changes resolved for `view`\n * (`accepted` by default). The input document is not modified.\n */\nexport function resolveTrackedChanges(\n doc: ProsemirrorNode,\n view: TrackedChangesView = \"accepted\",\n): ProsemirrorNode {\n if (view === \"all\") return doc;\n const changes = getTrackedChanges(doc);\n if (changes.length === 0) return doc;\n const tr = new Transform(doc);\n applyDecision(tr, changes, view === \"accepted\" ? \"accept\" : \"reject\");\n return tr.doc;\n}\n\n// ---------------------------------------------------------------------------\n// Tracking\n// ---------------------------------------------------------------------------\n\nfunction hasInlineContent(fragment: Fragment): boolean {\n let found = false;\n fragment.descendants((node) => {\n if (found) return false;\n if (node.isInline) found = true;\n return !found;\n });\n return found;\n}\n\n/**\n * Removes deleted text and review marks/attributes from a slice (used for\n * paste), so pasting tracked content pastes it as if all changes were\n * accepted.\n */\nfunction acceptSlice(slice: Slice, schema: Schema): Slice {\n const insertion = schema.marks[INSERTION_MARK_NAME];\n const deletion = schema.marks[DELETION_MARK_NAME];\n const format = schema.marks[FORMAT_CHANGE_MARK_NAME];\n const mapFragment = (fragment: Fragment): Fragment => {\n const children: ProsemirrorNode[] = [];\n fragment.forEach((child) => {\n if (child.isInline) {\n if (deletion && deletion.isInSet(child.marks)) return;\n let marks = child.marks;\n if (insertion) marks = insertion.removeFromSet(marks);\n if (format) marks = format.removeFromSet(marks);\n children.push(marks === child.marks ? child : child.mark(marks));\n } else if (child.attrs?.[BLOCK_CHANGE_ATTRIBUTE]) {\n children.push(\n child.type.create(\n { ...child.attrs, [BLOCK_CHANGE_ATTRIBUTE]: null },\n mapFragment(child.content),\n child.marks,\n ),\n );\n } else {\n children.push(child.copy(mapFragment(child.content)));\n }\n });\n return Fragment.from(children);\n };\n return new Slice(mapFragment(slice.content), slice.openStart, slice.openEnd);\n}\n\n/** Copy of `fragment` without inline marks (adjacent text is merged). */\nfunction stripMarks(fragment: Fragment): Fragment {\n const children: ProsemirrorNode[] = [];\n fragment.forEach((child) => {\n children.push(child.isInline ? child.mark(ProsemirrorMark.none) : child.copy(stripMarks(child.content)));\n });\n return Fragment.fromArray(children);\n}\n\ninterface TrackContext {\n author: ReviewAuthor;\n now: () => number;\n generateId: () => string;\n insertion: MarkType;\n deletion: MarkType;\n /** Present when formatting is tracked. */\n format?: MarkType;\n trackBlocks: boolean;\n ignoredMarks: ReadonlySet<string>;\n ignoredAttributes: ReadonlySet<string>;\n /** Move detection for this batch of transactions, when moves are tracked. */\n moves?: MoveContext;\n}\n\n/** A tracked cut that the next paste can turn into the source of a move. */\ninterface PendingMove {\n /** Change id of the cut's deletion (becomes the move id). */\n id: string;\n /** The cut text (normalized). */\n text: string;\n authorId: string;\n}\n\ninterface MoveContext {\n /** The user action of the batch (`uiEvent` meta). */\n event: \"cut\" | \"paste\" | \"drop\" | null;\n /** The last tracked cut, for a paste. */\n pending: PendingMove | null;\n isMove: (text: string) => boolean;\n /** Set by `trackTransactions` when the batch is a cut that can become a move. */\n cut?: PendingMove;\n}\n\nconst SENTENCE_END = /[.!?…]$/;\nconst TRAILING_CLOSERS = /[\"'”’)]+$/;\nconst TRAILING_SPACES = /[  ]+$/;\n\n/**\n * Whether moved text is big enough to be recorded as a move rather than a\n * deletion and an insertion, as in Word: a paragraph (the text holds a\n * paragraph break) or at least a sentence of two or more words.\n */\nexport function isMoveSized(text: string): boolean {\n const trimmed = text.replace(TRAILING_SPACES, \"\");\n if (trimmed.includes(\"\\n\")) return true;\n const sentence = trimmed.replace(TRAILING_CLOSERS, \"\");\n return SENTENCE_END.test(sentence) && sentence.trim().split(/\\s+/).length >= 2;\n}\n\n/**\n * Drops white space so moved text can be compared across block boundaries\n * and mark splits.\n */\nconst normalizeMoveText = (text: string) => text.replace(/\\s+/g, \"\");\n\n/**\n * The text of a deleted slice that the deletion mark will record: neither\n * the author's own insertions (really deleted) nor text deleted before.\n * Blocks are separated by `\\n`.\n */\nfunction deletedText(slice: Slice, ctx: TrackContext): string {\n let text = \"\";\n let blocks = 0;\n slice.content.descendants((node) => {\n if (node.isTextblock) {\n if (blocks++ > 0) text += \"\\n\";\n return true;\n }\n if (!node.isText) return true;\n const insertion = ctx.insertion.isInSet(node.marks);\n if (insertion && insertion.attrs.authorId === ctx.author.id) return false;\n if (ctx.deletion.isInSet(node.marks)) return false;\n text += node.text ?? \"\";\n return false;\n });\n return text;\n}\n\n/** Text of a document range with blocks separated by `\\n`. */\nconst rangeText = (doc: ProsemirrorNode, from: number, to: number) =>\n doc.textBetween(from, to, \"\\n\", \"\");\n\n/**\n * Adds the move id to the source deletion of a pending cut. Returns `false`\n * when that deletion is no longer in the document.\n */\nfunction linkMoveSource(tr: Transform, pending: PendingMove, ctx: TrackContext): boolean {\n const segments: { from: number; to: number; mark: ProsemirrorMark }[] = [];\n tr.doc.descendants((node, pos) => {\n if (!node.isInline) return true;\n const mark = ctx.deletion.isInSet(node.marks);\n if (mark && mark.attrs.changeId === pending.id && !mark.attrs[MOVE_ID_ATTRIBUTE]) {\n segments.push({ from: pos, to: pos + node.nodeSize, mark });\n }\n return false;\n });\n if (segments.length === 0) return false;\n const text = segments.map((s) => rangeText(tr.doc, s.from, s.to)).join(\"\");\n if (normalizeMoveText(text) !== pending.text) return false;\n segments.forEach(({ from, to, mark }) =>\n tr.addMark(from, to, ctx.deletion.create({ ...mark.attrs, [MOVE_ID_ATTRIBUTE]: pending.id })),\n );\n return true;\n}\n\nfunction newAttributes(ctx: TrackContext): TrackedChangeAttributes {\n return {\n changeId: ctx.generateId(),\n authorId: ctx.author.id,\n authorName: ctx.author.name,\n authorColor: ctx.author.color ?? null,\n createdAt: ctx.now(),\n };\n}\n\nfunction baseAttributes(attrs: TrackedChangeAttributes): TrackedChangeAttributes {\n return {\n changeId: attrs.changeId,\n authorId: attrs.authorId,\n authorName: attrs.authorName,\n authorColor: attrs.authorColor ?? null,\n createdAt: attrs.createdAt,\n };\n}\n\n/**\n * Reuses the attributes of an adjacent change of the same type by the same\n * author, so typing (or backspacing) a word produces one change, not one per\n * character.\n */\nfunction adjacentAttributes(\n doc: ProsemirrorNode,\n from: number,\n to: number,\n type: MarkType,\n ctx: TrackContext,\n): TrackedChangeAttributes | null {\n const candidates = [doc.resolve(from).nodeBefore, doc.resolve(to).nodeAfter];\n for (const node of candidates) {\n const mark = node && type.isInSet(node.marks);\n // Typing next to moved text is not part of the move.\n if (\n mark &&\n mark.attrs.authorId === ctx.author.id &&\n mark.attrs.changeId &&\n !mark.attrs[MOVE_ID_ATTRIBUTE]\n ) {\n return changeAttributes(mark);\n }\n }\n return null;\n}\n\n/**\n * Marks deleted content with the deletion mark so it can be put back.\n * Text inserted by the same author (an untracked-in-effect draft) is dropped,\n * i.e. really deleted. Returns `null` if the slice holds content that cannot\n * carry the mark, in which case the deletion is not tracked.\n */\nfunction markDeletedSlice(\n slice: Slice,\n parentType: NodeType,\n attrs: TrackedChangeAttributes,\n ctx: TrackContext,\n): { slice: Slice; hasContent: boolean } | null {\n let ok = true;\n let hasContent = false;\n const deletionMark = ctx.deletion.create(attrs);\n const mapFragment = (fragment: Fragment, parent: NodeType): Fragment => {\n const children: ProsemirrorNode[] = [];\n fragment.forEach((child) => {\n if (child.isInline) {\n const insertion = ctx.insertion.isInSet(child.marks);\n if (insertion && insertion.attrs.authorId === ctx.author.id) return;\n hasContent = true;\n if (ctx.deletion.isInSet(child.marks)) {\n children.push(child);\n return;\n }\n if (!parent.allowsMarkType(ctx.deletion)) {\n ok = false;\n children.push(child);\n return;\n }\n children.push(child.mark(deletionMark.addToSet(child.marks)));\n } else {\n children.push(child.copy(mapFragment(child.content, child.type)));\n }\n });\n return Fragment.from(children);\n };\n const content = mapFragment(slice.content, parentType);\n if (!ok) return null;\n return { slice: new Slice(content, slice.openStart, slice.openEnd), hasContent };\n}\n\nfunction shouldSkip(tr: Transaction): boolean {\n return (\n !tr.docChanged ||\n tr.getMeta(TRACK_CHANGES_SKIP_META) === true ||\n !!tr.getMeta(trackChangesPluginKey) ||\n // Undo/redo replays already-tracked history.\n !!tr.getMeta(\"history$\") ||\n // Remote collaborative changes were tracked by their author.\n !!tr.getMeta(\"y-sync$\") ||\n // Housekeeping appended by other plugins (e.g. trailing nodes).\n !!tr.getMeta(\"appendedTransaction\")\n );\n}\n\ninterface PendingRestore {\n pos: number;\n slice: Slice;\n parentType: NodeType;\n /** Nothing was inserted in place of the deleted content. */\n pureDelete: boolean;\n backspace: boolean;\n /** The slice is whole table rows, cells or tables (tracked as whole blocks). */\n wholeTable: boolean;\n}\n\ninterface PendingBlock {\n previous: TrackedBlockSnapshot;\n existing: TrackedBlockChangeAttributes | null;\n}\n\n/** Formatting marks of a node, as JSON (review and ignored marks excluded). */\nfunction formattingOf(marks: readonly ProsemirrorMark[], ctx: TrackContext): TrackedMark[] {\n return marks\n .filter((m) => !REVIEW_MARK_NAMES.has(m.type.name) && !ctx.ignoredMarks.has(m.type.name))\n .map((m) => m.toJSON() as TrackedMark);\n}\n\nfunction mergeRanges(ranges: { from: number; to: number }[]): { from: number; to: number }[] {\n const sorted = ranges.filter((r) => r.to > r.from).sort((a, b) => a.from - b.from);\n const merged: { from: number; to: number }[] = [];\n sorted.forEach((range) => {\n const last = merged[merged.length - 1];\n if (last && range.from <= last.to) last.to = Math.max(last.to, range.to);\n else merged.push({ ...range });\n });\n return merged;\n}\n\n/**\n * Records formatting changes on `ranges` (final-document positions) by\n * comparing each piece of text with the same text in `oldDoc`.\n */\nfunction trackFormatting(\n tr: Transaction,\n ranges: { from: number; to: number }[],\n oldDoc: ProsemirrorNode,\n back: Mapping,\n inserted: { from: number; to: number }[],\n ctx: TrackContext,\n): boolean {\n const format = ctx.format!;\n const doc = tr.doc;\n let changed = false;\n let batch: TrackedChangeAttributes | null = null;\n\n mergeRanges(ranges).forEach(({ from: a, to: b }) => {\n const segments: { from: number; to: number; oldNode: ProsemirrorNode; node: ProsemirrorNode }[] = [];\n doc.nodesBetween(a, b, (node, pos, parent) => {\n if (!node.isInline) return true;\n const s = Math.max(a, pos);\n const e = Math.min(b, pos + node.nodeSize);\n if (e <= s || !parent || !parent.type.allowsMarkType(format)) return false;\n // Formatting a tracked insertion is part of the insertion.\n if (ctx.insertion.isInSet(node.marks)) return false;\n if (inserted.some((r) => s < r.to && e > r.from)) return false;\n const os = back.map(s, 1);\n const oe = back.map(e, -1);\n if (oe - os !== e - s) return false;\n oldDoc.nodesBetween(os, oe, (oldNode, oldPos) => {\n if (!oldNode.isInline) return true;\n const ss = Math.max(os, oldPos);\n const ee = Math.min(oe, oldPos + oldNode.nodeSize);\n if (ee > ss && !ctx.insertion.isInSet(oldNode.marks)) {\n segments.push({ from: s + (ss - os), to: s + (ee - os), oldNode, node });\n }\n return false;\n });\n return false;\n });\n\n segments.forEach(({ from, to, oldNode, node }) => {\n const existing = format.isInSet(oldNode.marks);\n const original: TrackedMark[] = existing\n ? (existing.attrs.before as TrackedMark[]) ?? []\n : formattingOf(oldNode.marks, ctx);\n const current = formattingOf(node.marks, ctx);\n if (sameValue(original, current)) {\n if (existing || format.isInSet(node.marks)) {\n tr.removeMark(from, to, format);\n changed = true;\n }\n return;\n }\n let attrs: TrackedChangeAttributes;\n if (existing && existing.attrs.authorId === ctx.author.id && existing.attrs.changeId) {\n attrs = baseAttributes(changeAttributes(existing));\n } else {\n if (!batch) {\n const description = describeFormatChange(diffFormatting(original, current));\n const neighbours = [doc.resolve(from).nodeBefore, doc.resolve(to).nodeAfter];\n for (const neighbour of neighbours) {\n const mark = neighbour && format.isInSet(neighbour.marks);\n if (\n mark &&\n mark.attrs.authorId === ctx.author.id &&\n mark.attrs.changeId &&\n describeFormatChange(diffFormatting(mark.attrs.before ?? [], mark.attrs.after ?? [])) ===\n description\n ) {\n batch = baseAttributes(changeAttributes(mark));\n break;\n }\n }\n batch = batch ?? newAttributes(ctx);\n }\n attrs = batch;\n }\n tr.addMark(from, to, format.create({ ...attrs, before: original, after: current }));\n changed = true;\n });\n });\n return changed;\n}\n\nfunction trackBlocks(\n tr: Transaction,\n pending: Map<number, PendingBlock>,\n ctx: TrackContext,\n): boolean {\n let changed = false;\n let batch: TrackedChangeAttributes | null = null;\n pending.forEach(({ previous, existing }, contentPos) => {\n if (contentPos < 0 || contentPos > tr.doc.content.size) return;\n const $pos = tr.doc.resolve(contentPos);\n const node = $pos.parent;\n if (!node.isTextblock || $pos.parentOffset !== 0 || $pos.depth === 0) return;\n if (!node.type.spec.attrs || !(BLOCK_CHANGE_ATTRIBUTE in node.type.spec.attrs)) return;\n const pos = $pos.before();\n // Inserted/deleted blocks (from a comparison) keep their record.\n if (existing?.operation || node.attrs[BLOCK_CHANGE_ATTRIBUTE]?.operation) return;\n const original = existing?.previous ?? previous;\n const current = blockSnapshot(tr.doc, pos);\n if (sameBlock(original, current, ctx.ignoredAttributes)) {\n if (node.attrs[BLOCK_CHANGE_ATTRIBUTE]) {\n tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, null);\n changed = true;\n }\n return;\n }\n const base =\n existing && existing.authorId === ctx.author.id && existing.changeId\n ? baseAttributes(existing)\n : (batch = batch ?? newAttributes(ctx));\n const value: TrackedBlockChangeAttributes = { ...base, previous: original };\n if (!sameValue(node.attrs[BLOCK_CHANGE_ATTRIBUTE], value)) {\n tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, value);\n changed = true;\n }\n });\n return changed;\n}\n\n/** Records textblocks whose type, attributes or wrappers changed in a step. */\nfunction collectBlockChanges(\n step: ReplaceAroundStep | AttrStep,\n docBefore: ProsemirrorNode,\n docAfter: ProsemirrorNode,\n rest: Mapping,\n pending: Map<number, PendingBlock>,\n ctx: TrackContext,\n): void {\n const record = (oldPos: number, newPos: number) => {\n const key = rest.map(newPos + 1);\n if (pending.has(key)) return;\n const oldNode = docBefore.nodeAt(oldPos)!;\n const previous = blockSnapshot(docBefore, oldPos);\n if (sameBlock(previous, blockSnapshot(docAfter, newPos), ctx.ignoredAttributes)) return;\n pending.set(key, {\n previous,\n existing: (oldNode.attrs[BLOCK_CHANGE_ATTRIBUTE] as TrackedBlockChangeAttributes) ?? null,\n });\n };\n\n if (step instanceof AttrStep) {\n const node = docBefore.nodeAt(step.pos);\n if (\n node?.isTextblock &&\n step.attr !== BLOCK_CHANGE_ATTRIBUTE &&\n !ctx.ignoredAttributes.has(step.attr)\n ) {\n record(step.pos, step.pos);\n }\n return;\n }\n\n const map = step.getMap();\n const back = map.invert();\n const start = map.map(step.from, -1);\n const end = map.map(step.to, 1);\n docAfter.nodesBetween(start, end, (node, pos) => {\n if (!node.isTextblock) return true;\n const oldContent = back.map(pos + 1);\n if (oldContent < 1 || oldContent > docBefore.content.size) return false;\n const $old = docBefore.resolve(oldContent);\n if (\n $old.parent.isTextblock &&\n $old.parentOffset === 0 &&\n $old.parent.content.size === node.content.size\n ) {\n record($old.before(), pos);\n }\n return false;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Table structure\n// ---------------------------------------------------------------------------\n\n/** `tableRole` of table nodes (set by TipTap's table extensions / prosemirror-tables). */\nconst TABLE_ROLES = new Set([\"table\", \"row\", \"cell\", \"header_cell\"]);\n\nconst isTableStructure = (node: ProsemirrorNode) => TABLE_ROLES.has(node.type.spec.tableRole as string);\n\nconst supportsBlockChange = (node: ProsemirrorNode) =>\n !!node.type.spec.attrs && BLOCK_CHANGE_ATTRIBUTE in node.type.spec.attrs;\n\n/** A closed slice of whole tables, rows or cells (e.g. Delete Row / Column / Table). */\nfunction isWholeTableSlice(slice: Slice): boolean {\n if (slice.openStart !== 0 || slice.openEnd !== 0 || slice.content.childCount === 0) return false;\n let whole = true;\n slice.content.forEach((child) => {\n if (!isTableStructure(child)) whole = false;\n });\n return whole;\n}\n\n/** Every textblock inside can carry a `blockChange` record. */\nfunction canMarkTableBlocks(slice: Slice): boolean {\n let ok = true;\n let any = false;\n slice.content.descendants((node) => {\n if (!ok) return false;\n if (node.isTextblock) {\n any = true;\n if (!supportsBlockChange(node)) ok = false;\n return false;\n }\n return true;\n });\n return ok && any;\n}\n\n/** All textblocks are whole-block insertions by the current author. */\nfunction isOwnTableInsertion(slice: Slice, ctx: TrackContext): boolean {\n let own = true;\n slice.content.descendants((node) => {\n if (!own) return false;\n if (node.isTextblock) {\n const record = node.attrs[BLOCK_CHANGE_ATTRIBUTE] as TrackedBlockChangeAttributes | null;\n if (record?.operation !== \"insert\" || record.authorId !== ctx.author.id) own = false;\n return false;\n }\n return true;\n });\n return own;\n}\n\n/**\n * Records whole tables, rows and cells inserted by a `ReplaceStep` (final\n * document ranges). Open (partial) table nodes at the slice edges are\n * skipped: they merge into existing ones.\n */\nfunction collectInsertedTableNodes(\n slice: Slice,\n from: number,\n rest: Mapping,\n out: { from: number; to: number }[],\n): void {\n slice.content.descendants((node, offset) => {\n if (!isTableStructure(node)) return !node.isTextblock;\n const pos = from - slice.openStart + offset;\n if (pos < from) return true; // An open node at the start: look inside.\n const start = rest.map(pos, 1);\n const end = rest.map(pos + node.nodeSize, -1);\n if (end - start === node.nodeSize) out.push({ from: start, to: end });\n return false;\n });\n}\n\n/**\n * Gives every textblock in `from..to` a whole-block `blockChange` record\n * (`operation: \"insert\" | \"delete\"`, one change id), so accepting a deletion\n * or rejecting an insertion removes the rows/cells/table around them.\n */\nfunction markTableBlocks(\n tr: Transform,\n from: number,\n to: number,\n operation: TrackedBlockOperation,\n attrs: TrackedChangeAttributes,\n): boolean {\n const positions: number[] = [];\n tr.doc.nodesBetween(from, to, (node, pos) => {\n if (!node.isTextblock) return true;\n if (pos >= from && pos + node.nodeSize <= to && supportsBlockChange(node)) positions.push(pos);\n return false;\n });\n positions.forEach((pos) => {\n const record: TrackedBlockChangeAttributes = {\n ...baseAttributes(attrs),\n previous: blockSnapshot(tr.doc, pos),\n operation,\n };\n tr.setNodeAttribute(pos, BLOCK_CHANGE_ATTRIBUTE, record);\n });\n return positions.length > 0;\n}\n\n/**\n * Builds a transaction that turns the user's raw edits into tracked changes:\n * inserted content gets the insertion mark, deleted content is put back with\n * the deletion mark, formatting changes on existing text get the\n * `formatChange` mark and textblock type/wrapping changes get the\n * `blockChange` attribute. Paragraph splits and joins are not tracked.\n */\nfunction trackTransactions(\n transactions: readonly Transaction[],\n oldState: EditorState,\n newState: EditorState,\n ctx: TrackContext,\n): Transaction | null {\n const maps: StepMap[] = [];\n transactions.forEach((t) => maps.push(...(t.mapping.maps as StepMap[])));\n\n const inserted: { from: number; to: number }[] = [];\n const restores: PendingRestore[] = [];\n const tableInserts: { from: number; to: number }[] = [];\n const formatRanges: { from: number; to: number }[] = [];\n const blocks = new Map<number, PendingBlock>();\n let mapIndex = 0;\n const single =\n transactions.length === 1 && transactions[0].steps.length === 1;\n\n transactions.forEach((t) => {\n const skip = shouldSkip(t);\n t.steps.forEach((step, i) => {\n const index = mapIndex + i;\n if (skip) return;\n const rest = new Mapping(maps.slice(index + 1));\n const docBefore = t.docs[i];\n\n if (step instanceof AddMarkStep || step instanceof RemoveMarkStep) {\n const name = step.mark.type.name;\n if (ctx.format && !REVIEW_MARK_NAMES.has(name) && !ctx.ignoredMarks.has(name)) {\n formatRanges.push({ from: rest.map(step.from, 1), to: rest.map(step.to, -1) });\n }\n return;\n }\n if (step instanceof ReplaceAroundStep || step instanceof AttrStep) {\n if (ctx.trackBlocks) {\n const docAfter = t.docs[i + 1] ?? t.doc;\n collectBlockChanges(step, docBefore, docAfter, rest, blocks, ctx);\n }\n return;\n }\n if (!(step instanceof ReplaceStep)) return;\n const { from, to, slice } = step as unknown as { from: number; to: number; slice: Slice };\n\n // Replacing content with the same content: only marks may differ.\n if (to > from && slice.size === to - from) {\n const deleted = docBefore.slice(from, to);\n if (\n slice.openStart === deleted.openStart &&\n slice.openEnd === deleted.openEnd &&\n stripMarks(slice.content).eq(stripMarks(deleted.content))\n ) {\n if (ctx.format && !slice.content.eq(deleted.content)) {\n formatRanges.push({ from: rest.map(from, 1), to: rest.map(to, -1) });\n }\n return;\n }\n }\n\n if (ctx.trackBlocks && slice.size > 0) {\n collectInsertedTableNodes(slice, from, rest, tableInserts);\n }\n if (slice.size > 0 && hasInlineContent(slice.content)) {\n const start = rest.map(from, 1);\n const end = rest.map(from + slice.size, -1);\n if (end > start) inserted.push({ from: start, to: end });\n }\n if (to > from) {\n const deleted = docBefore.slice(from, to);\n const wholeTable = ctx.trackBlocks && isWholeTableSlice(deleted);\n if (!wholeTable && !hasInlineContent(deleted.content)) return;\n const $from = docBefore.resolve(from);\n const before = i === 0 ? t.before : null;\n const sel = before === oldState.doc ? oldState.selection : null;\n restores.push({\n pos: rest.map(from, -1),\n slice: deleted,\n parentType: $from.node(Math.max(0, $from.depth - deleted.openStart)).type,\n pureDelete: slice.size === 0,\n wholeTable,\n backspace:\n single && slice.size === 0 && !!sel && sel.empty && sel.from === to,\n });\n }\n });\n mapIndex += t.steps.length;\n });\n\n if (\n inserted.length === 0 &&\n tableInserts.length === 0 &&\n restores.length === 0 &&\n formatRanges.length === 0 &&\n blocks.size === 0\n ) {\n return null;\n }\n\n const tr = newState.tr;\n let changed = false;\n\n // Formatting and block attributes first: they do not move positions.\n if (formatRanges.length && ctx.format) {\n const back = new Mapping(maps).invert();\n changed = trackFormatting(tr, formatRanges, oldState.doc, back, inserted, ctx) || changed;\n }\n if (blocks.size) {\n changed = trackBlocks(tr, blocks, ctx) || changed;\n }\n\n // Moves: a paste of the text the last tracked cut deleted, or a drag of a\n // range within the document, links the deletion and the insertion.\n const moves = ctx.moves;\n let insertAttrs: TrackedChangeAttributes | null = null;\n let deleteAttrs: TrackedChangeAttributes | null = null;\n const moveAttrs = (id: string): TrackedChangeAttributes => ({\n changeId: id,\n authorId: ctx.author.id,\n authorName: ctx.author.name,\n authorColor: ctx.author.color ?? null,\n createdAt: ctx.now(),\n [MOVE_ID_ATTRIBUTE]: id,\n });\n if (moves && inserted.length) {\n const insertedText = normalizeMoveText(\n inserted.map((r) => rangeText(tr.doc, r.from, r.to)).join(\"\"),\n );\n const pending = moves.pending;\n if (\n moves.event === \"paste\" &&\n pending &&\n pending.authorId === ctx.author.id &&\n insertedText === pending.text &&\n linkMoveSource(tr, pending, ctx)\n ) {\n insertAttrs = moveAttrs(pending.id);\n } else if (moves.event === \"drop\" && restores.length) {\n const raw = restores.map((r) => deletedText(r.slice, ctx)).join(\"\\n\");\n if (insertedText && normalizeMoveText(raw) === insertedText && moves.isMove(raw)) {\n insertAttrs = deleteAttrs = moveAttrs(ctx.generateId());\n }\n }\n }\n if (moves?.event === \"cut\" && restores.length) {\n // A cut gets a change of its own, so the paste after it moves exactly it.\n deleteAttrs = newAttributes(ctx);\n const raw = restores.map((r) => deletedText(r.slice, ctx)).join(\"\\n\");\n if (normalizeMoveText(raw) && moves.isMove(raw)) {\n moves.cut = { id: deleteAttrs.changeId, text: normalizeMoveText(raw), authorId: ctx.author.id };\n }\n }\n\n inserted.forEach(({ from, to }) => {\n tr.removeMark(from, to, ctx.deletion);\n tr.removeMark(from, to, ctx.insertion);\n if (ctx.format) tr.removeMark(from, to, ctx.format);\n const attrs =\n insertAttrs ?? adjacentAttributes(tr.doc, from, to, ctx.insertion, ctx) ?? newAttributes(ctx);\n tr.addMark(from, to, ctx.insertion.create(attrs));\n changed = true;\n });\n if (insertAttrs) changed = true;\n\n // Inserted rows, columns (cells) and tables are whole-block insertions:\n // rejecting removes them. Their text is part of the block change.\n if (tableInserts.length) {\n const attrs = newAttributes(ctx);\n tableInserts.forEach(({ from, to }) => {\n const start = tr.mapping.map(from, 1);\n const end = tr.mapping.map(to, -1);\n const node = tr.doc.nodeAt(start);\n if (end <= start || !node || !isTableStructure(node) || node.nodeSize !== end - start) return;\n tr.removeMark(start, end, ctx.insertion);\n changed = markTableBlocks(tr, start, end, \"insert\", attrs) || changed;\n });\n }\n let tableDeleteAttrs: TrackedChangeAttributes | null = null;\n\n let cursor: number | null = null;\n restores\n .sort((a, b) => b.pos - a.pos)\n .forEach((restore) => {\n const pos = tr.mapping.map(restore.pos, -1);\n const attrs =\n deleteAttrs ?? adjacentAttributes(tr.doc, pos, pos, ctx.deletion, ctx) ?? newAttributes(ctx);\n if (restore.wholeTable && canMarkTableBlocks(restore.slice)) {\n // Rows/cells/tables the same author inserted are really deleted.\n if (isOwnTableInsertion(restore.slice, ctx)) return;\n tableDeleteAttrs = tableDeleteAttrs ?? deleteAttrs ?? newAttributes(ctx);\n const sizeBefore = tr.doc.content.size;\n try {\n tr.replace(pos, pos, restore.slice);\n } catch {\n return;\n }\n const end = pos + (tr.doc.content.size - sizeBefore);\n markTableBlocks(tr, pos, end, \"delete\", tableDeleteAttrs);\n changed = true;\n return;\n }\n if (!hasInlineContent(restore.slice.content)) return;\n const marked = markDeletedSlice(restore.slice, restore.parentType, attrs, ctx);\n if (!marked || !marked.hasContent) return;\n const sizeBefore = tr.doc.content.size;\n try {\n tr.replace(pos, pos, marked.slice);\n } catch {\n return;\n }\n changed = true;\n // Backspace leaves the caret before the struck-through text so the\n // next Backspace continues backwards; Delete leaves it after.\n if (restores.length === 1 && restore.pureDelete && newState.selection.empty) {\n cursor = restore.backspace ? pos : pos + (tr.doc.content.size - sizeBefore);\n }\n });\n\n if (!changed) return null;\n if (cursor !== null) {\n try {\n tr.setSelection(TextSelection.create(tr.doc, cursor));\n } catch {\n // Keep the mapped selection.\n }\n }\n tr.setMeta(TRACK_CHANGES_SKIP_META, true);\n return tr;\n}\n\nfunction buildSnapshot(state: EditorState, author: ReviewAuthor): TrackChangesSnapshot {\n const plugin = trackChangesPluginKey.getState(state);\n const changes = getTrackedChanges(state.doc);\n return {\n enabled: plugin?.enabled ?? false,\n author: plugin?.author ?? author,\n changes,\n currentChangeId: getTrackedChangeAt(state, state.selection.from, changes)?.id ?? null,\n };\n}\n\n/**\n * Lets code blocks (`marks: \"\"`) carry the insertion and deletion marks.\n * The node's own `marks` spec wins over `extendNodeSchema`, so the allowed\n * mark set of the built schema is extended instead.\n */\nfunction allowTrackingInCodeBlocks(schema: Schema): void {\n const marks = [schema.marks[INSERTION_MARK_NAME], schema.marks[DELETION_MARK_NAME]].filter(\n Boolean,\n ) as MarkType[];\n Object.values(schema.nodes).forEach((type) => {\n const markSet = type.markSet;\n if (!type.isTextblock || !type.spec.code || markSet === null) return;\n const missing = marks.filter((mark) => !markSet.includes(mark));\n if (missing.length) {\n (type as { markSet: readonly MarkType[] | null }).markSet = [...markSet, ...missing];\n }\n });\n}\n\n/**\n * Suggestion mode / track changes. When enabled, typed and pasted content is\n * marked as an insertion and deleted content is kept but marked as a\n * deletion (struck through) until the change is accepted or rejected.\n * Formatting changes on existing text are recorded with the `formatChange`\n * mark, and textblock type/attribute/wrapping changes with the `blockChange`\n * node attribute. A cut and paste (or a drag) of a sentence or paragraph is\n * recorded as a move: a deletion and an insertion linked by `moveId`, listed\n * as one change and accepted or rejected together.\n *\n * Deleting your own insertion really deletes it. Paragraph splits and joins\n * are not tracked. IME composition is tracked once the composition ends.\n */\nexport const TrackChanges = Extension.create<TrackChangesOptions, TrackChangesStorage>({\n name: \"trackChanges\",\n\n addOptions() {\n return {\n enabled: false,\n author: { id: \"anonymous\", name: \"Anonymous\" },\n now: () => Date.now(),\n generateId: () => createReviewId(\"change\"),\n insertionHTMLAttributes: {},\n deletionHTMLAttributes: {},\n formatChangeHTMLAttributes: {},\n blockChangeClass: \"pixerate-block-change border-l-2 border-amber-400 pl-2 dark:border-amber-500\",\n blockInsertedClass:\n \"pixerate-block-inserted underline decoration-emerald-500 text-emerald-700 dark:text-emerald-400\",\n blockDeletedClass:\n \"pixerate-block-deleted line-through decoration-red-500 text-red-600 dark:text-red-400\",\n trackFormatting: true,\n trackBlocks: true,\n trackCodeBlocks: true,\n blockTypes: [\"paragraph\", \"heading\", \"codeBlock\"],\n ignoredMarks: [],\n ignoredBlockAttributes: [\"id\"],\n trackMoves: true,\n isMove: isMoveSized,\n moveHTMLAttributes: {},\n };\n },\n\n addStorage() {\n return {\n store: new TrackChangesStore({\n enabled: this.options.enabled,\n author: this.options.author,\n changes: [],\n currentChangeId: null,\n }),\n };\n },\n\n addGlobalAttributes() {\n if (this.options?.trackBlocks === false) return [];\n const blockClass = this.options?.blockChangeClass ?? \"\";\n const insertedClass = this.options?.blockInsertedClass ?? \"\";\n const deletedClass = this.options?.blockDeletedClass ?? \"\";\n return [\n {\n types: this.options?.blockTypes ?? [\"paragraph\", \"heading\", \"codeBlock\"],\n attributes: {\n [BLOCK_CHANGE_ATTRIBUTE]: {\n default: null,\n keepOnSplit: false,\n parseHTML: (el: HTMLElement) => parseJSON(el.getAttribute(\"data-block-change\"), null),\n renderHTML: (attrs: Record<string, any>) => {\n const value = attrs[BLOCK_CHANGE_ATTRIBUTE];\n if (!value) return {};\n const operation = (value as TrackedBlockChangeAttributes).operation;\n const extra = operation === \"insert\" ? insertedClass : operation === \"delete\" ? deletedClass : \"\";\n const className = [blockClass, extra].filter(Boolean).join(\" \");\n return {\n \"data-block-change\": JSON.stringify(value),\n ...(operation ? { \"data-block-operation\": operation } : {}),\n ...(className ? { class: className } : {}),\n };\n },\n },\n },\n },\n ];\n },\n\n addExtensions() {\n const insertionAttrs = this.options?.insertionHTMLAttributes ?? {};\n const deletionAttrs = this.options?.deletionHTMLAttributes ?? {};\n const formatAttrs = this.options?.formatChangeHTMLAttributes ?? {};\n const moveAttrs = this.options?.moveHTMLAttributes ?? {};\n const markOptions = (attrs: Record<string, any>) => ({\n ...(Object.keys(attrs).length ? { HTMLAttributes: attrs } : {}),\n ...(Object.keys(moveAttrs).length ? { moveHTMLAttributes: moveAttrs } : {}),\n });\n const configured = <T extends typeof TrackedInsertion>(mark: T, attrs: Record<string, any>) => {\n const options = markOptions(attrs);\n return Object.keys(options).length ? mark.configure(options) : mark;\n };\n return [\n configured(TrackedInsertion, insertionAttrs),\n configured(TrackedDeletion, deletionAttrs),\n Object.keys(formatAttrs).length\n ? TrackedFormatChange.configure({ HTMLAttributes: formatAttrs })\n : TrackedFormatChange,\n ];\n },\n\n onBeforeCreate() {\n if (this.options.trackCodeBlocks) allowTrackingInCodeBlocks(this.editor.schema);\n },\n\n addCommands() {\n const decide =\n (decision: \"accept\" | \"reject\", id: string | null, onlyMoves = false) =>\n ({ state, tr, dispatch }: { state: EditorState; tr: Transaction; dispatch?: any }) => {\n const all = getTrackedChanges(state.doc);\n const changes =\n id === null ? all : all.filter((c) => c.id === id && (!onlyMoves || c.type === \"move\"));\n if (changes.length === 0) return false;\n if (dispatch) {\n applyDecision(tr, changes, decision);\n tr.setMeta(TRACK_CHANGES_SKIP_META, true);\n }\n return true;\n };\n\n const select =\n (direction: 1 | -1) =>\n ({ state, tr, dispatch }: { state: EditorState; tr: Transaction; dispatch?: any }) => {\n const changes = getTrackedChanges(state.doc);\n if (changes.length === 0) return false;\n const { from } = state.selection;\n const start = (c: TrackedChange) => selectionRange(c)[0];\n const target =\n direction === 1\n ? changes.find((c) => start(c) > from) ?? changes[0]\n : [...changes].reverse().find((c) => start(c) < from) ?? changes[changes.length - 1];\n if (dispatch) {\n const [anchor, head] = selectionRange(target);\n tr.setSelection(TextSelection.create(tr.doc, anchor, head)).scrollIntoView();\n }\n return true;\n };\n\n return {\n setTrackChanges:\n (enabled) =>\n ({ tr, dispatch }) => {\n if (dispatch) {\n tr.setMeta(trackChangesPluginKey, { type: \"setEnabled\", enabled });\n tr.setMeta(\"addToHistory\", false);\n }\n return true;\n },\n toggleTrackChanges:\n () =>\n ({ state, commands }) =>\n commands.setTrackChanges(!isTrackChangesEnabled(state)),\n setTrackChangesAuthor:\n (author) =>\n ({ tr, dispatch }) => {\n if (dispatch) {\n tr.setMeta(trackChangesPluginKey, { type: \"setAuthor\", author });\n tr.setMeta(\"addToHistory\", false);\n }\n return true;\n },\n acceptChange: (id) => decide(\"accept\", id),\n acceptMove: (id) => decide(\"accept\", id, true),\n rejectMove: (id) => decide(\"reject\", id, true),\n rejectChange: (id) => decide(\"reject\", id),\n acceptAllChanges: () => decide(\"accept\", null),\n rejectAllChanges: () => decide(\"reject\", null),\n selectNextChange: () => select(1),\n selectPreviousChange: () => select(-1),\n selectChange:\n (id) =>\n ({ state, tr, dispatch }) => {\n const change = getTrackedChanges(state.doc).find((c) => c.id === id);\n if (!change) return false;\n if (dispatch) {\n const [anchor, head] = selectionRange(change);\n tr.setSelection(TextSelection.create(tr.doc, anchor, head)).scrollIntoView();\n }\n return true;\n },\n };\n },\n\n addProseMirrorPlugins() {\n const options = this.options;\n const store = this.storage.store;\n const ignoredMarks = new Set(options.ignoredMarks ?? []);\n const ignoredAttributes = new Set(options.ignoredBlockAttributes ?? []);\n let editorView: EditorView | null = null;\n // Transactions applied during IME composition. Changing the document\n // around the composition would break the IME, so they are tracked\n // together once it ends.\n let pending: { oldState: EditorState; transactions: Transaction[] } | null = null;\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n // The last tracked cut, which the paste right after it turns into a move.\n let pendingMove: PendingMove | null = null;\n const isMove = options.isMove ?? isMoveSized;\n\n const scheduleFlush = () => {\n if (flushTimer) clearTimeout(flushTimer);\n flushTimer = setTimeout(() => {\n flushTimer = null;\n const view = editorView;\n if (!view || !pending) return;\n if (view.composing) {\n scheduleFlush();\n return;\n }\n view.dispatch(view.state.tr.setMeta(\"trackChangesFlush\", true));\n }, 0);\n };\n\n return [\n new Plugin<TrackChangesPluginState>({\n key: trackChangesPluginKey,\n state: {\n init: () => ({ enabled: options.enabled, author: options.author }),\n apply: (tr, value) => {\n const meta = tr.getMeta(trackChangesPluginKey) as TrackChangesMeta | undefined;\n if (!meta) return value;\n if (meta.type === \"setEnabled\") {\n return meta.enabled === value.enabled ? value : { ...value, enabled: meta.enabled };\n }\n return { ...value, author: meta.author };\n },\n },\n appendTransaction: (transactions, oldState, newState) => {\n const plugin = trackChangesPluginKey.getState(oldState);\n const tracking = !!plugin?.enabled;\n if (!tracking && !pending) {\n if (transactions.some((t) => t.docChanged)) pendingMove = null;\n return null;\n }\n\n if (editorView?.composing && transactions.some((t) => t.docChanged)) {\n if (tracking) {\n if (!pending) pending = { oldState, transactions: [] };\n pending.transactions.push(...transactions);\n }\n return null;\n }\n\n let trs: readonly Transaction[] = transactions;\n let fromState = oldState;\n if (pending) {\n trs = [...pending.transactions, ...transactions];\n fromState = pending.oldState;\n pending = null;\n }\n if (!tracking && trs === transactions) return null;\n const author = plugin?.author ?? options.author;\n const userTransactions = trs.filter((t) => !shouldSkip(t));\n let moves: MoveContext | undefined;\n if (userTransactions.length) {\n const uiEvent =\n userTransactions.length === 1 ? userTransactions[0].getMeta(\"uiEvent\") : null;\n const event = uiEvent === \"cut\" || uiEvent === \"paste\" || uiEvent === \"drop\" ? uiEvent : null;\n if (options.trackMoves !== false) {\n moves = { event, pending: event === \"paste\" ? pendingMove : null, isMove };\n }\n // Any other edit ends the cut-and-paste sequence.\n pendingMove = null;\n }\n const result = trackTransactions(trs, fromState, newState, {\n author,\n now: options.now,\n generateId: options.generateId,\n insertion: newState.schema.marks[INSERTION_MARK_NAME],\n deletion: newState.schema.marks[DELETION_MARK_NAME],\n format:\n options.trackFormatting !== false\n ? newState.schema.marks[FORMAT_CHANGE_MARK_NAME]\n : undefined,\n trackBlocks: options.trackBlocks !== false,\n ignoredMarks,\n ignoredAttributes,\n moves,\n });\n if (moves?.cut) pendingMove = moves.cut;\n return result;\n },\n props: {\n transformPasted: (slice, view) => acceptSlice(slice, view.state.schema),\n handleDOMEvents: {\n compositionend: () => {\n if (pending) scheduleFlush();\n return false;\n },\n },\n },\n view: (view) => {\n editorView = view;\n store.set(buildSnapshot(view.state, options.author));\n return {\n update(view, prevState) {\n if (\n prevState.doc !== view.state.doc ||\n !prevState.selection.eq(view.state.selection) ||\n trackChangesPluginKey.getState(prevState) !==\n trackChangesPluginKey.getState(view.state)\n ) {\n store.set(buildSnapshot(view.state, options.author));\n }\n if (pending && !view.composing) scheduleFlush();\n },\n destroy() {\n editorView = null;\n pending = null;\n if (flushTimer) clearTimeout(flushTimer);\n },\n };\n },\n }),\n ];\n },\n});\n"]}
@@ -8,7 +8,7 @@ import {
8
8
  BLOCK_CHANGE_ATTRIBUTE,
9
9
  MOVE_ID_ATTRIBUTE,
10
10
  resolveTrackedChanges
11
- } from "./chunk-UAKDG62Z.js";
11
+ } from "./chunk-SWE3NPOU.js";
12
12
 
13
13
  // src/serializers/plain-text.ts
14
14
  import { DOMSerializer, Slice, Fragment, Node as ProsemirrorNode } from "@tiptap/pm/model";
@@ -176,6 +176,61 @@ function parseHtmlFragment(html) {
176
176
  function decodeHtmlEntities(text) {
177
177
  return text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#0*39;/g, "'").replace(/&nbsp;/g, "\xA0").replace(/&amp;/g, "&");
178
178
  }
179
+ var CELL_TAGS = /* @__PURE__ */ new Set(["TD", "TH"]);
180
+ function blockLines(nodes) {
181
+ const lines = [];
182
+ let current = null;
183
+ const flush = () => {
184
+ if (current !== null) {
185
+ lines.push(current);
186
+ current = null;
187
+ }
188
+ };
189
+ const visit = (node) => {
190
+ if (node.nodeType === 3) {
191
+ const value = node.nodeValue ?? "";
192
+ if (current === null && value.trim() === "") return;
193
+ current = (current ?? "") + value;
194
+ return;
195
+ }
196
+ if (node.nodeType !== 1) return;
197
+ const element = node;
198
+ const tag = element.tagName.toUpperCase();
199
+ if (tag === "BR") {
200
+ current = (current ?? "") + "\n";
201
+ return;
202
+ }
203
+ if (tag === "TR") {
204
+ flush();
205
+ const cells = [];
206
+ element.childNodes.forEach((child) => {
207
+ if (child.nodeType !== 1 || !CELL_TAGS.has(child.tagName.toUpperCase())) return;
208
+ const cell = child;
209
+ cells.push(
210
+ blockLines(Array.from(cell.childNodes)).filter((line) => line !== "").join(" ").replace(/[\t\n]+/g, " ")
211
+ );
212
+ const span = parseInt(cell.getAttribute("colspan") ?? "1", 10);
213
+ for (let i = 1; i < Math.min(Number.isFinite(span) ? span : 1, 1e3); i++) cells.push("");
214
+ });
215
+ lines.push(cells.join(" "));
216
+ return;
217
+ }
218
+ if (!BLOCK_TAGS.has(tag)) {
219
+ node.childNodes.forEach(visit);
220
+ return;
221
+ }
222
+ flush();
223
+ const linesBefore = lines.length;
224
+ node.childNodes.forEach(visit);
225
+ flush();
226
+ if (lines.length === linesBefore && tag !== "HR" && tag !== "TABLE") {
227
+ lines.push("");
228
+ }
229
+ };
230
+ nodes.forEach(visit);
231
+ flush();
232
+ return lines;
233
+ }
179
234
  function htmlToPlainText(html, options = {}) {
180
235
  if (!html) return "";
181
236
  const trackedResolved = stripTrackedChanges(
@@ -191,44 +246,9 @@ function htmlToPlainText(html, options = {}) {
191
246
  if (typeof document !== "undefined") {
192
247
  const fragment = parseHtmlFragment(mentionNormalized);
193
248
  fragment.querySelectorAll("br.ProseMirror-trailingBreak").forEach((br) => br.remove());
194
- const lines = [];
195
- let current = null;
196
- const flush = () => {
197
- if (current !== null) {
198
- lines.push(current);
199
- current = null;
200
- }
201
- };
202
- const visit = (node) => {
203
- if (node.nodeType === 3) {
204
- const value = node.nodeValue ?? "";
205
- if (current === null && value.trim() === "") return;
206
- current = (current ?? "") + value;
207
- return;
208
- }
209
- if (node.nodeType !== 1) return;
210
- const tag = node.tagName.toUpperCase();
211
- if (tag === "BR") {
212
- current = (current ?? "") + "\n";
213
- return;
214
- }
215
- if (!BLOCK_TAGS.has(tag)) {
216
- node.childNodes.forEach(visit);
217
- return;
218
- }
219
- flush();
220
- const linesBefore = lines.length;
221
- node.childNodes.forEach(visit);
222
- flush();
223
- if (lines.length === linesBefore && tag !== "HR") {
224
- lines.push("");
225
- }
226
- };
227
- fragment.childNodes.forEach(visit);
228
- flush();
229
- return lines.join("\n");
249
+ return blockLines(Array.from(fragment.childNodes)).join("\n");
230
250
  }
231
- const text = mentionNormalized.replace(/<br\s*\/?>/gi, "\n").replace(
251
+ const text = mentionNormalized.replace(/<\/p>\s*<\/t([dh])>/gi, "</t$1>").replace(/<\/t[dh]>\s*(?=<t[dh][\s>])/gi, " ").replace(/<\/tr>/gi, "\n").replace(/<br\s*\/?>/gi, "\n").replace(
232
252
  /<\/(?:p|div|li|pre|blockquote|h[1-6])>(?:\s*<\/(?:li|ul|ol|blockquote|div)>)*/gi,
233
253
  "\n"
234
254
  );
@@ -575,6 +595,8 @@ var ALLOWED_HTML_TAGS = /* @__PURE__ */ new Set([
575
595
  "blockquote",
576
596
  "br",
577
597
  "code",
598
+ "col",
599
+ "colgroup",
578
600
  "del",
579
601
  "details",
580
602
  "div",
@@ -589,6 +611,7 @@ var ALLOWED_HTML_TAGS = /* @__PURE__ */ new Set([
589
611
  "i",
590
612
  "img",
591
613
  "input",
614
+ "ins",
592
615
  "kbd",
593
616
  "li",
594
617
  "mark",
@@ -617,6 +640,7 @@ var ALLOWED_HTML_ATTRS = /* @__PURE__ */ new Set([
617
640
  "checked",
618
641
  "class",
619
642
  "colspan",
643
+ "colwidth",
620
644
  "disabled",
621
645
  "height",
622
646
  "href",
@@ -737,4 +761,4 @@ export {
737
761
  markdownToTipTapHtml,
738
762
  getEditorMarkdown
739
763
  };
740
- //# sourceMappingURL=chunk-ECCCYKGL.js.map
764
+ //# sourceMappingURL=chunk-JOIREKL7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/serializers/plain-text.ts","../src/serializers/html-scan.ts","../src/serializers/mentions.ts","../src/serializers/markdown.ts"],"sourcesContent":["import { DOMSerializer, Slice, Fragment, Node as ProsemirrorNode, type Schema } from \"@tiptap/pm/model\";\nimport {\n BLOCK_CHANGE_ATTRIBUTE,\n MOVE_ID_ATTRIBUTE,\n resolveTrackedChanges,\n type TrackedChangesView,\n} from \"../extensions/track-changes\";\nimport { findTagSpans, replaceTagSpans, stripTags } from \"./html-scan\";\n\nexport interface PlainTextOptions {\n /**\n * How tracked changes (`TrackChanges` extension) appear in the text.\n * Defaults to `\"accepted\"`: deleted text is dropped and inserted text kept,\n * so suggested deletions never leak into exported text. `\"original\"` drops\n * insertions and keeps deletions; `\"all\"` keeps both.\n */\n trackedChanges?: TrackedChangesView;\n}\n\n/**\n * Match tracked deletion / insertion elements. They never nest within\n * themselves, so a lazy match is enough.\n */\nconst TRACKED_DELETION_ATTRIBUTE = /\\bdata-change-type=[\"']deletion[\"']/i;\nconst TRACKED_INSERTION_ATTRIBUTE = /\\bdata-change-type=[\"']insertion[\"']/i;\n\nfunction stripTrackedChanges(html: string, view: TrackedChangesView): string {\n if (view === \"all\" || !html.includes(\"data-change-type\")) return html;\n return view === \"accepted\"\n ? replaceTagSpans(html, \"del\", TRACKED_DELETION_ATTRIBUTE, () => \"\")\n : replaceTagSpans(html, \"ins\", TRACKED_INSERTION_ATTRIBUTE, () => \"\");\n}\n\n/**\n * Escapes characters that have syntactic meaning in HTML text.\n */\nexport function escapeHtml(text: string): string {\n if (!text) return \"\";\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#039;\");\n}\n\n/**\n * Matches strings that are already TipTap paragraph HTML (start with `<p>` and\n * end with `</p>`), as opposed to plain text that merely mentions a `<p>` tag.\n */\nconst PARAGRAPH_HTML_REGEX = /^\\s*<p[\\s>][\\s\\S]*<\\/p>\\s*$/i;\n\n/**\n * Converts a plain-text string with newlines into HTML paragraphs suitable\n * for TipTap initial content or setContent, preserving line breaks.\n * Input that is already paragraph HTML (`<p>…</p>`) is passed through as-is.\n */\nexport function plainTextToTipTapHtml(text: string): string {\n if (!text) return \"<p></p>\";\n if (PARAGRAPH_HTML_REGEX.test(text)) {\n return text;\n }\n return text\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/\\r/g, \"\\n\")\n .split(\"\\n\")\n .map((line) => `<p>${escapeHtml(line)}</p>`)\n .join(\"\");\n}\n\n/**\n * Builds an open ProseMirror slice with one paragraph per line, so inserting it\n * splits the surrounding paragraph like typing Enter would. Use it for\n * plain-text paste handling (`clipboardTextParser`) and literal text insertion.\n */\nexport function plainTextToSlice(text: string, schema: Schema): Slice {\n const nodes: ProsemirrorNode[] = [];\n text\n .replace(/\\r\\n?/g, \"\\n\")\n .split(\"\\n\")\n .forEach((line) => {\n const nodeJson: any = { type: \"paragraph\" };\n if (line.length > 0) {\n nodeJson.content = [{ type: \"text\", text: line }];\n }\n try {\n nodes.push(ProsemirrorNode.fromJSON(schema, nodeJson));\n } catch {\n // Ignore invalid node\n }\n });\n return Slice.maxOpen(Fragment.fromArray(nodes));\n}\n\n/**\n * Block-level elements that start a new line in plain-text output.\n */\nconst BLOCK_TAGS = new Set([\n \"P\", \"DIV\", \"LI\", \"UL\", \"OL\", \"PRE\", \"BLOCKQUOTE\", \"TABLE\", \"TR\",\n \"H1\", \"H2\", \"H3\", \"H4\", \"H5\", \"H6\", \"HR\",\n]);\n\nconst MENTION_TYPE_ATTRIBUTE = /\\bdata-type=[\"']mention[\"']/i;\n\nfunction getHtmlAttribute(attrs: string, name: string): string | undefined {\n const match = new RegExp(`\\\\b${name}=(?:\"([^\"]*)\"|'([^']*)')`, \"i\").exec(\n attrs,\n );\n return match ? (match[1] ?? match[2]) : undefined;\n}\n\n/**\n * Parses HTML into an inert fragment. Content of a `<template>` element is not\n * rendered, so `<img onerror>` and similar handlers never run.\n */\nfunction parseHtmlFragment(html: string): DocumentFragment {\n const template = document.createElement(\"template\");\n template.innerHTML = html;\n return template.content;\n}\n\nfunction decodeHtmlEntities(text: string): string {\n return text\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&#0*39;/g, \"'\")\n .replace(/&nbsp;/g, \"\\u00a0\")\n .replace(/&amp;/g, \"&\");\n}\n\nconst CELL_TAGS = new Set([\"TD\", \"TH\"]);\n\n/**\n * Walks DOM nodes and emits one line per *innermost* block, so nested blocks\n * (e.g. TipTap's `<li><p>…</p></li>`) are not repeated. A table row is one\n * line of tab-separated cells: a cell's paragraphs are joined with spaces and\n * a cell spanning several columns is followed by empty cells.\n */\nfunction blockLines(nodes: readonly ChildNode[]): string[] {\n const lines: string[] = [];\n let current: string | null = null;\n const flush = () => {\n if (current !== null) {\n lines.push(current);\n current = null;\n }\n };\n const visit = (node: Node) => {\n if (node.nodeType === 3) {\n const value = node.nodeValue ?? \"\";\n // Skip formatting whitespace between blocks.\n if (current === null && value.trim() === \"\") return;\n current = (current ?? \"\") + value;\n return;\n }\n if (node.nodeType !== 1) return;\n const element = node as Element;\n const tag = element.tagName.toUpperCase();\n if (tag === \"BR\") {\n current = (current ?? \"\") + \"\\n\";\n return;\n }\n if (tag === \"TR\") {\n flush();\n const cells: string[] = [];\n element.childNodes.forEach((child) => {\n if (child.nodeType !== 1 || !CELL_TAGS.has((child as Element).tagName.toUpperCase())) return;\n const cell = child as Element;\n cells.push(\n blockLines(Array.from(cell.childNodes))\n .filter((line) => line !== \"\")\n .join(\" \")\n .replace(/[\\t\\n]+/g, \" \"),\n );\n const span = parseInt(cell.getAttribute(\"colspan\") ?? \"1\", 10);\n for (let i = 1; i < Math.min(Number.isFinite(span) ? span : 1, 1000); i++) cells.push(\"\");\n });\n lines.push(cells.join(\"\\t\"));\n return;\n }\n if (!BLOCK_TAGS.has(tag)) {\n node.childNodes.forEach(visit);\n return;\n }\n flush();\n const linesBefore = lines.length;\n node.childNodes.forEach(visit);\n flush();\n // An empty block (e.g. <p></p>) is an empty line.\n if (lines.length === linesBefore && tag !== \"HR\" && tag !== \"TABLE\") {\n lines.push(\"\");\n }\n };\n nodes.forEach(visit);\n flush();\n return lines;\n}\n\n/**\n * Extracts plain text from an HTML string or TipTap HTML, preserving newlines\n * between block tags. Table rows become lines of tab-separated cells.\n */\nexport function htmlToPlainText(html: string, options: PlainTextOptions = {}): string {\n if (!html) return \"\";\n\n // Normalize mentions so data-label is preserved as @label even if textContent is empty\n const trackedResolved = stripTrackedChanges(\n html,\n options.trackedChanges ?? \"accepted\",\n );\n const mentionNormalized = replaceTagSpans(trackedResolved, \"span\", MENTION_TYPE_ATTRIBUTE, (match, attrs) => {\n const label =\n getHtmlAttribute(attrs, \"data-label\") || getHtmlAttribute(attrs, \"data-id\");\n const role = getHtmlAttribute(attrs, \"data-role\");\n // A role is written as `@label:role`, the text syntax `extractMentions` reads.\n const validRole = role && /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?$/.test(role) ? role : \"\";\n return label ? `@${label}${validRole ? `:${validRole}` : \"\"}` : match;\n });\n\n if (typeof document !== \"undefined\") {\n const fragment = parseHtmlFragment(mentionNormalized);\n fragment\n .querySelectorAll(\"br.ProseMirror-trailingBreak\")\n .forEach((br) => br.remove());\n\n return blockLines(Array.from(fragment.childNodes)).join(\"\\n\");\n }\n\n // Node.js fallback regex. Table cells are tab-separated and rows end lines\n // (a cell's own closing paragraph is dropped first so it does not end the line).\n const text = mentionNormalized\n .replace(/<\\/p>\\s*<\\/t([dh])>/gi, \"</t$1>\")\n .replace(/<\\/t[dh]>\\s*(?=<t[dh][\\s>])/gi, \"\\t\")\n .replace(/<\\/tr>/gi, \"\\n\")\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n // A closing block, plus any closing wrappers right after it, ends one line.\n .replace(\n /<\\/(?:p|div|li|pre|blockquote|h[1-6])>(?:\\s*<\\/(?:li|ul|ol|blockquote|div)>)*/gi,\n \"\\n\",\n );\n return decodeHtmlEntities(stripTags(text)).replace(/\\n$/, \"\");\n}\n\n/**\n * Whole-block insertions/deletions (`blockChange.operation`, from document\n * comparison) have no `<ins>`/`<del>` elements to strip, and resolving a\n * moved paragraph removes its whole block, so documents with either are\n * resolved first and serialized from the resolved document.\n */\nfunction resolvedBlockOperationsHtml(editor: any, view: TrackedChangesView): string | null {\n const doc: ProsemirrorNode | undefined = editor?.state?.doc;\n if (!doc || view === \"all\" || typeof document === \"undefined\") return null;\n let found = false;\n doc.descendants((node) => {\n if (found) return false;\n if (node.isTextblock) {\n if (node.attrs?.[BLOCK_CHANGE_ATTRIBUTE]?.operation) found = true;\n else node.forEach((child) => {\n if (child.marks.some((mark) => mark.attrs?.[MOVE_ID_ATTRIBUTE])) found = true;\n });\n return false;\n }\n return true;\n });\n if (!found) return null;\n const resolved = resolveTrackedChanges(doc, view);\n const container = document.createElement(\"div\");\n container.appendChild(DOMSerializer.fromSchema(doc.type.schema).serializeFragment(resolved.content));\n return container.innerHTML;\n}\n\n/**\n * Extracts plain text from a TipTap editor instance, preserving newline separators\n * between block nodes and converting mention nodes to @label. Tracked changes\n * are rendered as accepted unless `options.trackedChanges` says otherwise.\n */\nexport function getEditorText(editor: any, options: PlainTextOptions = {}): string {\n if (!editor) return \"\";\n const html =\n resolvedBlockOperationsHtml(editor, options.trackedChanges ?? \"accepted\") ??\n (typeof editor.getHTML === \"function\" ? editor.getHTML() : \"\");\n if (html) {\n return htmlToPlainText(html, options);\n }\n if (typeof editor.getText === \"function\") {\n try {\n return editor.getText({ blockSeparator: \"\\n\" });\n } catch {\n return editor.getText();\n }\n }\n return \"\";\n}\n\nexport interface ExtractedMention {\n id: string;\n label: string;\n type?: string;\n /** Asset role (`data-role`), when set. */\n role?: string;\n avatarUrl?: string;\n color?: string;\n [key: string]: any;\n}\n\nimport { extractMentions } from \"./mentions\";\n\n/**\n * Traverses an editor instance, ProseMirror document, JSONContent, or HTML string\n * and extracts all mention attributes without needing regex.\n */\nexport function extractMentionsFromDoc(docOrEditor: any): ExtractedMention[] {\n if (!docOrEditor) return [];\n const mentions: ExtractedMention[] = [];\n\n // TipTap editor or ProseMirror Node with .descendants()\n const doc =\n docOrEditor?.state?.doc ||\n (typeof docOrEditor?.descendants === \"function\" ? docOrEditor : null);\n\n if (doc && typeof doc.descendants === \"function\") {\n doc.descendants((node: any) => {\n if (node.type?.name === \"mention\" && node.attrs) {\n mentions.push({\n id: node.attrs.id || \"\",\n label: node.attrs.label || \"\",\n type: node.attrs.type,\n avatarUrl: node.attrs.avatarUrl,\n color: node.attrs.color,\n ...node.attrs,\n ...(node.attrs.assetRole ? { role: node.attrs.assetRole } : {}),\n });\n }\n });\n return mentions;\n }\n\n // JSONContent tree\n if (typeof docOrEditor === \"object\" && docOrEditor.content) {\n const walk = (nodes: any[]) => {\n for (const node of nodes) {\n if (node.type === \"mention\" && node.attrs) {\n mentions.push({\n id: node.attrs.id || \"\",\n label: node.attrs.label || \"\",\n type: node.attrs.type,\n avatarUrl: node.attrs.avatarUrl,\n color: node.attrs.color,\n ...node.attrs,\n ...(node.attrs.assetRole ? { role: node.attrs.assetRole } : {}),\n });\n }\n if (Array.isArray(node.content)) {\n walk(node.content);\n }\n }\n };\n walk(docOrEditor.content);\n return mentions;\n }\n\n // HTML String\n if (typeof docOrEditor === \"string\") {\n if (typeof document !== \"undefined\") {\n parseHtmlFragment(docOrEditor).querySelectorAll('span[data-type=\"mention\"]').forEach((el) => {\n mentions.push({\n id: el.getAttribute(\"data-id\") || \"\",\n label: el.getAttribute(\"data-label\") || \"\",\n type: el.getAttribute(\"data-type-name\") || undefined,\n avatarUrl: el.getAttribute(\"data-avatar-url\") || undefined,\n color: el.getAttribute(\"data-color\") || undefined,\n ...(el.getAttribute(\"data-role\") ? { role: el.getAttribute(\"data-role\")! } : {}),\n });\n });\n } else {\n for (const span of findTagSpans(docOrEditor, \"span\", MENTION_TYPE_ATTRIBUTE, false)) {\n const attrStr = span.attrs;\n const getAttr = (name: string) => {\n const m = new RegExp(`${name}=[\"']([^\"']*)[\"']`, \"i\").exec(attrStr);\n return m ? m[1] : undefined;\n };\n mentions.push({\n id: getAttr(\"data-id\") || \"\",\n label: getAttr(\"data-label\") || \"\",\n type: getAttr(\"data-type-name\") || getAttr(\"data-entity-type\"),\n avatarUrl: getAttr(\"data-avatar-url\"),\n color: getAttr(\"data-color\"),\n ...(getAttr(\"data-role\") ? { role: getAttr(\"data-role\") } : {}),\n });\n }\n }\n\n // Fallback: If no HTML mention tags were found, extract raw @mentions from text or markdown\n if (mentions.length === 0) {\n const rawHandles = extractMentions(docOrEditor);\n for (const handle of rawHandles) {\n mentions.push({\n id: handle,\n label: handle,\n type: \"user\",\n });\n }\n }\n }\n\n return mentions;\n}\n","/**\n * Linear-time scanners for the HTML-ish strings the serializers handle.\n *\n * Regexes like `/<span\\b[^>]*…[^>]*>/`, `/<[^>]+>/g` or `/<pre[\\s\\S]*?<\\/pre>/g` are\n * quadratic (or worse) on malformed input: every opener without its closer\n * rescans to the end of the string. These helpers find each closer once and\n * reuse it, so untrusted input can't stall the main thread (see GOTCHAS).\n */\n\nexport interface TagSpan {\n /** Index of the opening `<tag`. */\n start: number;\n /** Index just past the opening tag's `>`. */\n openEnd: number;\n /** Index just past the close tag (or `openEnd` when the close tag isn't required). */\n end: number;\n /** Attribute text between `<span` and `>`. */\n attrs: string;\n}\n\n/**\n * Finds `<tag …attrs…>…</tag>` elements whose attributes match `attrTest`, in\n * one linear pass. A regex such as `/<span\\b(?=[^>]*…)([^>]*)>[\\s\\S]*?<\\/span>/g`\n * rescans to the end of the input from every `<span` when a `>` or `</span>` is\n * missing, which is quadratic on malformed input. Here the next `>` and the next\n * close tag are found once and reused until the scan passes them, and the scan\n * stops as soon as either no longer exists further on. The first close tag after\n * the opening tag ends the element (no nesting), as with a lazy regex.\n */\nexport function findTagSpans(\n html: string,\n tag: string,\n attrTest: RegExp | ((attrs: string) => boolean),\n requireClose: boolean,\n): TagSpan[] {\n const matches = typeof attrTest === \"function\" ? attrTest : (attrs: string) => attrTest.test(attrs);\n const spans: TagSpan[] = [];\n const openTag = new RegExp(`<${tag}\\\\b`, \"gi\");\n const closeTag = new RegExp(`</${tag}>`, \"gi\");\n const closeLength = tag.length + 3;\n let nextGt = -1;\n let nextClose = -1;\n let from = 0;\n for (;;) {\n openTag.lastIndex = from;\n const open = openTag.exec(html);\n if (!open) break;\n const start = open.index;\n const attrsStart = start + open[0].length;\n if (nextGt < attrsStart) {\n nextGt = html.indexOf(\">\", attrsStart);\n if (nextGt === -1) break;\n }\n const attrs = html.slice(attrsStart, nextGt);\n const openEnd = nextGt + 1;\n if (!matches(attrs)) {\n from = attrsStart;\n continue;\n }\n if (!requireClose) {\n spans.push({ start, openEnd, end: openEnd, attrs });\n from = openEnd;\n continue;\n }\n if (nextClose < openEnd) {\n closeTag.lastIndex = openEnd;\n const close = closeTag.exec(html);\n if (!close) break;\n nextClose = close.index;\n }\n const end = nextClose + closeLength;\n spans.push({ start, openEnd, end, attrs });\n from = end;\n }\n return spans;\n}\n\n/** Replaces each matching element (opening tag through close tag) using `replace`. */\nexport function replaceTagSpans(\n html: string,\n tag: string,\n attrTest: RegExp | ((attrs: string) => boolean),\n replace: (match: string, attrs: string) => string,\n): string {\n const spans = findTagSpans(html, tag, attrTest, true);\n if (spans.length === 0) return html;\n let out = \"\";\n let last = 0;\n for (const span of spans) {\n out += html.slice(last, span.start) + replace(html.slice(span.start, span.end), span.attrs);\n last = span.end;\n }\n return out + html.slice(last);\n}\n\n/**\n * Replaces `<…>` tags in one pass. Same result as `.replace(/<[^>]+>/g, replacement)`,\n * which is quadratic when many `<` have no closing `>`: each one rescans to the\n * end. Once no `>` is left, the rest of the input is plain text.\n */\nexport function stripTags(html: string, replacement = \"\"): string {\n let out = \"\";\n let from = 0;\n for (;;) {\n const lt = html.indexOf(\"<\", from);\n if (lt === -1) break;\n const gt = html.indexOf(\">\", lt + 1);\n if (gt === -1) break;\n if (gt === lt + 1) {\n // \"<>\" is not a tag (the regex needs at least one character).\n out += html.slice(from, gt + 1);\n } else {\n out += html.slice(from, lt) + replacement;\n }\n from = gt + 1;\n }\n return out + html.slice(from);\n}\n\n/**\n * Removes (or replaces) regions that start with a match of `open` and end at the\n * next `close`, in one pass. Equivalent to `.replace(/<open>[\\s\\S]*?<close>/g, …)`\n * but linear: such a lazy regex rescans to the end of the input from every\n * unclosed opener. The next close position is cached until the scan passes it,\n * and scanning stops once no close is left. `open` must have the `g` flag.\n */\nexport function replaceDelimited(\n text: string,\n open: RegExp,\n close: string,\n replace: (match: string) => string = () => \"\",\n caseInsensitiveClose = false,\n): string {\n const haystack = caseInsensitiveClose ? asciiLower(text) : text;\n const needle = caseInsensitiveClose ? close.toLowerCase() : close;\n let out = \"\";\n let last = 0;\n let from = 0;\n let nextClose = -1;\n for (;;) {\n open.lastIndex = from;\n const opened = open.exec(text);\n if (!opened) break;\n const afterOpen = opened.index + Math.max(opened[0].length, 1);\n if (nextClose < afterOpen) {\n nextClose = haystack.indexOf(needle, afterOpen);\n if (nextClose === -1) break;\n }\n const end = nextClose + close.length;\n out += text.slice(last, opened.index) + replace(text.slice(opened.index, end));\n last = from = end;\n }\n return out + text.slice(last);\n}\n\n/** Lower-cases ASCII letters only, so indices match the original string. */\nfunction asciiLower(text: string): string {\n return text.replace(/[A-Z]+/g, (m) => m.toLowerCase());\n}\n","import { escapeHtml } from \"./plain-text\";\nimport {\n MENTION_REGEX,\n NODE_ROLE_MARK,\n formatMentionHandle,\n readRoleSuffix,\n type MentionRoleSyntax,\n} from \"./mention-text\";\n\nexport {\n MENTION_REGEX,\n findTextMentions,\n formatMentionText,\n setMentionRoleInText,\n type MentionRoleSyntax,\n type TextMention,\n} from \"./mention-text\";\nimport { findTagSpans, replaceDelimited, stripTags } from \"./html-scan\";\nimport type { MentionEntity } from \"../extensions/mention\";\n\n\n/**\n * Split regex identifying markdown code blocks, inline code, URLs, and existing HTML tags.\n * Used by preprocessMarkdownMentions to ensure non-mention regions remain completely untouched.\n */\nexport const MARKDOWN_MENTION_SPLIT_REGEX =\n /(```[\\s\\S]*?```|~~~[\\s\\S]*?~~~|<pre[\\s\\S]*?<\\/pre>|<code[\\s\\S]*?<\\/code>|`[^`\\n]+`|<span\\b[^>]*\\b(?:data-type=[\"']mention[\"']|class=[\"'][^\"']*pixerate-mention-node[^\"']*[\"']|data-mention=[\"'][^\"']*[\"'])[^>]*>[\\s\\S]*?<\\/span>|<[^>]+>|\\]\\([^)]+\\)|(?:https?|file|vscode|cursor|mailto):\\/\\/[^\\s<)]+)/gi;\n\nexport interface MentionSegment {\n type: \"text\" | \"mention\";\n value: string;\n handle?: string;\n /** Role written after the handle (`@fox:style`), when role syntax is enabled. */\n role?: string;\n}\n\n\n/** A mention with its role (asset mentions: `@fox:style`, or `data-role` on a mention node). */\nexport interface MentionDetail {\n handle: string;\n role?: string;\n}\n\nexport interface ExtractMentionsOptions {\n /** Return `{ handle, role }` objects instead of handles. */\n details?: boolean;\n /** Read `@handle:role` suffixes in text (see {@link MentionRoleSyntax}). Mention nodes' `data-role` is always read in `details` mode. */\n roles?: MentionRoleSyntax;\n}\n\nexport interface PreprocessMarkdownMentionsOptions {\n /**\n * Known pool of mentionable entities (users, agents, teams, etc.).\n */\n mentionables?: MentionEntity[];\n /**\n * Default entity type assigned when a handle does not match any entry in mentionables.\n * Defaults to 'user'.\n */\n defaultType?: string;\n /**\n * Custom resolver callback to derive entity metadata for unmatched handles.\n */\n resolveMention?: (handle: string) => Partial<MentionEntity> | null | undefined;\n /**\n * Read `@handle:role` and emit the role as `data-role` (the `:role` text is\n * consumed). Off by default. See {@link MentionRoleSyntax}.\n */\n roles?: MentionRoleSyntax;\n}\n\n/**\n * Strips code blocks and inline code from markdown or HTML content:\n * - Fenced code blocks: ```...``` and ~~~...~~~\n * - Inline backtick code: `...`\n * - HTML code/pre tags: <pre>...</pre>, <code>...</code>\n * Used to avoid false-positive mention parsing and keyword analysis within code snippets, terminal logs, or stack traces.\n */\nexport function stripMarkdownCode(content: string): string {\n if (!content || typeof content !== \"string\") return \"\";\n // Linear scans instead of lazy `[\\s\\S]*?` regexes, which rescan to the end of the\n // input from every unclosed opener (see html-scan.ts).\n let text = replaceDelimited(content, /```/g, \"```\");\n text = replaceDelimited(text, /~~~/g, \"~~~\");\n text = replaceDelimited(text, /<pre/gi, \"</pre>\", undefined, true);\n text = replaceDelimited(text, /<code/gi, \"</code>\", undefined, true);\n return text.replace(/`[^`\\n]*`/g, \"\");\n}\n\n/** True for the attributes of a mention node: `data-type=\"mention\"` or the `pixerate-mention-node` class. */\nfunction isMentionNodeAttributes(attrs: string): boolean {\n if (/\\bdata-type=[\"']mention[\"']/i.test(attrs)) return true;\n const classAttr = /\\bclass=([\"'])/i.exec(attrs);\n if (!classAttr) return false;\n const valueStart = classAttr.index + classAttr[0].length;\n const valueEnd = attrs.indexOf(classAttr[1], valueStart);\n return valueEnd !== -1 && attrs.slice(valueStart, valueEnd).includes(\"pixerate-mention-node\");\n}\n\n/**\n * Extracts unique mention handles from a text, markdown, or HTML string,\n * ignoring trailing punctuation, code blocks, URLs, and pre-existing HTML mention attributes.\n *\n * With `{ details: true }` it returns `{ handle, role }` objects, unique per\n * handle and role: roles come from mention nodes' `data-role` and, when\n * `roles` is set, from `@handle:role` text. Without options the result is\n * unchanged.\n */\nexport function extractMentions(content: string, options?: ExtractMentionsOptions & { details?: false }): string[];\nexport function extractMentions(content: string, options: ExtractMentionsOptions & { details: true }): MentionDetail[];\nexport function extractMentions(content: string, options?: ExtractMentionsOptions): string[] | MentionDetail[];\nexport function extractMentions(content: string, options: ExtractMentionsOptions = {}): string[] | MentionDetail[] {\n if (!content || typeof content !== \"string\") return [];\n const details = options.details === true;\n\n // Normalize existing HTML mention nodes (e.g. from TipTap or rich text serializers)\n // so data-mention-suggestion-char=\"@\" does not trigger spurious quoted matches against attributes,\n // and ensure the actual mentioned label or ID is preserved cleanly.\n // A linear scan: the equivalent regex (`<span\\b[^>]*\\b(?:…)[^>]*>`) backtracks\n // cubically on malformed input without a `>` (see html-scan.ts).\n let normalizedMentionNodes = \"\";\n let last = 0;\n for (const span of findTagSpans(content, \"span\", isMentionNodeAttributes, true)) {\n const labelMatch = /\\bdata-label=[\"']([^\"']+)[\"']/i.exec(span.attrs);\n const idMatch = labelMatch ? null : /\\bdata-id=[\"']([^\"']+)[\"']/i.exec(span.attrs);\n const inner = content.slice(span.openEnd, span.end - \"</span>\".length);\n let replacement = labelMatch ? ` @${labelMatch[1]} ` : idMatch ? ` @${idMatch[1]} ` : ` ${inner} `;\n if (details && (labelMatch || idMatch)) {\n const roleMatch = /\\bdata-role=[\"']([^\"']+)[\"']/i.exec(span.attrs);\n const role = roleMatch && /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9])?$/.test(roleMatch[1]) ? roleMatch[1] : null;\n const label = (labelMatch ?? idMatch)![1];\n replacement = ` ${formatMentionHandle(label)}${role ? NODE_ROLE_MARK + role : \"\"} `;\n }\n normalizedMentionNodes += content.slice(last, span.start) + replacement;\n last = span.end;\n }\n normalizedMentionNodes += content.slice(last);\n\n const cleanContent = stripMarkdownCode(normalizedMentionNodes);\n // Strip URLs and markdown link destinations so @package or @handles in URLs are not extracted as mentions\n const withoutUrls = cleanContent\n .replace(/(?:https?|ftp|file|vscode|cursor|mailto):\\/\\/[^\\s<>()]+/gi, \"\");\n // Markdown link destinations: `](…)` → `]` (linear; `/\\]\\([^)]*\\)/g` is quadratic\n // when many `](` have no `)`).\n const withoutLinks = replaceDelimited(withoutUrls, /\\]\\(/g, \")\", () => \"]\");\n // Strip remaining HTML tags so attributes containing '@' or '>' aren't treated as mention triggers\n const textOnly = stripTags(withoutLinks, \" \");\n\n const mentions: string[] = [];\n const detailed: MentionDetail[] = [];\n const seen = new Set<string>();\n\n const regex = new RegExp(MENTION_REGEX.source, \"g\");\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(textOnly)) !== null) {\n const handle = (match[1] ?? match[2] ?? match[3] ?? \"\").trim();\n if (details) {\n const suffix = readRoleSuffix(textOnly, regex.lastIndex, options.roles);\n if (suffix) regex.lastIndex = suffix.end;\n const key = `${handle}\\u0000${suffix?.role ?? \"\"}`;\n if (handle && !seen.has(key)) {\n seen.add(key);\n detailed.push(suffix ? { handle, role: suffix.role } : { handle });\n }\n continue;\n }\n if (handle && !seen.has(handle)) {\n seen.add(handle);\n mentions.push(handle);\n }\n }\n\n return details ? detailed : mentions;\n}\n\n/**\n * Segments a text string into plain text parts and mention tokens.\n * Correctly leaves trailing punctuation (like \".\") as part of the following text segment.\n */\nexport function parseMentionSegments(\n content: string,\n options: { roles?: MentionRoleSyntax } = {},\n): MentionSegment[] {\n if (!content) return [];\n\n const segments: MentionSegment[] = [];\n const regex = new RegExp(MENTION_REGEX.source, \"g\");\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = regex.exec(content)) !== null) {\n const fullMatch = match[0];\n const atIndex = fullMatch.indexOf(\"@\");\n const prefix = atIndex > 0 ? fullMatch.slice(0, atIndex) : \"\";\n let mentionToken = fullMatch.slice(atIndex);\n const handle = match[1] ?? match[2] ?? match[3];\n const matchStart = match.index + prefix.length;\n const suffix =\n options.roles && content.charAt(regex.lastIndex) === \":\"\n ? readRoleSuffix(content, regex.lastIndex, options.roles)\n : null;\n if (suffix) {\n mentionToken = content.slice(matchStart, suffix.end);\n regex.lastIndex = suffix.end;\n }\n\n if (matchStart > lastIndex) {\n segments.push({\n type: \"text\",\n value: content.slice(lastIndex, matchStart),\n });\n }\n\n segments.push({\n type: \"mention\",\n value: mentionToken,\n handle,\n ...(suffix ? { role: suffix.role } : {}),\n });\n\n lastIndex = matchStart + mentionToken.length;\n }\n\n if (lastIndex < content.length) {\n segments.push({\n type: \"text\",\n value: content.slice(lastIndex),\n });\n }\n\n return segments;\n}\n\n/**\n * Preprocesses markdown text by turning raw @mention handles into TipTap-compatible\n * mention HTML spans (<span class=\"pixerate-mention-node\" data-type=\"mention\" ...>),\n * ensuring code blocks, inline code, existing mention nodes, and URLs remain immune.\n */\nexport function preprocessMarkdownMentions(\n content: string,\n optionsOrMentionables: MentionEntity[] | PreprocessMarkdownMentionsOptions = []\n): string {\n if (!content || typeof content !== \"string\") return \"\";\n\n const options: PreprocessMarkdownMentionsOptions = Array.isArray(optionsOrMentionables)\n ? { mentionables: optionsOrMentionables }\n : optionsOrMentionables;\n\n const allMentionables = options.mentionables || [];\n const defaultType = options.defaultType || \"user\";\n const resolveMention = options.resolveMention;\n\n const parts = content.split(MARKDOWN_MENTION_SPLIT_REGEX);\n\n return parts\n .map((part) => {\n if (!part) return \"\";\n\n // Preserve code fences, code tags, inline backticks, existing mention spans,\n // HTML tags, markdown link targets, and raw URLs untouched.\n if (\n part.startsWith(\"```\") ||\n part.startsWith(\"~~~\") ||\n part.startsWith(\"`\") ||\n part.startsWith(\"](\") ||\n /^(?:https?|file|vscode|cursor|mailto):\\/\\//i.test(part) ||\n /^<pre[\\s>]/i.test(part) ||\n /^<code[\\s>]/i.test(part) ||\n /^<[^>]+>$/i.test(part) ||\n /^<span\\b[^>]*\\b(?:data-type=[\"']mention[\"']|class=[\"'][^\"']*pixerate-mention-node[^\"']*[\"']|data-mention=[\"'][^\"']*[\"'])/i.test(\n part\n )\n ) {\n return part;\n }\n\n const regex = new RegExp(MENTION_REGEX.source, \"g\");\n const render = (\n match: string,\n quotedHandle: string | undefined,\n escapedQuotedHandle: string | undefined,\n standardHandle: string | undefined,\n role?: string,\n ): string => {\n const atIndex = match.indexOf(\"@\");\n const prefix = atIndex > 0 ? match.slice(0, atIndex) : \"\";\n const rawHandle = (quotedHandle || escapedQuotedHandle || standardHandle || \"\").trim();\n if (!rawHandle) return match;\n\n const lowerHandle = rawHandle.toLowerCase();\n const entity = allMentionables.find((e) => {\n if (e.label?.toLowerCase() === lowerHandle) return true;\n if (e.handle?.toLowerCase() === lowerHandle) return true;\n if (e.name?.toLowerCase() === lowerHandle) return true;\n if (e.id?.toLowerCase() === lowerHandle) return true;\n if (e.type === \"agent\" && `agent${(e.handle || e.label || \"\").toLowerCase()}` === lowerHandle) return true;\n if (\n e.aliases?.some(\n (a: string) =>\n a.toLowerCase() === lowerHandle ||\n (e.type === \"agent\" && `agent${a.toLowerCase()}` === lowerHandle)\n )\n ) {\n return true;\n }\n return false;\n });\n\n let id = entity?.id;\n let label = entity?.label || entity?.handle || rawHandle;\n let type = entity?.type;\n let avatarUrl = entity?.avatarUrl;\n let color = entity?.color || entity?.hexColor;\n\n if (!entity) {\n const customResolved = resolveMention?.(rawHandle);\n if (customResolved) {\n id = customResolved.id ?? id;\n label = customResolved.label ?? customResolved.handle ?? label;\n type = customResolved.type ?? type;\n avatarUrl = customResolved.avatarUrl ?? avatarUrl;\n color = customResolved.color ?? customResolved.hexColor ?? color;\n } else {\n const stripped = lowerHandle.replace(/^agent[\\s\\-_]*/i, \"\");\n if (\n [\"supervisor\", \"coordinator\", \"planner\", \"validator\", \"executor\"].includes(stripped)\n ) {\n type = \"agent\";\n id = stripped;\n label = stripped;\n } else if (lowerHandle === \"agents\") {\n type = \"team\";\n id = \"agents\";\n label = \"agents\";\n } else {\n type = defaultType;\n id = rawHandle;\n label = rawHandle;\n }\n }\n }\n\n const avatarAttr = avatarUrl ? ` data-avatar-url=\"${escapeHtml(avatarUrl)}\"` : \"\";\n const colorAttr = color ? ` data-color=\"${escapeHtml(color)}\"` : \"\";\n\n return `${prefix}<span class=\"pixerate-mention-node\" data-type=\"mention\" data-id=\"${escapeHtml(\n id || label\n )}\" data-label=\"${escapeHtml(label)}\" data-mention-suggestion-char=\"@\" data-type-name=\"${escapeHtml(\n type || defaultType\n )}\"${avatarAttr}${colorAttr}${role ? ` data-role=\"${escapeHtml(role)}\"` : \"\"}>@${escapeHtml(label)}</span>`;\n };\n if (!options.roles) {\n return part.replace(regex, (match, quotedHandle, escapedQuotedHandle, standardHandle) =>\n render(match, quotedHandle, escapedQuotedHandle, standardHandle),\n );\n }\n // Role syntax: consume a `:role` suffix after each mention.\n let out = \"\";\n let last = 0;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(part)) !== null) {\n const suffix = readRoleSuffix(part, regex.lastIndex, options.roles);\n const rendered = render(match[0], match[1], match[2], match[3], suffix?.role);\n if (suffix && rendered !== match[0]) regex.lastIndex = suffix.end;\n out += part.slice(last, match.index) + rendered;\n last = regex.lastIndex;\n }\n return out + part.slice(last);\n })\n .join(\"\");\n}\n","import MarkdownIt from \"markdown-it\";\nimport taskLists from \"markdown-it-task-lists\";\nimport { escapeHtml, getEditorText, type PlainTextOptions } from \"./plain-text\";\nimport { resolveTrackedChanges } from \"../extensions/track-changes\";\n\nconst md = new MarkdownIt({\n html: true,\n linkify: true,\n breaks: true,\n}).use(taskLists);\n\nconst defaultValidateLink = md.validateLink;\nmd.validateLink = (url: string) => {\n if (\n url.startsWith(\"file://\") ||\n url.startsWith(\"vscode://\") ||\n url.startsWith(\"cursor://\")\n ) {\n return true;\n }\n return defaultValidateLink.call(md, url);\n};\n\n/**\n * Tags allowed through from raw HTML embedded in markdown (e.g. mention spans\n * and file links that tiptap-markdown serializes as HTML). Anything else is\n * rendered as escaped text. `input` is limited to checkboxes, which\n * markdown-it-task-lists emits as raw HTML.\n */\nconst ALLOWED_HTML_TAGS = new Set([\n \"a\", \"abbr\", \"b\", \"blockquote\", \"br\", \"code\", \"col\", \"colgroup\", \"del\", \"details\", \"div\", \"em\",\n \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"hr\", \"i\", \"img\", \"input\", \"ins\", \"kbd\", \"li\", \"mark\",\n \"ol\", \"p\", \"pre\", \"s\", \"span\", \"strong\", \"sub\", \"summary\", \"sup\", \"table\",\n \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"tr\", \"u\", \"ul\",\n]);\n\nconst ALLOWED_HTML_ATTRS = new Set([\n \"alt\", \"align\", \"checked\", \"class\", \"colspan\", \"colwidth\", \"disabled\", \"height\", \"href\",\n \"rel\", \"rowspan\", \"src\", \"start\", \"target\", \"title\", \"type\", \"width\",\n]);\n\nconst HTML_TAG_REGEX =\n /<(\\/?)([a-zA-Z][a-zA-Z0-9-]*)((?:\\s+[^\\s\"'>\\/=]+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s\"'=<>`]+))?)*)\\s*\\/?>/g;\nconst HTML_ATTR_REGEX =\n /([^\\s\"'>\\/=]+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/g;\n\nfunction sanitizeTag(\n tagText: string,\n isClosing: string,\n rawTagName: string,\n rawAttrs: string,\n stripDisallowed: boolean,\n): string {\n const tagName = rawTagName.toLowerCase();\n const reject = () => (stripDisallowed ? \"\" : escapeHtml(tagText));\n if (!ALLOWED_HTML_TAGS.has(tagName)) return reject();\n if (isClosing) return `</${tagName}>`;\n if (tagName === \"input\" && !/\\stype\\s*=\\s*[\"']?checkbox[\"']?[\\s>/]/i.test(tagText)) {\n return reject();\n }\n\n let attrs = \"\";\n let attrMatch: RegExpExecArray | null;\n HTML_ATTR_REGEX.lastIndex = 0;\n while ((attrMatch = HTML_ATTR_REGEX.exec(rawAttrs)) !== null) {\n const name = attrMatch[1].toLowerCase();\n if (!ALLOWED_HTML_ATTRS.has(name) && !/^data-[a-z0-9_.-]+$/.test(name)) {\n continue;\n }\n const rawValue = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4];\n if (rawValue === undefined) {\n attrs += ` ${name}`;\n continue;\n }\n const value = md.utils.unescapeAll(rawValue);\n if (\n (name === \"href\" || name === \"src\") &&\n // Browsers ignore control characters and whitespace inside schemes.\n !md.validateLink(value.replace(/[\\u0000-\\u0020\\u007f]/g, \"\"))\n ) {\n continue;\n }\n attrs += ` ${name}=\"${escapeHtml(value)}\"`;\n }\n return `<${tagName}${attrs}>`;\n}\n\nexport interface SanitizeHtmlOptions {\n /**\n * What to do with tags outside the allowlist: `escape` (default) renders\n * them as literal text, `strip` removes the tag but keeps its text content.\n */\n disallowedTags?: \"escape\" | \"strip\";\n}\n\n/**\n * Sanitizes raw HTML with the same allowlist the markdown renderer applies to\n * embedded HTML: event handlers, `style` and unsafe URL schemes are dropped,\n * and disallowed tags are escaped (or removed with `disallowedTags: \"strip\"`).\n * It is string-based, so it works without a DOM and never runs handlers.\n */\nexport function sanitizeHtml(html: string, options: SanitizeHtmlOptions = {}): string {\n return sanitizeEmbeddedHtml(html, options.disallowedTags === \"strip\");\n}\n\nfunction sanitizeEmbeddedHtml(html: string, stripDisallowed = false): string {\n let result = \"\";\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n HTML_TAG_REGEX.lastIndex = 0;\n while ((match = HTML_TAG_REGEX.exec(html)) !== null) {\n result += html.slice(lastIndex, match.index).replace(/</g, \"&lt;\");\n result += sanitizeTag(match[0], match[1], match[2], match[3], stripDisallowed);\n lastIndex = match.index + match[0].length;\n }\n return result + html.slice(lastIndex).replace(/</g, \"&lt;\");\n}\n\nconst defaultHtmlInline = md.renderer.rules.html_inline;\nconst defaultHtmlBlock = md.renderer.rules.html_block;\nmd.renderer.rules.html_inline = (tokens, idx, options, env, self) =>\n env?.allowUnsafeHtml\n ? defaultHtmlInline!(tokens, idx, options, env, self)\n : sanitizeEmbeddedHtml(tokens[idx].content);\nmd.renderer.rules.html_block = (tokens, idx, options, env, self) =>\n env?.allowUnsafeHtml\n ? defaultHtmlBlock!(tokens, idx, options, env, self)\n : sanitizeEmbeddedHtml(tokens[idx].content);\n\nimport {\n preprocessMarkdownMentions,\n type PreprocessMarkdownMentionsOptions,\n} from \"./mentions\";\nimport type { MentionEntity } from \"../extensions/mention\";\n\n/**\n * Preprocesses file-protocol and editor-protocol links in markdown content:\n * converts [label](file://...), vscode://, and cursor:// links into anchor tags\n * annotated with data-file-link=\"true\", preserving code backticks in labels.\n */\nexport function preprocessMarkdownFileLinks(content: string): string {\n if (!content) return \"\";\n return content.replace(\n /\\[([^\\]]+)\\]\\(((?:file|vscode|cursor):\\/\\/[^)]+)\\)/g,\n (_match, rawLabel, href) => {\n const trimmed = rawLabel.trim();\n const codeMatch = trimmed.match(/^`([^`]+)`$/);\n const labelContent = codeMatch\n ? `<code>${escapeHtml(codeMatch[1])}</code>`\n : escapeHtml(trimmed);\n return `<a href=\"${escapeHtml(href)}\" data-file-link=\"true\" title=\"Click to copy file path\">${labelContent}</a>`;\n }\n );\n}\n\nexport interface MarkdownToHtmlOptions {\n /**\n * Preprocess file, vscode, and cursor links into clickable data-file-link tags.\n */\n preprocessFileLinks?: boolean;\n /**\n * Whether to preprocess @mentions into TipTap mention node HTML spans.\n */\n preprocessMentions?: boolean;\n /**\n * Pool of mentionables to pass to preprocessMarkdownMentions.\n */\n mentionables?: MentionEntity[];\n /**\n * Full options for preprocessMarkdownMentions.\n */\n preprocessOptions?: PreprocessMarkdownMentionsOptions;\n /**\n * Render raw HTML embedded in the markdown without sanitizing it. By default,\n * embedded HTML is restricted to an allowlist of tags and attributes (event\n * handlers, `style` and unsafe URL schemes are removed). Only enable this for\n * fully trusted input.\n */\n allowUnsafeHtml?: boolean;\n}\n\n/**\n * Converts a markdown string into HTML suitable for TipTap initial content or setContent.\n * Raw HTML embedded in the markdown is sanitized unless `allowUnsafeHtml` is set.\n */\nexport function markdownToTipTapHtml(\n markdown: string,\n options?: MarkdownToHtmlOptions\n): string {\n if (!markdown) return \"<p></p>\";\n let content = markdown;\n if (options?.preprocessFileLinks) {\n content = preprocessMarkdownFileLinks(content);\n }\n if (\n options?.preprocessMentions ||\n options?.mentionables ||\n options?.preprocessOptions\n ) {\n const mentionOpts = options.preprocessOptions || {\n mentionables: options.mentionables,\n };\n content = preprocessMarkdownMentions(content, mentionOpts);\n }\n return md.render(content, { allowUnsafeHtml: options?.allowUnsafeHtml });\n}\n\n/**\n * Extracts clean markdown from a TipTap editor instance.\n * If the editor has the Markdown extension configured, it uses editor.storage.markdown.getMarkdown().\n * Otherwise, it falls back to getEditorText(editor).\n * Tracked changes are exported as accepted unless `options.trackedChanges`\n * says otherwise (`\"all\"` renders deletions as `~~strikethrough~~`).\n */\nexport function getEditorMarkdown(editor: any, options: PlainTextOptions = {}): string {\n if (!editor) return \"\";\n const markdown = editor.storage?.markdown;\n if (typeof markdown?.getMarkdown === \"function\") {\n const doc = editor.state?.doc;\n if (doc && typeof markdown.serializer?.serialize === \"function\") {\n const resolved = resolveTrackedChanges(doc, options.trackedChanges ?? \"accepted\");\n if (resolved !== doc) return markdown.serializer.serialize(resolved);\n }\n return markdown.getMarkdown();\n }\n return getEditorText(editor, options);\n}\n"],"mappings":"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","names":["match"]}