@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.
- package/README.md +23 -1
- package/dist/ai-edits/apply.js +139 -32
- package/dist/ai-edits/headless.d.ts +3 -0
- package/dist/ai-edits/headless.js +57 -63
- package/dist/ai-edits/note-story-inventory.d.ts +7 -0
- package/dist/ai-edits/note-story-inventory.js +11 -0
- package/dist/ai-edits/read.js +16 -9
- package/dist/ai-edits/revisionStretches.js +52 -1
- package/dist/ai-edits/table-row-column-mutations.js +6 -1
- package/dist/compare/reproducible-package.js +2 -1
- package/dist/compare/verification.js +1 -0
- package/dist/compat/eigenpal.d.ts +4 -2
- package/dist/compat/eigenpal.js +2 -2
- package/dist/controller/canonicalClipboard.js +2 -1
- package/dist/controller/canonicalListRendering.d.ts +4 -6
- package/dist/controller/canonicalListRendering.js +3 -157
- package/dist/controller/canonicalSession.js +3 -2
- package/dist/controller/canonicalStructure.js +10 -7
- package/dist/controller/fontReadiness.js +1 -1
- package/dist/controller/headerFooterEditorManager.d.ts +1 -0
- package/dist/controller/headerFooterEditorManager.js +11 -0
- package/dist/controller/hiddenEditorManager.d.ts +1 -1
- package/dist/controller/hiddenEditorManager.js +3 -1
- package/dist/controller/layoutPipeline.d.ts +3 -1
- package/dist/controller/layoutPipeline.js +19 -4
- package/dist/controller/layoutSession.d.ts +4 -1
- package/dist/display-list/build/fontTable.js +7 -5
- package/dist/display-list/build/furniture.d.ts +1 -0
- package/dist/display-list/build/furniture.js +1 -0
- package/dist/display-list/build/imagePrimitives.js +3 -6
- package/dist/display-list/build/watermarkPrimitives.d.ts +2 -1
- package/dist/display-list/build/watermarkPrimitives.js +9 -3
- package/dist/display-list/editor/pageRenderer.js +1 -0
- package/dist/docx/bookmarkIdNormalization.d.ts +10 -0
- package/dist/docx/bookmarkIdNormalization.js +100 -0
- package/dist/docx/bookmarkParser.js +4 -2
- package/dist/docx/canonicalStoryRepack.js +3 -2
- package/dist/docx/commentTextParagraph.d.ts +6 -0
- package/dist/docx/commentTextParagraph.js +15 -0
- package/dist/docx/conformance.js +6 -1
- package/dist/docx/diagramPreview.js +2 -1
- package/dist/docx/documentParser.d.ts +13 -1
- package/dist/docx/documentParser.js +1 -1
- package/dist/docx/encryption/compoundFile.d.ts +0 -8
- package/dist/docx/encryption/compoundFile.js +2 -1
- package/dist/docx/encryption/cryptoBytes.d.ts +2 -2
- package/dist/docx/encryption/cryptoBytes.js +3 -18
- package/dist/docx/encryption/encryptionInfo.d.ts +0 -5
- package/dist/docx/encryption/encryptionInfo.js +2 -1
- package/dist/docx/ensureParaIds.js +2 -1
- package/dist/docx/groupDrawingParser.js +6 -1
- package/dist/docx/importedIdCollisions.d.ts +136 -0
- package/dist/docx/importedIdCollisions.js +215 -0
- package/dist/docx/listModelNormalization.d.ts +10 -0
- package/dist/docx/listModelNormalization.js +166 -0
- package/dist/docx/metadataPrivacy.js +3 -2
- package/dist/docx/metafileRaster.js +2 -1
- package/dist/docx/numberingParser.js +2 -2
- package/dist/docx/packageParts.js +6 -5
- package/dist/docx/paragraphParser.js +6 -4
- package/dist/docx/paragraphPropertySource.js +1 -2
- package/dist/docx/parseContext.d.ts +2 -1
- package/dist/docx/parseContext.js +1 -0
- package/dist/docx/parseWarningMessage.js +1 -0
- package/dist/docx/parser.js +10 -5
- package/dist/docx/preservedRunContent.js +11 -2
- package/dist/docx/relsParser.js +1 -1
- package/dist/docx/removeHeaderFooterParts.js +8 -7
- package/dist/docx/replyToComment.js +3 -13
- package/dist/docx/rezip.js +90 -90
- package/dist/docx/runParser.js +12 -6
- package/dist/docx/runPropertyPresence.d.ts +11 -0
- package/dist/docx/runPropertyPresence.js +14 -0
- package/dist/docx/selectedXmlIdentityScan.d.ts +10 -0
- package/dist/docx/selectedXmlIdentityScan.js +36 -0
- package/dist/docx/selectiveSave.js +19 -17
- package/dist/docx/selectiveXmlPatch.js +44 -52
- package/dist/docx/serializer/commentSerializer.d.ts +3 -1
- package/dist/docx/serializer/commentSerializer.js +11 -2
- package/dist/docx/serializer/runSerializer.js +5 -4
- package/dist/docx/serializer/tableSerializer.js +2 -1
- package/dist/docx/serializer/trackedChangeAttributes.d.ts +1 -3
- package/dist/docx/serializer/trackedChangeAttributes.js +1 -11
- package/dist/docx/server/applyDocxXmlPatchProposal.js +2 -2
- package/dist/docx/server/boundedArchive.js +2 -1
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +2 -1
- package/dist/docx/server/inspectDocxPackage.js +5 -1
- package/dist/docx/server/validateDocxConformance.js +2 -1
- package/dist/docx/tableParser.js +7 -4
- package/dist/docx/tableRowPropertyPresence.d.ts +9 -0
- package/dist/docx/tableRowPropertyPresence.js +15 -0
- package/dist/docx/unzip.js +2 -1
- package/dist/docx/xmlEncoding.js +6 -5
- package/dist/fonts/embeddedFontFingerprint.d.ts +5 -0
- package/dist/fonts/embeddedFontFingerprint.js +18 -0
- package/dist/fonts/embeddedFonts.d.ts +1 -1
- package/dist/fonts/embeddedFonts.js +1 -1
- package/dist/fonts/fontsourceFaces.js +2 -1
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/fonts/sfnt/parse.js +6 -9
- package/dist/fonts/sfnt/subset.js +2 -1
- package/dist/fonts/sfnt/tables.js +2 -3
- package/dist/fonts/sfnt/woff.js +2 -1
- package/dist/headless-layout.js +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -2
- package/dist/internal/revisionResolutionInline.d.ts +2 -1
- package/dist/internal/revisionResolutionInline.js +22 -1
- package/dist/internal/wholeStoryRevisionResolution.js +24 -7
- package/dist/layout-bridge/convert/flowConversionShared.d.ts +4 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +5 -1
- package/dist/layout-bridge/convert/footnoteLayout.js +16 -3
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +6 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +13 -14
- package/dist/layout-bridge/convert/paragraphConversion.js +8 -1
- package/dist/layout-bridge/convert/paragraphRuns.js +2 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +25 -1
- package/dist/layout-engine/measure/cache.js +39 -7
- package/dist/layout-engine/measure/crossRunWords.js +12 -2
- package/dist/layout-engine/measure/lineTypography.js +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.js +1 -0
- package/dist/layout-engine/measure/measureContainer.js +3 -4
- package/dist/layout-engine/measure/measureHelpers.d.ts +6 -9
- package/dist/layout-engine/measure/measureHelpers.js +21 -18
- package/dist/layout-engine/measure/measureParagraph.js +2 -0
- package/dist/layout-engine/measure/measureTypes.d.ts +2 -0
- package/dist/layout-engine/types.d.ts +4 -1
- package/dist/layout-painter/renderPage.d.ts +1 -0
- package/dist/layout-painter/renderPage.js +11 -3
- package/dist/layout-painter/renderParagraph.js +13 -3
- package/dist/layout-painter/renderWatermark.d.ts +1 -0
- package/dist/layout-painter/renderWatermark.js +7 -3
- package/dist/managers/AutoSaveManager.js +2 -1
- package/dist/managers/EditorModeManager.d.ts +10 -1
- package/dist/managers/EditorModeManager.js +15 -1
- package/dist/managers/TableSelectionManager.d.ts +1 -1
- package/dist/managers/autoSaveCodec.js +2 -6
- package/dist/managers/editorShortcuts.d.ts +2 -1
- package/dist/managers/editorShortcuts.js +2 -1
- package/dist/managers/reviewDisplayMode.d.ts +8 -0
- package/dist/managers/reviewDisplayMode.js +15 -0
- package/dist/paged-layout/pageText.d.ts +11 -2
- package/dist/paged-layout/pageText.js +8 -3
- package/dist/pdf/fonts.js +5 -14
- package/dist/pdf/images.js +3 -2
- package/dist/pdf/objects.d.ts +0 -11
- package/dist/pdf/objects.js +7 -9
- package/dist/pdf/writePdf.js +2 -1
- package/dist/prosemirror/canonicalSelectionRange.d.ts +4 -1
- package/dist/prosemirror/canonicalSelectionRange.js +9 -3
- package/dist/prosemirror/commands/comments.js +51 -13
- package/dist/prosemirror/containerFinalParagraph.d.ts +8 -8
- package/dist/prosemirror/containerFinalParagraph.js +8 -8
- package/dist/prosemirror/conversion/fromProseDoc.js +9 -4
- package/dist/prosemirror/conversion/toProseDoc.js +4 -4
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +8 -2
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +39 -3
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.d.ts +8 -2
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +4 -3
- package/dist/prosemirror/extensions/features/ImageDragExtension.js +4 -3
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +4 -3
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +9 -3
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +6 -5
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +8 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +11 -3
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/markUtils.js +1 -1
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +16 -2
- package/dist/prosemirror/findReplaceSelection.d.ts +11 -1
- package/dist/prosemirror/findReplaceSelection.js +15 -1
- package/dist/prosemirror/markupViewProjection.d.ts +10 -1
- package/dist/prosemirror/markupViewProjection.js +21 -3
- package/dist/prosemirror/pluginKeyOwner.d.ts +13 -0
- package/dist/prosemirror/pluginKeyOwner.js +35 -0
- package/dist/prosemirror/plugins/aiCitationDecorations.d.ts +11 -4
- package/dist/prosemirror/plugins/aiCitationDecorations.js +16 -3
- package/dist/prosemirror/plugins/aiSuggestionDecorations.d.ts +11 -4
- package/dist/prosemirror/plugins/aiSuggestionDecorations.js +18 -3
- package/dist/prosemirror/plugins/anonymizationDecorations.d.ts +11 -11
- package/dist/prosemirror/plugins/anonymizationDecorations.js +23 -13
- package/dist/prosemirror/plugins/autocompleteSuggestion.d.ts +8 -2
- package/dist/prosemirror/plugins/autocompleteSuggestion.js +27 -5
- package/dist/prosemirror/plugins/contentControlWidgets.d.ts +8 -2
- package/dist/prosemirror/plugins/contentControlWidgets.js +4 -3
- package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +6 -5
- package/dist/prosemirror/plugins/createDocScanPlugin.js +1 -1
- package/dist/prosemirror/plugins/documentNumbering.js +5 -3
- package/dist/prosemirror/plugins/documentStyleState.d.ts +8 -2
- package/dist/prosemirror/plugins/documentStyleState.js +2 -2
- package/dist/prosemirror/plugins/documentStyles.js +1 -1
- package/dist/prosemirror/plugins/selectionTracker.d.ts +8 -2
- package/dist/prosemirror/plugins/selectionTracker.js +4 -3
- package/dist/prosemirror/plugins/suggestionMode.d.ts +8 -2
- package/dist/prosemirror/plugins/suggestionMode.js +4 -3
- package/dist/prosemirror/plugins/templateDirectives.d.ts +8 -2
- package/dist/prosemirror/plugins/templateDirectives.js +19 -4
- package/dist/prosemirror/plugins/templatePreviewValues.d.ts +8 -2
- package/dist/prosemirror/plugins/templatePreviewValues.js +27 -5
- package/dist/prosemirror/plugins/templateSlashMenu.d.ts +8 -2
- package/dist/prosemirror/plugins/templateSlashMenu.js +23 -5
- package/dist/prosemirror/plugins/transactionInvariants.js +29 -4
- package/dist/prosemirror/replacedAnnotations.d.ts +15 -1
- package/dist/prosemirror/replacedAnnotations.js +71 -12
- package/dist/prosemirror/runFormattingReconciliation.d.ts +3 -1
- package/dist/prosemirror/runFormattingReconciliation.js +10 -2
- package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +5 -7
- package/dist/prosemirror/runIdentityAcrossRevisions.js +66 -38
- package/dist/prosemirror/runPropertyChangeResolution.js +13 -1
- package/dist/utils/base64.d.ts +1 -31
- package/dist/utils/base64.js +19 -33
- package/dist/utils/clipboard.js +5 -4
- package/dist/utils/colorResolver.js +11 -2
- package/dist/utils/findReplace.d.ts +5 -0
- package/dist/utils/fontLoader.d.ts +2 -2
- package/dist/utils/fontLoader.js +2 -7
- package/dist/utils/fontResolver.d.ts +7 -3
- package/dist/utils/fontResolver.js +6 -8
- package/dist/utils/replaceText.js +21 -4
- package/dist/utils/sliceText.d.ts +2 -0
- package/dist/utils/sliceText.js +2 -0
- package/dist/utils/textEncoding.d.ts +2 -0
- package/dist/utils/textEncoding.js +2 -0
- package/dist/utils/tiffConverter.d.ts +0 -9
- package/dist/utils/tiffConverter.js +2 -4
- package/package.json +73 -31
|
@@ -1,8 +1,32 @@
|
|
|
1
|
+
import { createPluginKeyOwner } from "../pluginKeyOwner.js";
|
|
1
2
|
import { scanDirectives } from "./templateDirectives.js";
|
|
2
|
-
import { Plugin
|
|
3
|
+
import { Plugin } from "prosemirror-state";
|
|
3
4
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
4
5
|
import { isFieldPath } from "@stll/template-conditions";
|
|
5
6
|
//#region src/prosemirror/plugins/templatePreviewValues.ts
|
|
7
|
+
/**
|
|
8
|
+
* Template Preview Values Plugin
|
|
9
|
+
*
|
|
10
|
+
* Live fill preview for template documents: given a map of field path →
|
|
11
|
+
* typed value, every matching `{{path}}` marker is visually replaced by
|
|
12
|
+
* the value — the marker text is hidden by an inline decoration and the
|
|
13
|
+
* value is injected as a widget in its place. The document itself is
|
|
14
|
+
* never modified; clearing the preview restores the markers.
|
|
15
|
+
*
|
|
16
|
+
* The same preview hides conditional blocks the host reports as not
|
|
17
|
+
* applying: `conditions` maps an `{% if expr %}` expression to whether
|
|
18
|
+
* its block applies, and a `false` block is hidden from its opener
|
|
19
|
+
* through its `{% endif %}`. folio evaluates no expression.
|
|
20
|
+
*
|
|
21
|
+
* Two render modes: `highlighted` paints the substituted values with the
|
|
22
|
+
* preview accent so it is unmistakably a preview, and keeps every
|
|
23
|
+
* directive tag visible; `plain` renders the values as ordinary text and
|
|
24
|
+
* drops the tag paragraphs of a block the host has ruled on, so it
|
|
25
|
+
* approximates the generated document rather than the template.
|
|
26
|
+
*
|
|
27
|
+
* Updates are pushed via {@link setTemplatePreviewValues}; the host wires
|
|
28
|
+
* this to its fill inputs.
|
|
29
|
+
*/
|
|
6
30
|
/** Concatenated plain text of a preview value. */
|
|
7
31
|
const templatePreviewValueText = (value) => typeof value === "string" ? value : value.runs.map((run) => run.text).join("");
|
|
8
32
|
/** Stable identity of a preview value (text + formatting), for decoration
|
|
@@ -26,9 +50,7 @@ const templatePreviewHidesWholeBlock = (range, block) => range.from <= block.pmS
|
|
|
26
50
|
const ORIGINAL_CLASS = "folio-template-preview-original";
|
|
27
51
|
/** Hides a conditional block the host reported as not applying. */
|
|
28
52
|
const HIDDEN_CLASS = "folio-template-preview-hidden";
|
|
29
|
-
const
|
|
30
|
-
const keyHolder = globalThis;
|
|
31
|
-
const templatePreviewValuesKey = keyHolder[KEY_HOLDER_SYMBOL] ?? (keyHolder[KEY_HOLDER_SYMBOL] = new PluginKey("templatePreviewValues"));
|
|
53
|
+
const templatePreviewValuesKey = createPluginKeyOwner("templatePreviewValues");
|
|
32
54
|
/** A rich span as DOM: text wrapped in `<em>` / `<strong>` per its flags. */
|
|
33
55
|
function buildSpanNode(span) {
|
|
34
56
|
let node = document.createTextNode(span.text);
|
|
@@ -267,7 +289,7 @@ function buildDecorationSet(doc, { entries, hidden }, mode) {
|
|
|
267
289
|
}
|
|
268
290
|
function createTemplatePreviewValuesPlugin() {
|
|
269
291
|
return new Plugin({
|
|
270
|
-
key: templatePreviewValuesKey,
|
|
292
|
+
key: templatePreviewValuesKey.createKey(),
|
|
271
293
|
state: {
|
|
272
294
|
init() {
|
|
273
295
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EditorState, Plugin,
|
|
1
|
+
import { EditorState, Plugin, Transaction } from "prosemirror-state";
|
|
2
2
|
//#region src/prosemirror/plugins/templateSlashMenu.d.ts
|
|
3
3
|
type TemplateSlashMenuState = {
|
|
4
4
|
active: false;
|
|
@@ -34,7 +34,13 @@ type TemplateSlashMenuPluginOptions = {
|
|
|
34
34
|
*/
|
|
35
35
|
onKeyAction?: (action: TemplateSlashMenuKeyAction) => boolean;
|
|
36
36
|
};
|
|
37
|
-
declare const templateSlashMenuKey:
|
|
37
|
+
declare const templateSlashMenuKey: Readonly<{
|
|
38
|
+
key: string;
|
|
39
|
+
createKey: () => import("prosemirror-state").PluginKey<TemplateSlashMenuState>;
|
|
40
|
+
owns: (plugin: Plugin<TemplateSlashMenuState>) => boolean;
|
|
41
|
+
get: (state: EditorState) => Plugin<TemplateSlashMenuState> | undefined;
|
|
42
|
+
getState: (state: EditorState) => TemplateSlashMenuState | undefined;
|
|
43
|
+
}>;
|
|
38
44
|
declare const templateSlashMenuPlugin: (options?: TemplateSlashMenuPluginOptions) => Plugin<TemplateSlashMenuState>;
|
|
39
45
|
declare const getTemplateSlashMenu: (state: EditorState) => TemplateSlashMenuState;
|
|
40
46
|
/** Clear the trigger. Set on the host's marker-insert transaction so the
|
|
@@ -1,6 +1,26 @@
|
|
|
1
|
+
import { createPluginKeyOwner } from "../pluginKeyOwner.js";
|
|
1
2
|
import { getTemplateDirectives } from "./templateDirectives.js";
|
|
2
|
-
import { Plugin
|
|
3
|
+
import { Plugin } from "prosemirror-state";
|
|
3
4
|
//#region src/prosemirror/plugins/templateSlashMenu.ts
|
|
5
|
+
/**
|
|
6
|
+
* Template slash-menu trigger plugin.
|
|
7
|
+
*
|
|
8
|
+
* Watches the template editor for a `/` typed at a marker boundary (start of a
|
|
9
|
+
* text block or right after whitespace) and tracks the query the author types
|
|
10
|
+
* after it. The plugin owns only the *trigger* state — `{active, from, query}` —
|
|
11
|
+
* and the keyboard contract while active; the floating menu UI and the actual
|
|
12
|
+
* marker insertion live in the host (template-studio), which subscribes via
|
|
13
|
+
* {@link TemplateSlashMenuPluginOptions.onChange} and resolves keys via
|
|
14
|
+
* {@link TemplateSlashMenuPluginOptions.onKeyAction}.
|
|
15
|
+
*
|
|
16
|
+
* The trigger guard is deliberately strict so the menu never misfires inside
|
|
17
|
+
* ordinary legal prose: dates (`30/06`), `and/or`, and URLs all have a non-space
|
|
18
|
+
* char before the `/`, so only a leading or post-whitespace `/` opens the menu.
|
|
19
|
+
*
|
|
20
|
+
* Insertion is host-driven: a doc-changing transaction the host dispatches to
|
|
21
|
+
* insert the marker carries a `clear` meta so the plugin tears down without the
|
|
22
|
+
* generic doc-change reset racing the host's selection move.
|
|
23
|
+
*/
|
|
4
24
|
const IDLE = {
|
|
5
25
|
active: false,
|
|
6
26
|
from: null,
|
|
@@ -8,9 +28,7 @@ const IDLE = {
|
|
|
8
28
|
query: ""
|
|
9
29
|
};
|
|
10
30
|
const QUERY_CHAR = /[\p{L}\p{N}_.-]/u;
|
|
11
|
-
const
|
|
12
|
-
const keyHolder = globalThis;
|
|
13
|
-
const templateSlashMenuKey = keyHolder[KEY_HOLDER_SYMBOL] ?? (keyHolder[KEY_HOLDER_SYMBOL] = new PluginKey("templateSlashMenu"));
|
|
31
|
+
const templateSlashMenuKey = createPluginKeyOwner("templateSlashMenu");
|
|
14
32
|
/** Whether the char immediately before `pos` is a marker boundary (start of the
|
|
15
33
|
* text block or whitespace). This is the misfire guard: a `/` mid-token (dates,
|
|
16
34
|
* URLs, and/or) has a word char before it and never opens the menu. */
|
|
@@ -78,7 +96,7 @@ const tryOpenTrigger = (view) => {
|
|
|
78
96
|
};
|
|
79
97
|
const templateSlashMenuPlugin = (options = {}) => {
|
|
80
98
|
return new Plugin({
|
|
81
|
-
key: templateSlashMenuKey,
|
|
99
|
+
key: templateSlashMenuKey.createKey(),
|
|
82
100
|
state: {
|
|
83
101
|
init: () => IDLE,
|
|
84
102
|
apply: (tr, value, _oldState, newState) => {
|
|
@@ -1,16 +1,41 @@
|
|
|
1
|
+
import { sliceText } from "../../utils/sliceText.js";
|
|
1
2
|
import { expectParagraphAttrs } from "../attrs/index.js";
|
|
2
3
|
import { effectiveParagraphNumberingReference, paragraphNumberingSourcesFromBoundary } from "../numberingAttr.js";
|
|
4
|
+
import { createPluginKeyOwner } from "../pluginKeyOwner.js";
|
|
3
5
|
import { validateProseMirrorDocument } from "../validation.js";
|
|
4
6
|
import { getDocumentNumbering, hasDocumentNumbering } from "./documentNumbering.js";
|
|
5
|
-
import { Plugin
|
|
7
|
+
import { Plugin } from "prosemirror-state";
|
|
6
8
|
//#region src/prosemirror/plugins/transactionInvariants.ts
|
|
9
|
+
/**
|
|
10
|
+
* Transaction invariants: a development and test aid that checks the editor
|
|
11
|
+
* document after every transaction, so a command that leaves a document the
|
|
12
|
+
* save path will refuse fails where it happens rather than at save.
|
|
13
|
+
*
|
|
14
|
+
* Off by default. {@link ExtensionManager} installs it when
|
|
15
|
+
* `globalThis.__folioTransactionInvariants` is `{ enabled: true }` at the time
|
|
16
|
+
* the runtime is initialized; the core test preload sets that flag, which turns
|
|
17
|
+
* every editor test built on the extension manager into a validity check. A
|
|
18
|
+
* host may set it in a development build. Production code never reads it
|
|
19
|
+
* otherwise.
|
|
20
|
+
*
|
|
21
|
+
* The checks are the ones the document-model validator makes that the editor
|
|
22
|
+
* state can answer on its own:
|
|
23
|
+
* - the ProseMirror document is structurally valid (node and mark attributes,
|
|
24
|
+
* bookmark pairing), incrementally cached per node;
|
|
25
|
+
* - every paragraph numbering reference names an instance the document's
|
|
26
|
+
* numbering defines.
|
|
27
|
+
*/
|
|
7
28
|
/** Whether new extension-manager runtimes install the invariant plugin. */
|
|
8
29
|
const areTransactionInvariantsEnabled = () => globalThis.__folioTransactionInvariants?.enabled === true;
|
|
9
30
|
const MAX_REPORTED_STEPS_LENGTH = 4e3;
|
|
10
31
|
const describeSteps = (transactions) => {
|
|
11
32
|
const steps = transactions.flatMap((transaction) => transaction.steps.map((step) => step.toJSON()));
|
|
12
33
|
const json = JSON.stringify(steps);
|
|
13
|
-
return json.length > MAX_REPORTED_STEPS_LENGTH ? `${
|
|
34
|
+
return json.length > MAX_REPORTED_STEPS_LENGTH ? `${sliceText({
|
|
35
|
+
text: json,
|
|
36
|
+
units: "codeUnits",
|
|
37
|
+
end: MAX_REPORTED_STEPS_LENGTH
|
|
38
|
+
})}… (${json.length} characters)` : json;
|
|
14
39
|
};
|
|
15
40
|
var TransactionInvariantError = class extends Error {
|
|
16
41
|
issues;
|
|
@@ -69,14 +94,14 @@ const introducedIssues = (before, after, transactions) => {
|
|
|
69
94
|
return false;
|
|
70
95
|
});
|
|
71
96
|
};
|
|
72
|
-
const transactionInvariantsKey =
|
|
97
|
+
const transactionInvariantsKey = createPluginKeyOwner("transactionInvariants");
|
|
73
98
|
/**
|
|
74
99
|
* Throw a {@link TransactionInvariantError} naming the offending steps when a
|
|
75
100
|
* document-changing transaction leaves the state with an issue it did not
|
|
76
101
|
* have before.
|
|
77
102
|
*/
|
|
78
103
|
const createTransactionInvariantPlugin = () => new Plugin({
|
|
79
|
-
key: transactionInvariantsKey,
|
|
104
|
+
key: transactionInvariantsKey.createKey(),
|
|
80
105
|
appendTransaction(transactions, oldState, newState) {
|
|
81
106
|
if (!transactions.some((transaction) => transaction.docChanged)) return null;
|
|
82
107
|
const issues = checkEditorStateInvariants(newState);
|
|
@@ -19,6 +19,20 @@ declare const NON_INCLUSIVE_MARK_DISPOSITION: {
|
|
|
19
19
|
};
|
|
20
20
|
/** The mark names in the schema that a replacement must not silently drop. */
|
|
21
21
|
declare const nonInclusiveMarkNames: (schema: Schema) => string[];
|
|
22
|
+
/** One typed point for a comment whose aligned coverage has no content left. */
|
|
23
|
+
declare const commentPoint: (schema: Schema, id: number) => Node;
|
|
24
|
+
type ResolvedCommentPointsOptions = {
|
|
25
|
+
doc: Node;
|
|
26
|
+
removes: (node: Node, position: number) => boolean;
|
|
27
|
+
retiresStructure: (node: Node, position: number) => boolean;
|
|
28
|
+
};
|
|
29
|
+
/** The same collector serves standalone commands and the existing bulk walk. */
|
|
30
|
+
declare const createResolvedCommentPointCollector: ({ removes, retiresStructure }: Omit<ResolvedCommentPointsOptions, "doc">) => {
|
|
31
|
+
visit: (node: Node, position: number, previous?: Node) => boolean;
|
|
32
|
+
finish: () => Map<number, number>;
|
|
33
|
+
};
|
|
34
|
+
/** Point fallback excludes comments belonging to retired blocks and table cells. */
|
|
35
|
+
declare const resolvedCommentPoints: ({ doc, removes, retiresStructure }: ResolvedCommentPointsOptions) => Map<number, number>;
|
|
22
36
|
type ReplacedAnnotations = {
|
|
23
37
|
/** Marks the replacement must carry, in document order. */
|
|
24
38
|
carried: readonly Mark[];
|
|
@@ -88,4 +102,4 @@ declare const annotatedReplacement: ({ annotations: { carried, leading, trailing
|
|
|
88
102
|
*/
|
|
89
103
|
declare const inheritedReplacementMarks: (doc: Node, from: number, to: number) => readonly Mark[];
|
|
90
104
|
//#endregion
|
|
91
|
-
export { NON_INCLUSIVE_MARK_DISPOSITION, ReplacedAnnotations, addCarriedMarks, annotatedReplacement, commentsAcrossBlockBoundary, commentsForTrackedInsertion, hasReplacedAnnotations, inheritedReplacementMarks, nonInclusiveMarkNames, placeTrackedInsertion, surveyReplacedAnnotations };
|
|
105
|
+
export { NON_INCLUSIVE_MARK_DISPOSITION, ReplacedAnnotations, addCarriedMarks, annotatedReplacement, commentPoint, commentsAcrossBlockBoundary, commentsForTrackedInsertion, createResolvedCommentPointCollector, hasReplacedAnnotations, inheritedReplacementMarks, nonInclusiveMarkNames, placeTrackedInsertion, resolvedCommentPoints, surveyReplacedAnnotations };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { expectParagraphAttrs, expectTableCellAttrs, expectTableRowAttrs } from "./attrs/index.js";
|
|
1
2
|
import { BOOKMARK_BOUNDARY_NODE_NAME } from "./extensions/nodes/BookmarkBoundaryExtension.js";
|
|
2
3
|
import { COMMENT_REFERENCE_NODE_NAME } from "./extensions/nodes/CommentReferenceExtension.js";
|
|
3
4
|
import { MOVE_RANGE_BOUNDARY_NODE_NAME } from "./extensions/nodes/MoveRangeBoundaryExtension.js";
|
|
4
5
|
import { RANGE_ANCHOR_NODE_NAME } from "./extensions/nodes/RangeAnchorExtension.js";
|
|
5
6
|
import { TEXT_BOX_ANCHOR_NODE_NAME } from "./extensions/nodes/TextBoxAnchorExtension.js";
|
|
6
7
|
import { readMoveRangeBoundaryAttrs } from "./moveRangeBoundaryAttrs.js";
|
|
8
|
+
import { readRangeAnchorAttrs } from "./rangeAnchorAttrs.js";
|
|
7
9
|
import { isZeroWidthAnchor } from "./zeroWidthAnchors.js";
|
|
8
10
|
import { Fragment, Mark } from "prosemirror-model";
|
|
9
11
|
//#region src/prosemirror/replacedAnnotations.ts
|
|
@@ -21,15 +23,12 @@ import { Fragment, Mark } from "prosemirror-model";
|
|
|
21
23
|
* hyperlink simply stops being a link. Zero-width anchors fare worse still: a
|
|
22
24
|
* plain replace deletes them outright.
|
|
23
25
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* character it covered is gone. One rule per marker kind, and that is the
|
|
29
|
-
* whole contract:
|
|
26
|
+
* Text replacement preserves a comment's aligned coverage. Unchanged text
|
|
27
|
+
* keeps its ranges; inserted text takes the comments of the replaced aligned
|
|
28
|
+
* span. A range with no remaining content becomes a point at the replacement
|
|
29
|
+
* start. The applier decides alignment once for direct and tracked edits.
|
|
30
30
|
*
|
|
31
|
-
* - `comment` mark:
|
|
32
|
-
* a range that started inside it starts at the replacement instead.
|
|
31
|
+
* - `comment` mark: carried over its aligned replacement span.
|
|
33
32
|
* - `hyperlink` mark: the replacement carries the first link the span held, so
|
|
34
33
|
* replacing a link's display text leaves the link on the new text. A span
|
|
35
34
|
* crossing two links keeps the one it opens in, because the mark excludes
|
|
@@ -66,9 +65,8 @@ import { Fragment, Mark } from "prosemirror-model";
|
|
|
66
65
|
* - every other mark: formatting, and left to boundary inheritance, which
|
|
67
66
|
* already gives the replacement the formatting of the text it replaces.
|
|
68
67
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* the comment: the one case where a range is meant to disappear.
|
|
68
|
+
* Removing the owning block removes its comment. Replacing its last covered
|
|
69
|
+
* text within a surviving block retains a point rather than dropping it.
|
|
72
70
|
*/
|
|
73
71
|
/**
|
|
74
72
|
* What a replacement does with each mark the schema declares
|
|
@@ -90,6 +88,67 @@ const NON_INCLUSIVE_MARK_DISPOSITION = {
|
|
|
90
88
|
/** The mark names in the schema that a replacement must not silently drop. */
|
|
91
89
|
const nonInclusiveMarkNames = (schema) => Object.entries(schema.marks).filter(([, type]) => type.spec.inclusive === false).map(([name]) => name);
|
|
92
90
|
const isCarried = (mark) => NON_INCLUSIVE_MARK_DISPOSITION[mark.type.name] === "carry";
|
|
91
|
+
/** One typed point for a comment whose aligned coverage has no content left. */
|
|
92
|
+
const commentPoint = (schema, id) => schema.node(RANGE_ANCHOR_NODE_NAME, {
|
|
93
|
+
start: {
|
|
94
|
+
type: "commentRangeStart",
|
|
95
|
+
id
|
|
96
|
+
},
|
|
97
|
+
end: {
|
|
98
|
+
type: "commentRangeEnd",
|
|
99
|
+
id
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
/** The same collector serves standalone commands and the existing bulk walk. */
|
|
103
|
+
const createResolvedCommentPointCollector = ({ removes, retiresStructure }) => {
|
|
104
|
+
const points = /* @__PURE__ */ new Map();
|
|
105
|
+
const retained = /* @__PURE__ */ new Set();
|
|
106
|
+
const visit = (node, position, previous) => {
|
|
107
|
+
if (node.type.name === "paragraph") {
|
|
108
|
+
const mark = expectParagraphAttrs(node).pPrMark;
|
|
109
|
+
const precedingMark = previous?.type.name === "paragraph" ? expectParagraphAttrs(previous).pPrMark : null;
|
|
110
|
+
if ((mark?.kind === "del" || mark?.kind === "moveFrom") && retiresStructure(node, position) || (precedingMark?.kind === "del" || precedingMark?.kind === "moveFrom") && previous !== void 0 && retiresStructure(previous, position - previous.nodeSize)) {
|
|
111
|
+
let survivingContent = false;
|
|
112
|
+
node.descendants((inline, offset) => {
|
|
113
|
+
if (inline.isInline && !isZeroWidthAnchor(inline) && !removes(inline, position + 1 + offset)) survivingContent = true;
|
|
114
|
+
return !survivingContent;
|
|
115
|
+
});
|
|
116
|
+
if (!survivingContent) return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (node.type.name === "tableRow" && expectTableRowAttrs(node).trDel && retiresStructure(node, position)) return false;
|
|
120
|
+
if ((node.type.name === "tableCell" || node.type.name === "tableHeader") && expectTableCellAttrs(node).cellMarker?.kind === "del" && retiresStructure(node, position)) return false;
|
|
121
|
+
if (node.type.name === "rangeAnchor") {
|
|
122
|
+
const attrs = readRangeAnchorAttrs(node);
|
|
123
|
+
if (attrs.ok && attrs.value.start.type === "commentRangeStart") retained.add(attrs.value.start.id);
|
|
124
|
+
}
|
|
125
|
+
if (!node.isInline || isZeroWidthAnchor(node)) return true;
|
|
126
|
+
for (const mark of node.marks) {
|
|
127
|
+
if (mark.type.name !== "comment") continue;
|
|
128
|
+
const id = mark.attrs["commentId"];
|
|
129
|
+
if (!removes(node, position)) retained.add(id);
|
|
130
|
+
else if (!points.has(id)) points.set(id, position);
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
};
|
|
134
|
+
const finish = () => {
|
|
135
|
+
for (const id of retained) points.delete(id);
|
|
136
|
+
return points;
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
visit,
|
|
140
|
+
finish
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
/** Point fallback excludes comments belonging to retired blocks and table cells. */
|
|
144
|
+
const resolvedCommentPoints = ({ doc, removes, retiresStructure }) => {
|
|
145
|
+
const collector = createResolvedCommentPointCollector({
|
|
146
|
+
removes,
|
|
147
|
+
retiresStructure
|
|
148
|
+
});
|
|
149
|
+
doc.descendants((node, position, parent, index) => collector.visit(node, position, index > 0 ? parent?.maybeChild(index - 1) ?? void 0 : void 0));
|
|
150
|
+
return collector.finish();
|
|
151
|
+
};
|
|
93
152
|
/** Anchors an edit must put back, and where relative to the replacement. */
|
|
94
153
|
const ANCHOR_PLACEMENT = {
|
|
95
154
|
[COMMENT_REFERENCE_NODE_NAME]: "after",
|
|
@@ -349,4 +408,4 @@ const inheritedReplacementMarks = (doc, from, to) => {
|
|
|
349
408
|
return (to === from ? $from.marks() : $from.marksAcross(doc.resolve(to))) ?? Mark.none;
|
|
350
409
|
};
|
|
351
410
|
//#endregion
|
|
352
|
-
export { NON_INCLUSIVE_MARK_DISPOSITION, addCarriedMarks, annotatedReplacement, commentsAcrossBlockBoundary, commentsForTrackedInsertion, hasReplacedAnnotations, inheritedReplacementMarks, nonInclusiveMarkNames, placeTrackedInsertion, surveyReplacedAnnotations };
|
|
411
|
+
export { NON_INCLUSIVE_MARK_DISPOSITION, addCarriedMarks, annotatedReplacement, commentPoint, commentsAcrossBlockBoundary, commentsForTrackedInsertion, createResolvedCommentPointCollector, hasReplacedAnnotations, inheritedReplacementMarks, nonInclusiveMarkNames, placeTrackedInsertion, resolvedCommentPoints, surveyReplacedAnnotations };
|
|
@@ -9,6 +9,8 @@ type ReadAuthoredRunFormattingOptions = {
|
|
|
9
9
|
};
|
|
10
10
|
/** Read authored run properties without confusing inherited visuals for direct formatting. */
|
|
11
11
|
declare const readAuthoredRunFormatting: ({ context, marks, styleResolver }: ReadAuthoredRunFormattingOptions) => document_d_exports.TextFormatting;
|
|
12
|
+
/** Preserve the authored distinction between an absent and empty property set in revisions. */
|
|
13
|
+
declare const readAuthoredRunPropertySnapshot: (options: ReadAuthoredRunFormattingOptions) => document_d_exports.TextFormatting | undefined;
|
|
12
14
|
type ReconcileRunFormattingMarksOptions = {
|
|
13
15
|
authoredFormatting: document_d_exports.TextFormatting;
|
|
14
16
|
context: ParagraphRunStyleContext;
|
|
@@ -22,4 +24,4 @@ type ReconcileRunFormattingMarksOptions = {
|
|
|
22
24
|
*/
|
|
23
25
|
declare const reconcileRunFormattingMarks: ({ authoredFormatting, context, node, styleResolver }: ReconcileRunFormattingMarksOptions) => readonly Mark[];
|
|
24
26
|
//#endregion
|
|
25
|
-
export { readAuthoredRunFormatting, reconcileRunFormattingMarks };
|
|
27
|
+
export { readAuthoredRunFormatting, readAuthoredRunPropertySnapshot, reconcileRunFormattingMarks };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { mergeTextFormatting } from "../utils/textFormattingMerge.js";
|
|
2
|
-
import { expectCharacterStyleMarkAttrs } from "./attrs/index.js";
|
|
2
|
+
import { expectCharacterStyleMarkAttrs, expectRunIdentityMarkAttrs } from "./attrs/index.js";
|
|
3
3
|
import { textFormattingToMarks } from "./extensions/marks/markUtils.js";
|
|
4
4
|
import { marksToTextFormatting } from "./runFormattingFromMarks.js";
|
|
5
5
|
import { RUN_FORMATTING_MARK_NAMES } from "./runFormattingMarkNames.js";
|
|
6
|
+
import { RUN_IDENTITY_MARK_NAME } from "./runIdentity.js";
|
|
6
7
|
import { getParagraphMarkSuppressionOverrides, hasDirectRunFormatting, isParagraphMarkSuppressionEligible, paragraphFormattingForRun, resolveEffectiveRunStyleFormatting } from "./runStyleFormatting.js";
|
|
7
8
|
import { Mark } from "prosemirror-model";
|
|
8
9
|
//#region src/prosemirror/runFormattingReconciliation.ts
|
|
@@ -14,6 +15,13 @@ const readAuthoredRunFormatting = ({ context, marks, styleResolver }) => marksTo
|
|
|
14
15
|
paragraphMarkPrecedesStyle: context.paragraphMarkPrecedesStyle,
|
|
15
16
|
...styleResolver !== void 0 ? { styleResolver } : {}
|
|
16
17
|
});
|
|
18
|
+
/** Preserve the authored distinction between an absent and empty property set in revisions. */
|
|
19
|
+
const readAuthoredRunPropertySnapshot = (options) => {
|
|
20
|
+
const formatting = readAuthoredRunFormatting(options);
|
|
21
|
+
if (Object.keys(formatting).length > 0) return formatting;
|
|
22
|
+
const identity = options.marks.find(({ type }) => type.name === RUN_IDENTITY_MARK_NAME);
|
|
23
|
+
return identity && expectRunIdentityMarkAttrs(identity).emptyFormatting ? formatting : void 0;
|
|
24
|
+
};
|
|
17
25
|
/**
|
|
18
26
|
* Rebuild a run's physical formatting marks from its authored properties and
|
|
19
27
|
* current style context. This is the inverse of {@link readAuthoredRunFormatting}:
|
|
@@ -54,4 +62,4 @@ const reconcileRunFormattingMarks = ({ authoredFormatting, context, node, styleR
|
|
|
54
62
|
]);
|
|
55
63
|
};
|
|
56
64
|
//#endregion
|
|
57
|
-
export { readAuthoredRunFormatting, reconcileRunFormattingMarks };
|
|
65
|
+
export { readAuthoredRunFormatting, readAuthoredRunPropertySnapshot, reconcileRunFormattingMarks };
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { Node } from "prosemirror-model";
|
|
2
2
|
//#region src/prosemirror/runIdentityAcrossRevisions.d.ts
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
8
|
-
declare const continueRunIdentitiesAcrossRevisions: (nodes: readonly Node[]) => readonly Node[];
|
|
3
|
+
/** The continuation policy covers every declared source-carrier mark. */
|
|
4
|
+
declare const SOURCE_CARRIER_MARK_NAMES: ("hyperlink" | "runIdentity")[];
|
|
5
|
+
/** Every source-carrier family uses the same revision-boundary continuation policy. */
|
|
6
|
+
declare const continueSourceCarrierIdentitiesAcrossRevisions: (nodes: readonly Node[]) => readonly Node[];
|
|
9
7
|
//#endregion
|
|
10
|
-
export {
|
|
8
|
+
export { SOURCE_CARRIER_MARK_NAMES, continueSourceCarrierIdentitiesAcrossRevisions };
|
|
@@ -1,52 +1,73 @@
|
|
|
1
|
-
import { expectRunIdentityMarkAttrs } from "./attrs/index.js";
|
|
1
|
+
import { expectHyperlinkMarkAttrs, expectRunIdentityMarkAttrs } from "./attrs/index.js";
|
|
2
2
|
import "./extensions/nodes/SdtExtension.js";
|
|
3
3
|
import { RUN_IDENTITY_MARK_NAME } from "./runIdentity.js";
|
|
4
4
|
import { Fragment, Mark } from "prosemirror-model";
|
|
5
5
|
//#region src/prosemirror/runIdentityAcrossRevisions.ts
|
|
6
6
|
/**
|
|
7
|
-
* One
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `w:r` is its own authored run, so resolving the revision would leave two
|
|
13
|
-
* runs where the source had one. The package cannot say whether the two were
|
|
14
|
-
* one run, but runs that carry the same attributes and that only revised
|
|
15
|
-
* content holds apart are what such a cut writes, so the later piece takes the
|
|
16
|
-
* earlier one's identity. The pieces stay separate runs while the revision
|
|
17
|
-
* holds them apart, and become one run once it is resolved and they state the
|
|
18
|
-
* same properties again (a run joins its owner only then, see `fromProseDoc`).
|
|
19
|
-
*
|
|
20
|
-
* Runs the package writes side by side under the same revision state keep
|
|
21
|
-
* their own identities.
|
|
7
|
+
* One source identity for matching runs and hyperlinks a revision cut apart.
|
|
8
|
+
* Serialized fragments mint separate identities on reopen. Matching fragments
|
|
9
|
+
* across a revision boundary or revised-only bridge take the earlier identity,
|
|
10
|
+
* so resolution can rejoin them. Every non-identity attribute must match.
|
|
11
|
+
* Adjacent carriers under the same revision state keep their source identities.
|
|
22
12
|
*/
|
|
23
13
|
const REVISION_MARK_NAMES = /* @__PURE__ */ new Set([
|
|
24
14
|
"insertion",
|
|
25
15
|
"deletion",
|
|
26
16
|
"runPropertyChange"
|
|
27
17
|
]);
|
|
28
|
-
const
|
|
18
|
+
const CARRIER_OWNERS = [{
|
|
19
|
+
markName: RUN_IDENTITY_MARK_NAME,
|
|
20
|
+
read: (mark) => {
|
|
21
|
+
const attrs = expectRunIdentityMarkAttrs(mark);
|
|
22
|
+
return {
|
|
23
|
+
id: attrs.id,
|
|
24
|
+
payloadKey: JSON.stringify({
|
|
25
|
+
...attrs,
|
|
26
|
+
id: 0
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}, {
|
|
31
|
+
markName: "hyperlink",
|
|
32
|
+
read: (mark) => {
|
|
33
|
+
const attrs = expectHyperlinkMarkAttrs(mark);
|
|
34
|
+
if (typeof attrs._docxHyperlinkIndex !== "number") return void 0;
|
|
35
|
+
return {
|
|
36
|
+
id: attrs._docxHyperlinkIndex,
|
|
37
|
+
payloadKey: JSON.stringify({
|
|
38
|
+
...attrs,
|
|
39
|
+
_docxHyperlinkIndex: 0
|
|
40
|
+
})
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}];
|
|
44
|
+
/** The continuation policy covers every declared source-carrier mark. */
|
|
45
|
+
const SOURCE_CARRIER_MARK_NAMES = CARRIER_OWNERS.map(({ markName }) => markName);
|
|
46
|
+
const identityOf = (node, owner) => {
|
|
47
|
+
const mark = node.marks.find((candidate) => candidate.type.name === owner.markName);
|
|
48
|
+
if (!mark) return void 0;
|
|
49
|
+
const identity = owner.read(mark);
|
|
50
|
+
return identity === void 0 ? void 0 : {
|
|
51
|
+
mark,
|
|
52
|
+
...identity
|
|
53
|
+
};
|
|
54
|
+
};
|
|
29
55
|
const revisionMarksOf = (node) => node.marks.filter((mark) => REVISION_MARK_NAMES.has(mark.type.name));
|
|
30
|
-
/** The identity's attrs without the id: what the run carries. */
|
|
31
|
-
const payloadKey = (identity) => JSON.stringify({
|
|
32
|
-
...expectRunIdentityMarkAttrs(identity),
|
|
33
|
-
id: 0
|
|
34
|
-
});
|
|
35
56
|
/** Whether `right` continues `left`'s run across a revision boundary. */
|
|
36
|
-
const
|
|
37
|
-
const leftIdentity = identityOf(left);
|
|
38
|
-
const rightIdentity = identityOf(right);
|
|
39
|
-
if (!leftIdentity || !rightIdentity || leftIdentity.eq(rightIdentity)) return false;
|
|
40
|
-
return (bridged || !Mark.sameSet(revisionMarksOf(left), revisionMarksOf(right))) && payloadKey
|
|
57
|
+
const continuesCarrier = ({ left, right, bridged, owner }) => {
|
|
58
|
+
const leftIdentity = identityOf(left, owner);
|
|
59
|
+
const rightIdentity = identityOf(right, owner);
|
|
60
|
+
if (!leftIdentity || !rightIdentity || leftIdentity.mark.eq(rightIdentity.mark)) return false;
|
|
61
|
+
return (bridged || !Mark.sameSet(revisionMarksOf(left), revisionMarksOf(right))) && leftIdentity.payloadKey === rightIdentity.payloadKey;
|
|
41
62
|
};
|
|
42
63
|
/**
|
|
43
|
-
* `nodes`, one inline container's content, with each
|
|
44
|
-
* the identity of
|
|
64
|
+
* `nodes`, one inline container's content, with each revision fragment given
|
|
65
|
+
* the identity of its matching source carrier. A control is a container of its
|
|
45
66
|
* own. `nodes` itself comes back when nothing changes.
|
|
46
67
|
*/
|
|
47
|
-
const
|
|
68
|
+
const continueIdentities = (nodes, owner) => {
|
|
48
69
|
const renamed = /* @__PURE__ */ new Map();
|
|
49
|
-
/** The last
|
|
70
|
+
/** The last carrier with an identity, while only revised content follows it. */
|
|
50
71
|
let anchor;
|
|
51
72
|
let bridged = false;
|
|
52
73
|
let changed = false;
|
|
@@ -57,25 +78,26 @@ const continueRunIdentitiesAcrossRevisions = (nodes) => {
|
|
|
57
78
|
node.forEach((child) => {
|
|
58
79
|
children.push(child);
|
|
59
80
|
});
|
|
60
|
-
const continued =
|
|
81
|
+
const continued = continueIdentities(children, owner);
|
|
61
82
|
if (continued === children) return node;
|
|
62
83
|
changed = true;
|
|
63
84
|
return node.copy(Fragment.fromArray([...continued]));
|
|
64
85
|
}
|
|
65
|
-
const identity = identityOf(node);
|
|
86
|
+
const identity = identityOf(node, owner);
|
|
66
87
|
if (!identity) {
|
|
67
88
|
if (anchor && revisionMarksOf(node).length > 0) bridged = true;
|
|
68
89
|
else anchor = void 0;
|
|
69
90
|
return node;
|
|
70
91
|
}
|
|
71
|
-
const id =
|
|
92
|
+
const id = identity.id;
|
|
72
93
|
let replacement = renamed.get(id);
|
|
73
|
-
if (!replacement && anchor &&
|
|
94
|
+
if (!replacement && anchor && continuesCarrier({
|
|
74
95
|
left: anchor,
|
|
75
96
|
right: node,
|
|
76
|
-
bridged
|
|
97
|
+
bridged,
|
|
98
|
+
owner
|
|
77
99
|
})) {
|
|
78
|
-
replacement = identityOf(anchor);
|
|
100
|
+
replacement = identityOf(anchor, owner)?.mark;
|
|
79
101
|
if (replacement) renamed.set(id, replacement);
|
|
80
102
|
}
|
|
81
103
|
const next = replacement ? node.mark(replacement.addToSet(node.marks)) : node;
|
|
@@ -86,5 +108,11 @@ const continueRunIdentitiesAcrossRevisions = (nodes) => {
|
|
|
86
108
|
});
|
|
87
109
|
return changed ? result : nodes;
|
|
88
110
|
};
|
|
111
|
+
/** Every source-carrier family uses the same revision-boundary continuation policy. */
|
|
112
|
+
const continueSourceCarrierIdentitiesAcrossRevisions = (nodes) => {
|
|
113
|
+
let continued = nodes;
|
|
114
|
+
for (const owner of CARRIER_OWNERS) continued = continueIdentities(continued, owner);
|
|
115
|
+
return continued;
|
|
116
|
+
};
|
|
89
117
|
//#endregion
|
|
90
|
-
export {
|
|
118
|
+
export { SOURCE_CARRIER_MARK_NAMES, continueSourceCarrierIdentitiesAcrossRevisions };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { expectRunIdentityMarkAttrs } from "./attrs/index.js";
|
|
1
2
|
import { runFormattingInlineAtomDisposition } from "./runFormattingInlineCarriers.js";
|
|
2
3
|
import { RUN_FORMATTING_MARK_NAMES } from "./runFormattingMarkNames.js";
|
|
3
4
|
import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "./runFormattingReconciliation.js";
|
|
5
|
+
import { RUN_IDENTITY_MARK_NAME, runIdentityAttrs } from "./runIdentity.js";
|
|
4
6
|
//#region src/prosemirror/runPropertyChangeResolution.ts
|
|
5
7
|
/**
|
|
6
8
|
* Rebuild the pre-change run marks against the paragraph's live style cascade.
|
|
@@ -8,12 +10,22 @@ import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "./runFor
|
|
|
8
10
|
* owner so their reject semantics cannot diverge at the bulk threshold.
|
|
9
11
|
*/
|
|
10
12
|
const reconstructRejectedRunFormattingMarks = ({ node, paragraphContext, previousFormatting, styleResolver }) => {
|
|
11
|
-
|
|
13
|
+
const formatting = reconcileRunFormattingMarks({
|
|
12
14
|
authoredFormatting: previousFormatting ?? {},
|
|
13
15
|
context: paragraphContext,
|
|
14
16
|
node,
|
|
15
17
|
...styleResolver !== void 0 ? { styleResolver } : {}
|
|
16
18
|
}).filter(({ type }) => RUN_FORMATTING_MARK_NAMES.has(type.name));
|
|
19
|
+
if (previousFormatting === void 0 || Object.keys(previousFormatting).length > 0) return formatting;
|
|
20
|
+
const identityType = node.type.schema.marks[RUN_IDENTITY_MARK_NAME];
|
|
21
|
+
if (!identityType) return formatting;
|
|
22
|
+
const identity = node.marks.find(({ type }) => type === identityType);
|
|
23
|
+
const attrs = identity ? expectRunIdentityMarkAttrs(identity) : void 0;
|
|
24
|
+
return [...formatting, identityType.create(runIdentityAttrs(attrs?.id ?? 0, {
|
|
25
|
+
preservedAttributes: attrs?.preservedAttributes ?? void 0,
|
|
26
|
+
preserved: attrs?.preserved ?? void 0,
|
|
27
|
+
emptyFormatting: true
|
|
28
|
+
}))];
|
|
17
29
|
};
|
|
18
30
|
/** Restored deletions inherit the live paragraph cascade without authoring their historical visuals. */
|
|
19
31
|
const restoreHistoricalRunFormatting = ({ node, paragraphContext, styleResolver }) => {
|
package/dist/utils/base64.d.ts
CHANGED
|
@@ -1,35 +1,5 @@
|
|
|
1
|
+
import { bytesToBase64, encodeBase64Portable } from "@stll/utf8-boundaries";
|
|
1
2
|
//#region src/utils/base64.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* The package's one bytes-to-base64 encoder.
|
|
4
|
-
*
|
|
5
|
-
* Every call site that spelled this itself got a different answer to the same
|
|
6
|
-
* two questions, and one of them got the first question wrong:
|
|
7
|
-
*
|
|
8
|
-
* - `btoa(new TextDecoder("latin1").decode(bytes))` is not a binary string.
|
|
9
|
-
* The Encoding standard maps the `latin1` label to windows-1252, so bytes
|
|
10
|
-
* 0x80-0x9F decode to characters outside Latin-1 and `btoa` throws
|
|
11
|
-
* `InvalidCharacterError` on them. Ordinary image bytes contain those values,
|
|
12
|
-
* so the browser path of that spelling fails on real input.
|
|
13
|
-
* - `String.fromCharCode(...chunk)` is correct but spreads a chunk's worth of
|
|
14
|
-
* arguments per call, and the intermediate binary string is as large as the
|
|
15
|
-
* input.
|
|
16
|
-
*
|
|
17
|
-
* Neither question has to be asked per call site, so it is asked here once.
|
|
18
|
-
* The runtime's own encoder answers it when the runtime has one, and the
|
|
19
|
-
* portable encoder below answers it when not; both produce canonical base64,
|
|
20
|
-
* so the choice is invisible in the output.
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* Encode into exactly one intermediate buffer: ASCII codes are written into a
|
|
24
|
-
* single array and decoded once, so a megabyte-scale input leaves the output
|
|
25
|
-
* string and nothing else.
|
|
26
|
-
*
|
|
27
|
-
* Exported because the runtimes the tests run on all carry a native encoder,
|
|
28
|
-
* and the fallback is the branch the browsers that lack one take.
|
|
29
|
-
*/
|
|
30
|
-
declare const encodeBase64Portable: (bytes: Uint8Array) => string;
|
|
31
|
-
/** Encode bytes as canonical, padded, standard-alphabet base64. */
|
|
32
|
-
declare const bytesToBase64: (bytes: Uint8Array) => string;
|
|
33
3
|
/** Encode bytes as a `data:` URL with the given media type. */
|
|
34
4
|
declare const bytesToDataUrl: (bytes: Uint8Array, mimeType: string) => string;
|
|
35
5
|
//#endregion
|