@stll/folio-core 0.59.0 → 0.61.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 (226) hide show
  1. package/README.md +23 -1
  2. package/dist/ai-edits/apply.js +139 -32
  3. package/dist/ai-edits/headless.d.ts +3 -0
  4. package/dist/ai-edits/headless.js +57 -63
  5. package/dist/ai-edits/note-story-inventory.d.ts +7 -0
  6. package/dist/ai-edits/note-story-inventory.js +11 -0
  7. package/dist/ai-edits/read.js +16 -9
  8. package/dist/ai-edits/revisionStretches.js +52 -1
  9. package/dist/ai-edits/table-row-column-mutations.js +6 -1
  10. package/dist/compare/reproducible-package.js +2 -1
  11. package/dist/compare/verification.js +1 -0
  12. package/dist/compat/eigenpal.d.ts +4 -2
  13. package/dist/compat/eigenpal.js +2 -2
  14. package/dist/controller/canonicalClipboard.js +2 -1
  15. package/dist/controller/canonicalListRendering.d.ts +4 -6
  16. package/dist/controller/canonicalListRendering.js +3 -157
  17. package/dist/controller/canonicalSession.js +3 -2
  18. package/dist/controller/canonicalStructure.js +10 -7
  19. package/dist/controller/fontReadiness.js +1 -1
  20. package/dist/controller/headerFooterEditorManager.d.ts +1 -0
  21. package/dist/controller/headerFooterEditorManager.js +11 -0
  22. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  23. package/dist/controller/hiddenEditorManager.js +3 -1
  24. package/dist/controller/layoutPipeline.d.ts +3 -1
  25. package/dist/controller/layoutPipeline.js +19 -4
  26. package/dist/controller/layoutSession.d.ts +4 -1
  27. package/dist/display-list/build/fontTable.js +7 -5
  28. package/dist/display-list/build/furniture.d.ts +1 -0
  29. package/dist/display-list/build/furniture.js +1 -0
  30. package/dist/display-list/build/imagePrimitives.js +3 -6
  31. package/dist/display-list/build/watermarkPrimitives.d.ts +2 -1
  32. package/dist/display-list/build/watermarkPrimitives.js +9 -3
  33. package/dist/display-list/editor/pageRenderer.js +1 -0
  34. package/dist/docx/bookmarkIdNormalization.d.ts +10 -0
  35. package/dist/docx/bookmarkIdNormalization.js +100 -0
  36. package/dist/docx/bookmarkParser.js +4 -2
  37. package/dist/docx/canonicalStoryRepack.js +3 -2
  38. package/dist/docx/commentTextParagraph.d.ts +6 -0
  39. package/dist/docx/commentTextParagraph.js +15 -0
  40. package/dist/docx/conformance.js +6 -1
  41. package/dist/docx/diagramPreview.js +2 -1
  42. package/dist/docx/documentParser.d.ts +13 -1
  43. package/dist/docx/documentParser.js +1 -1
  44. package/dist/docx/encryption/compoundFile.d.ts +0 -8
  45. package/dist/docx/encryption/compoundFile.js +2 -1
  46. package/dist/docx/encryption/cryptoBytes.d.ts +2 -2
  47. package/dist/docx/encryption/cryptoBytes.js +3 -18
  48. package/dist/docx/encryption/encryptionInfo.d.ts +0 -5
  49. package/dist/docx/encryption/encryptionInfo.js +2 -1
  50. package/dist/docx/ensureParaIds.js +2 -1
  51. package/dist/docx/groupDrawingParser.js +6 -1
  52. package/dist/docx/importedIdCollisions.d.ts +136 -0
  53. package/dist/docx/importedIdCollisions.js +215 -0
  54. package/dist/docx/listModelNormalization.d.ts +10 -0
  55. package/dist/docx/listModelNormalization.js +166 -0
  56. package/dist/docx/metadataPrivacy.js +3 -2
  57. package/dist/docx/metafileRaster.js +2 -1
  58. package/dist/docx/numberingParser.js +2 -2
  59. package/dist/docx/packageParts.js +6 -5
  60. package/dist/docx/paragraphParser.js +6 -4
  61. package/dist/docx/paragraphPropertySource.js +1 -2
  62. package/dist/docx/parseContext.d.ts +2 -1
  63. package/dist/docx/parseContext.js +1 -0
  64. package/dist/docx/parseWarningMessage.js +1 -0
  65. package/dist/docx/parser.js +10 -5
  66. package/dist/docx/preservedRunContent.js +11 -2
  67. package/dist/docx/relsParser.js +1 -1
  68. package/dist/docx/removeHeaderFooterParts.js +8 -7
  69. package/dist/docx/replyToComment.js +3 -13
  70. package/dist/docx/rezip.js +90 -90
  71. package/dist/docx/runParser.js +12 -6
  72. package/dist/docx/runPropertyPresence.d.ts +11 -0
  73. package/dist/docx/runPropertyPresence.js +14 -0
  74. package/dist/docx/selectedXmlIdentityScan.d.ts +10 -0
  75. package/dist/docx/selectedXmlIdentityScan.js +36 -0
  76. package/dist/docx/selectiveSave.js +19 -17
  77. package/dist/docx/selectiveXmlPatch.js +44 -52
  78. package/dist/docx/serializer/commentSerializer.d.ts +3 -1
  79. package/dist/docx/serializer/commentSerializer.js +11 -2
  80. package/dist/docx/serializer/runSerializer.js +5 -4
  81. package/dist/docx/serializer/tableSerializer.js +2 -1
  82. package/dist/docx/serializer/trackedChangeAttributes.d.ts +1 -3
  83. package/dist/docx/serializer/trackedChangeAttributes.js +1 -11
  84. package/dist/docx/server/applyDocxXmlPatchProposal.js +2 -2
  85. package/dist/docx/server/boundedArchive.js +2 -1
  86. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +2 -1
  87. package/dist/docx/server/inspectDocxPackage.js +5 -1
  88. package/dist/docx/server/validateDocxConformance.js +2 -1
  89. package/dist/docx/tableParser.js +7 -4
  90. package/dist/docx/tableRowPropertyPresence.d.ts +9 -0
  91. package/dist/docx/tableRowPropertyPresence.js +15 -0
  92. package/dist/docx/unzip.js +2 -1
  93. package/dist/docx/xmlEncoding.js +6 -5
  94. package/dist/fonts/embeddedFontFingerprint.d.ts +5 -0
  95. package/dist/fonts/embeddedFontFingerprint.js +18 -0
  96. package/dist/fonts/embeddedFonts.d.ts +1 -1
  97. package/dist/fonts/embeddedFonts.js +1 -1
  98. package/dist/fonts/fontsourceFaces.js +2 -1
  99. package/dist/fonts/headlessMeasure.js +0 -0
  100. package/dist/fonts/sfnt/parse.js +6 -9
  101. package/dist/fonts/sfnt/subset.js +2 -1
  102. package/dist/fonts/sfnt/tables.js +2 -3
  103. package/dist/fonts/sfnt/woff.js +2 -1
  104. package/dist/headless-layout.js +1 -0
  105. package/dist/index.d.ts +4 -2
  106. package/dist/index.js +2 -2
  107. package/dist/internal/revisionResolutionInline.d.ts +2 -1
  108. package/dist/internal/revisionResolutionInline.js +22 -1
  109. package/dist/internal/wholeStoryRevisionResolution.js +24 -7
  110. package/dist/layout-bridge/convert/flowConversionShared.d.ts +4 -0
  111. package/dist/layout-bridge/convert/footnoteLayout.d.ts +5 -1
  112. package/dist/layout-bridge/convert/footnoteLayout.js +16 -3
  113. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +6 -0
  114. package/dist/layout-bridge/convert/headerFooterLayout.js +13 -14
  115. package/dist/layout-bridge/convert/paragraphConversion.js +8 -1
  116. package/dist/layout-bridge/convert/paragraphRuns.js +2 -1
  117. package/dist/layout-bridge/convert/toFlowBlocks.js +25 -1
  118. package/dist/layout-engine/measure/cache.js +39 -7
  119. package/dist/layout-engine/measure/crossRunWords.js +12 -2
  120. package/dist/layout-engine/measure/lineTypography.js +1 -0
  121. package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
  122. package/dist/layout-engine/measure/listMarkerWidth.js +1 -0
  123. package/dist/layout-engine/measure/measureContainer.js +3 -4
  124. package/dist/layout-engine/measure/measureHelpers.d.ts +6 -9
  125. package/dist/layout-engine/measure/measureHelpers.js +21 -18
  126. package/dist/layout-engine/measure/measureParagraph.js +2 -0
  127. package/dist/layout-engine/measure/measureTypes.d.ts +2 -0
  128. package/dist/layout-engine/types.d.ts +4 -1
  129. package/dist/layout-painter/renderPage.d.ts +1 -0
  130. package/dist/layout-painter/renderPage.js +11 -3
  131. package/dist/layout-painter/renderParagraph.js +13 -3
  132. package/dist/layout-painter/renderWatermark.d.ts +1 -0
  133. package/dist/layout-painter/renderWatermark.js +7 -3
  134. package/dist/managers/AutoSaveManager.js +2 -1
  135. package/dist/managers/EditorModeManager.d.ts +10 -1
  136. package/dist/managers/EditorModeManager.js +15 -1
  137. package/dist/managers/TableSelectionManager.d.ts +1 -1
  138. package/dist/managers/autoSaveCodec.js +2 -6
  139. package/dist/managers/editorShortcuts.d.ts +2 -1
  140. package/dist/managers/editorShortcuts.js +2 -1
  141. package/dist/managers/reviewDisplayMode.d.ts +8 -0
  142. package/dist/managers/reviewDisplayMode.js +15 -0
  143. package/dist/paged-layout/pageText.d.ts +11 -2
  144. package/dist/paged-layout/pageText.js +8 -3
  145. package/dist/pdf/fonts.js +5 -14
  146. package/dist/pdf/images.js +3 -2
  147. package/dist/pdf/objects.d.ts +0 -11
  148. package/dist/pdf/objects.js +7 -9
  149. package/dist/pdf/writePdf.js +2 -1
  150. package/dist/prosemirror/canonicalSelectionRange.d.ts +4 -1
  151. package/dist/prosemirror/canonicalSelectionRange.js +9 -3
  152. package/dist/prosemirror/commands/comments.js +51 -13
  153. package/dist/prosemirror/containerFinalParagraph.d.ts +8 -8
  154. package/dist/prosemirror/containerFinalParagraph.js +8 -8
  155. package/dist/prosemirror/conversion/fromProseDoc.js +9 -4
  156. package/dist/prosemirror/conversion/toProseDoc.js +4 -4
  157. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +8 -2
  158. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +39 -3
  159. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.d.ts +8 -2
  160. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +4 -3
  161. package/dist/prosemirror/extensions/features/ImageDragExtension.js +4 -3
  162. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +4 -3
  163. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +9 -3
  164. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +6 -5
  165. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +8 -2
  166. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +11 -3
  167. package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +4 -3
  168. package/dist/prosemirror/extensions/marks/markUtils.js +1 -1
  169. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +16 -2
  170. package/dist/prosemirror/findReplaceSelection.d.ts +11 -1
  171. package/dist/prosemirror/findReplaceSelection.js +15 -1
  172. package/dist/prosemirror/markupViewProjection.d.ts +10 -1
  173. package/dist/prosemirror/markupViewProjection.js +21 -3
  174. package/dist/prosemirror/pluginKeyOwner.d.ts +13 -0
  175. package/dist/prosemirror/pluginKeyOwner.js +35 -0
  176. package/dist/prosemirror/plugins/aiCitationDecorations.d.ts +11 -4
  177. package/dist/prosemirror/plugins/aiCitationDecorations.js +16 -3
  178. package/dist/prosemirror/plugins/aiSuggestionDecorations.d.ts +11 -4
  179. package/dist/prosemirror/plugins/aiSuggestionDecorations.js +18 -3
  180. package/dist/prosemirror/plugins/anonymizationDecorations.d.ts +11 -11
  181. package/dist/prosemirror/plugins/anonymizationDecorations.js +23 -13
  182. package/dist/prosemirror/plugins/autocompleteSuggestion.d.ts +8 -2
  183. package/dist/prosemirror/plugins/autocompleteSuggestion.js +27 -5
  184. package/dist/prosemirror/plugins/contentControlWidgets.d.ts +8 -2
  185. package/dist/prosemirror/plugins/contentControlWidgets.js +4 -3
  186. package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +6 -5
  187. package/dist/prosemirror/plugins/createDocScanPlugin.js +1 -1
  188. package/dist/prosemirror/plugins/documentNumbering.js +5 -3
  189. package/dist/prosemirror/plugins/documentStyleState.d.ts +8 -2
  190. package/dist/prosemirror/plugins/documentStyleState.js +2 -2
  191. package/dist/prosemirror/plugins/documentStyles.js +1 -1
  192. package/dist/prosemirror/plugins/selectionTracker.d.ts +8 -2
  193. package/dist/prosemirror/plugins/selectionTracker.js +4 -3
  194. package/dist/prosemirror/plugins/suggestionMode.d.ts +8 -2
  195. package/dist/prosemirror/plugins/suggestionMode.js +4 -3
  196. package/dist/prosemirror/plugins/templateDirectives.d.ts +8 -2
  197. package/dist/prosemirror/plugins/templateDirectives.js +19 -4
  198. package/dist/prosemirror/plugins/templatePreviewValues.d.ts +8 -2
  199. package/dist/prosemirror/plugins/templatePreviewValues.js +27 -5
  200. package/dist/prosemirror/plugins/templateSlashMenu.d.ts +8 -2
  201. package/dist/prosemirror/plugins/templateSlashMenu.js +23 -5
  202. package/dist/prosemirror/plugins/transactionInvariants.js +29 -4
  203. package/dist/prosemirror/replacedAnnotations.d.ts +15 -1
  204. package/dist/prosemirror/replacedAnnotations.js +71 -12
  205. package/dist/prosemirror/runFormattingReconciliation.d.ts +3 -1
  206. package/dist/prosemirror/runFormattingReconciliation.js +10 -2
  207. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +5 -7
  208. package/dist/prosemirror/runIdentityAcrossRevisions.js +66 -38
  209. package/dist/prosemirror/runPropertyChangeResolution.js +13 -1
  210. package/dist/utils/base64.d.ts +1 -31
  211. package/dist/utils/base64.js +19 -33
  212. package/dist/utils/clipboard.js +5 -4
  213. package/dist/utils/colorResolver.js +11 -2
  214. package/dist/utils/findReplace.d.ts +5 -0
  215. package/dist/utils/fontLoader.d.ts +2 -2
  216. package/dist/utils/fontLoader.js +2 -7
  217. package/dist/utils/fontResolver.d.ts +7 -3
  218. package/dist/utils/fontResolver.js +6 -8
  219. package/dist/utils/replaceText.js +21 -4
  220. package/dist/utils/sliceText.d.ts +2 -0
  221. package/dist/utils/sliceText.js +2 -0
  222. package/dist/utils/textEncoding.d.ts +2 -0
  223. package/dist/utils/textEncoding.js +2 -0
  224. package/dist/utils/tiffConverter.d.ts +0 -9
  225. package/dist/utils/tiffConverter.js +2 -4
  226. package/package.json +73 -31
@@ -2,11 +2,47 @@ import { setProseParagraphMarkupWithPropertySource } from "../../../docx/paragra
2
2
  import { getTransactionDirtyRange } from "../../../paged-layout/transactionDirtyRange.js";
3
3
  import { detectBaseDirection } from "../../../utils/baseDirection.js";
4
4
  import { directionIsAutoManaged } from "../../paragraphDirection.js";
5
+ import { createPluginKeyOwner } from "../../pluginKeyOwner.js";
5
6
  import { createExtension } from "../create.js";
6
7
  import { ignoreTrackedChanges } from "./ParagraphChangeTrackerExtension.js";
7
- import { Plugin, PluginKey } from "prosemirror-state";
8
+ import { Plugin } from "prosemirror-state";
8
9
  //#region src/prosemirror/extensions/features/AutoBidiDetectionExtension.ts
9
- const autoBidiDetectionKey = new PluginKey("autoBidiDetection");
10
+ /**
11
+ * AutoBidiDetection — sets the paragraph `direction` (to `{ source: "auto" }`)
12
+ * on RTL-led paragraphs that arrived without an explicit direction decision.
13
+ *
14
+ * Why: Folio only ever rendered/exported RTL when a paragraph already carried
15
+ * `w:bidi` (from a Word import) or the user toggled it by hand. Content that
16
+ * never had a flag — AI-generated text, Markdown/HTML/plain-text imports, paste,
17
+ * or just typing Arabic without the shortcut — stayed left-to-right both in the
18
+ * editor and in the exported `.docx`. The ProseMirror doc is the single source
19
+ * of truth for both the layout painter (`toFlowBlocks`) and the serializer
20
+ * (`fromProseDoc`), so writing the attribute here fixes the editor and the Word
21
+ * export from one place.
22
+ *
23
+ * Detection uses the first-strong-character rule (`detectBaseDirection`), the
24
+ * same logic the layout painter uses for base direction.
25
+ *
26
+ * Auto vs. explicit: only "auto-managed" paragraphs are (re-)evaluated — those
27
+ * whose `direction` is undecided (absent) or was set by this detector
28
+ * (`source: "auto"`). A manual user toggle or imported `w:bidi`
29
+ * (`source: "manual"`) is authoritative and never touched. Because the detector
30
+ * re-evaluates its own decisions, replacing an auto-RTL paragraph's text with
31
+ * Latin clears it again (no stale "sticky" RTL).
32
+ *
33
+ * The `direction` discriminated union is editor-runtime state. Its `"auto"`
34
+ * source is a *rendering* aid only — `directionToAuthoredBidi` maps it to
35
+ * `undefined`, so an auto-detected paragraph never serializes `w:bidi` on its
36
+ * own; only a manual decision (an explicit user toggle, or `w:bidi` the
37
+ * source document already carried) is authored content. This keeps a no-edit
38
+ * save byte-identical on direction, while the editor and layout painter still
39
+ * treat `"auto"` as RTL (`directionToBidi` / `directionIsRtl`) so detected
40
+ * text displays correctly before the user ever makes it explicit.
41
+ *
42
+ * Mirrors the appendTransaction + ensure-in-state pattern of
43
+ * `ParaIdAllocatorExtension`.
44
+ */
45
+ const autoBidiDetectionKey = createPluginKeyOwner("autoBidiDetection");
10
46
  const paragraphDirectionalText = (node) => {
11
47
  let text = "";
12
48
  node.descendants((child) => {
@@ -95,7 +131,7 @@ const ensureBaseDirectionInState = (state) => {
95
131
  return state.apply(applyBidiUpdates(state, updates));
96
132
  };
97
133
  const createAutoBidiDetectionPlugin = () => new Plugin({
98
- key: autoBidiDetectionKey,
134
+ key: autoBidiDetectionKey.createKey(),
99
135
  appendTransaction(transactions, _oldState, newState) {
100
136
  if (!transactions.some((t) => t.docChanged)) return null;
101
137
  const [first, ...rest] = transactions;
@@ -1,7 +1,13 @@
1
1
  import { Extension } from "../types.js";
2
- import { PluginKey } from "prosemirror-state";
2
+ import { Plugin } from "prosemirror-state";
3
3
  //#region src/prosemirror/extensions/features/EmptyParagraphFormatExtension.d.ts
4
- declare const emptyParagraphFormatKey: PluginKey<any>;
4
+ declare const emptyParagraphFormatKey: Readonly<{
5
+ key: string;
6
+ createKey: () => import("prosemirror-state").PluginKey<unknown>;
7
+ owns: (plugin: Plugin<unknown>) => boolean;
8
+ get: (state: import("prosemirror-state").EditorState) => Plugin<unknown> | undefined;
9
+ getState: (state: import("prosemirror-state").EditorState) => unknown;
10
+ }>;
5
11
  declare const EmptyParagraphFormatExtension: (options?: Partial<Record<string, unknown>> | undefined) => Extension;
6
12
  //#endregion
7
13
  export { EmptyParagraphFormatExtension, emptyParagraphFormatKey };
@@ -1,6 +1,7 @@
1
+ import { createPluginKeyOwner } from "../../pluginKeyOwner.js";
1
2
  import { createExtension } from "../create.js";
2
3
  import { textFormattingToMarks } from "../marks/markUtils.js";
3
- import { Plugin, PluginKey } from "prosemirror-state";
4
+ import { Plugin } from "prosemirror-state";
4
5
  //#region src/prosemirror/extensions/features/EmptyParagraphFormatExtension.ts
5
6
  /**
6
7
  * EmptyParagraphFormat — re-establishes stored marks for an empty styled
@@ -22,7 +23,7 @@ import { Plugin, PluginKey } from "prosemirror-state";
22
23
  * (bold/italic/color/…). Plain body paragraphs (font + size only) are left
23
24
  * untouched so ordinary typed text stays mark-free and serializes cleanly.
24
25
  */
25
- const emptyParagraphFormatKey = new PluginKey("emptyParagraphFormat");
26
+ const emptyParagraphFormatKey = createPluginKeyOwner("emptyParagraphFormat");
26
27
  /**
27
28
  * Run-formatting properties the painter does NOT reproduce from
28
29
  * `defaultTextFormatting` on its own (it forwards only font family + size).
@@ -37,7 +38,7 @@ function hasNonFontDefaults(dtf) {
37
38
  }
38
39
  function createEmptyParagraphFormatPlugin() {
39
40
  return new Plugin({
40
- key: emptyParagraphFormatKey,
41
+ key: emptyParagraphFormatKey.createKey(),
41
42
  appendTransaction(transactions, _oldState, newState) {
42
43
  if (!transactions.some((t) => t.selectionSet || t.docChanged)) return null;
43
44
  const { selection } = newState;
@@ -1,5 +1,6 @@
1
+ import { createPluginKeyOwner } from "../../pluginKeyOwner.js";
1
2
  import { createExtension } from "../create.js";
2
- import { NodeSelection, Plugin, PluginKey } from "prosemirror-state";
3
+ import { NodeSelection, Plugin } from "prosemirror-state";
3
4
  //#region src/prosemirror/extensions/features/ImageDragExtension.ts
4
5
  /**
5
6
  * Image Drag Extension — handles drag-to-reposition for images
@@ -10,12 +11,12 @@ import { NodeSelection, Plugin, PluginKey } from "prosemirror-state";
10
11
  * - Inline images: reorder within text flow via ProseMirror's default drag
11
12
  * - Custom drag initiation from mousedown on selected images
12
13
  */
13
- const imageDragKey = new PluginKey("imageDrag");
14
+ const imageDragKey = createPluginKeyOwner("imageDrag");
14
15
  const ImageDragExtension = createExtension({
15
16
  name: "imageDrag",
16
17
  onSchemaReady(_ctx) {
17
18
  return { plugins: [new Plugin({
18
- key: imageDragKey,
19
+ key: imageDragKey.createKey(),
19
20
  props: { handleDOMEvents: {
20
21
  dragstart(view, event) {
21
22
  const { selection } = view.state;
@@ -1,5 +1,6 @@
1
+ import { createPluginKeyOwner } from "../../pluginKeyOwner.js";
1
2
  import { createExtension } from "../create.js";
2
- import { Plugin, PluginKey } from "prosemirror-state";
3
+ import { Plugin } from "prosemirror-state";
3
4
  //#region src/prosemirror/extensions/features/JoinedRunStyleExtension.ts
4
5
  /**
5
6
  * Runs that a join moves into another paragraph take that paragraph's style.
@@ -16,7 +17,7 @@ import { Plugin, PluginKey } from "prosemirror-state";
16
17
  * {@link JOINED_RUNS_RESTYLED_META}, and is left as it is.
17
18
  */
18
19
  const JOINED_RUNS_RESTYLED_META = "joinedRunsRestyled";
19
- const pastedTableEndKey = new PluginKey("pastedTableEnd");
20
+ const pastedTableEndKey = createPluginKeyOwner("pastedTableEnd");
20
21
  /**
21
22
  * A table pasted over a story's last paragraph leaves that paragraph, emptied,
22
23
  * after it: a story ends with a paragraph, and the paste replaced the words,
@@ -24,7 +25,7 @@ const pastedTableEndKey = new PluginKey("pastedTableEnd");
24
25
  * paragraph, so the empty one is put back.
25
26
  */
26
27
  const createPastedTableEndPlugin = () => new Plugin({
27
- key: pastedTableEndKey,
28
+ key: pastedTableEndKey.createKey(),
28
29
  appendTransaction(transactions, oldState, newState) {
29
30
  if (!transactions.some((transaction) => transaction.docChanged && transaction.getMeta("uiEvent") === "paste")) return null;
30
31
  if (newState.doc.lastChild?.type.spec["tableRole"] !== "table" || oldState.doc.lastChild?.type.name !== "paragraph") return null;
@@ -1,12 +1,18 @@
1
1
  import { Extension } from "../types.js";
2
2
  import { Node } from "prosemirror-model";
3
- import { EditorState, PluginKey, Transaction } from "prosemirror-state";
3
+ import { EditorState, Plugin, Transaction } from "prosemirror-state";
4
4
  //#region src/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts
5
5
  type ParagraphPropertySourceSeed = {
6
6
  contract: string | null;
7
7
  tokens: ReadonlySet<string>;
8
8
  };
9
- declare const paraIdAllocatorKey: PluginKey<ParagraphPropertySourceSeed>;
9
+ declare const paraIdAllocatorKey: Readonly<{
10
+ key: string;
11
+ createKey: () => import("prosemirror-state").PluginKey<ParagraphPropertySourceSeed>;
12
+ owns: (plugin: Plugin<ParagraphPropertySourceSeed>) => boolean;
13
+ get: (state: EditorState) => Plugin<ParagraphPropertySourceSeed> | undefined;
14
+ getState: (state: EditorState) => ParagraphPropertySourceSeed | undefined;
15
+ }>;
10
16
  /**
11
17
  * Transaction meta asking this plugin to derive the ids it mints from a
12
18
  * caller-supplied seed instead of `Math.random()`.
@@ -17,7 +23,7 @@ declare const paraIdAllocatorKey: PluginKey<ParagraphPropertySourceSeed>;
17
23
  * be deterministic, so it says so on its own transaction rather than leaving
18
24
  * the plugin to guess.
19
25
  */
20
- declare const deterministicParaIdSeedKey: PluginKey<string>;
26
+ declare const deterministicParaIdSeedKey = "stll.folio.meta.deterministicParaIdSeed";
21
27
  /** Ask the allocator to mint reproducible ids for the paragraphs `tr` creates. */
22
28
  declare const requestDeterministicParaIds: (tr: Transaction, seed: string) => Transaction;
23
29
  type ParagraphPropertySourceKeeper = {
@@ -1,11 +1,12 @@
1
1
  import { PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR, getExplicitParagraphPropertySourceTransfers, getProseDocumentParagraphPropertySourceContract, getProseParagraphPropertySourceToken, paragraphPropertySourceTokenMatchesContract, recreateProseNodeWithParagraphPropertySource, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource } from "../../../docx/paragraphPropertySource.js";
2
2
  import { deterministicHexId, generateHexId } from "../../../utils/hexId.js";
3
+ import { createPluginKeyOwner } from "../../pluginKeyOwner.js";
3
4
  import { sweepPositions } from "../../positionSweep.js";
4
5
  import { createExtension } from "../create.js";
5
6
  import { ignoreTrackedChanges } from "./ParagraphChangeTrackerExtension.js";
6
7
  import { panic } from "better-result";
7
8
  import { Fragment } from "prosemirror-model";
8
- import { Plugin, PluginKey } from "prosemirror-state";
9
+ import { Plugin } from "prosemirror-state";
9
10
  //#region src/prosemirror/extensions/features/ParaIdAllocatorExtension.ts
10
11
  /**
11
12
  * ParaIdAllocator — assigns a stable `w14:paraId` to every paragraph.
@@ -44,7 +45,7 @@ import { Plugin, PluginKey } from "prosemirror-state";
44
45
  * split therefore restores the same allocated id instead of minting
45
46
  * another one.
46
47
  */
47
- const paraIdAllocatorKey = new PluginKey("paraIdAllocator");
48
+ const paraIdAllocatorKey = createPluginKeyOwner("paraIdAllocator");
48
49
  /**
49
50
  * Transaction meta asking this plugin to derive the ids it mints from a
50
51
  * caller-supplied seed instead of `Math.random()`.
@@ -55,7 +56,7 @@ const paraIdAllocatorKey = new PluginKey("paraIdAllocator");
55
56
  * be deterministic, so it says so on its own transaction rather than leaving
56
57
  * the plugin to guess.
57
58
  */
58
- const deterministicParaIdSeedKey = new PluginKey("deterministicParaIdSeed");
59
+ const deterministicParaIdSeedKey = "stll.folio.meta.deterministicParaIdSeed";
59
60
  /** Ask the allocator to mint reproducible ids for the paragraphs `tr` creates. */
60
61
  const requestDeterministicParaIds = (tr, seed) => tr.setMeta(deterministicParaIdSeedKey, seed);
61
62
  /**
@@ -297,7 +298,7 @@ const ensureParaIdsInState = (state) => {
297
298
  return state.apply(tr);
298
299
  };
299
300
  const createParaIdAllocatorPlugin = () => new Plugin({
300
- key: paraIdAllocatorKey,
301
+ key: paraIdAllocatorKey.createKey(),
301
302
  state: {
302
303
  init: (_config, state) => collectParagraphPropertySourceSeed(state.doc),
303
304
  apply: (_transaction, seed) => seed
@@ -307,7 +308,7 @@ const createParaIdAllocatorPlugin = () => new Plugin({
307
308
  const paragraphSourceSeed = paraIdAllocatorKey.getState(oldState);
308
309
  if (!paragraphSourceSeed) panic("ParaId allocator lost its paragraph-property source seed");
309
310
  const keeperPositions = mapParagraphKeepers(oldState.doc, transactions, paragraphSourceSeed);
310
- const deterministicSeed = transactions.map((transaction) => transaction.getMeta(deterministicParaIdSeedKey)).find((value) => typeof value === "string") ?? null;
311
+ const deterministicSeed = transactions.map((transaction) => transaction.getMeta("stll.folio.meta.deterministicParaIdSeed")).find((value) => typeof value === "string") ?? null;
311
312
  const census = collectParagraphCensus(newState.doc, paragraphSourceSeed);
312
313
  const updates = collectParaIdUpdatesFromCensus(census, keeperPositions.paraIds, deterministicSeed);
313
314
  const paragraphSourceUpdates = collectParagraphPropertySourceUpdates(census, keeperPositions.sourceTokens, transactions);
@@ -2,9 +2,15 @@ import { document_d_exports } from "../../../types/document.js";
2
2
  import { Extension } from "../types.js";
3
3
  import { RemovedSectionReference, TrackedSectionEndpointRemoval } from "../../../internal/sectionEndpointResolution.js";
4
4
  import { Node } from "prosemirror-model";
5
- import { EditorState, Plugin, PluginKey, Transaction } from "prosemirror-state";
5
+ import { EditorState, Plugin, Transaction } from "prosemirror-state";
6
6
  //#region src/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts
7
- declare const paragraphChangeTrackerKey: PluginKey<InternalParagraphChangeTrackerState>;
7
+ declare const paragraphChangeTrackerKey: Readonly<{
8
+ key: string;
9
+ createKey: () => import("prosemirror-state").PluginKey<InternalParagraphChangeTrackerState>;
10
+ owns: (plugin: Plugin<InternalParagraphChangeTrackerState>) => boolean;
11
+ get: (state: EditorState) => Plugin<InternalParagraphChangeTrackerState> | undefined;
12
+ getState: (state: EditorState) => InternalParagraphChangeTrackerState | undefined;
13
+ }>;
8
14
  type ParagraphChangeTrackerState = {
9
15
  /** Set of paraIds that were modified since last clear */
10
16
  changedParaIds: Set<string>;
@@ -1,15 +1,23 @@
1
1
  import { withTrackedSectionEndpointRemoval } from "../../../internal/sectionEndpointResolution.js";
2
2
  import { canonicalJson } from "../../../utils/canonicalJson.js";
3
3
  import { enclosingParagraphIndexed, nodeAtIndexed, nodesBetweenIndexed } from "../../indexedNodeLookup.js";
4
+ import { createPluginKeyOwner } from "../../pluginKeyOwner.js";
4
5
  import { sweepPositions } from "../../positionSweep.js";
5
6
  import { sectionPropertiesOf } from "../../sectionCarrier.js";
6
7
  import { sectionMarkEditsOf } from "../../sectionMarkEdits.js";
7
8
  import { createExtension } from "../create.js";
8
9
  import { AddMarkStep, AddNodeMarkStep, AttrStep, RemoveMarkStep, RemoveNodeMarkStep } from "prosemirror-transform";
9
- import { Plugin, PluginKey } from "prosemirror-state";
10
+ import { Plugin } from "prosemirror-state";
10
11
  import { isHistoryTransaction } from "prosemirror-history";
11
12
  //#region src/prosemirror/extensions/features/ParagraphChangeTrackerExtension.ts
12
- const paragraphChangeTrackerKey = new PluginKey("paragraphChangeTracker");
13
+ /**
14
+ * Paragraph Change Tracker Extension
15
+ *
16
+ * Watches ProseMirror transactions and records which paragraph IDs (paraId)
17
+ * were modified. Also detects paragraph and block-structure changes.
18
+ * Used by the selective save system to patch only changed paragraphs in document.xml.
19
+ */
20
+ const paragraphChangeTrackerKey = createPluginKeyOwner("paragraphChangeTracker");
13
21
  const CLEAR_META = "clear";
14
22
  const IGNORE_META = "ignore";
15
23
  const STRUCTURAL_META = "structural";
@@ -196,7 +204,7 @@ const mapStepTouches = (tr, batches) => {
196
204
  };
197
205
  function createParagraphChangeTrackerPlugin() {
198
206
  return new Plugin({
199
- key: paragraphChangeTrackerKey,
207
+ key: paragraphChangeTrackerKey.createKey(),
200
208
  state: {
201
209
  init(_config, state) {
202
210
  const counts = countDocumentStructure(state.doc);
@@ -1,9 +1,10 @@
1
1
  import { expectRunIdentityMarkAttrs } from "../../attrs/index.js";
2
2
  import { nodesBetweenIndexed } from "../../indexedNodeLookup.js";
3
+ import { createPluginKeyOwner } from "../../pluginKeyOwner.js";
3
4
  import { sweepPositions } from "../../positionSweep.js";
4
5
  import { RUN_IDENTITY_ATTRIBUTE, RUN_IDENTITY_MARK_NAME, runIdentityAttrs } from "../../runIdentity.js";
5
6
  import { createMarkExtension } from "../create.js";
6
- import { Plugin, PluginKey } from "prosemirror-state";
7
+ import { Plugin } from "prosemirror-state";
7
8
  import { isHistoryTransaction, undoDepth } from "prosemirror-history";
8
9
  //#region src/prosemirror/extensions/marks/RunIdentityExtension.ts
9
10
  /**
@@ -28,7 +29,7 @@ import { isHistoryTransaction, undoDepth } from "prosemirror-history";
28
29
  * supplies the session the format itself says such a run belongs to.
29
30
  */
30
31
  const CANONICAL_RUN_IDENTITY_ID = /^(?:0|[1-9]\d*)$/u;
31
- const runIdentityStripKey = new PluginKey("runIdentityStrip");
32
+ const runIdentityStripKey = createPluginKeyOwner("runIdentityStrip");
32
33
  /**
33
34
  * Every range `transactions` inserted, in the document they produced.
34
35
  *
@@ -87,7 +88,7 @@ const insertedRanges = (transactions, skip = () => false) => {
87
88
  * collaborative apply all arrive here and nowhere else in common.
88
89
  */
89
90
  const createRunIdentityStripPlugin = () => new Plugin({
90
- key: runIdentityStripKey,
91
+ key: runIdentityStripKey.createKey(),
91
92
  appendTransaction(transactions, oldState, newState) {
92
93
  if (!transactions.some((transaction) => transaction.docChanged)) return null;
93
94
  const markType = newState.schema.marks[RUN_IDENTITY_MARK_NAME];
@@ -409,7 +409,7 @@ function isMarkActive(state, type, attrs) {
409
409
  });
410
410
  let hasMark = false;
411
411
  state.doc.nodesBetween(from, to, (node) => {
412
- if (node.isText) {
412
+ if (node.isText && !(RUN_FORMATTING_MARK_NAMES.has(markType.name) && node.marks.some((mark) => mark.type.name === "footnoteRef"))) {
413
413
  const mark = markType.isInSet(node.marks);
414
414
  if (mark) {
415
415
  if (!attrs) {
@@ -1,6 +1,7 @@
1
1
  import { readCommentReferenceAttrs } from "../../commentReferenceAttrs.js";
2
2
  import { planCommentReferenceRepairs } from "../../commentReferenceIntegrity.js";
3
3
  import { readRangeAnchorAttrs } from "../../rangeAnchorAttrs.js";
4
+ import { trackedRunInlineAtomDisposition } from "../../trackedRunInlineAtoms.js";
4
5
  import { createNodeExtension } from "../create.js";
5
6
  import "./RangeAnchorExtension.js";
6
7
  import { Plugin } from "prosemirror-state";
@@ -10,7 +11,16 @@ const COMMENT_REFERENCE_NODE_NAME = "commentReference";
10
11
  const surveyCommentReferences = (doc) => {
11
12
  const references = [];
12
13
  const markEnds = /* @__PURE__ */ new Map();
14
+ const unanchoredMarks = [];
13
15
  doc.descendants((node, position) => {
16
+ const transparent = trackedRunInlineAtomDisposition(node) === "transparent";
17
+ if (transparent) {
18
+ for (const mark of node.marks) if (mark.type.name === "comment") unanchoredMarks.push({
19
+ position,
20
+ size: node.nodeSize,
21
+ mark
22
+ });
23
+ }
14
24
  if (node.type.name === "commentReference") {
15
25
  const attrs = readCommentReferenceAttrs(node);
16
26
  references.push(attrs.ok ? {
@@ -33,6 +43,7 @@ const surveyCommentReferences = (doc) => {
33
43
  }
34
44
  return false;
35
45
  }
46
+ if (transparent) return false;
36
47
  if (!node.isInline) return true;
37
48
  for (const mark of node.marks) {
38
49
  if (mark.type.name !== "comment") continue;
@@ -44,6 +55,7 @@ const surveyCommentReferences = (doc) => {
44
55
  });
45
56
  return {
46
57
  references,
58
+ unanchoredMarks,
47
59
  markExtents: [...markEnds].map(([commentId, end]) => ({
48
60
  commentId,
49
61
  end
@@ -84,9 +96,11 @@ const CommentReferenceExtension = createNodeExtension({
84
96
  if (!transactions.some(({ docChanged }) => docChanged)) return null;
85
97
  const referenceType = newState.schema.nodes[COMMENT_REFERENCE_NODE_NAME];
86
98
  if (!referenceType) return null;
87
- const repairs = planCommentReferenceRepairs(surveyCommentReferences(newState.doc));
88
- if (repairs.length === 0) return null;
99
+ const survey = surveyCommentReferences(newState.doc);
100
+ const repairs = planCommentReferenceRepairs(survey);
101
+ if (repairs.length === 0 && survey.unanchoredMarks.length === 0) return null;
89
102
  const transaction = newState.tr;
103
+ for (const { position, size, mark } of survey.unanchoredMarks) transaction.removeMark(position, position + size, mark);
90
104
  for (const repair of repairs) {
91
105
  if (repair.type === "delete") {
92
106
  transaction.delete(repair.position, repair.position + repair.size);
@@ -1,5 +1,7 @@
1
+ import { DisplayMode } from "../managers/EditorModeManager.js";
1
2
  import { FindOptions } from "../utils/findReplace.js";
2
3
  import { Node } from "prosemirror-model";
4
+ import { EditorState } from "prosemirror-state";
3
5
  //#region src/prosemirror/findReplaceSelection.d.ts
4
6
  /**
5
7
  * Positional fields of a document search match that the range resolver needs.
@@ -26,5 +28,13 @@ type ProseMirrorFindMatch = FindMatchPosition & FindMatchRange & {
26
28
  };
27
29
  declare function resolveFindMatchRange(doc: Node, match: FindMatchPosition): FindMatchRange | null;
28
30
  declare function findInProseMirrorDocument(doc: Node, searchText: string, options: FindOptions): ProseMirrorFindMatch[];
31
+ type FindInMarkupViewOptions = {
32
+ state: EditorState;
33
+ markupView: DisplayMode;
34
+ searchText: string;
35
+ options: FindOptions;
36
+ };
37
+ /** Search the displayed story; returned ranges address the authored editor document. */
38
+ declare const findInMarkupView: ({ state, markupView, searchText, options }: FindInMarkupViewOptions) => ProseMirrorFindMatch[];
29
39
  //#endregion
30
- export { FindMatchPosition, FindMatchRange, ProseMirrorFindMatch, findInProseMirrorDocument, resolveFindMatchRange };
40
+ export { FindMatchPosition, FindMatchRange, ProseMirrorFindMatch, findInMarkupView, findInProseMirrorDocument, resolveFindMatchRange };
@@ -1,6 +1,7 @@
1
1
  import { findAllMatches } from "../utils/findReplace.js";
2
2
  import { decodeOoxmlSymbolCharacter } from "../utils/ooxmlSymbol.js";
3
3
  import { expectSymbolAttrs } from "./attrs/index.js";
4
+ import { projectMarkupView } from "./markupViewProjection.js";
4
5
  //#region src/prosemirror/findReplaceSelection.ts
5
6
  function resolveFindMatchRange(doc, match) {
6
7
  let resolved = null;
@@ -40,6 +41,19 @@ function findInProseMirrorDocument(doc, searchText, options) {
40
41
  });
41
42
  return matches;
42
43
  }
44
+ /** Search the displayed story; returned ranges address the authored editor document. */
45
+ const findInMarkupView = ({ state, markupView, searchText, options }) => {
46
+ const projection = projectMarkupView(state, markupView);
47
+ if (projection.type === "authored") return findInProseMirrorDocument(state.doc, searchText, options);
48
+ return findInProseMirrorDocument(projection.doc, searchText, options).map((match) => ({
49
+ paragraphIndex: match.paragraphIndex,
50
+ startOffset: match.startOffset,
51
+ endOffset: match.endOffset,
52
+ text: match.text,
53
+ from: projection.toEditor(match.from, 1),
54
+ to: projection.toEditor(match.to, -1)
55
+ }));
56
+ };
43
57
  function forEachSearchParagraph(doc, visit) {
44
58
  let paragraphIndex = 0;
45
59
  const walkBlocks = (container, contentStart) => {
@@ -139,4 +153,4 @@ function getSearchableParagraphText(paragraph) {
139
153
  return text;
140
154
  }
141
155
  //#endregion
142
- export { findInProseMirrorDocument, resolveFindMatchRange };
156
+ export { findInMarkupView, findInProseMirrorDocument, resolveFindMatchRange };
@@ -1,3 +1,4 @@
1
+ import { document_d_exports } from "../types/document.js";
1
2
  import { DisplayMode } from "../managers/EditorModeManager.js";
2
3
  import { RemovedSectionReference } from "../internal/sectionEndpointResolution.js";
3
4
  import { indexedPositionMap } from "../internal/indexedPositionMap.js";
@@ -93,5 +94,13 @@ declare const projectMarkupView: (state: EditorState, view: DisplayMode) => Mark
93
94
  * of text it cannot enter.
94
95
  */
95
96
  declare const visibleCaretPosition: (projection: MarkupViewProjection, position: number) => number;
97
+ type ProjectStoryMarkupViewOptions = {
98
+ doc: Node;
99
+ markupView: DisplayMode;
100
+ styles?: document_d_exports.StyleDefinitions | null | undefined;
101
+ numbering?: document_d_exports.NumberingDefinitions | undefined;
102
+ };
103
+ /** Detached stories use the same state-aware projection owner as the body. */
104
+ declare const projectStoryMarkupView: ({ doc, markupView, styles, numbering }: ProjectStoryMarkupViewOptions) => MarkupViewProjection;
96
105
  //#endregion
97
- export { MarkupViewProjection, markupViewResolutionMode, projectMarkupView, resolveStateStory, visibleCaretPosition };
106
+ export { MarkupViewProjection, markupViewResolutionMode, projectMarkupView, projectStoryMarkupView, resolveStateStory, visibleCaretPosition };
@@ -1,9 +1,10 @@
1
1
  import { indexedPositionMap } from "../internal/indexedPositionMap.js";
2
2
  import { finalRevisionParagraphRanges } from "../internal/revisionResolutionTracking.js";
3
3
  import { resolveWholeStory } from "../internal/wholeStoryRevisionResolution.js";
4
- import { getDocumentNumbering } from "./plugins/documentNumbering.js";
4
+ import { createDocumentNumberingPlugin, getDocumentNumbering } from "./plugins/documentNumbering.js";
5
5
  import { getDocumentStyleResolver } from "./plugins/documentStyleState.js";
6
- import { Selection } from "prosemirror-state";
6
+ import { createDocumentStyleContextPlugin } from "./plugins/documentStyles.js";
7
+ import { EditorState, Selection } from "prosemirror-state";
7
8
  //#region src/prosemirror/markupViewProjection.ts
8
9
  /** Resolve every revision in the state's story with the state's own styles and numbering. */
9
10
  const resolveStateStory = (state, mode) => resolveWholeStory({
@@ -84,5 +85,22 @@ const visibleCaretPosition = (projection, position) => {
84
85
  const near = Selection.near(projection.doc.resolve(bounded), -1);
85
86
  return projection.toEditor(near.head, -1);
86
87
  };
88
+ const storyProjectionStates = /* @__PURE__ */ new WeakMap();
89
+ /** Detached stories use the same state-aware projection owner as the body. */
90
+ const projectStoryMarkupView = ({ doc, markupView, styles, numbering }) => {
91
+ if (markupView === "all-markup") return AUTHORED;
92
+ const cached = storyProjectionStates.get(doc);
93
+ if (cached && cached.styles === styles && cached.numbering === numbering) return projectMarkupView(cached.state, markupView);
94
+ const state = EditorState.create({
95
+ doc,
96
+ plugins: [createDocumentStyleContextPlugin(styles), createDocumentNumberingPlugin(numbering)]
97
+ });
98
+ storyProjectionStates.set(doc, {
99
+ styles,
100
+ numbering,
101
+ state
102
+ });
103
+ return projectMarkupView(state, markupView);
104
+ };
87
105
  //#endregion
88
- export { markupViewResolutionMode, projectMarkupView, resolveStateStory, visibleCaretPosition };
106
+ export { markupViewResolutionMode, projectMarkupView, projectStoryMarkupView, resolveStateStory, visibleCaretPosition };
@@ -0,0 +1,13 @@
1
+ import { EditorState, Plugin, PluginKey } from "prosemirror-state";
2
+ //#region src/prosemirror/pluginKeyOwner.d.ts
3
+ type PluginKeyOwner<State> = Readonly<{
4
+ key: string;
5
+ createKey: () => PluginKey<State>;
6
+ owns: (plugin: Plugin<State>) => boolean;
7
+ get: (state: EditorState) => Plugin<State> | undefined;
8
+ getState: (state: EditorState) => State | undefined;
9
+ }>;
10
+ /** An immutable plugin family descriptor; each plugin owns its actual key. */
11
+ declare const createPluginKeyOwner: <State = unknown>(name: string) => PluginKeyOwner<State>;
12
+ //#endregion
13
+ export { PluginKeyOwner, createPluginKeyOwner };
@@ -0,0 +1,35 @@
1
+ import { panic } from "better-result";
2
+ import { PluginKey } from "prosemirror-state";
3
+ //#region src/prosemirror/pluginKeyOwner.ts
4
+ const PLUGIN_KEY_PREFIX = "stll.folio.plugin.";
5
+ const METADATA_KEY_PREFIX = "stll.folio.meta.";
6
+ const PLUGIN_OWNER_BRAND = Symbol.for("stll.folio.plugin.owner");
7
+ /** An immutable plugin family descriptor; each plugin owns its actual key. */
8
+ const createPluginKeyOwner = (name) => {
9
+ const pluginsByState = /* @__PURE__ */ new WeakMap();
10
+ const owns = (plugin) => plugin.spec.key !== void 0 && PLUGIN_OWNER_BRAND in plugin.spec.key && plugin.spec.key[PLUGIN_OWNER_BRAND] === name;
11
+ const get = (state) => {
12
+ if (pluginsByState.has(state)) return pluginsByState.get(state);
13
+ let found;
14
+ for (const plugin of state.plugins) {
15
+ if (!owns(plugin)) continue;
16
+ if (found) panic(`Multiple plugins belong to ${name} in one editor state`);
17
+ found = plugin;
18
+ }
19
+ pluginsByState.set(state, found);
20
+ return found;
21
+ };
22
+ return Object.freeze({
23
+ key: `${METADATA_KEY_PREFIX}${name}`,
24
+ createKey: () => {
25
+ const key = new PluginKey(`${PLUGIN_KEY_PREFIX}${name}`);
26
+ Object.defineProperty(key, PLUGIN_OWNER_BRAND, { value: name });
27
+ return key;
28
+ },
29
+ owns,
30
+ get,
31
+ getState: (state) => get(state)?.getState(state)
32
+ });
33
+ };
34
+ //#endregion
35
+ export { createPluginKeyOwner };
@@ -1,7 +1,14 @@
1
- import { Plugin, PluginKey } from "prosemirror-state";
1
+ import { PluginKeyOwner } from "../pluginKeyOwner.js";
2
+ import { Plugin } from "prosemirror-state";
2
3
  import { DecorationSet } from "prosemirror-view";
3
4
  //#region src/prosemirror/plugins/aiCitationDecorations.d.ts
4
- declare const aiCitationDecorationsKey: PluginKey<AICitationDecorationState>;
5
+ declare const aiCitationDecorationsKey: Readonly<{
6
+ key: string;
7
+ createKey: () => import("prosemirror-state").PluginKey<AICitationDecorationState>;
8
+ owns: (plugin: Plugin<AICitationDecorationState>) => boolean;
9
+ get: (state: import("prosemirror-state").EditorState) => Plugin<AICitationDecorationState> | undefined;
10
+ getState: (state: import("prosemirror-state").EditorState) => AICitationDecorationState | undefined;
11
+ }>;
5
12
  declare const SET_META = "set";
6
13
  declare const ACTIVE_META = "active";
7
14
  type AICitationRange = {
@@ -23,14 +30,14 @@ type AICitationDecorationState = {
23
30
  */
24
31
  declare function createAICitationDecorationsPlugin(): Plugin<AICitationDecorationState>;
25
32
  declare function setAICitationsMeta(citations: AICitationRange[]): {
26
- key: PluginKey<AICitationDecorationState>;
33
+ key: PluginKeyOwner<AICitationDecorationState>;
27
34
  payload: {
28
35
  type: typeof SET_META;
29
36
  citations: AICitationRange[];
30
37
  };
31
38
  };
32
39
  declare function setActiveCitationMeta(activeId: string | null): {
33
- key: PluginKey<AICitationDecorationState>;
40
+ key: PluginKeyOwner<AICitationDecorationState>;
34
41
  payload: {
35
42
  type: typeof ACTIVE_META;
36
43
  activeId: string | null;
@@ -1,7 +1,20 @@
1
- import { Plugin, PluginKey } from "prosemirror-state";
1
+ import { createPluginKeyOwner } from "../pluginKeyOwner.js";
2
+ import { Plugin } from "prosemirror-state";
2
3
  import { Decoration, DecorationSet } from "prosemirror-view";
3
4
  //#region src/prosemirror/plugins/aiCitationDecorations.ts
4
- const aiCitationDecorationsKey = new PluginKey("aiCitationDecorations");
5
+ /**
6
+ * AI Citation Decorations Plugin
7
+ *
8
+ * Renders pointer-back-to-source highlights on Folio (DOCX) ranges
9
+ * cited by an AI answer. Distinct from the suggestion-decoration
10
+ * plugin: citations are read-only pointers (no edit semantics) and
11
+ * usually appear briefly when the user clicks a citation chip in the
12
+ * thread.
13
+ *
14
+ * The host pushes updates via the `setAICitationsMeta` /
15
+ * `setActiveCitationMeta` helpers below.
16
+ */
17
+ const aiCitationDecorationsKey = createPluginKeyOwner("aiCitationDecorations");
5
18
  const SET_META = "set";
6
19
  const ACTIVE_META = "active";
7
20
  function buildDecorationSet(doc, citations, activeId) {
@@ -31,7 +44,7 @@ function buildDecorationSet(doc, citations, activeId) {
31
44
  */
32
45
  function createAICitationDecorationsPlugin() {
33
46
  return new Plugin({
34
- key: aiCitationDecorationsKey,
47
+ key: aiCitationDecorationsKey.createKey(),
35
48
  state: {
36
49
  init(_, state) {
37
50
  return {