@beyondwork/docx-react-component 1.0.6 → 1.0.8
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 +3 -2
- package/package.json +16 -4
- package/src/api/public-types.ts +2 -0
- package/src/compare/diff-engine.ts +48 -33
- package/src/core/schema/text-schema.ts +5 -1
- package/src/core/selection/mapping.ts +10 -0
- package/src/formats/xlsx/io/xlsx-session.ts +3 -1
- package/src/formats/xlsx/model/sheet.ts +2 -0
- package/src/io/docx-session.ts +50 -47
- package/src/io/export/serialize-comments.ts +6 -5
- package/src/io/export/serialize-headers-footers.ts +6 -1
- package/src/io/export/serialize-main-document.ts +24 -0
- package/src/io/export/serialize-runtime-revisions.ts +7 -3
- package/src/io/normalize/normalize-text.ts +4 -7
- package/src/io/ooxml/parse-comments.ts +4 -3
- package/src/io/ooxml/parse-main-document.ts +1 -1
- package/src/legal/cross-references.ts +1 -1
- package/src/legal/defined-terms.ts +1 -1
- package/src/model/canonical-document.ts +6 -1
- package/src/review/store/revision-store.ts +2 -0
- package/src/runtime/document-runtime.ts +1 -0
- package/src/runtime/revision-runtime.ts +1 -1
- package/src/runtime/surface-projection.ts +43 -12
- package/src/runtime/table-commands.ts +8 -8
- package/src/ui/WordReviewEditor.tsx +3 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +4 -3
- package/src/validation/compatibility-engine.ts +14 -0
- package/src/validation/compatibility-report.ts +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/bwllaming/React-OOXML-Office/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
`@beyondwork/docx-react-component` is
|
|
5
|
+
`@beyondwork/docx-react-component` is the shipped product in this repository: `WordReviewEditor`, a fidelity-first React editor for legal-review-safe `docx` workflows. Wave 21 lands the package-facing docs, npm metadata, publish workflow, and `pnpm pack` proof for this surface.
|
|
6
6
|
|
|
7
7
|
The broader repository is still evolving toward a layered `react-ooxml-office` platform, but the source reality is unchanged:
|
|
8
8
|
|
|
@@ -22,6 +22,7 @@ Current packaging truth:
|
|
|
22
22
|
|
|
23
23
|
- the package is ESM-only
|
|
24
24
|
- exports point at shipped TypeScript source entry points
|
|
25
|
+
- `types` and subpath `types` entries resolve to the shipped source-backed TypeScript contracts
|
|
25
26
|
- consumers need a bundler or runtime that can resolve `.ts` and `.tsx` ESM imports
|
|
26
27
|
- consumers should import `@beyondwork/docx-react-component/ui-tailwind/theme/editor-theme.css` once and provide a Tailwind v4 CSS pipeline for it
|
|
27
28
|
- package and source identifiers remain docx-first until a deliberate rename lands
|
|
@@ -104,7 +105,7 @@ Shared platform and planned xlsx docs:
|
|
|
104
105
|
|
|
105
106
|
- `.github/workflows/publish.yml` publishes on `v*` tags after verifying the tag matches `package.json`
|
|
106
107
|
- `pnpm pack --dry-run` is the baseline package proof for this wave
|
|
107
|
-
- npm provenance is enabled in `publishConfig`
|
|
108
|
+
- npm provenance is enabled in `publishConfig` and in the publish workflow invocation
|
|
108
109
|
- the published package currently ships source ESM entry points plus TypeScript source-backed `types` exports
|
|
109
110
|
- the Microsoft Open XML SDK remains CI/internal-service only, never part of the shipped browser runtime
|
|
110
111
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beyondwork/docx-react-component",
|
|
3
3
|
"publisher": "beyondwork",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.8",
|
|
5
5
|
"description": "Embeddable React Word (docx) editor with review, comments, tracked changes, and round-trip OOXML fidelity.",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"**/*.css"
|
|
9
|
+
],
|
|
7
10
|
"files": [
|
|
8
11
|
"README.md",
|
|
9
12
|
"LICENSE.md",
|
|
@@ -26,9 +29,6 @@
|
|
|
26
29
|
"types": "./src/ui-tailwind/index.ts",
|
|
27
30
|
"import": "./src/ui-tailwind/index.ts"
|
|
28
31
|
},
|
|
29
|
-
"./io/docx-session": "./src/io/docx-session.ts",
|
|
30
|
-
"./runtime/document-runtime": "./src/runtime/document-runtime.ts",
|
|
31
|
-
"./api/public-types": "./src/api/public-types.ts",
|
|
32
32
|
"./ui-tailwind/theme/editor-theme.css": "./src/ui-tailwind/theme/editor-theme.css",
|
|
33
33
|
"./package.json": "./package.json"
|
|
34
34
|
},
|
|
@@ -45,6 +45,14 @@
|
|
|
45
45
|
"prosemirror"
|
|
46
46
|
],
|
|
47
47
|
"author": "",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/bwllaming/React-OOXML-Office.git"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/bwllaming/React-OOXML-Office#readme",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/bwllaming/React-OOXML-Office/issues"
|
|
55
|
+
},
|
|
48
56
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
49
57
|
"dependencies": {
|
|
50
58
|
"@radix-ui/react-popover": "^1.1.15",
|
|
@@ -73,6 +81,7 @@
|
|
|
73
81
|
"@chllming/wave-orchestration": "^0.9.8",
|
|
74
82
|
"@types/react": "19.2.14",
|
|
75
83
|
"@types/react-dom": "19.2.3",
|
|
84
|
+
"@typescript/native-preview": "7.0.0-dev.20260409.1",
|
|
76
85
|
"jsdom": "^29.0.1",
|
|
77
86
|
"prosemirror-commands": "^1.7.1",
|
|
78
87
|
"prosemirror-keymap": "^1.2.3",
|
|
@@ -91,7 +100,10 @@
|
|
|
91
100
|
"test": "bash scripts/run-workspace-tests.sh",
|
|
92
101
|
"test:repo": "pnpm exec tsx --test $(find test -type f \\( -name '*.test.ts' -o -name '*.test.tsx' \\) | sort)",
|
|
93
102
|
"test:harness": "pnpm --filter @docx-react-component/react-word-editor-harness test",
|
|
103
|
+
"lint": "pnpm run lint:no-authored-js && pnpm run lint:tsgo && pnpm run lint:tsgo:harness",
|
|
94
104
|
"lint:no-authored-js": "bash scripts/check-no-authored-js.sh",
|
|
105
|
+
"lint:tsgo": "tsgo --noEmit -p tsconfig.build.json",
|
|
106
|
+
"lint:tsgo:harness": "pnpm --filter @docx-react-component/react-word-editor-harness lint:tsgo",
|
|
95
107
|
"context7:api-check": "bash scripts/context7-export-env.sh run bash scripts/context7-api-check.sh",
|
|
96
108
|
"wave:doctor": "bash scripts/context7-export-env.sh run pnpm exec wave doctor --json",
|
|
97
109
|
"wave:dry-run": "bash scripts/context7-export-env.sh run pnpm exec wave launch --lane main --dry-run --no-dashboard",
|
package/src/api/public-types.ts
CHANGED
|
@@ -385,6 +385,7 @@ export type WordReviewEditorEvent =
|
|
|
385
385
|
stats: DocumentStats;
|
|
386
386
|
compatibility: CompatibilityReport;
|
|
387
387
|
comments: CommentSidebarSnapshot;
|
|
388
|
+
trackedChanges: TrackedChangesSnapshot;
|
|
388
389
|
}
|
|
389
390
|
| {
|
|
390
391
|
type: "dirty_changed";
|
|
@@ -506,6 +507,7 @@ export interface WordReviewEditorRef {
|
|
|
506
507
|
getCompatibilityReport(): CompatibilityReport;
|
|
507
508
|
getWarnings(): EditorWarning[];
|
|
508
509
|
getComments(): CommentSidebarSnapshot;
|
|
510
|
+
getTrackedChanges(): TrackedChangesSnapshot;
|
|
509
511
|
}
|
|
510
512
|
|
|
511
513
|
export interface WordReviewEditorProps {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type HyperlinkNode,
|
|
7
7
|
type InlineNode,
|
|
8
8
|
type ParagraphNode,
|
|
9
|
+
type RevisionRecord,
|
|
9
10
|
type TextNode,
|
|
10
11
|
} from "../model/canonical-document.ts";
|
|
11
12
|
import type { DocumentVersionSnapshot } from "./snapshot.ts";
|
|
@@ -319,39 +320,35 @@ function createParagraphRevisionRecords(
|
|
|
319
320
|
previousWasParagraph = false;
|
|
320
321
|
}
|
|
321
322
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
323
|
+
const entries: Array<[string, RevisionRecord]> = [];
|
|
324
|
+
pendingRevisions.forEach((revision, index) => {
|
|
325
|
+
const position = positionByParagraphIndex.get(revision.paragraphIndex);
|
|
326
|
+
if (position === undefined) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
329
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
})
|
|
353
|
-
.filter((entry): entry is readonly [string, CanonicalDocument["review"]["revisions"][string]] => Boolean(entry)),
|
|
354
|
-
);
|
|
330
|
+
const changeId = `change-${index + 1}`;
|
|
331
|
+
const record: RevisionRecord = {
|
|
332
|
+
changeId,
|
|
333
|
+
kind: revision.kind,
|
|
334
|
+
anchor: {
|
|
335
|
+
kind: "range",
|
|
336
|
+
range: { from: position, to: position },
|
|
337
|
+
assoc: { start: -1, end: 1 },
|
|
338
|
+
},
|
|
339
|
+
authorId,
|
|
340
|
+
createdAt,
|
|
341
|
+
status: "open",
|
|
342
|
+
warningIds: [],
|
|
343
|
+
metadata: {
|
|
344
|
+
source: "runtime",
|
|
345
|
+
importedRevisionForm:
|
|
346
|
+
revision.kind === "insertion" ? "paragraph-insertion" : "paragraph-deletion",
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
entries.push([changeId, record]);
|
|
350
|
+
});
|
|
351
|
+
return Object.fromEntries(entries);
|
|
355
352
|
}
|
|
356
353
|
|
|
357
354
|
function getComparableBlockKey(block: BlockNode): string {
|
|
@@ -434,11 +431,17 @@ function getInlineLength(node: InlineNode): number {
|
|
|
434
431
|
case "tab":
|
|
435
432
|
case "hard_break":
|
|
436
433
|
case "column_break":
|
|
434
|
+
case "symbol":
|
|
437
435
|
case "image":
|
|
438
436
|
case "opaque_inline":
|
|
439
437
|
case "footnote_ref":
|
|
440
438
|
case "bookmark_start":
|
|
441
439
|
case "bookmark_end":
|
|
440
|
+
case "chart_preview":
|
|
441
|
+
case "smartart_preview":
|
|
442
|
+
case "shape":
|
|
443
|
+
case "wordart":
|
|
444
|
+
case "vml_shape":
|
|
442
445
|
return 1;
|
|
443
446
|
}
|
|
444
447
|
}
|
|
@@ -471,6 +474,8 @@ function getInlineDisplayText(node: InlineNode): string {
|
|
|
471
474
|
case "hard_break":
|
|
472
475
|
case "column_break":
|
|
473
476
|
return "\n";
|
|
477
|
+
case "symbol":
|
|
478
|
+
return node.char;
|
|
474
479
|
case "hyperlink":
|
|
475
480
|
return node.children.map(getInlineDisplayText).join("");
|
|
476
481
|
case "field":
|
|
@@ -484,6 +489,16 @@ function getInlineDisplayText(node: InlineNode): string {
|
|
|
484
489
|
case "bookmark_start":
|
|
485
490
|
case "bookmark_end":
|
|
486
491
|
return "";
|
|
492
|
+
case "chart_preview":
|
|
493
|
+
return "[Chart]";
|
|
494
|
+
case "smartart_preview":
|
|
495
|
+
return "[SmartArt]";
|
|
496
|
+
case "shape":
|
|
497
|
+
return node.text ?? "[Shape]";
|
|
498
|
+
case "wordart":
|
|
499
|
+
return node.text;
|
|
500
|
+
case "vml_shape":
|
|
501
|
+
return node.text ?? "[VML Shape]";
|
|
487
502
|
}
|
|
488
503
|
}
|
|
489
504
|
|
|
@@ -517,7 +532,7 @@ function mergePreservationStores(
|
|
|
517
532
|
};
|
|
518
533
|
}
|
|
519
534
|
|
|
520
|
-
function mergeRecordCatalog<T extends
|
|
535
|
+
function mergeRecordCatalog<T extends object>(base: T, target: T): T {
|
|
521
536
|
return projectValue({ ...base, ...target });
|
|
522
537
|
}
|
|
523
538
|
|
|
@@ -94,6 +94,10 @@ export function parseTextStory(content: unknown): TextStory {
|
|
|
94
94
|
continue;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
if (block.type !== "opaque_block") {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
97
101
|
units.push({
|
|
98
102
|
kind: "opaque_block",
|
|
99
103
|
fragmentId: block.fragmentId,
|
|
@@ -484,7 +488,7 @@ function createEmptyParagraph(): ParagraphNode {
|
|
|
484
488
|
const EMPTY_PARAGRAPH_PROPERTIES: ParagraphProperties = {};
|
|
485
489
|
|
|
486
490
|
function cloneMarks(marks: TextMark[]): TextMark[] {
|
|
487
|
-
return marks.map((mark) => ({
|
|
491
|
+
return marks.map((mark) => ({ ...mark }));
|
|
488
492
|
}
|
|
489
493
|
|
|
490
494
|
function haveEqualMarks(left?: TextMark[], right?: TextMark[]): boolean {
|
|
@@ -101,6 +101,16 @@ export function normalizeRange(range: DocRange): DocRange {
|
|
|
101
101
|
: { from: range.to, to: range.from };
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
export function getEffectiveRange(anchor: EditorAnchorProjection): DocRange {
|
|
105
|
+
if (anchor.kind === "range") {
|
|
106
|
+
return anchor.range;
|
|
107
|
+
}
|
|
108
|
+
if (anchor.kind === "node") {
|
|
109
|
+
return { from: anchor.at, to: anchor.at };
|
|
110
|
+
}
|
|
111
|
+
return anchor.lastKnownRange;
|
|
112
|
+
}
|
|
113
|
+
|
|
104
114
|
export function mapPosition(
|
|
105
115
|
position: Position,
|
|
106
116
|
assoc: Assoc,
|
|
@@ -301,6 +301,8 @@ function convertCachedFormulaValue(
|
|
|
301
301
|
// Helpers
|
|
302
302
|
// ---------------------------------------------------------------------------
|
|
303
303
|
|
|
304
|
+
const UTF8_DECODER = new TextDecoder("utf-8");
|
|
305
|
+
|
|
304
306
|
function decodePartBytes(bytes: Uint8Array): string {
|
|
305
|
-
return
|
|
307
|
+
return UTF8_DECODER.decode(bytes);
|
|
306
308
|
}
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import type { CellAddress, CellKey, CellValue, ColIndex, RowIndex } from "./cell.ts";
|
|
9
9
|
import { cellKey } from "./cell.ts";
|
|
10
10
|
|
|
11
|
+
export type { ColIndex, RowIndex } from "./cell.ts";
|
|
12
|
+
|
|
11
13
|
// ---------------------------------------------------------------------------
|
|
12
14
|
// Row and column metadata (sparse)
|
|
13
15
|
// ---------------------------------------------------------------------------
|
package/src/io/docx-session.ts
CHANGED
|
@@ -853,7 +853,9 @@ function exportDocxEditorSession(
|
|
|
853
853
|
exportSession.replaceOwnedPart({
|
|
854
854
|
path: state.sourceSubPartPaths.themePartPath,
|
|
855
855
|
bytes: sourceThemePart.bytes,
|
|
856
|
-
contentType:
|
|
856
|
+
contentType:
|
|
857
|
+
sourceThemePart.contentType ??
|
|
858
|
+
"application/vnd.openxmlformats-officedocument.theme+xml",
|
|
857
859
|
relationships: sourceThemePart.relationships,
|
|
858
860
|
compression: sourceThemePart.compression,
|
|
859
861
|
});
|
|
@@ -1061,13 +1063,14 @@ function normalizeImportedRevisionRecords(
|
|
|
1061
1063
|
return {
|
|
1062
1064
|
...parsed,
|
|
1063
1065
|
revisions: parsed.revisions.map((revision) => {
|
|
1064
|
-
|
|
1066
|
+
const { anchor } = revision;
|
|
1067
|
+
if (anchor.kind !== "range" || revision.metadata.preserveOnlyReason) {
|
|
1065
1068
|
return revision;
|
|
1066
1069
|
}
|
|
1067
1070
|
|
|
1068
1071
|
const preserveOnlyReason =
|
|
1069
1072
|
getStructuralPreserveOnlyReason(revision, paragraphRanges) ??
|
|
1070
|
-
(opaqueRanges.some((range) => rangesIntersect(range,
|
|
1073
|
+
(opaqueRanges.some((range) => rangesIntersect(range, anchor.range))
|
|
1071
1074
|
? "Imported revision overlaps preserve-only OOXML and remains preserve-only."
|
|
1072
1075
|
: undefined);
|
|
1073
1076
|
|
|
@@ -1092,23 +1095,27 @@ function normalizeImportedCommentThreads(
|
|
|
1092
1095
|
revisions: readonly ReviewRevisionRecord[],
|
|
1093
1096
|
): NormalizedImportedCommentsResult {
|
|
1094
1097
|
const opaqueRanges = Object.values(opaqueFragments).map((fragment) => fragment.lastKnownRange);
|
|
1095
|
-
const preserveOnlyRevisionRanges = revisions
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
)
|
|
1102
|
-
|
|
1098
|
+
const preserveOnlyRevisionRanges = revisions.flatMap((revision) => {
|
|
1099
|
+
const { anchor } = revision;
|
|
1100
|
+
if (
|
|
1101
|
+
anchor.kind !== "range" ||
|
|
1102
|
+
typeof revision.metadata.preserveOnlyReason !== "string" ||
|
|
1103
|
+
revision.metadata.preserveOnlyReason.length === 0
|
|
1104
|
+
) {
|
|
1105
|
+
return [];
|
|
1106
|
+
}
|
|
1107
|
+
return [anchor.range];
|
|
1108
|
+
});
|
|
1103
1109
|
const preserveOnlyCommentIds = new Set(parsed.diagnostics.map((diagnostic) => diagnostic.commentId));
|
|
1104
1110
|
const additionalDiagnostics: CommentImportDiagnostic[] = [];
|
|
1105
|
-
const normalizedThreads = parsed.threads.map((thread) => {
|
|
1106
|
-
|
|
1111
|
+
const normalizedThreads: CommentThread[] = parsed.threads.map((thread) => {
|
|
1112
|
+
const { anchor } = thread;
|
|
1113
|
+
if (anchor.kind !== "range") {
|
|
1107
1114
|
preserveOnlyCommentIds.add(thread.commentId);
|
|
1108
1115
|
return thread;
|
|
1109
1116
|
}
|
|
1110
1117
|
|
|
1111
|
-
const opaqueOverlap = opaqueRanges.some((range) => rangesIntersect(range,
|
|
1118
|
+
const opaqueOverlap = opaqueRanges.some((range) => rangesIntersect(range, anchor.range));
|
|
1112
1119
|
if (opaqueOverlap) {
|
|
1113
1120
|
preserveOnlyCommentIds.add(thread.commentId);
|
|
1114
1121
|
additionalDiagnostics.push({
|
|
@@ -1120,13 +1127,13 @@ function normalizeImportedCommentThreads(
|
|
|
1120
1127
|
});
|
|
1121
1128
|
return {
|
|
1122
1129
|
...thread,
|
|
1123
|
-
anchor: createDetachedAnchor(
|
|
1130
|
+
anchor: createDetachedAnchor(anchor.range, "importAmbiguity"),
|
|
1124
1131
|
status: "detached",
|
|
1125
1132
|
};
|
|
1126
1133
|
}
|
|
1127
1134
|
|
|
1128
1135
|
const preserveOnlyRevisionOverlap = preserveOnlyRevisionRanges.some((range) =>
|
|
1129
|
-
rangesIntersect(range,
|
|
1136
|
+
rangesIntersect(range, anchor.range),
|
|
1130
1137
|
);
|
|
1131
1138
|
if (preserveOnlyRevisionOverlap) {
|
|
1132
1139
|
preserveOnlyCommentIds.add(thread.commentId);
|
|
@@ -1139,7 +1146,7 @@ function normalizeImportedCommentThreads(
|
|
|
1139
1146
|
});
|
|
1140
1147
|
return {
|
|
1141
1148
|
...thread,
|
|
1142
|
-
anchor: createDetachedAnchor(
|
|
1149
|
+
anchor: createDetachedAnchor(anchor.range, "importAmbiguity"),
|
|
1143
1150
|
status: "detached",
|
|
1144
1151
|
};
|
|
1145
1152
|
}
|
|
@@ -1196,13 +1203,14 @@ function getStructuralPreserveOnlyReason(
|
|
|
1196
1203
|
paragraphRanges: ReadonlyArray<{ start: number; end: number }>,
|
|
1197
1204
|
): string | undefined {
|
|
1198
1205
|
const form = revision.metadata.importedRevisionForm;
|
|
1199
|
-
|
|
1206
|
+
const { anchor } = revision;
|
|
1207
|
+
if (!form || anchor.kind !== "range") {
|
|
1200
1208
|
return undefined;
|
|
1201
1209
|
}
|
|
1202
1210
|
|
|
1203
1211
|
if (
|
|
1204
1212
|
(form === "run-insertion" || form === "run-deletion") &&
|
|
1205
|
-
|
|
1213
|
+
anchor.range.from === anchor.range.to
|
|
1206
1214
|
) {
|
|
1207
1215
|
return "Imported zero-width run revision remains preserve-only.";
|
|
1208
1216
|
}
|
|
@@ -1210,9 +1218,9 @@ function getStructuralPreserveOnlyReason(
|
|
|
1210
1218
|
if (form === "paragraph-insertion" || form === "paragraph-deletion") {
|
|
1211
1219
|
const paragraphBoundary = paragraphRanges.find(
|
|
1212
1220
|
(boundary) =>
|
|
1213
|
-
boundary.end ===
|
|
1214
|
-
(
|
|
1215
|
-
|
|
1221
|
+
boundary.end === anchor.range.from ||
|
|
1222
|
+
(anchor.range.from >= boundary.start &&
|
|
1223
|
+
anchor.range.from <= boundary.end),
|
|
1216
1224
|
);
|
|
1217
1225
|
return paragraphBoundary
|
|
1218
1226
|
? undefined
|
|
@@ -1221,8 +1229,8 @@ function getStructuralPreserveOnlyReason(
|
|
|
1221
1229
|
|
|
1222
1230
|
const paragraphBoundary = paragraphRanges.find(
|
|
1223
1231
|
(boundary) =>
|
|
1224
|
-
|
|
1225
|
-
|
|
1232
|
+
anchor.range.from >= boundary.start &&
|
|
1233
|
+
anchor.range.to <= boundary.end,
|
|
1226
1234
|
);
|
|
1227
1235
|
return paragraphBoundary
|
|
1228
1236
|
? undefined
|
|
@@ -1256,29 +1264,22 @@ function collectCanonicalParagraphRanges(
|
|
|
1256
1264
|
}
|
|
1257
1265
|
|
|
1258
1266
|
function measureCanonicalParagraph(paragraph: CanonicalDocumentEnvelope["content"]["children"][number] & { type: "paragraph" }): number {
|
|
1259
|
-
return paragraph.children.reduce((size, child) => {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
return size + child.text.length;
|
|
1263
|
-
case "tab":
|
|
1264
|
-
case "hard_break":
|
|
1265
|
-
case "image":
|
|
1266
|
-
case "opaque_inline":
|
|
1267
|
-
return size + 1;
|
|
1268
|
-
case "hyperlink":
|
|
1269
|
-
return (
|
|
1270
|
-
size +
|
|
1271
|
-
child.children.reduce((childSize, entry) => {
|
|
1272
|
-
switch (entry.type) {
|
|
1273
|
-
case "text":
|
|
1274
|
-
return childSize + entry.text.length;
|
|
1275
|
-
case "tab":
|
|
1276
|
-
case "hard_break":
|
|
1277
|
-
return childSize + 1;
|
|
1278
|
-
}
|
|
1279
|
-
}, 0)
|
|
1280
|
-
);
|
|
1267
|
+
return paragraph.children.reduce<number>((size, child) => {
|
|
1268
|
+
if (child.type === "text") {
|
|
1269
|
+
return size + child.text.length;
|
|
1281
1270
|
}
|
|
1271
|
+
if (child.type === "hyperlink") {
|
|
1272
|
+
return (
|
|
1273
|
+
size +
|
|
1274
|
+
child.children.reduce<number>((childSize, entry) => {
|
|
1275
|
+
if (entry.type === "text") {
|
|
1276
|
+
return childSize + entry.text.length;
|
|
1277
|
+
}
|
|
1278
|
+
return childSize + 1;
|
|
1279
|
+
}, 0)
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
return size + 1;
|
|
1282
1283
|
}, 0);
|
|
1283
1284
|
}
|
|
1284
1285
|
|
|
@@ -1728,12 +1729,14 @@ function cloneRelationship(relationship: OpcRelationship): OpcRelationship {
|
|
|
1728
1729
|
return { ...relationship };
|
|
1729
1730
|
}
|
|
1730
1731
|
|
|
1732
|
+
const UTF8_DECODER = new TextDecoder("utf-8");
|
|
1733
|
+
|
|
1731
1734
|
function decodeUtf8(bytes: Uint8Array | undefined): string {
|
|
1732
1735
|
if (!bytes) {
|
|
1733
1736
|
return "";
|
|
1734
1737
|
}
|
|
1735
1738
|
|
|
1736
|
-
return
|
|
1739
|
+
return UTF8_DECODER.decode(bytes);
|
|
1737
1740
|
}
|
|
1738
1741
|
|
|
1739
1742
|
function toUint8Array(bytes: Uint8Array | ArrayBuffer): Uint8Array {
|
|
@@ -255,15 +255,16 @@ export function serializeCommentAnchorsIntoDocumentXml(
|
|
|
255
255
|
options.exportCommentIds ?? createCommentExportIdMap(threads);
|
|
256
256
|
|
|
257
257
|
for (const thread of threads) {
|
|
258
|
-
|
|
258
|
+
const { anchor } = thread;
|
|
259
|
+
if (anchor.kind !== "range") {
|
|
259
260
|
skippedCommentIds.push(thread.commentId);
|
|
260
261
|
continue;
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
const paragraph = paragraphs.find(
|
|
264
265
|
(candidate) =>
|
|
265
|
-
|
|
266
|
-
|
|
266
|
+
anchor.range.from >= candidate.start &&
|
|
267
|
+
anchor.range.to <= candidate.end,
|
|
267
268
|
);
|
|
268
269
|
|
|
269
270
|
if (!paragraph) {
|
|
@@ -271,8 +272,8 @@ export function serializeCommentAnchorsIntoDocumentXml(
|
|
|
271
272
|
continue;
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
const startIndex = paragraph.boundaries.get(
|
|
275
|
-
const endIndex = paragraph.boundaries.get(
|
|
275
|
+
const startIndex = paragraph.boundaries.get(anchor.range.from);
|
|
276
|
+
const endIndex = paragraph.boundaries.get(anchor.range.to);
|
|
276
277
|
|
|
277
278
|
if (startIndex === undefined || endIndex === undefined) {
|
|
278
279
|
skippedCommentIds.push(thread.commentId);
|
|
@@ -140,7 +140,12 @@ function serializeInlineNode(node: InlineNode): string {
|
|
|
140
140
|
case "bookmark_end":
|
|
141
141
|
case "column_break":
|
|
142
142
|
case "symbol":
|
|
143
|
-
|
|
143
|
+
case "chart_preview":
|
|
144
|
+
case "smartart_preview":
|
|
145
|
+
case "shape":
|
|
146
|
+
case "wordart":
|
|
147
|
+
case "vml_shape":
|
|
148
|
+
default:
|
|
144
149
|
return "";
|
|
145
150
|
}
|
|
146
151
|
}
|
|
@@ -153,6 +153,15 @@ export function serializeMainDocument(
|
|
|
153
153
|
continue;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
if (block.type === "section_break") {
|
|
157
|
+
if (block.propertiesXml) {
|
|
158
|
+
sectionPropertiesXml = block.propertiesXml;
|
|
159
|
+
}
|
|
160
|
+
cursor += 1;
|
|
161
|
+
previousWasParagraph = false;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
156
165
|
const blockXml = serializeOpaqueBlock(block, state);
|
|
157
166
|
if (looksLikeSectionPropertiesXml(blockXml)) {
|
|
158
167
|
sectionPropertiesXml = blockXml;
|
|
@@ -365,6 +374,12 @@ function serializeTableInlineNode(
|
|
|
365
374
|
const childrenXml = node.children.map((child) => serializeTableInlineNode(child, state)).join("");
|
|
366
375
|
return `${hyperlinkOpen}${childrenXml}</w:hyperlink>`;
|
|
367
376
|
}
|
|
377
|
+
case "field":
|
|
378
|
+
case "bookmark_start":
|
|
379
|
+
case "bookmark_end":
|
|
380
|
+
case "footnote_ref":
|
|
381
|
+
default:
|
|
382
|
+
return "";
|
|
368
383
|
}
|
|
369
384
|
}
|
|
370
385
|
|
|
@@ -772,6 +787,15 @@ function serializeInlineNode(
|
|
|
772
787
|
boundaries,
|
|
773
788
|
};
|
|
774
789
|
}
|
|
790
|
+
case "field":
|
|
791
|
+
case "bookmark_start":
|
|
792
|
+
case "bookmark_end":
|
|
793
|
+
case "footnote_ref":
|
|
794
|
+
default: {
|
|
795
|
+
const boundaries = new Map<number, number>();
|
|
796
|
+
boundaries.set(cursor, xmlOffset);
|
|
797
|
+
return { xml: "", cursor, boundaries };
|
|
798
|
+
}
|
|
775
799
|
}
|
|
776
800
|
}
|
|
777
801
|
|
|
@@ -94,13 +94,17 @@ function createRangeRevisionReplacement(
|
|
|
94
94
|
boundaries: readonly RevisionParagraphBoundary[],
|
|
95
95
|
revision: RevisionRecord,
|
|
96
96
|
): XmlReplacement | undefined {
|
|
97
|
-
const
|
|
97
|
+
const { anchor } = revision;
|
|
98
|
+
if (anchor.kind !== "range") {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const paragraphBoundary = findParagraphBoundaryForRange(boundaries, anchor.range.from, anchor.range.to);
|
|
98
102
|
if (!paragraphBoundary) {
|
|
99
103
|
return undefined;
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
const startIndex = paragraphBoundary.boundaries.get(
|
|
103
|
-
const endIndex = paragraphBoundary.boundaries.get(
|
|
106
|
+
const startIndex = paragraphBoundary.boundaries.get(anchor.range.from);
|
|
107
|
+
const endIndex = paragraphBoundary.boundaries.get(anchor.range.to);
|
|
104
108
|
if (startIndex === undefined || endIndex === undefined || endIndex < startIndex) {
|
|
105
109
|
return undefined;
|
|
106
110
|
}
|
|
@@ -368,14 +368,11 @@ function normalizeHyperlink(node: ParsedHyperlinkNode): {
|
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
function measureHyperlink(node: ParsedHyperlinkNode): number {
|
|
371
|
-
return node.children.reduce((size, child) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
return size + child.text.length;
|
|
375
|
-
case "tab":
|
|
376
|
-
case "hard_break":
|
|
377
|
-
return size + 1;
|
|
371
|
+
return node.children.reduce<number>((size, child) => {
|
|
372
|
+
if (child.type === "text") {
|
|
373
|
+
return size + child.text.length;
|
|
378
374
|
}
|
|
375
|
+
return size + 1;
|
|
379
376
|
}, 0);
|
|
380
377
|
}
|
|
381
378
|
|
|
@@ -124,14 +124,15 @@ export function parseCommentsFromOoxml(
|
|
|
124
124
|
}));
|
|
125
125
|
const createdBy = rootDefinition.authorId ?? entries[0]?.authorId ?? "unknown";
|
|
126
126
|
const createdAt = rootDefinition.createdAt ?? entries[0]?.createdAt ?? "1970-01-01T00:00:00.000Z";
|
|
127
|
+
const lastEntry = entries.length > 0 ? entries[entries.length - 1] : undefined;
|
|
127
128
|
const resolution =
|
|
128
129
|
rootDefinition.isDone
|
|
129
130
|
? {
|
|
130
|
-
resolvedAt:
|
|
131
|
-
resolvedBy:
|
|
131
|
+
resolvedAt: lastEntry?.createdAt ?? createdAt,
|
|
132
|
+
resolvedBy: lastEntry?.authorId ?? createdBy,
|
|
132
133
|
}
|
|
133
134
|
: undefined;
|
|
134
|
-
const detachedRange = toDetachedRange(anchor);
|
|
135
|
+
const detachedRange = anchor ? toDetachedRange(anchor) : { from: 0, to: 0 };
|
|
135
136
|
|
|
136
137
|
if (
|
|
137
138
|
start === undefined ||
|
|
@@ -1249,7 +1249,7 @@ function parseHyperlink(
|
|
|
1249
1249
|
};
|
|
1250
1250
|
}
|
|
1251
1251
|
|
|
1252
|
-
const children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode> = [];
|
|
1252
|
+
const children: Array<ParsedTextNode | ParsedBreakNode | ParsedColumnBreakNode | ParsedTabNode | ParsedSymbolNode> = [];
|
|
1253
1253
|
|
|
1254
1254
|
for (const child of node.children) {
|
|
1255
1255
|
if (child.type !== "element") {
|
|
@@ -80,7 +80,7 @@ export function parseCrossReferencesFromDocumentXml(xml: string): CrossReference
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
for (const pattern of detectCrossReferencePatterns(flattenParagraphText(block))) {
|
|
83
|
+
for (const pattern of detectCrossReferencePatterns(flattenParagraphText(block as unknown as ParagraphNode))) {
|
|
84
84
|
results.push({
|
|
85
85
|
source: "text-pattern",
|
|
86
86
|
kind: pattern.kind,
|
|
@@ -35,7 +35,7 @@ export function collectDefinedTermsFromDocumentXml(xml: string): DefinedTerm[] {
|
|
|
35
35
|
const parsed = parseMainDocumentXml(xml);
|
|
36
36
|
const paragraphs = parsed.blocks
|
|
37
37
|
.filter((block): block is typeof parsed.blocks[number] & { type: "paragraph" } => block.type === "paragraph")
|
|
38
|
-
.map(flattenParagraphText);
|
|
38
|
+
.map((block) => flattenParagraphText(block as unknown as ParagraphNode));
|
|
39
39
|
|
|
40
40
|
return buildDefinedTermCatalog(paragraphs);
|
|
41
41
|
}
|
|
@@ -56,7 +56,7 @@ export interface CanonicalDocument {
|
|
|
56
56
|
styles: StylesCatalog;
|
|
57
57
|
numbering: NumberingCatalog;
|
|
58
58
|
media: MediaCatalog;
|
|
59
|
-
content:
|
|
59
|
+
content: DocumentRootNode;
|
|
60
60
|
review: ReviewStore;
|
|
61
61
|
preservation: PreservationStore;
|
|
62
62
|
diagnostics: DiagnosticStore;
|
|
@@ -70,6 +70,7 @@ export interface DocumentMetadata {
|
|
|
70
70
|
language?: string;
|
|
71
71
|
keywords?: string[];
|
|
72
72
|
category?: string;
|
|
73
|
+
importMode?: string;
|
|
73
74
|
customProperties: Record<string, string>;
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -456,7 +457,9 @@ export interface SectionBreakNode {
|
|
|
456
457
|
export type InlineNode =
|
|
457
458
|
| TextNode
|
|
458
459
|
| HardBreakNode
|
|
460
|
+
| ColumnBreakNode
|
|
459
461
|
| TabNode
|
|
462
|
+
| SymbolNode
|
|
460
463
|
| HyperlinkNode
|
|
461
464
|
| ImageNode
|
|
462
465
|
| FieldNode
|
|
@@ -765,6 +768,7 @@ export interface DiagnosticErrorEntry {
|
|
|
765
768
|
diagnosticId: string;
|
|
766
769
|
code:
|
|
767
770
|
| "load_failed"
|
|
771
|
+
| "import_failed"
|
|
768
772
|
| "export_failed"
|
|
769
773
|
| "package_corrupt"
|
|
770
774
|
| "validation_failed"
|
|
@@ -773,6 +777,7 @@ export interface DiagnosticErrorEntry {
|
|
|
773
777
|
message: string;
|
|
774
778
|
isFatal: boolean;
|
|
775
779
|
source: "import" | "runtime" | "validation" | "datastore" | "export";
|
|
780
|
+
details?: unknown;
|
|
776
781
|
}
|
|
777
782
|
|
|
778
783
|
export function createCanonicalDocument(
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
type RevisionStatus,
|
|
14
14
|
} from "./revision-types.ts";
|
|
15
15
|
|
|
16
|
+
export type { RevisionRecord, RevisionKind, RevisionStatus } from "./revision-types.ts";
|
|
17
|
+
|
|
16
18
|
export interface RevisionStore {
|
|
17
19
|
version: "revision-store/1";
|
|
18
20
|
revisions: Record<string, RevisionRecord>;
|
|
@@ -134,6 +134,7 @@ export function createDocumentRuntime(
|
|
|
134
134
|
stats: toPublicDocumentStats(state),
|
|
135
135
|
compatibility: toPublicCompatibilityReport(createDerivedCompatibility(state)),
|
|
136
136
|
comments: cachedRenderSnapshot.comments,
|
|
137
|
+
trackedChanges: cachedRenderSnapshot.trackedChanges,
|
|
137
138
|
});
|
|
138
139
|
if (options.fatalError) {
|
|
139
140
|
emit({
|
|
@@ -42,7 +42,7 @@ export function applyRevisionRuntimeCommand(
|
|
|
42
42
|
: listBatchRevisionIds(options.state.store);
|
|
43
43
|
|
|
44
44
|
const outcomes: RevisionActionOutcome[] = [];
|
|
45
|
-
const mappings: Array<{ revisionId: string; steps: number }> = [];
|
|
45
|
+
const mappings: Array<{ revisionId: string; mapping: TransactionMapping; steps: number }> = [];
|
|
46
46
|
const appliedRevisionIds: string[] = [];
|
|
47
47
|
const detachedRevisionIds = new Set<string>();
|
|
48
48
|
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ChartPreviewNode,
|
|
15
15
|
DocumentRootNode,
|
|
16
16
|
InlineNode,
|
|
17
|
+
MediaCatalog,
|
|
17
18
|
ParagraphNode,
|
|
18
19
|
SdtNode,
|
|
19
20
|
ShapeNode,
|
|
@@ -165,6 +166,26 @@ function createSurfaceBlock(
|
|
|
165
166
|
};
|
|
166
167
|
}
|
|
167
168
|
|
|
169
|
+
if (block.type === "section_break") {
|
|
170
|
+
const blockId = `section-break-${counters.opaque}`;
|
|
171
|
+
counters.opaque += 1;
|
|
172
|
+
return {
|
|
173
|
+
block: {
|
|
174
|
+
blockId,
|
|
175
|
+
kind: "opaque_block",
|
|
176
|
+
from: cursor,
|
|
177
|
+
to: cursor + 1,
|
|
178
|
+
fragmentId: blockId,
|
|
179
|
+
warningId: blockId,
|
|
180
|
+
label: "Section break",
|
|
181
|
+
detail: "Section properties preserved as a read-only boundary.",
|
|
182
|
+
state: "locked-preserve-only",
|
|
183
|
+
},
|
|
184
|
+
lockedFragmentIds: [],
|
|
185
|
+
nextCursor: cursor + 1,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
168
189
|
const paragraphIndex = counters.paragraph;
|
|
169
190
|
counters.paragraph += 1;
|
|
170
191
|
return createParagraphBlock(paragraphIndex, block, document, cursor);
|
|
@@ -394,6 +415,14 @@ function appendInlineSegments(
|
|
|
394
415
|
return appendComplexPreviewSegment(paragraph, node, start, "WordArt", createWordArtDetail(node));
|
|
395
416
|
case "vml_shape":
|
|
396
417
|
return appendComplexPreviewSegment(paragraph, node, start, "VML shape", createVmlDetail(node));
|
|
418
|
+
case "column_break":
|
|
419
|
+
case "symbol":
|
|
420
|
+
case "field":
|
|
421
|
+
case "bookmark_start":
|
|
422
|
+
case "bookmark_end":
|
|
423
|
+
case "footnote_ref":
|
|
424
|
+
default:
|
|
425
|
+
return { nextCursor: start + 1, lockedFragmentIds: [] };
|
|
397
426
|
}
|
|
398
427
|
}
|
|
399
428
|
|
|
@@ -499,7 +528,18 @@ function createPlainText(
|
|
|
499
528
|
}
|
|
500
529
|
|
|
501
530
|
function cloneMarks(marks: TextMark[]): Array<"bold" | "italic" | "underline" | "strikethrough"> {
|
|
502
|
-
|
|
531
|
+
const supported: Array<"bold" | "italic" | "underline" | "strikethrough"> = [];
|
|
532
|
+
for (const mark of marks) {
|
|
533
|
+
if (
|
|
534
|
+
mark.type === "bold" ||
|
|
535
|
+
mark.type === "italic" ||
|
|
536
|
+
mark.type === "underline" ||
|
|
537
|
+
mark.type === "strikethrough"
|
|
538
|
+
) {
|
|
539
|
+
supported.push(mark.type);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return supported;
|
|
503
543
|
}
|
|
504
544
|
|
|
505
545
|
function normalizeDocumentRoot(content: unknown): DocumentRootNode {
|
|
@@ -556,15 +596,6 @@ function createFloatingImageDetail(
|
|
|
556
596
|
return parts.join(" ");
|
|
557
597
|
}
|
|
558
598
|
|
|
559
|
-
function hasMediaItem(media:
|
|
560
|
-
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const items = media.items;
|
|
565
|
-
if (!items || typeof items !== "object" || Array.isArray(items)) {
|
|
566
|
-
return false;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
return mediaId in items;
|
|
599
|
+
function hasMediaItem(media: MediaCatalog, mediaId: string): boolean {
|
|
600
|
+
return mediaId in media.items;
|
|
570
601
|
}
|
|
@@ -60,22 +60,22 @@ function withTableGuard(
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export const addRowBefore: PMCommand = (state, dispatch
|
|
64
|
-
pmAddRowBefore(state, dispatch
|
|
63
|
+
export const addRowBefore: PMCommand = (state, dispatch) =>
|
|
64
|
+
pmAddRowBefore(state, dispatch);
|
|
65
65
|
|
|
66
|
-
export const addRowAfter: PMCommand = (state, dispatch
|
|
67
|
-
pmAddRowAfter(state, dispatch
|
|
66
|
+
export const addRowAfter: PMCommand = (state, dispatch) =>
|
|
67
|
+
pmAddRowAfter(state, dispatch);
|
|
68
68
|
|
|
69
69
|
export const deleteRow: PMCommand = withTableGuard((state) => {
|
|
70
70
|
const table = tableAtSelection(state);
|
|
71
71
|
return table !== null && table.childCount > 1;
|
|
72
72
|
}, pmDeleteRow);
|
|
73
73
|
|
|
74
|
-
export const addColumnBefore: PMCommand = (state, dispatch
|
|
75
|
-
pmAddColumnBefore(state, dispatch
|
|
74
|
+
export const addColumnBefore: PMCommand = (state, dispatch) =>
|
|
75
|
+
pmAddColumnBefore(state, dispatch);
|
|
76
76
|
|
|
77
|
-
export const addColumnAfter: PMCommand = (state, dispatch
|
|
78
|
-
pmAddColumnAfter(state, dispatch
|
|
77
|
+
export const addColumnAfter: PMCommand = (state, dispatch) =>
|
|
78
|
+
pmAddColumnAfter(state, dispatch);
|
|
79
79
|
|
|
80
80
|
export const deleteColumn: PMCommand = withTableGuard((state) => {
|
|
81
81
|
const table = tableAtSelection(state);
|
|
@@ -113,6 +113,7 @@ export function __createWordReviewEditorRefBridge(
|
|
|
113
113
|
getCompatibilityReport: () => runtime.getCompatibilityReport(),
|
|
114
114
|
getWarnings: () => runtime.getWarnings(),
|
|
115
115
|
getComments: () => runtime.getRenderSnapshot().comments,
|
|
116
|
+
getTrackedChanges: () => runtime.getRenderSnapshot().trackedChanges,
|
|
116
117
|
};
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -498,6 +499,7 @@ export const WordReviewEditor = forwardRef<WordReviewEditorRef, WordReviewEditor
|
|
|
498
499
|
getCompatibilityReport: () => activeRuntime.getCompatibilityReport(),
|
|
499
500
|
getWarnings: () => activeRuntime.getWarnings(),
|
|
500
501
|
getComments: () => activeRuntime.getRenderSnapshot().comments,
|
|
502
|
+
getTrackedChanges: () => activeRuntime.getRenderSnapshot().trackedChanges,
|
|
501
503
|
}),
|
|
502
504
|
[activeRuntime, currentUser.userId, documentId, runtime],
|
|
503
505
|
);
|
|
@@ -1292,6 +1294,7 @@ function createReadyEvent(
|
|
|
1292
1294
|
stats: snapshot.documentStats,
|
|
1293
1295
|
compatibility: runtime.getCompatibilityReport(),
|
|
1294
1296
|
comments: snapshot.comments,
|
|
1297
|
+
trackedChanges: snapshot.trackedChanges,
|
|
1295
1298
|
};
|
|
1296
1299
|
}
|
|
1297
1300
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { Node as PMNode } from "prosemirror-model";
|
|
12
|
+
import type { NodeViewConstructor, ViewMutationRecord } from "prosemirror-view";
|
|
12
13
|
|
|
13
14
|
const TABLE_LAYOUT_SYNC_EVENT = "pm-table-layout-sync";
|
|
14
15
|
|
|
@@ -251,7 +252,7 @@ export class TableNodeView {
|
|
|
251
252
|
this.dom.removeEventListener(TABLE_LAYOUT_SYNC_EVENT, this.onSyncRequest);
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
ignoreMutation(record:
|
|
255
|
+
ignoreMutation(record: ViewMutationRecord): boolean {
|
|
255
256
|
return record.type === "attributes" && record.target === this.dom;
|
|
256
257
|
}
|
|
257
258
|
|
|
@@ -336,7 +337,7 @@ export class TableCellNodeView {
|
|
|
336
337
|
return true;
|
|
337
338
|
}
|
|
338
339
|
|
|
339
|
-
ignoreMutation(record:
|
|
340
|
+
ignoreMutation(record: ViewMutationRecord): boolean {
|
|
340
341
|
return record.type === "attributes" && record.target === this.dom;
|
|
341
342
|
}
|
|
342
343
|
}
|
|
@@ -347,7 +348,7 @@ export class TableCellNodeView {
|
|
|
347
348
|
* Pass this object directly to the EditorView constructor options:
|
|
348
349
|
* new EditorView(mount, { nodeViews: tableNodeViews, ... })
|
|
349
350
|
*/
|
|
350
|
-
export const tableNodeViews = {
|
|
351
|
+
export const tableNodeViews: { [node: string]: NodeViewConstructor } = {
|
|
351
352
|
table: (node: PMNode) => new TableNodeView(node),
|
|
352
353
|
table_row: (node: PMNode) => new TableRowNodeView(node),
|
|
353
354
|
table_cell: (node: PMNode) => new TableCellNodeView(node),
|
|
@@ -200,6 +200,20 @@ function measureInlineNode(
|
|
|
200
200
|
case "opaque_inline":
|
|
201
201
|
flags.runs = true;
|
|
202
202
|
return 1;
|
|
203
|
+
case "column_break":
|
|
204
|
+
case "symbol":
|
|
205
|
+
case "field":
|
|
206
|
+
case "bookmark_start":
|
|
207
|
+
case "bookmark_end":
|
|
208
|
+
case "footnote_ref":
|
|
209
|
+
case "chart_preview":
|
|
210
|
+
case "smartart_preview":
|
|
211
|
+
case "shape":
|
|
212
|
+
case "wordart":
|
|
213
|
+
case "vml_shape":
|
|
214
|
+
default:
|
|
215
|
+
flags.runs = true;
|
|
216
|
+
return 1;
|
|
203
217
|
}
|
|
204
218
|
}
|
|
205
219
|
|
|
@@ -147,7 +147,7 @@ export type {
|
|
|
147
147
|
EditorWarning,
|
|
148
148
|
} from "./diagnostics.ts";
|
|
149
149
|
|
|
150
|
-
function flattenUnique<Value extends
|
|
150
|
+
function flattenUnique<Value extends object>(
|
|
151
151
|
items: readonly Value[],
|
|
152
152
|
): readonly Value[] {
|
|
153
153
|
const deduped = new Map<string, Value>();
|