@beyondwork/docx-react-component 1.0.47 → 1.0.49
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 +16 -11
- package/package.json +30 -41
- package/src/api/public-types.ts +199 -13
- package/src/compare/diff-engine.ts +4 -0
- package/src/core/commands/add-scope.ts +257 -0
- package/src/core/commands/formatting-commands.ts +2 -0
- package/src/core/commands/index.ts +9 -1
- package/src/core/commands/text-commands.ts +3 -1
- package/src/core/schema/text-schema.ts +95 -1
- package/src/core/selection/anchor-conversion.ts +112 -0
- package/src/core/selection/review-anchors.ts +108 -3
- package/src/core/state/text-transaction.ts +103 -7
- package/src/internal/harness-debug-ports.ts +168 -0
- package/src/io/chart-preview-resolver.ts +59 -1
- package/src/io/docx-session.ts +226 -38
- package/src/io/export/serialize-main-document.ts +46 -0
- package/src/io/export/serialize-paragraph-formatting.ts +8 -0
- package/src/io/export/serialize-run-formatting.ts +10 -1
- package/src/io/export/serialize-settings.ts +421 -0
- package/src/io/export/serialize-styles.ts +10 -0
- package/src/io/normalize/normalize-text.ts +1 -0
- package/src/io/ooxml/chart/chart-style-table.ts +543 -0
- package/src/io/ooxml/chart/color-palette.ts +101 -0
- package/src/io/ooxml/chart/compose-series-color.ts +147 -0
- package/src/io/ooxml/chart/parse-axis.ts +277 -0
- package/src/io/ooxml/chart/parse-chart-space.ts +885 -0
- package/src/io/ooxml/chart/parse-series.ts +635 -0
- package/src/io/ooxml/chart/resolve-color.ts +261 -0
- package/src/io/ooxml/chart/types.ts +439 -0
- package/src/io/ooxml/parse-block-structure.ts +99 -0
- package/src/io/ooxml/parse-complex-content.ts +90 -2
- package/src/io/ooxml/parse-main-document.ts +156 -1
- package/src/io/ooxml/parse-paragraph-formatting.ts +46 -0
- package/src/io/ooxml/parse-run-formatting.ts +49 -0
- package/src/io/ooxml/parse-scope-markers.ts +184 -0
- package/src/io/ooxml/parse-settings-blueprint.ts +349 -0
- package/src/io/ooxml/parse-settings.ts +97 -1
- package/src/io/ooxml/parse-styles.ts +65 -0
- package/src/io/ooxml/parse-theme.ts +2 -127
- package/src/io/ooxml/property-grab-bag.ts +211 -0
- package/src/io/ooxml/xml-attr-helpers.ts +59 -1
- package/src/io/ooxml/xml-parser.ts +142 -0
- package/src/model/canonical-document.ts +160 -0
- package/src/model/scope-markers.ts +144 -0
- package/src/runtime/collab/base-doc-fingerprint.ts +99 -0
- package/src/runtime/collab/checkpoint-election.ts +75 -0
- package/src/runtime/collab/checkpoint-scheduler.ts +204 -0
- package/src/runtime/collab/checkpoint-store.ts +115 -0
- package/src/runtime/collab/event-types.ts +27 -0
- package/src/runtime/collab/index.ts +29 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +167 -0
- package/src/runtime/collab/runtime-collab-sync.ts +330 -0
- package/src/runtime/collab/workflow-shared.ts +247 -0
- package/src/runtime/document-locations.ts +1 -9
- package/src/runtime/document-outline.ts +1 -9
- package/src/runtime/document-runtime.ts +288 -65
- package/src/runtime/editor-surface/capabilities.ts +63 -50
- package/src/runtime/hyperlink-color-resolver.ts +119 -0
- package/src/runtime/layout/layout-engine-version.ts +8 -1
- package/src/runtime/prerender/cache-envelope.ts +19 -7
- package/src/runtime/prerender/cache-key.ts +25 -14
- package/src/runtime/prerender/canonical-document-hash.ts +63 -0
- package/src/runtime/prerender/customxml-cache.ts +211 -0
- package/src/runtime/prerender/customxml-probe.ts +78 -0
- package/src/runtime/prerender/prerender-document.ts +74 -7
- package/src/runtime/scope-resolver.ts +148 -0
- package/src/runtime/scope-tag-registry.ts +10 -0
- package/src/runtime/surface-projection.ts +102 -37
- package/src/runtime/theme-color-resolver.ts +188 -0
- package/src/runtime/workflow-markup.ts +7 -18
- package/src/ui/WordReviewEditor.tsx +48 -2
- package/src/ui/editor-runtime-boundary.ts +42 -1
- package/src/ui/headless/selection-helpers.ts +10 -23
- package/src/ui/runtime-shortcut-dispatch.ts +12 -7
- package/src/ui/unsupported-previews-policy.ts +23 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +10 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +1 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +47 -0
- package/src/ui-tailwind/page-stack/use-visible-block-range.ts +88 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +16 -1
package/src/io/docx-session.ts
CHANGED
|
@@ -44,8 +44,9 @@ import {
|
|
|
44
44
|
normalizeParsedTextDocument,
|
|
45
45
|
normalizeParsedTextDocumentAsync,
|
|
46
46
|
} from "./normalize/normalize-text.ts";
|
|
47
|
-
import { resolveChartPreviewsForDocument } from "./chart-preview-resolver.ts";
|
|
47
|
+
import { createChartPartLookup, resolveChartPreviewsForDocument } from "./chart-preview-resolver.ts";
|
|
48
48
|
import { type LoadScheduler } from "./load-scheduler.ts";
|
|
49
|
+
import type { CacheEnvelope } from "../runtime/prerender/cache-envelope.ts";
|
|
49
50
|
import {
|
|
50
51
|
CONTENT_TYPES_PATH,
|
|
51
52
|
PACKAGE_RELATIONSHIPS_PATH,
|
|
@@ -59,12 +60,14 @@ import {
|
|
|
59
60
|
getDocumentBackedWorkflowMetadata,
|
|
60
61
|
parseWorkflowPayloadEnvelopeFromPackage,
|
|
61
62
|
resolvePayloadPartPath,
|
|
62
|
-
resolveWorkflowPayloadPartPaths,
|
|
63
63
|
WORKFLOW_PAYLOAD_CONTENT_TYPE,
|
|
64
64
|
WORKFLOW_PAYLOAD_CUSTOM_PROPS_CONTENT_TYPE,
|
|
65
65
|
WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
|
|
66
66
|
WORKFLOW_PAYLOAD_CUSTOM_PROPS_RELATIONSHIP_TYPE,
|
|
67
67
|
WORKFLOW_PAYLOAD_ITEM_PROPS_CONTENT_TYPE,
|
|
68
|
+
WORKFLOW_PAYLOAD_ITEM_PROPS_PART_PATH,
|
|
69
|
+
WORKFLOW_PAYLOAD_PART_PATH,
|
|
70
|
+
WORKFLOW_PAYLOAD_RELATIONSHIP_TYPE,
|
|
68
71
|
} from "./ooxml/workflow-payload.ts";
|
|
69
72
|
import {
|
|
70
73
|
classifyCorruptPackageError,
|
|
@@ -74,6 +77,10 @@ import {
|
|
|
74
77
|
import { buildAppPropertiesXml } from "./export/build-app-properties-xml.ts";
|
|
75
78
|
import { createExportSession } from "./export/export-session.ts";
|
|
76
79
|
import { serializeMainDocument } from "./export/serialize-main-document.ts";
|
|
80
|
+
import {
|
|
81
|
+
serializeSettingsXml,
|
|
82
|
+
WORD_SETTINGS_CONTENT_TYPE,
|
|
83
|
+
} from "./export/serialize-settings.ts";
|
|
77
84
|
import {
|
|
78
85
|
parseRevisionsFromDocumentXml,
|
|
79
86
|
parseRevisionsFromStoryXml,
|
|
@@ -262,6 +269,21 @@ interface ImportedDocxState {
|
|
|
262
269
|
sourceDocumentAttributes: Record<string, string>;
|
|
263
270
|
sourceNumberingPartPath?: string;
|
|
264
271
|
sourceNumberingRelationshipId?: string;
|
|
272
|
+
/**
|
|
273
|
+
* Resolved `/word/settings.xml` part path when the source package carried
|
|
274
|
+
* one. Threaded through to the export path so the settings serializer can
|
|
275
|
+
* call `replaceOwnedPart` with the right relationship target.
|
|
276
|
+
*/
|
|
277
|
+
sourceSettingsPartPath?: string;
|
|
278
|
+
/**
|
|
279
|
+
* Original settings.xml bytes decoded as UTF-8. Passed to
|
|
280
|
+
* `serializeSettingsXml(settings, sourceXml)` as the graft source so
|
|
281
|
+
* unmodelled top-level children (`<w:defaultTabStop>`,
|
|
282
|
+
* `<w:documentProtection>`, mail-merge state, etc.) survive verbatim
|
|
283
|
+
* through round-trip. Undefined when the source package lacked a
|
|
284
|
+
* settings part.
|
|
285
|
+
*/
|
|
286
|
+
sourceSettingsXml?: string;
|
|
265
287
|
sourceCommentsPartPath?: string;
|
|
266
288
|
sourceCommentsRelationshipId?: string;
|
|
267
289
|
sourceCommentsRootTag?: string;
|
|
@@ -426,11 +448,17 @@ export function loadDocxEditorSession(
|
|
|
426
448
|
)
|
|
427
449
|
: createEmptyNumberingCatalog();
|
|
428
450
|
const mediaParts = collectInlineMediaParts(sourcePackage);
|
|
451
|
+
const chartPartLookup = createChartPartLookup(
|
|
452
|
+
sourcePackage,
|
|
453
|
+
mainDocumentPath,
|
|
454
|
+
documentPart.relationships,
|
|
455
|
+
);
|
|
429
456
|
const parsedDocument = parseMainDocumentXml(
|
|
430
457
|
sourceDocumentXml,
|
|
431
458
|
documentPart.relationships,
|
|
432
459
|
mediaParts,
|
|
433
460
|
mainDocumentPath,
|
|
461
|
+
chartPartLookup,
|
|
434
462
|
);
|
|
435
463
|
const protectionRanges = extractProtectionRanges(parsedDocument.blocks);
|
|
436
464
|
const normalizedDocument = normalizeParsedTextDocument(
|
|
@@ -829,6 +857,9 @@ export function loadDocxEditorSession(
|
|
|
829
857
|
relationship.type === NUMBERING_RELATIONSHIP_TYPE &&
|
|
830
858
|
relationship.targetMode === "internal",
|
|
831
859
|
)?.id,
|
|
860
|
+
sourceSettingsPartPath: settingsPartPath,
|
|
861
|
+
sourceSettingsXml:
|
|
862
|
+
settingsXmlForProtection.length > 0 ? settingsXmlForProtection : undefined,
|
|
832
863
|
sourceCommentsPartPath: commentsPartPath,
|
|
833
864
|
sourceCommentsRelationshipId: documentPart.relationships.find(
|
|
834
865
|
(relationship) =>
|
|
@@ -896,7 +927,7 @@ export function loadDocxEditorSession(
|
|
|
896
927
|
}
|
|
897
928
|
}
|
|
898
929
|
|
|
899
|
-
interface LoadDocxEditorSessionAsyncOptions extends LoadDocxEditorSessionOptions {
|
|
930
|
+
export interface LoadDocxEditorSessionAsyncOptions extends LoadDocxEditorSessionOptions {
|
|
900
931
|
/**
|
|
901
932
|
* Scheduler that the async loader awaits between parse stages. Callers
|
|
902
933
|
* in DOM environments should construct this with `createLoadScheduler()`
|
|
@@ -907,6 +938,27 @@ interface LoadDocxEditorSessionAsyncOptions extends LoadDocxEditorSessionOptions
|
|
|
907
938
|
* behaves like the sync path from the test harness POV.
|
|
908
939
|
*/
|
|
909
940
|
scheduler: LoadScheduler;
|
|
941
|
+
/**
|
|
942
|
+
* L7 Phase 2.5 Plan B B.6b — optional laycache envelope. When supplied,
|
|
943
|
+
* `loadDocxEditorSessionAsync` still performs the cheap OPC read +
|
|
944
|
+
* workflow-payload parse (needed for `initialEditorStatePayload`,
|
|
945
|
+
* `workflowOverlay`, and `workflowMetadata`), then skips the five
|
|
946
|
+
* expensive stages — `parseMainDocumentXml`,
|
|
947
|
+
* `normalizeParsedTextDocumentAsync`, `parseCommentsFromOoxml`,
|
|
948
|
+
* `parseStylesXml`, and `createImportedCanonicalDocument` — and uses
|
|
949
|
+
* `envelope.canonicalDocument` directly (reference-equal, no clone).
|
|
950
|
+
*
|
|
951
|
+
* Callers obtain a validated envelope by calling
|
|
952
|
+
* `tryReadLaycacheEnvelope(bytes)` before invoking this function; when
|
|
953
|
+
* the probe returns `null`, omit this field and the loader runs the
|
|
954
|
+
* full parse.
|
|
955
|
+
*
|
|
956
|
+
* Async-only — the sync `loadDocxEditorSession` does not honor this
|
|
957
|
+
* option. `buildCompatibilityReport` and
|
|
958
|
+
* `resolveChartPreviewsForDocument` still run on the short-circuit
|
|
959
|
+
* path because their outputs are required by downstream consumers.
|
|
960
|
+
*/
|
|
961
|
+
laycacheEnvelope?: CacheEnvelope;
|
|
910
962
|
}
|
|
911
963
|
|
|
912
964
|
/**
|
|
@@ -1006,6 +1058,137 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1006
1058
|
}
|
|
1007
1059
|
|
|
1008
1060
|
try {
|
|
1061
|
+
// L7 Phase 2.5 Plan B B.6b — loader short-circuit. Hand
|
|
1062
|
+
// `envelope.canonicalDocument` through reference-equal and skip the
|
|
1063
|
+
// five expensive parse stages. The four `onLoadStage` callbacks still
|
|
1064
|
+
// fire in order — `body` and `styles-numbering-comments` emit with
|
|
1065
|
+
// near-zero duration — so host progress bars are unaffected.
|
|
1066
|
+
if (options.laycacheEnvelope) {
|
|
1067
|
+
stages.emit("body");
|
|
1068
|
+
stages.emit("styles-numbering-comments");
|
|
1069
|
+
|
|
1070
|
+
const canonicalDocument = options.laycacheEnvelope.canonicalDocument;
|
|
1071
|
+
|
|
1072
|
+
// `extractProtectionRanges` needs `parsedDocument.blocks` (which we
|
|
1073
|
+
// are skipping), so the short-circuit uses an empty ranges list;
|
|
1074
|
+
// document-level `editType` / `enforcement` still come from
|
|
1075
|
+
// settings.xml so read-only docs stay read-only.
|
|
1076
|
+
const settingsPartPath = resolveDocumentRelatedPartPath(
|
|
1077
|
+
sourcePackage,
|
|
1078
|
+
mainDocumentPath,
|
|
1079
|
+
documentPart.relationships,
|
|
1080
|
+
SETTINGS_RELATIONSHIP_TYPE,
|
|
1081
|
+
SETTINGS_PART_PATH,
|
|
1082
|
+
);
|
|
1083
|
+
const settingsXml =
|
|
1084
|
+
settingsPartPath && sourcePackage.parts.has(settingsPartPath)
|
|
1085
|
+
? decodeUtf8(
|
|
1086
|
+
sourcePackage.parts.get(settingsPartPath)?.bytes ?? new Uint8Array(),
|
|
1087
|
+
)
|
|
1088
|
+
: "";
|
|
1089
|
+
const documentProtection = extractDocumentProtection(settingsXml);
|
|
1090
|
+
const protectionSnapshot = buildProtectionSnapshot(documentProtection, []);
|
|
1091
|
+
|
|
1092
|
+
// Chart previews (`previewMediaId` is host-dependent) aren't cached
|
|
1093
|
+
// in the envelope, so we still resolve them on the short-circuit.
|
|
1094
|
+
const documentWithChartPreviews = (await resolveChartPreviewsForDocument(
|
|
1095
|
+
canonicalDocument,
|
|
1096
|
+
sourcePackage,
|
|
1097
|
+
options.hostAdapter,
|
|
1098
|
+
)) as CanonicalDocumentEnvelope;
|
|
1099
|
+
|
|
1100
|
+
const timestamp = new Date().toISOString();
|
|
1101
|
+
const compatibility = buildCompatibilityReport({
|
|
1102
|
+
document: documentWithChartPreviews,
|
|
1103
|
+
generatedAt: timestamp,
|
|
1104
|
+
});
|
|
1105
|
+
await scheduler.yield();
|
|
1106
|
+
|
|
1107
|
+
const snapshot = createImportedSnapshot({
|
|
1108
|
+
documentId: options.documentId,
|
|
1109
|
+
editorBuild,
|
|
1110
|
+
timestamp,
|
|
1111
|
+
document: documentWithChartPreviews,
|
|
1112
|
+
compatibility: toPublicCompatibilityReport(compatibility),
|
|
1113
|
+
protectionSnapshot,
|
|
1114
|
+
sourcePackage: createPersistedSourcePackage(sourceBytes, options.sourceLabel),
|
|
1115
|
+
workflowOverlay: embeddedWorkflowOverlay,
|
|
1116
|
+
workflowMetadata: embeddedWorkflowMetadata,
|
|
1117
|
+
});
|
|
1118
|
+
const snapshotIssues = validatePersistedEditorSnapshot(snapshot);
|
|
1119
|
+
if (snapshotIssues.length > 0) {
|
|
1120
|
+
const firstIssue = snapshotIssues[0];
|
|
1121
|
+
return createDiagnosticsSession(
|
|
1122
|
+
options,
|
|
1123
|
+
createValidationImportDiagnostics({
|
|
1124
|
+
message: `DOCX import produced an invalid editor state during validation${firstIssue ? ` (${firstIssue.path}: ${firstIssue.message})` : "."}`,
|
|
1125
|
+
source: "import",
|
|
1126
|
+
details: {
|
|
1127
|
+
issueCount: snapshotIssues.length,
|
|
1128
|
+
firstIssuePath: firstIssue?.path,
|
|
1129
|
+
},
|
|
1130
|
+
}),
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// Build `initialSessionState` inline — bypassing
|
|
1135
|
+
// `editorSessionStateFromPersistedSnapshot`'s structuredClone so
|
|
1136
|
+
// `session.initialSessionState.canonicalDocument` is reference-equal
|
|
1137
|
+
// to `envelope.canonicalDocument` (cloning a large canonical document
|
|
1138
|
+
// defeats part of the cache gain).
|
|
1139
|
+
const sessionState: EditorSessionState = {
|
|
1140
|
+
sessionVersion: "editor-session-state/1",
|
|
1141
|
+
schemaVersion: snapshot.schemaVersion,
|
|
1142
|
+
documentId: snapshot.documentId,
|
|
1143
|
+
docId: snapshot.docId,
|
|
1144
|
+
createdAt: snapshot.createdAt,
|
|
1145
|
+
updatedAt: snapshot.updatedAt,
|
|
1146
|
+
editorBuild: snapshot.editorBuild,
|
|
1147
|
+
canonicalDocument: snapshot.canonicalDocument,
|
|
1148
|
+
compatibility: snapshot.compatibility,
|
|
1149
|
+
warningLog: snapshot.warningLog,
|
|
1150
|
+
protectionSnapshot: snapshot.protectionSnapshot,
|
|
1151
|
+
sourcePackage: snapshot.sourcePackage,
|
|
1152
|
+
workflowOverlay: snapshot.workflowOverlay,
|
|
1153
|
+
workflowMetadata: snapshot.workflowMetadata,
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
// The short-circuit path does not carry an `ImportedDocxState`, so
|
|
1157
|
+
// `exportDocx` lazily re-runs the cold path on first invocation and
|
|
1158
|
+
// memoizes. Keeps the warm-load fast while preserving byte-exact
|
|
1159
|
+
// export correctness.
|
|
1160
|
+
let lazyColdExport: LoadedDocxEditorSession["exportDocx"] | undefined;
|
|
1161
|
+
const exportDocx: LoadedDocxEditorSession["exportDocx"] = async (
|
|
1162
|
+
nextSessionStateOrSnapshot,
|
|
1163
|
+
exportOptions,
|
|
1164
|
+
) => {
|
|
1165
|
+
if (!lazyColdExport) {
|
|
1166
|
+
const { laycacheEnvelope: _unused, ...coldOptions } = options;
|
|
1167
|
+
void _unused;
|
|
1168
|
+
const coldSession = await loadDocxEditorSessionAsync(coldOptions);
|
|
1169
|
+
if (coldSession.fatalError) {
|
|
1170
|
+
throw new Error(
|
|
1171
|
+
`DOCX export via short-circuit fallback failed cold load: ${coldSession.fatalError.message ?? "fatal error"}`,
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
lazyColdExport = coldSession.exportDocx;
|
|
1175
|
+
}
|
|
1176
|
+
return lazyColdExport(nextSessionStateOrSnapshot, exportOptions);
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
stages.emit("skeleton-ready");
|
|
1180
|
+
return {
|
|
1181
|
+
initialSessionState: sessionState,
|
|
1182
|
+
initialSnapshot: snapshot,
|
|
1183
|
+
readOnly: false,
|
|
1184
|
+
protectionSnapshot,
|
|
1185
|
+
exportDocx,
|
|
1186
|
+
...(embeddedWorkflowPayload?.editorState
|
|
1187
|
+
? { initialEditorStatePayload: embeddedWorkflowPayload.editorState }
|
|
1188
|
+
: {}),
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1009
1192
|
const sourceDocumentXml = decodeUtf8(documentPart.bytes);
|
|
1010
1193
|
const importedRevisions = parseRevisionsFromDocumentXml(sourceDocumentXml);
|
|
1011
1194
|
const numberingPartPath = resolveDocumentRelatedPartPath(
|
|
@@ -1021,11 +1204,17 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1021
1204
|
)
|
|
1022
1205
|
: createEmptyNumberingCatalog();
|
|
1023
1206
|
const mediaParts = collectInlineMediaParts(sourcePackage);
|
|
1207
|
+
const chartPartLookup = createChartPartLookup(
|
|
1208
|
+
sourcePackage,
|
|
1209
|
+
mainDocumentPath,
|
|
1210
|
+
documentPart.relationships,
|
|
1211
|
+
);
|
|
1024
1212
|
const parsedDocument = parseMainDocumentXml(
|
|
1025
1213
|
sourceDocumentXml,
|
|
1026
1214
|
documentPart.relationships,
|
|
1027
1215
|
mediaParts,
|
|
1028
1216
|
mainDocumentPath,
|
|
1217
|
+
chartPartLookup,
|
|
1029
1218
|
);
|
|
1030
1219
|
await scheduler.yield();
|
|
1031
1220
|
const protectionRanges = extractProtectionRanges(parsedDocument.blocks);
|
|
@@ -1440,6 +1629,9 @@ export async function loadDocxEditorSessionAsync(
|
|
|
1440
1629
|
relationship.type === NUMBERING_RELATIONSHIP_TYPE &&
|
|
1441
1630
|
relationship.targetMode === "internal",
|
|
1442
1631
|
)?.id,
|
|
1632
|
+
sourceSettingsPartPath: settingsPartPath,
|
|
1633
|
+
sourceSettingsXml:
|
|
1634
|
+
settingsXmlForProtection.length > 0 ? settingsXmlForProtection : undefined,
|
|
1443
1635
|
sourceCommentsPartPath: commentsPartPath,
|
|
1444
1636
|
sourceCommentsRelationshipId: documentPart.relationships.find(
|
|
1445
1637
|
(relationship) =>
|
|
@@ -1734,23 +1926,29 @@ function exportDocxEditorSession(
|
|
|
1734
1926
|
}
|
|
1735
1927
|
}
|
|
1736
1928
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1929
|
+
// Settings.xml is owned when the source package carried one OR the canonical
|
|
1930
|
+
// model carries settings we need to re-emit. The `canReuse && signatureMatch`
|
|
1931
|
+
// short-circuit above already skips re-export entirely for no-edit sessions,
|
|
1932
|
+
// so every path that reaches here is willing to emit a rebuilt settings.xml.
|
|
1933
|
+
const settingsPartPath =
|
|
1934
|
+
state.sourceSettingsPartPath ?? SETTINGS_PART_PATH;
|
|
1935
|
+
const hasSettingsSurface =
|
|
1936
|
+
Boolean(state.sourceSettingsPartPath) ||
|
|
1937
|
+
exportedSubParts?.settings !== undefined;
|
|
1741
1938
|
|
|
1742
1939
|
const exportSession = createExportSession(state.sourcePackage, [
|
|
1743
1940
|
state.sourceDocumentPartPath,
|
|
1744
1941
|
APP_PROPERTIES_PART_PATH,
|
|
1745
1942
|
CORE_PROPERTIES_PART_PATH,
|
|
1746
|
-
|
|
1747
|
-
|
|
1943
|
+
WORKFLOW_PAYLOAD_PART_PATH,
|
|
1944
|
+
WORKFLOW_PAYLOAD_ITEM_PROPS_PART_PATH,
|
|
1748
1945
|
WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH,
|
|
1749
1946
|
numberingPartPath,
|
|
1750
1947
|
commentsPartPath,
|
|
1751
1948
|
commentsExtendedPartPath,
|
|
1752
1949
|
commentsIdsPartPath,
|
|
1753
1950
|
peoplePartPath,
|
|
1951
|
+
...(hasSettingsSurface ? [settingsPartPath] : []),
|
|
1754
1952
|
...subPartOwnedPaths,
|
|
1755
1953
|
]);
|
|
1756
1954
|
|
|
@@ -1784,6 +1982,22 @@ function exportDocxEditorSession(
|
|
|
1784
1982
|
});
|
|
1785
1983
|
}
|
|
1786
1984
|
|
|
1985
|
+
if (hasSettingsSurface) {
|
|
1986
|
+
// Canonical settings ∅ + no source settings → omit the owned-part write
|
|
1987
|
+
// (hasSettingsSurface is already false in that case). Otherwise route
|
|
1988
|
+
// through the graft serializer so unmodelled children round-trip via
|
|
1989
|
+
// source bytes while canonical mutations land.
|
|
1990
|
+
const canonicalSettings = exportedSubParts?.settings ?? {};
|
|
1991
|
+
const settingsXml = serializeSettingsXml(canonicalSettings, state.sourceSettingsXml);
|
|
1992
|
+
exportSession.replaceOwnedPart({
|
|
1993
|
+
path: settingsPartPath,
|
|
1994
|
+
bytes: new TextEncoder().encode(settingsXml),
|
|
1995
|
+
contentType:
|
|
1996
|
+
state.sourcePackage.parts.get(settingsPartPath)?.contentType ??
|
|
1997
|
+
WORD_SETTINGS_CONTENT_TYPE,
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
|
|
1787
2001
|
if (serializedComments.serializedCommentIds.length > 0 || state.sourceCommentsPartPath) {
|
|
1788
2002
|
exportSession.replaceOwnedPart({
|
|
1789
2003
|
path: commentsPartPath,
|
|
@@ -1977,14 +2191,7 @@ function exportDocxEditorSession(
|
|
|
1977
2191
|
ensureHostMetadataParts(exportSession, state.sourcePackage, currentDocument);
|
|
1978
2192
|
// Schema 1.2: pass through editorState payload collected by the runtime channel.
|
|
1979
2193
|
const internalEditorState = (options as { _editorState?: import("./ooxml/workflow-payload.ts").EditorStatePayload } | undefined)?._editorState;
|
|
1980
|
-
ensureWorkflowPayloadParts(
|
|
1981
|
-
exportSession,
|
|
1982
|
-
sessionState,
|
|
1983
|
-
currentDocument,
|
|
1984
|
-
state.sourcePackage,
|
|
1985
|
-
internalEditorState,
|
|
1986
|
-
workflowPayloadPartPaths,
|
|
1987
|
-
);
|
|
2194
|
+
ensureWorkflowPayloadParts(exportSession, sessionState, currentDocument, state.sourcePackage, internalEditorState);
|
|
1988
2195
|
|
|
1989
2196
|
return {
|
|
1990
2197
|
bytes: exportSession.serialize(),
|
|
@@ -3711,7 +3918,6 @@ function ensureWorkflowPayloadParts(
|
|
|
3711
3918
|
document: CanonicalDocumentEnvelope,
|
|
3712
3919
|
sourcePackage: OpcPackage,
|
|
3713
3920
|
editorState?: import("./ooxml/workflow-payload.ts").EditorStatePayload,
|
|
3714
|
-
precomputedPartPaths?: { payloadPartPath: string; itemPropsPartPath: string },
|
|
3715
3921
|
): void {
|
|
3716
3922
|
const payloadParts = buildWorkflowPayloadParts({
|
|
3717
3923
|
sourcePackage,
|
|
@@ -3727,37 +3933,19 @@ function ensureWorkflowPayloadParts(
|
|
|
3727
3933
|
return;
|
|
3728
3934
|
}
|
|
3729
3935
|
|
|
3730
|
-
// Export ownership is resolved before session creation, so any precomputed
|
|
3731
|
-
// paths must stay in lockstep with buildWorkflowPayloadParts for the same
|
|
3732
|
-
// source package and documentId.
|
|
3733
|
-
if (precomputedPartPaths) {
|
|
3734
|
-
if (
|
|
3735
|
-
precomputedPartPaths.payloadPartPath !== payloadParts.payloadPartPath ||
|
|
3736
|
-
precomputedPartPaths.itemPropsPartPath !== payloadParts.itemPropsPartPath
|
|
3737
|
-
) {
|
|
3738
|
-
throw new Error(
|
|
3739
|
-
`ensureWorkflowPayloadParts: precomputed payload part paths `
|
|
3740
|
-
+ `(${precomputedPartPaths.payloadPartPath} / ${precomputedPartPaths.itemPropsPartPath}) `
|
|
3741
|
-
+ `do not match buildWorkflowPayloadParts output `
|
|
3742
|
-
+ `(${payloadParts.payloadPartPath} / ${payloadParts.itemPropsPartPath}). `
|
|
3743
|
-
+ `This is a bug in the export orchestration.`,
|
|
3744
|
-
);
|
|
3745
|
-
}
|
|
3746
|
-
}
|
|
3747
|
-
|
|
3748
3936
|
const payloadPart = sourcePackage.parts.get(payloadParts.payloadPartPath);
|
|
3749
3937
|
const itemPropsPart = sourcePackage.parts.get(payloadParts.itemPropsPartPath);
|
|
3750
3938
|
const customPropsPart = sourcePackage.parts.get(WORKFLOW_PAYLOAD_CUSTOM_PROPS_PART_PATH);
|
|
3751
3939
|
|
|
3752
3940
|
exportSession.replaceOwnedPart({
|
|
3753
|
-
path:
|
|
3941
|
+
path: payloadParts.payloadPartPath,
|
|
3754
3942
|
bytes: new TextEncoder().encode(payloadParts.payloadPartXml),
|
|
3755
3943
|
contentType: payloadPart?.contentType ?? WORKFLOW_PAYLOAD_CONTENT_TYPE,
|
|
3756
3944
|
relationships: payloadParts.payloadRelationships,
|
|
3757
3945
|
compression: payloadPart?.compression,
|
|
3758
3946
|
});
|
|
3759
3947
|
exportSession.replaceOwnedPart({
|
|
3760
|
-
path:
|
|
3948
|
+
path: payloadParts.itemPropsPartPath,
|
|
3761
3949
|
bytes: new TextEncoder().encode(payloadParts.itemPropsXml),
|
|
3762
3950
|
contentType: itemPropsPart?.contentType ?? WORKFLOW_PAYLOAD_ITEM_PROPS_CONTENT_TYPE,
|
|
3763
3951
|
compression: itemPropsPart?.compression,
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
} from "../../model/canonical-document.ts";
|
|
16
16
|
import type { OpcRelationship } from "../ooxml/part-manifest.ts";
|
|
17
17
|
import type { RevisionParagraphBoundary } from "../ooxml/revision-boundaries.ts";
|
|
18
|
+
import { SCOPE_MARKER_BOOKMARK_PREFIX } from "../ooxml/parse-scope-markers.ts";
|
|
18
19
|
import { getOpaqueFragment } from "../../preservation/store.ts";
|
|
19
20
|
import { retainRelationshipsForFragment } from "../../preservation/relationship-retention.ts";
|
|
20
21
|
import { serializeParagraphNumberingProperties } from "./serialize-numbering.ts";
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
} from "./table-properties-xml.ts";
|
|
26
27
|
import { twip } from "./twip.ts";
|
|
27
28
|
import { escapeXmlAttribute } from "./escape-xml-attribute.ts";
|
|
29
|
+
import { emitPropertyGrabBag } from "../ooxml/property-grab-bag.ts";
|
|
28
30
|
|
|
29
31
|
const HYPERLINK_RELATIONSHIP_TYPE =
|
|
30
32
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
|
@@ -571,6 +573,21 @@ function serializeTableInlineNode(
|
|
|
571
573
|
);
|
|
572
574
|
case "bookmark_end":
|
|
573
575
|
return `<w:bookmarkEnd w:id="${escapeXmlAttribute(node.bookmarkId)}"/>`;
|
|
576
|
+
case "scope_marker_start": {
|
|
577
|
+
// S1 — scope markers export as w:bookmarkStart with the reserved
|
|
578
|
+
// `bw:scope:` name prefix. The synthetic w:id is keyed on scopeId so
|
|
579
|
+
// the matching end element references the same id.
|
|
580
|
+
const bkId = `scope-${node.scopeId}`;
|
|
581
|
+
const name = `${SCOPE_MARKER_BOOKMARK_PREFIX}${node.scopeId}`;
|
|
582
|
+
return (
|
|
583
|
+
`<w:bookmarkStart w:id="${escapeXmlAttribute(bkId)}"` +
|
|
584
|
+
` w:name="${escapeXmlAttribute(name)}"/>`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
case "scope_marker_end": {
|
|
588
|
+
const bkId = `scope-${node.scopeId}`;
|
|
589
|
+
return `<w:bookmarkEnd w:id="${escapeXmlAttribute(bkId)}"/>`;
|
|
590
|
+
}
|
|
574
591
|
case "footnote_ref": {
|
|
575
592
|
const refElement =
|
|
576
593
|
node.noteKind === "footnote"
|
|
@@ -1060,6 +1077,27 @@ function serializeInlineNode(
|
|
|
1060
1077
|
boundaries.set(cursor, xmlOffset + xml.length);
|
|
1061
1078
|
return { xml, cursor, boundaries };
|
|
1062
1079
|
}
|
|
1080
|
+
case "scope_marker_start": {
|
|
1081
|
+
// S1 — mirror the bookmark_start shape with the reserved `bw:scope:`
|
|
1082
|
+
// name prefix. See serializeInline() above for the same convention.
|
|
1083
|
+
const bkId = `scope-${node.scopeId}`;
|
|
1084
|
+
const name = `${SCOPE_MARKER_BOOKMARK_PREFIX}${node.scopeId}`;
|
|
1085
|
+
const xml =
|
|
1086
|
+
`<w:bookmarkStart w:id="${escapeXmlAttribute(bkId)}"` +
|
|
1087
|
+
` w:name="${escapeXmlAttribute(name)}"/>`;
|
|
1088
|
+
const boundaries = new Map<number, number>();
|
|
1089
|
+
boundaries.set(cursor, xmlOffset);
|
|
1090
|
+
boundaries.set(cursor, xmlOffset + xml.length);
|
|
1091
|
+
return { xml, cursor, boundaries };
|
|
1092
|
+
}
|
|
1093
|
+
case "scope_marker_end": {
|
|
1094
|
+
const bkId = `scope-${node.scopeId}`;
|
|
1095
|
+
const xml = `<w:bookmarkEnd w:id="${escapeXmlAttribute(bkId)}"/>`;
|
|
1096
|
+
const boundaries = new Map<number, number>();
|
|
1097
|
+
boundaries.set(cursor, xmlOffset);
|
|
1098
|
+
boundaries.set(cursor, xmlOffset + xml.length);
|
|
1099
|
+
return { xml, cursor, boundaries };
|
|
1100
|
+
}
|
|
1063
1101
|
case "footnote_ref": {
|
|
1064
1102
|
const refElement =
|
|
1065
1103
|
node.noteKind === "footnote"
|
|
@@ -1570,6 +1608,14 @@ export function serializeSectionPropertiesXml(props: SectionProperties): string
|
|
|
1570
1608
|
}
|
|
1571
1609
|
}
|
|
1572
1610
|
|
|
1611
|
+
// O2 Slice 4 grab-bag: unmodelled sectPr children preserved verbatim
|
|
1612
|
+
// from import. Emitted last so the modelled body stays ECMA-376 canonical
|
|
1613
|
+
// and extension-namespace properties land at the tail of <w:sectPr>.
|
|
1614
|
+
const grabBagXml = emitPropertyGrabBag(props.unknownPropertyChildren);
|
|
1615
|
+
if (grabBagXml.length > 0) {
|
|
1616
|
+
children.push(grabBagXml);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1573
1619
|
if (children.length === 0) {
|
|
1574
1620
|
return "<w:sectPr/>";
|
|
1575
1621
|
}
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ParagraphSpacing,
|
|
13
13
|
TabStop,
|
|
14
14
|
} from "../../model/canonical-document.ts";
|
|
15
|
+
import { emitPropertyGrabBag } from "../ooxml/property-grab-bag.ts";
|
|
15
16
|
import { buildRunPropertiesXml } from "./serialize-run-formatting.ts";
|
|
16
17
|
|
|
17
18
|
function escXml(value: string): string {
|
|
@@ -147,6 +148,13 @@ export function buildParagraphPropertiesXml(
|
|
|
147
148
|
if (markXml) parts.push(markXml);
|
|
148
149
|
}
|
|
149
150
|
|
|
151
|
+
// 16. Grab-bag: unmodelled pPr children preserved verbatim from import.
|
|
152
|
+
// Emitted last so the typed emit order above stays ECMA-376 canonical and
|
|
153
|
+
// any extension-namespace properties land after the modelled set. Word
|
|
154
|
+
// tolerates extra trailing children inside <w:pPr> better than it
|
|
155
|
+
// tolerates interleaving them with the typed set.
|
|
156
|
+
parts.push(emitPropertyGrabBag(pPr.unknownPropertyChildren));
|
|
157
|
+
|
|
150
158
|
const body = parts.filter(Boolean).join("");
|
|
151
159
|
return body.length > 0 ? `<w:pPr>${body}</w:pPr>` : "";
|
|
152
160
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { CanonicalRunFormatting } from "../../model/canonical-document.ts";
|
|
11
|
+
import { emitPropertyGrabBag } from "../ooxml/property-grab-bag.ts";
|
|
11
12
|
|
|
12
13
|
function escXml(value: string): string {
|
|
13
14
|
return value
|
|
@@ -57,10 +58,12 @@ export function buildRunPropertiesXml(rPr: CanonicalRunFormatting | undefined):
|
|
|
57
58
|
parts.push(toggleEl("vanish", rPr.vanish));
|
|
58
59
|
|
|
59
60
|
// 8. color
|
|
60
|
-
if (rPr.colorHex || rPr.colorThemeSlot) {
|
|
61
|
+
if (rPr.colorHex || rPr.colorThemeSlot || rPr.colorThemeTint || rPr.colorThemeShade) {
|
|
61
62
|
const attrs: string[] = [];
|
|
62
63
|
if (rPr.colorHex) attrs.push(`w:val="${escXml(rPr.colorHex)}"`);
|
|
63
64
|
if (rPr.colorThemeSlot) attrs.push(`w:themeColor="${escXml(rPr.colorThemeSlot)}"`);
|
|
65
|
+
if (rPr.colorThemeTint) attrs.push(`w:themeTint="${escXml(rPr.colorThemeTint)}"`);
|
|
66
|
+
if (rPr.colorThemeShade) attrs.push(`w:themeShade="${escXml(rPr.colorThemeShade)}"`);
|
|
64
67
|
parts.push(`<w:color ${attrs.join(" ")}/>`);
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -85,6 +88,12 @@ export function buildRunPropertiesXml(rPr: CanonicalRunFormatting | undefined):
|
|
|
85
88
|
if (rPr.fontSizeHalfPoints !== undefined) parts.push(`<w:sz w:val="${rPr.fontSizeHalfPoints}"/>`);
|
|
86
89
|
if (rPr.fontSizeCsHalfPoints !== undefined) parts.push(`<w:szCs w:val="${rPr.fontSizeCsHalfPoints}"/>`);
|
|
87
90
|
|
|
91
|
+
// 15. Grab-bag: unmodelled rPr children preserved verbatim from import
|
|
92
|
+
// (extension-namespace properties like <w14:textOutline>, Word-internal
|
|
93
|
+
// knobs like <w:em>, <w:kern>). Emitted last so the typed body stays
|
|
94
|
+
// ECMA-376 canonical and Word tolerates extras at the tail of <w:rPr>.
|
|
95
|
+
parts.push(emitPropertyGrabBag(rPr.unknownPropertyChildren));
|
|
96
|
+
|
|
88
97
|
const body = parts.filter(Boolean).join("");
|
|
89
98
|
return body.length > 0 ? `<w:rPr>${body}</w:rPr>` : "";
|
|
90
99
|
}
|