@beyondwork/docx-react-component 1.0.28 → 1.0.30
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/package.json +26 -37
- package/src/api/public-types.ts +531 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/index.ts +201 -79
- package/src/core/commands/table-structure-commands.ts +138 -5
- package/src/core/state/text-transaction.ts +370 -3
- package/src/index.ts +41 -0
- package/src/io/docx-session.ts +318 -25
- package/src/io/export/serialize-footnotes.ts +41 -46
- package/src/io/export/serialize-headers-footers.ts +36 -40
- package/src/io/export/serialize-main-document.ts +55 -89
- package/src/io/export/serialize-numbering.ts +104 -4
- package/src/io/export/serialize-runtime-revisions.ts +196 -2
- package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
- package/src/io/export/table-properties-xml.ts +318 -0
- package/src/io/normalize/normalize-text.ts +34 -3
- package/src/io/ooxml/parse-comments.ts +6 -0
- package/src/io/ooxml/parse-footnotes.ts +69 -13
- package/src/io/ooxml/parse-headers-footers.ts +54 -11
- package/src/io/ooxml/parse-main-document.ts +112 -42
- package/src/io/ooxml/parse-numbering.ts +341 -26
- package/src/io/ooxml/parse-revisions.ts +118 -4
- package/src/io/ooxml/parse-styles.ts +176 -0
- package/src/io/ooxml/parse-tables.ts +34 -25
- package/src/io/ooxml/revision-boundaries.ts +127 -3
- package/src/io/ooxml/workflow-payload.ts +544 -0
- package/src/model/canonical-document.ts +91 -1
- package/src/model/snapshot.ts +112 -1
- package/src/preservation/store.ts +73 -3
- package/src/review/store/comment-store.ts +19 -1
- package/src/review/store/revision-actions.ts +29 -0
- package/src/review/store/revision-store.ts +12 -1
- package/src/review/store/revision-types.ts +11 -0
- package/src/runtime/context-analytics.ts +824 -0
- package/src/runtime/document-locations.ts +521 -0
- package/src/runtime/document-navigation.ts +14 -1
- package/src/runtime/document-outline.ts +440 -0
- package/src/runtime/document-runtime.ts +941 -45
- package/src/runtime/event-refresh-hints.ts +137 -0
- package/src/runtime/numbering-prefix.ts +67 -39
- package/src/runtime/page-layout-estimation.ts +100 -7
- package/src/runtime/resolved-numbering-geometry.ts +293 -0
- package/src/runtime/session-capabilities.ts +2 -2
- package/src/runtime/suggestions-snapshot.ts +137 -0
- package/src/runtime/surface-projection.ts +223 -27
- package/src/runtime/table-style-resolver.ts +409 -0
- package/src/runtime/view-state.ts +17 -1
- package/src/runtime/workflow-markup.ts +54 -14
- package/src/ui/WordReviewEditor.tsx +1269 -87
- package/src/ui/editor-command-bag.ts +7 -0
- package/src/ui/editor-runtime-boundary.ts +111 -10
- package/src/ui/editor-shell-view.tsx +17 -15
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-tool-context.ts +19 -0
- package/src/ui/headless/selection-tool-resolver.ts +752 -0
- package/src/ui/headless/selection-tool-types.ts +129 -0
- package/src/ui/headless/selection-toolbar-model.ts +10 -33
- package/src/ui/runtime-shortcut-dispatch.ts +365 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
- package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +1 -9
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +1 -5
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +8 -29
- package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +298 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +3 -3
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +57 -52
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +36 -52
- package/src/ui-tailwind/editor-surface/pm-schema.ts +56 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +87 -24
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +4 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +135 -32
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +74 -7
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +17 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +19 -17
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +10 -10
- package/src/ui-tailwind/status/tw-status-bar.tsx +10 -6
- package/src/ui-tailwind/theme/editor-theme.css +58 -40
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -4
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +250 -181
- package/src/ui-tailwind/tw-review-workspace.tsx +323 -280
- package/src/validation/compatibility-engine.ts +246 -2
- package/src/validation/docx-comment-proof.ts +24 -11
package/src/io/docx-session.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
import { createCanonicalDocumentId } from "../core/state/editor-state.ts";
|
|
24
24
|
import {
|
|
25
25
|
createDetachedAnchor,
|
|
26
|
+
storyTargetsEqual,
|
|
26
27
|
type EditorAnchorProjection as InternalEditorAnchorProjection,
|
|
27
28
|
} from "../core/selection/mapping.ts";
|
|
28
29
|
import { DOCX_MIME_TYPE } from "./opc/docx-package.ts";
|
|
@@ -42,6 +43,18 @@ import {
|
|
|
42
43
|
resolveRelationshipTarget,
|
|
43
44
|
type OpcRelationship,
|
|
44
45
|
} from "./ooxml/part-manifest.ts";
|
|
46
|
+
import {
|
|
47
|
+
buildWorkflowPayloadParts,
|
|
48
|
+
getDocumentBackedWorkflowMetadata,
|
|
49
|
+
parseWorkflowPayloadFromPackage,
|
|
50
|
+
WORKFLOW_PAYLOAD_CONTENT_TYPE,
|
|
51
|
+
WORKFLOW_PAYLOAD_CUSTOM_PROPS_CONTENT_TYPE,
|
|
52
|
+
WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
|
|
53
|
+
WORKFLOW_PAYLOAD_CUSTOM_PROPS_RELATIONSHIP_TYPE,
|
|
54
|
+
WORKFLOW_PAYLOAD_ITEM_PROPS_CONTENT_TYPE,
|
|
55
|
+
WORKFLOW_PAYLOAD_ITEM_PROPS_PART_PATH,
|
|
56
|
+
WORKFLOW_PAYLOAD_PART_PATH,
|
|
57
|
+
} from "./ooxml/workflow-payload.ts";
|
|
45
58
|
import {
|
|
46
59
|
classifyCorruptPackageError,
|
|
47
60
|
createBrokenRelationshipIssue,
|
|
@@ -49,7 +62,11 @@ import {
|
|
|
49
62
|
} from "./opc/corrupt-package.ts";
|
|
50
63
|
import { createExportSession } from "./export/export-session.ts";
|
|
51
64
|
import { serializeMainDocument } from "./export/serialize-main-document.ts";
|
|
52
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
parseRevisionsFromDocumentXml,
|
|
67
|
+
parseRevisionsFromStoryXml,
|
|
68
|
+
type ParsedRevisionsResult,
|
|
69
|
+
} from "./ooxml/parse-revisions.ts";
|
|
53
70
|
import { parseCommentsFromOoxml } from "./ooxml/parse-comments.ts";
|
|
54
71
|
import { parseNumberingXml } from "./ooxml/parse-numbering.ts";
|
|
55
72
|
import {
|
|
@@ -59,7 +76,11 @@ import {
|
|
|
59
76
|
serializeMergedCommentsXml,
|
|
60
77
|
} from "./export/serialize-comments.ts";
|
|
61
78
|
import { splitDocumentAtReviewBoundaries } from "./export/split-review-boundaries.ts";
|
|
62
|
-
import {
|
|
79
|
+
import { splitStoryBlocksForRuntimeRevisions } from "./export/split-story-blocks-for-runtime-revisions.ts";
|
|
80
|
+
import {
|
|
81
|
+
serializeRuntimeRevisionsIntoDocumentXml,
|
|
82
|
+
serializeRuntimeRevisionsIntoStoryXml,
|
|
83
|
+
} from "./export/serialize-runtime-revisions.ts";
|
|
63
84
|
import { createCommentStoreFromRuntimeComments } from "../review/store/runtime-comment-store.ts";
|
|
64
85
|
import type { CommentThread } from "../review/store/comment-store.ts";
|
|
65
86
|
import type { RevisionRecord as ReviewRevisionRecord } from "../review/store/revision-types.ts";
|
|
@@ -105,7 +126,9 @@ import { parseSettingsXml } from "./ooxml/parse-settings.ts";
|
|
|
105
126
|
import { parseStylesXml, type ParseStylesResult } from "./ooxml/parse-styles.ts";
|
|
106
127
|
import {
|
|
107
128
|
serializeHeaderXml,
|
|
129
|
+
serializeHeaderXmlWithRevisions,
|
|
108
130
|
serializeFooterXml,
|
|
131
|
+
serializeFooterXmlWithRevisions,
|
|
109
132
|
WORD_HEADER_CONTENT_TYPE,
|
|
110
133
|
WORD_FOOTER_CONTENT_TYPE,
|
|
111
134
|
} from "./export/serialize-headers-footers.ts";
|
|
@@ -199,6 +222,7 @@ export interface LoadedDocxEditorSession {
|
|
|
199
222
|
interface ImportedDocxState {
|
|
200
223
|
sourceBytes: Uint8Array;
|
|
201
224
|
sourcePackage: OpcPackage;
|
|
225
|
+
sourceDocumentXml: string;
|
|
202
226
|
sourceDocumentPartPath: string;
|
|
203
227
|
sourceDocumentRelationships: readonly OpcRelationship[];
|
|
204
228
|
sourceDocumentAttributes: Record<string, string>;
|
|
@@ -265,6 +289,7 @@ export function loadDocxEditorSession(
|
|
|
265
289
|
}),
|
|
266
290
|
);
|
|
267
291
|
}
|
|
292
|
+
const embeddedWorkflowMetadata = parseWorkflowPayloadFromPackage(sourcePackage);
|
|
268
293
|
|
|
269
294
|
const mainDocumentPath = resolveMainDocumentPartPath(sourcePackage);
|
|
270
295
|
const brokenRelationshipIssues = collectBrokenInternalRelationshipIssues(
|
|
@@ -403,6 +428,8 @@ export function loadDocxEditorSession(
|
|
|
403
428
|
normalizedDocument.preservation.opaqueFragments,
|
|
404
429
|
normalizedRevisions.revisions,
|
|
405
430
|
);
|
|
431
|
+
const importedStoryRevisions: ReviewRevisionRecord[] = [];
|
|
432
|
+
const importedStoryRevisionDiagnostics: ParsedRevisionsResult["diagnostics"] = [];
|
|
406
433
|
const subPartOpaqueState = createSubPartOpaqueImportState(
|
|
407
434
|
normalizedDocument.preservation.opaqueFragments,
|
|
408
435
|
normalizedDocument.diagnostics.warnings,
|
|
@@ -437,6 +464,7 @@ export function loadDocxEditorSession(
|
|
|
437
464
|
|
|
438
465
|
const xml = decodeUtf8(partBytes);
|
|
439
466
|
if (ref.kind === "header") {
|
|
467
|
+
const parsedHeaderRevisions = parseRevisionsFromStoryXml(xml);
|
|
440
468
|
const parsed = parseHeaderXml(xml);
|
|
441
469
|
parsedHeaders.push({
|
|
442
470
|
variant: ref.variant,
|
|
@@ -451,8 +479,24 @@ export function loadDocxEditorSession(
|
|
|
451
479
|
subPartOpaqueState,
|
|
452
480
|
),
|
|
453
481
|
});
|
|
482
|
+
importedStoryRevisions.push(
|
|
483
|
+
...parsedHeaderRevisions.revisions.map((revision): ReviewRevisionRecord => ({
|
|
484
|
+
...revision,
|
|
485
|
+
metadata: {
|
|
486
|
+
...revision.metadata,
|
|
487
|
+
storyTarget: {
|
|
488
|
+
kind: "header" as const,
|
|
489
|
+
relationshipId: ref.relationshipId,
|
|
490
|
+
variant: ref.variant,
|
|
491
|
+
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
})),
|
|
495
|
+
);
|
|
496
|
+
importedStoryRevisionDiagnostics.push(...parsedHeaderRevisions.diagnostics);
|
|
454
497
|
sourceHeaderPaths.push({ partPath, relationshipId: ref.relationshipId });
|
|
455
498
|
} else {
|
|
499
|
+
const parsedFooterRevisions = parseRevisionsFromStoryXml(xml);
|
|
456
500
|
const parsed = parseFooterXml(xml);
|
|
457
501
|
parsedFooters.push({
|
|
458
502
|
variant: ref.variant,
|
|
@@ -467,6 +511,21 @@ export function loadDocxEditorSession(
|
|
|
467
511
|
subPartOpaqueState,
|
|
468
512
|
),
|
|
469
513
|
});
|
|
514
|
+
importedStoryRevisions.push(
|
|
515
|
+
...parsedFooterRevisions.revisions.map((revision): ReviewRevisionRecord => ({
|
|
516
|
+
...revision,
|
|
517
|
+
metadata: {
|
|
518
|
+
...revision.metadata,
|
|
519
|
+
storyTarget: {
|
|
520
|
+
kind: "footer" as const,
|
|
521
|
+
relationshipId: ref.relationshipId,
|
|
522
|
+
variant: ref.variant,
|
|
523
|
+
...(ref.sectionIndex !== undefined ? { sectionIndex: ref.sectionIndex } : {}),
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
})),
|
|
527
|
+
);
|
|
528
|
+
importedStoryRevisionDiagnostics.push(...parsedFooterRevisions.diagnostics);
|
|
470
529
|
sourceFooterPaths.push({ partPath, relationshipId: ref.relationshipId });
|
|
471
530
|
}
|
|
472
531
|
}
|
|
@@ -624,6 +683,12 @@ export function loadDocxEditorSession(
|
|
|
624
683
|
source: "review" as const,
|
|
625
684
|
message: diagnostic.message,
|
|
626
685
|
})),
|
|
686
|
+
...importedStoryRevisionDiagnostics.map((diagnostic, index) => ({
|
|
687
|
+
diagnosticId: `diagnostic:story-revision-import-${index + 1}`,
|
|
688
|
+
warningId: `warning:story-revision-import-${diagnostic.revisionId}`,
|
|
689
|
+
source: "review" as const,
|
|
690
|
+
message: diagnostic.message,
|
|
691
|
+
})),
|
|
627
692
|
...normalizedComments.diagnostics.map((diagnostic, index) => ({
|
|
628
693
|
diagnosticId: `diagnostic:comment-import-${index + 1}`,
|
|
629
694
|
warningId: `warning:comment-import-${diagnostic.commentId}`,
|
|
@@ -635,7 +700,10 @@ export function loadDocxEditorSession(
|
|
|
635
700
|
},
|
|
636
701
|
review: {
|
|
637
702
|
comments: toRuntimeCommentRecords(normalizedComments.threads),
|
|
638
|
-
revisions: toRuntimeRevisionRecords(
|
|
703
|
+
revisions: toRuntimeRevisionRecords([
|
|
704
|
+
...normalizedRevisions.revisions,
|
|
705
|
+
...importedStoryRevisions,
|
|
706
|
+
]),
|
|
639
707
|
},
|
|
640
708
|
});
|
|
641
709
|
const compatibility = buildCompatibilityReport({
|
|
@@ -650,6 +718,7 @@ export function loadDocxEditorSession(
|
|
|
650
718
|
compatibility: toPublicCompatibilityReport(compatibility),
|
|
651
719
|
protectionSnapshot: importedProtectionSnapshot,
|
|
652
720
|
sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
|
|
721
|
+
workflowMetadata: embeddedWorkflowMetadata,
|
|
653
722
|
});
|
|
654
723
|
const snapshotIssues = validatePersistedEditorSnapshot(snapshot);
|
|
655
724
|
if (snapshotIssues.length > 0) {
|
|
@@ -670,6 +739,7 @@ export function loadDocxEditorSession(
|
|
|
670
739
|
const importedState: ImportedDocxState = {
|
|
671
740
|
sourceBytes: new Uint8Array(sourceBytes),
|
|
672
741
|
sourcePackage,
|
|
742
|
+
sourceDocumentXml,
|
|
673
743
|
sourceDocumentPartPath: mainDocumentPath,
|
|
674
744
|
sourceDocumentRelationships: documentPart.relationships,
|
|
675
745
|
sourceDocumentAttributes: extractDocumentRootAttributes(sourceDocumentXml),
|
|
@@ -757,9 +827,23 @@ function exportDocxEditorSession(
|
|
|
757
827
|
const signatureMatch = serializeCanonicalDocumentForExport(currentDocument) ===
|
|
758
828
|
state.initialCanonicalSignature;
|
|
759
829
|
const canReuse = canReuseSourceBytesForCurrentDocument(state, currentDocument);
|
|
830
|
+
const durableWorkflowMetadata = getDocumentBackedWorkflowMetadata(sessionState.workflowMetadata);
|
|
760
831
|
const commentCount = Object.keys(currentDocument.review?.comments ?? {}).length;
|
|
832
|
+
const currentRevisions = toReviewRevisionRecords(currentDocument.review.revisions);
|
|
833
|
+
const hasActiveImportedPreserveOnlyRevisions = currentRevisions.some(
|
|
834
|
+
(revision) =>
|
|
835
|
+
revision.status === "active" &&
|
|
836
|
+
revision.metadata.source === "import" &&
|
|
837
|
+
typeof revision.metadata.preserveOnlyReason === "string" &&
|
|
838
|
+
revision.metadata.preserveOnlyReason.length > 0,
|
|
839
|
+
);
|
|
761
840
|
|
|
762
|
-
if (
|
|
841
|
+
if (
|
|
842
|
+
signatureMatch &&
|
|
843
|
+
canReuse &&
|
|
844
|
+
durableWorkflowMetadata.definitions.length === 0 &&
|
|
845
|
+
durableWorkflowMetadata.entries.length === 0
|
|
846
|
+
) {
|
|
763
847
|
return {
|
|
764
848
|
bytes: new Uint8Array(state.sourceBytes),
|
|
765
849
|
mimeType: DOCX_MIME_TYPE,
|
|
@@ -781,29 +865,30 @@ function exportDocxEditorSession(
|
|
|
781
865
|
`DOCX export is blocked because ${blockingCommentCount} preserve-only comment anchors cannot be safely remapped after runtime edits.`,
|
|
782
866
|
);
|
|
783
867
|
}
|
|
784
|
-
const currentRevisions = toReviewRevisionRecords(currentDocument.review.revisions);
|
|
785
868
|
const actionableRevisions = currentRevisions.filter(
|
|
786
869
|
(revision) => getRevisionActionability(revision) === "actionable",
|
|
787
870
|
);
|
|
788
|
-
const
|
|
789
|
-
|
|
871
|
+
const mainStoryActionableRevisions = actionableRevisions.filter((revision) =>
|
|
872
|
+
!revision.metadata.storyTarget?.kind || revision.metadata.storyTarget.kind === "main"
|
|
790
873
|
);
|
|
791
|
-
if (secondaryStoryActionableRevisions.length > 0) {
|
|
792
|
-
throw new Error(
|
|
793
|
-
`DOCX export is blocked because ${secondaryStoryActionableRevisions.length} secondary-story tracked changes cannot yet be serialized safely.`,
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
874
|
const commentThreads = Object.values(
|
|
797
875
|
createCommentStoreFromRuntimeComments(currentDocument.review.comments).threads,
|
|
798
876
|
);
|
|
799
877
|
const ownedCommentThreads = commentThreads.filter(
|
|
800
878
|
(thread) => !preservedCommentIds.has(thread.commentId),
|
|
801
879
|
);
|
|
880
|
+
const revisionReadyMainContent = {
|
|
881
|
+
...currentDocument.content,
|
|
882
|
+
children: splitStoryBlocksForRuntimeRevisions(
|
|
883
|
+
currentDocument.content.children,
|
|
884
|
+
mainStoryActionableRevisions,
|
|
885
|
+
),
|
|
886
|
+
};
|
|
802
887
|
const serialized = serializeMainDocument(
|
|
803
888
|
splitDocumentAtReviewBoundaries(
|
|
804
|
-
|
|
889
|
+
revisionReadyMainContent as never,
|
|
805
890
|
ownedCommentThreads,
|
|
806
|
-
|
|
891
|
+
mainStoryActionableRevisions,
|
|
807
892
|
) as never,
|
|
808
893
|
currentDocument.preservation as never,
|
|
809
894
|
state.sourceDocumentRelationships,
|
|
@@ -815,8 +900,7 @@ function exportDocxEditorSession(
|
|
|
815
900
|
);
|
|
816
901
|
const revisionDocument = serializeRuntimeRevisionsIntoDocumentXml(
|
|
817
902
|
serialized.documentXml,
|
|
818
|
-
|
|
819
|
-
serialized.paragraphBoundaries,
|
|
903
|
+
mainStoryActionableRevisions,
|
|
820
904
|
);
|
|
821
905
|
if (revisionDocument.skippedRevisionIds.length > 0) {
|
|
822
906
|
throw new Error(
|
|
@@ -949,6 +1033,9 @@ function exportDocxEditorSession(
|
|
|
949
1033
|
state.sourceDocumentPartPath,
|
|
950
1034
|
APP_PROPERTIES_PART_PATH,
|
|
951
1035
|
CORE_PROPERTIES_PART_PATH,
|
|
1036
|
+
WORKFLOW_PAYLOAD_PART_PATH,
|
|
1037
|
+
WORKFLOW_PAYLOAD_ITEM_PROPS_PART_PATH,
|
|
1038
|
+
WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
|
|
952
1039
|
numberingPartPath,
|
|
953
1040
|
commentsPartPath,
|
|
954
1041
|
commentsExtendedPartPath,
|
|
@@ -957,9 +1044,18 @@ function exportDocxEditorSession(
|
|
|
957
1044
|
...subPartOwnedPaths,
|
|
958
1045
|
]);
|
|
959
1046
|
|
|
1047
|
+
const mainDocumentXmlForExport =
|
|
1048
|
+
signatureMatch &&
|
|
1049
|
+
durableWorkflowMetadata.definitions.length === 0 &&
|
|
1050
|
+
durableWorkflowMetadata.entries.length === 0 &&
|
|
1051
|
+
commentCount === 0 &&
|
|
1052
|
+
hasActiveImportedPreserveOnlyRevisions
|
|
1053
|
+
? state.sourceDocumentXml
|
|
1054
|
+
: protectedDocumentXml;
|
|
1055
|
+
|
|
960
1056
|
exportSession.replaceOwnedPart({
|
|
961
1057
|
path: state.sourceDocumentPartPath,
|
|
962
|
-
bytes: new TextEncoder().encode(
|
|
1058
|
+
bytes: new TextEncoder().encode(mainDocumentXmlForExport),
|
|
963
1059
|
contentType: MAIN_DOCUMENT_CONTENT_TYPE,
|
|
964
1060
|
relationships: nextRelationships,
|
|
965
1061
|
});
|
|
@@ -1023,36 +1119,128 @@ function exportDocxEditorSession(
|
|
|
1023
1119
|
}
|
|
1024
1120
|
|
|
1025
1121
|
if (exportedSubParts) {
|
|
1122
|
+
const headersByPartPath = new Map<
|
|
1123
|
+
string,
|
|
1124
|
+
{ header: (typeof exportedSubParts.headers)[number]; revisions: ReviewRevisionRecord[] }
|
|
1125
|
+
>();
|
|
1026
1126
|
for (const header of exportedSubParts.headers) {
|
|
1127
|
+
const entry = headersByPartPath.get(header.partPath) ?? { header, revisions: [] };
|
|
1128
|
+
const matchingRevisions = actionableRevisions.filter((revision) =>
|
|
1129
|
+
storyTargetsEqual(revision.metadata.storyTarget ?? { kind: "main" }, {
|
|
1130
|
+
kind: "header",
|
|
1131
|
+
relationshipId: header.relationshipId,
|
|
1132
|
+
variant: header.variant,
|
|
1133
|
+
...(header.sectionIndex !== undefined ? { sectionIndex: header.sectionIndex } : {}),
|
|
1134
|
+
})
|
|
1135
|
+
);
|
|
1136
|
+
entry.revisions.push(...matchingRevisions);
|
|
1137
|
+
if (matchingRevisions.length > 0) {
|
|
1138
|
+
entry.header = header;
|
|
1139
|
+
}
|
|
1140
|
+
headersByPartPath.set(header.partPath, entry);
|
|
1141
|
+
}
|
|
1142
|
+
for (const { header, revisions } of headersByPartPath.values()) {
|
|
1143
|
+
const serializedHeaderXml = serializeSecondaryStoryWithRuntimeRevisions(
|
|
1144
|
+
serializeHeaderXmlWithRevisions(header, revisions),
|
|
1145
|
+
revisions,
|
|
1146
|
+
`header ${header.partPath}`,
|
|
1147
|
+
);
|
|
1027
1148
|
exportSession.replaceOwnedPart({
|
|
1028
1149
|
path: header.partPath,
|
|
1029
|
-
bytes: new TextEncoder().encode(
|
|
1150
|
+
bytes: new TextEncoder().encode(serializedHeaderXml),
|
|
1030
1151
|
contentType:
|
|
1031
1152
|
state.sourcePackage.parts.get(header.partPath)?.contentType ?? WORD_HEADER_CONTENT_TYPE,
|
|
1032
1153
|
});
|
|
1033
1154
|
}
|
|
1155
|
+
const footersByPartPath = new Map<
|
|
1156
|
+
string,
|
|
1157
|
+
{ footer: (typeof exportedSubParts.footers)[number]; revisions: ReviewRevisionRecord[] }
|
|
1158
|
+
>();
|
|
1034
1159
|
for (const footer of exportedSubParts.footers) {
|
|
1160
|
+
const entry = footersByPartPath.get(footer.partPath) ?? { footer, revisions: [] };
|
|
1161
|
+
const matchingRevisions = actionableRevisions.filter((revision) =>
|
|
1162
|
+
storyTargetsEqual(revision.metadata.storyTarget ?? { kind: "main" }, {
|
|
1163
|
+
kind: "footer",
|
|
1164
|
+
relationshipId: footer.relationshipId,
|
|
1165
|
+
variant: footer.variant,
|
|
1166
|
+
...(footer.sectionIndex !== undefined ? { sectionIndex: footer.sectionIndex } : {}),
|
|
1167
|
+
})
|
|
1168
|
+
);
|
|
1169
|
+
entry.revisions.push(...matchingRevisions);
|
|
1170
|
+
if (matchingRevisions.length > 0) {
|
|
1171
|
+
entry.footer = footer;
|
|
1172
|
+
}
|
|
1173
|
+
footersByPartPath.set(footer.partPath, entry);
|
|
1174
|
+
}
|
|
1175
|
+
for (const { footer, revisions } of footersByPartPath.values()) {
|
|
1176
|
+
const serializedFooterXml = serializeSecondaryStoryWithRuntimeRevisions(
|
|
1177
|
+
serializeFooterXmlWithRevisions(footer, revisions),
|
|
1178
|
+
revisions,
|
|
1179
|
+
`footer ${footer.partPath}`,
|
|
1180
|
+
);
|
|
1035
1181
|
exportSession.replaceOwnedPart({
|
|
1036
1182
|
path: footer.partPath,
|
|
1037
|
-
bytes: new TextEncoder().encode(
|
|
1183
|
+
bytes: new TextEncoder().encode(serializedFooterXml),
|
|
1038
1184
|
contentType:
|
|
1039
1185
|
state.sourcePackage.parts.get(footer.partPath)?.contentType ?? WORD_FOOTER_CONTENT_TYPE,
|
|
1040
1186
|
});
|
|
1041
1187
|
}
|
|
1042
1188
|
if (exportedSubParts.footnoteCollection) {
|
|
1043
1189
|
if (state.sourceSubPartPaths.footnotesPartPath) {
|
|
1190
|
+
const serializedFootnotesXml = serializeFootnotesXml(
|
|
1191
|
+
exportedSubParts.footnoteCollection,
|
|
1192
|
+
Object.fromEntries(
|
|
1193
|
+
actionableRevisions
|
|
1194
|
+
.filter((revision) => revision.metadata.storyTarget?.kind === "footnote")
|
|
1195
|
+
.map((revision) =>
|
|
1196
|
+
revision.metadata.storyTarget?.kind === "footnote"
|
|
1197
|
+
? [revision.metadata.storyTarget.noteId, [] as ReviewRevisionRecord[]] as const
|
|
1198
|
+
: null,
|
|
1199
|
+
)
|
|
1200
|
+
.filter((entry): entry is readonly [string, ReviewRevisionRecord[]] => entry !== null)
|
|
1201
|
+
.map(([noteId]) => [
|
|
1202
|
+
noteId,
|
|
1203
|
+
actionableRevisions.filter(
|
|
1204
|
+
(revision) =>
|
|
1205
|
+
revision.metadata.storyTarget?.kind === "footnote" &&
|
|
1206
|
+
revision.metadata.storyTarget.noteId === noteId,
|
|
1207
|
+
),
|
|
1208
|
+
]),
|
|
1209
|
+
),
|
|
1210
|
+
);
|
|
1044
1211
|
exportSession.replaceOwnedPart({
|
|
1045
1212
|
path: state.sourceSubPartPaths.footnotesPartPath,
|
|
1046
|
-
bytes: new TextEncoder().encode(
|
|
1213
|
+
bytes: new TextEncoder().encode(serializedFootnotesXml),
|
|
1047
1214
|
contentType:
|
|
1048
1215
|
state.sourcePackage.parts.get(state.sourceSubPartPaths.footnotesPartPath)?.contentType ??
|
|
1049
1216
|
WORD_FOOTNOTES_CONTENT_TYPE,
|
|
1050
1217
|
});
|
|
1051
1218
|
}
|
|
1052
1219
|
if (state.sourceSubPartPaths.endnotesPartPath) {
|
|
1220
|
+
const serializedEndnotesXml = serializeEndnotesXml(
|
|
1221
|
+
exportedSubParts.footnoteCollection,
|
|
1222
|
+
Object.fromEntries(
|
|
1223
|
+
actionableRevisions
|
|
1224
|
+
.filter((revision) => revision.metadata.storyTarget?.kind === "endnote")
|
|
1225
|
+
.map((revision) =>
|
|
1226
|
+
revision.metadata.storyTarget?.kind === "endnote"
|
|
1227
|
+
? [revision.metadata.storyTarget.noteId, [] as ReviewRevisionRecord[]] as const
|
|
1228
|
+
: null,
|
|
1229
|
+
)
|
|
1230
|
+
.filter((entry): entry is readonly [string, ReviewRevisionRecord[]] => entry !== null)
|
|
1231
|
+
.map(([noteId]) => [
|
|
1232
|
+
noteId,
|
|
1233
|
+
actionableRevisions.filter(
|
|
1234
|
+
(revision) =>
|
|
1235
|
+
revision.metadata.storyTarget?.kind === "endnote" &&
|
|
1236
|
+
revision.metadata.storyTarget.noteId === noteId,
|
|
1237
|
+
),
|
|
1238
|
+
]),
|
|
1239
|
+
),
|
|
1240
|
+
);
|
|
1053
1241
|
exportSession.replaceOwnedPart({
|
|
1054
1242
|
path: state.sourceSubPartPaths.endnotesPartPath,
|
|
1055
|
-
bytes: new TextEncoder().encode(
|
|
1243
|
+
bytes: new TextEncoder().encode(serializedEndnotesXml),
|
|
1056
1244
|
contentType:
|
|
1057
1245
|
state.sourcePackage.parts.get(state.sourceSubPartPaths.endnotesPartPath)?.contentType ??
|
|
1058
1246
|
WORD_ENDNOTES_CONTENT_TYPE,
|
|
@@ -1076,6 +1264,7 @@ function exportDocxEditorSession(
|
|
|
1076
1264
|
}
|
|
1077
1265
|
|
|
1078
1266
|
ensureHostMetadataParts(exportSession, state.sourcePackage, currentDocument);
|
|
1267
|
+
ensureWorkflowPayloadParts(exportSession, sessionState, currentDocument, state.sourcePackage);
|
|
1079
1268
|
|
|
1080
1269
|
return {
|
|
1081
1270
|
bytes: exportSession.serialize(),
|
|
@@ -1455,6 +1644,7 @@ function createImportedSnapshot(input: {
|
|
|
1455
1644
|
compatibility: PersistedEditorSnapshot["compatibility"];
|
|
1456
1645
|
protectionSnapshot: ProtectionSnapshot;
|
|
1457
1646
|
sourcePackage?: PersistedEditorSnapshot["sourcePackage"];
|
|
1647
|
+
workflowMetadata?: PersistedEditorSnapshot["workflowMetadata"];
|
|
1458
1648
|
}): PersistedEditorSnapshot {
|
|
1459
1649
|
return {
|
|
1460
1650
|
snapshotVersion: "persisted-editor-snapshot/2",
|
|
@@ -1470,6 +1660,7 @@ function createImportedSnapshot(input: {
|
|
|
1470
1660
|
warningLog: input.compatibility.warnings,
|
|
1471
1661
|
protectionSnapshot: input.protectionSnapshot,
|
|
1472
1662
|
sourcePackage: input.sourcePackage,
|
|
1663
|
+
workflowMetadata: input.workflowMetadata,
|
|
1473
1664
|
};
|
|
1474
1665
|
}
|
|
1475
1666
|
|
|
@@ -1685,6 +1876,12 @@ function normalizeImportedCommentThreads(
|
|
|
1685
1876
|
...thread,
|
|
1686
1877
|
anchor: createDetachedAnchor(anchor.range, "importAmbiguity"),
|
|
1687
1878
|
status: "detached",
|
|
1879
|
+
metadata: {
|
|
1880
|
+
...thread.metadata,
|
|
1881
|
+
detachedReason: "opaque-region",
|
|
1882
|
+
actionabilityNote:
|
|
1883
|
+
"The comment body is preserved. The anchor overlaps opaque content that the editor cannot safely modify.",
|
|
1884
|
+
},
|
|
1688
1885
|
};
|
|
1689
1886
|
}
|
|
1690
1887
|
|
|
@@ -1706,6 +1903,12 @@ function normalizeImportedCommentThreads(
|
|
|
1706
1903
|
...thread,
|
|
1707
1904
|
anchor: createDetachedAnchor(anchor.range, "importAmbiguity"),
|
|
1708
1905
|
status: "detached",
|
|
1906
|
+
metadata: {
|
|
1907
|
+
...thread.metadata,
|
|
1908
|
+
detachedReason: "revision-overlap",
|
|
1909
|
+
actionabilityNote:
|
|
1910
|
+
"The comment body is preserved. The anchor overlaps preserve-only revision markup that the editor cannot safely modify.",
|
|
1911
|
+
},
|
|
1709
1912
|
};
|
|
1710
1913
|
}
|
|
1711
1914
|
|
|
@@ -1824,14 +2027,14 @@ function collectCanonicalParagraphRanges(
|
|
|
1824
2027
|
function measureCanonicalParagraph(paragraph: CanonicalDocumentEnvelope["content"]["children"][number] & { type: "paragraph" }): number {
|
|
1825
2028
|
return paragraph.children.reduce<number>((size, child) => {
|
|
1826
2029
|
if (child.type === "text") {
|
|
1827
|
-
return size + child.text.length;
|
|
2030
|
+
return size + Array.from(child.text).length;
|
|
1828
2031
|
}
|
|
1829
2032
|
if (child.type === "hyperlink") {
|
|
1830
2033
|
return (
|
|
1831
2034
|
size +
|
|
1832
2035
|
child.children.reduce<number>((childSize, entry) => {
|
|
1833
2036
|
if (entry.type === "text") {
|
|
1834
|
-
return childSize + entry.text.length;
|
|
2037
|
+
return childSize + Array.from(entry.text).length;
|
|
1835
2038
|
}
|
|
1836
2039
|
return childSize + 1;
|
|
1837
2040
|
}, 0)
|
|
@@ -1964,9 +2167,15 @@ function toRuntimeRevisionRecords(
|
|
|
1964
2167
|
source: revision.metadata.source,
|
|
1965
2168
|
storyTarget: revision.metadata.storyTarget,
|
|
1966
2169
|
preserveOnlyReason: revision.metadata.preserveOnlyReason,
|
|
2170
|
+
suggestionId: revision.metadata.suggestionId,
|
|
2171
|
+
semanticKind: revision.metadata.semanticKind,
|
|
2172
|
+
linkedRevisionIds: revision.metadata.linkedRevisionIds,
|
|
2173
|
+
predecessorSuggestionId: revision.metadata.predecessorSuggestionId,
|
|
1967
2174
|
importedRevisionForm: revision.metadata.importedRevisionForm,
|
|
1968
2175
|
originalRevisionType: revision.metadata.originalRevisionType,
|
|
1969
2176
|
ooxmlRevisionId: revision.metadata.ooxmlRevisionId,
|
|
2177
|
+
propertyChangeData: revision.metadata.propertyChangeData,
|
|
2178
|
+
moveData: revision.metadata.moveData,
|
|
1970
2179
|
},
|
|
1971
2180
|
status: revision.status === "active" ? "open" : revision.status,
|
|
1972
2181
|
} satisfies RuntimeRevisionRecord,
|
|
@@ -1988,14 +2197,38 @@ function toReviewRevisionRecords(
|
|
|
1988
2197
|
source: revision.metadata?.source ?? "runtime",
|
|
1989
2198
|
storyTarget: revision.metadata?.storyTarget,
|
|
1990
2199
|
preserveOnlyReason: revision.metadata?.preserveOnlyReason,
|
|
2200
|
+
suggestionId: revision.metadata?.suggestionId,
|
|
2201
|
+
semanticKind: revision.metadata?.semanticKind,
|
|
2202
|
+
linkedRevisionIds: revision.metadata?.linkedRevisionIds,
|
|
2203
|
+
predecessorSuggestionId: revision.metadata?.predecessorSuggestionId,
|
|
1991
2204
|
importedRevisionForm: revision.metadata?.importedRevisionForm,
|
|
1992
2205
|
originalRevisionType: revision.metadata?.originalRevisionType,
|
|
1993
2206
|
ooxmlRevisionId: revision.metadata?.ooxmlRevisionId,
|
|
2207
|
+
propertyChangeData: revision.metadata?.propertyChangeData,
|
|
2208
|
+
moveData: revision.metadata?.moveData,
|
|
1994
2209
|
},
|
|
1995
2210
|
status: revision.status === "open" ? "active" : revision.status,
|
|
1996
2211
|
}));
|
|
1997
2212
|
}
|
|
1998
2213
|
|
|
2214
|
+
function serializeSecondaryStoryWithRuntimeRevisions(
|
|
2215
|
+
xml: string,
|
|
2216
|
+
revisions: readonly ReviewRevisionRecord[],
|
|
2217
|
+
label: string,
|
|
2218
|
+
): string {
|
|
2219
|
+
if (revisions.length === 0) {
|
|
2220
|
+
return xml;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
const serialized = serializeRuntimeRevisionsIntoStoryXml(xml, revisions);
|
|
2224
|
+
if (serialized.skippedRevisionIds.length > 0) {
|
|
2225
|
+
throw new Error(
|
|
2226
|
+
`DOCX export is blocked because ${serialized.skippedRevisionIds.length} active revisions overlap unsupported serialization boundaries in ${label}.`,
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
return serialized.documentXml;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
1999
2232
|
export function stripCommentMarkup(
|
|
2000
2233
|
documentXml: string,
|
|
2001
2234
|
ownedCommentIds: readonly string[],
|
|
@@ -2399,7 +2632,12 @@ function toUint8Array(bytes: Uint8Array | ArrayBuffer): Uint8Array {
|
|
|
2399
2632
|
}
|
|
2400
2633
|
|
|
2401
2634
|
function serializeCanonicalDocumentForExport(document: CanonicalDocumentEnvelope): string {
|
|
2402
|
-
return createCanonicalDocumentSignature(
|
|
2635
|
+
return createCanonicalDocumentSignature({
|
|
2636
|
+
...document,
|
|
2637
|
+
docId: "__export__",
|
|
2638
|
+
createdAt: "__export__",
|
|
2639
|
+
updatedAt: "__export__",
|
|
2640
|
+
});
|
|
2403
2641
|
}
|
|
2404
2642
|
|
|
2405
2643
|
function canReuseSourceBytesForCurrentDocument(
|
|
@@ -2412,6 +2650,12 @@ function canReuseSourceBytesForCurrentDocument(
|
|
|
2412
2650
|
|
|
2413
2651
|
const commentThreads = Object.values(document.review.comments);
|
|
2414
2652
|
const hasLiveComments = commentThreads.some((thread) => thread.anchor.kind !== "detached");
|
|
2653
|
+
const hasRuntimeAuthoredActiveRevisions = Object.values(document.review.revisions).some((revision) =>
|
|
2654
|
+
revision.status === "open" && revision.metadata?.source === "runtime"
|
|
2655
|
+
);
|
|
2656
|
+
if (hasRuntimeAuthoredActiveRevisions) {
|
|
2657
|
+
return false;
|
|
2658
|
+
}
|
|
2415
2659
|
if (!hasLiveComments) {
|
|
2416
2660
|
return true;
|
|
2417
2661
|
}
|
|
@@ -2471,6 +2715,55 @@ function ensureHostMetadataParts(
|
|
|
2471
2715
|
});
|
|
2472
2716
|
}
|
|
2473
2717
|
|
|
2718
|
+
function ensureWorkflowPayloadParts(
|
|
2719
|
+
exportSession: ReturnType<typeof createExportSession>,
|
|
2720
|
+
sessionState: EditorSessionState,
|
|
2721
|
+
document: CanonicalDocumentEnvelope,
|
|
2722
|
+
sourcePackage: OpcPackage,
|
|
2723
|
+
): void {
|
|
2724
|
+
const payloadParts = buildWorkflowPayloadParts({
|
|
2725
|
+
sourcePackage,
|
|
2726
|
+
workflowMetadata: sessionState.workflowMetadata,
|
|
2727
|
+
documentId: sessionState.documentId,
|
|
2728
|
+
createdAt: document.createdAt,
|
|
2729
|
+
updatedAt: document.updatedAt,
|
|
2730
|
+
producerVersion: sessionState.editorBuild,
|
|
2731
|
+
});
|
|
2732
|
+
if (!payloadParts) {
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
const payloadPart = sourcePackage.parts.get(payloadParts.payloadPartPath);
|
|
2737
|
+
const itemPropsPart = sourcePackage.parts.get(payloadParts.itemPropsPartPath);
|
|
2738
|
+
const customPropsPart = sourcePackage.parts.get(WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH);
|
|
2739
|
+
|
|
2740
|
+
exportSession.replaceOwnedPart({
|
|
2741
|
+
path: payloadParts.payloadPartPath,
|
|
2742
|
+
bytes: new TextEncoder().encode(payloadParts.payloadPartXml),
|
|
2743
|
+
contentType: payloadPart?.contentType ?? WORKFLOW_PAYLOAD_CONTENT_TYPE,
|
|
2744
|
+
relationships: payloadParts.payloadRelationships,
|
|
2745
|
+
compression: payloadPart?.compression,
|
|
2746
|
+
});
|
|
2747
|
+
exportSession.replaceOwnedPart({
|
|
2748
|
+
path: payloadParts.itemPropsPartPath,
|
|
2749
|
+
bytes: new TextEncoder().encode(payloadParts.itemPropsXml),
|
|
2750
|
+
contentType: itemPropsPart?.contentType ?? WORKFLOW_PAYLOAD_ITEM_PROPS_CONTENT_TYPE,
|
|
2751
|
+
compression: itemPropsPart?.compression,
|
|
2752
|
+
});
|
|
2753
|
+
exportSession.replaceOwnedPart({
|
|
2754
|
+
path: WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
|
|
2755
|
+
bytes: new TextEncoder().encode(payloadParts.customPropertiesXml),
|
|
2756
|
+
contentType: customPropsPart?.contentType ?? WORKFLOW_PAYLOAD_CUSTOM_PROPS_CONTENT_TYPE,
|
|
2757
|
+
compression: customPropsPart?.compression,
|
|
2758
|
+
});
|
|
2759
|
+
|
|
2760
|
+
exportSession.ensurePackageRelationship({
|
|
2761
|
+
type: WORKFLOW_PAYLOAD_CUSTOM_PROPS_RELATIONSHIP_TYPE,
|
|
2762
|
+
target: WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
|
|
2763
|
+
preferredId: "rIdBwWorkflowCustomProps",
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2474
2767
|
function hasHostSafeMetadataPackageStructure(sourcePackage: OpcPackage): boolean {
|
|
2475
2768
|
const corePropertiesPart = sourcePackage.parts.get(CORE_PROPERTIES_PART_PATH);
|
|
2476
2769
|
const appPropertiesPart = sourcePackage.parts.get(APP_PROPERTIES_PART_PATH);
|
|
@@ -2819,7 +3112,7 @@ function collectProtectionRangesFromInlines(
|
|
|
2819
3112
|
function measureParsedInlineNode(node: ParsedInlineNode): number {
|
|
2820
3113
|
switch (node.type) {
|
|
2821
3114
|
case "text":
|
|
2822
|
-
return node.text.length;
|
|
3115
|
+
return Array.from(node.text).length;
|
|
2823
3116
|
case "tab":
|
|
2824
3117
|
case "hard_break":
|
|
2825
3118
|
case "column_break":
|