@beyondwork/docx-react-component 1.0.1 → 1.0.2
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 +44 -104
- package/package.json +76 -46
- package/src/README.md +85 -0
- package/src/api/README.md +22 -0
- package/src/api/public-types.ts +525 -0
- package/src/compare/diff-engine.ts +530 -0
- package/src/compare/export-redlines.ts +162 -0
- package/src/compare/snapshot.ts +37 -0
- package/src/component-inventory.md +99 -0
- package/src/core/README.md +10 -0
- package/src/core/commands/README.md +3 -0
- package/src/core/commands/formatting-commands.ts +161 -0
- package/src/core/commands/image-commands.ts +144 -0
- package/src/core/commands/index.ts +1013 -0
- package/src/core/commands/list-commands.ts +370 -0
- package/src/core/commands/review-commands.ts +108 -0
- package/src/core/commands/text-commands.ts +119 -0
- package/src/core/schema/README.md +3 -0
- package/src/core/schema/text-schema.ts +512 -0
- package/src/core/selection/README.md +3 -0
- package/src/core/selection/mapping.ts +238 -0
- package/src/core/selection/review-anchors.ts +94 -0
- package/src/core/state/README.md +3 -0
- package/src/core/state/editor-state.ts +580 -0
- package/src/core/state/text-transaction.ts +276 -0
- package/src/formats/xlsx/io/parse-shared-strings.ts +41 -0
- package/src/formats/xlsx/io/parse-sheet.ts +289 -0
- package/src/formats/xlsx/io/parse-styles.ts +57 -0
- package/src/formats/xlsx/io/parse-workbook.ts +75 -0
- package/src/formats/xlsx/io/xlsx-session.ts +306 -0
- package/src/formats/xlsx/model/cell.ts +189 -0
- package/src/formats/xlsx/model/sheet.ts +244 -0
- package/src/formats/xlsx/model/styles.ts +118 -0
- package/src/formats/xlsx/model/workbook.ts +449 -0
- package/src/index.ts +45 -0
- package/src/io/README.md +10 -0
- package/src/io/docx-session.ts +1763 -0
- package/src/io/export/README.md +3 -0
- package/src/io/export/export-session.ts +165 -0
- package/src/io/export/minimal-docx.ts +115 -0
- package/src/io/export/reattach-preserved-parts.ts +54 -0
- package/src/io/export/serialize-comments.ts +876 -0
- package/src/io/export/serialize-footnotes.ts +217 -0
- package/src/io/export/serialize-headers-footers.ts +200 -0
- package/src/io/export/serialize-main-document.ts +982 -0
- package/src/io/export/serialize-numbering.ts +97 -0
- package/src/io/export/serialize-revisions.ts +389 -0
- package/src/io/export/serialize-runtime-revisions.ts +265 -0
- package/src/io/export/serialize-tables.ts +147 -0
- package/src/io/export/split-review-boundaries.ts +194 -0
- package/src/io/normalize/README.md +3 -0
- package/src/io/normalize/normalize-text.ts +437 -0
- package/src/io/ooxml/README.md +3 -0
- package/src/io/ooxml/parse-comments.ts +779 -0
- package/src/io/ooxml/parse-complex-content.ts +287 -0
- package/src/io/ooxml/parse-fields.ts +438 -0
- package/src/io/ooxml/parse-footnotes.ts +403 -0
- package/src/io/ooxml/parse-headers-footers.ts +483 -0
- package/src/io/ooxml/parse-inline-media.ts +431 -0
- package/src/io/ooxml/parse-main-document.ts +1846 -0
- package/src/io/ooxml/parse-numbering.ts +425 -0
- package/src/io/ooxml/parse-revisions.ts +658 -0
- package/src/io/ooxml/parse-shapes.ts +271 -0
- package/src/io/ooxml/parse-tables.ts +568 -0
- package/src/io/ooxml/parse-theme.ts +314 -0
- package/src/io/ooxml/part-manifest.ts +136 -0
- package/src/io/ooxml/revision-boundaries.ts +351 -0
- package/src/io/opc/README.md +3 -0
- package/src/io/opc/corrupt-package.ts +166 -0
- package/src/io/opc/docx-package.ts +74 -0
- package/src/io/opc/package-reader.ts +320 -0
- package/src/io/opc/package-writer.ts +273 -0
- package/src/legal/bookmarks.ts +196 -0
- package/src/legal/cross-references.ts +356 -0
- package/src/legal/defined-terms.ts +203 -0
- package/src/model/README.md +3 -0
- package/src/model/canonical-document.ts +1911 -0
- package/src/model/cds-1.0.0.ts +196 -0
- package/src/model/snapshot.ts +393 -0
- package/src/preservation/README.md +3 -0
- package/src/preservation/markup-compatibility.ts +48 -0
- package/src/preservation/opaque-fragment-store.ts +89 -0
- package/src/preservation/opaque-region.ts +233 -0
- package/src/preservation/package-preservation.ts +120 -0
- package/src/preservation/preserved-part-manifest.ts +56 -0
- package/src/preservation/relationship-retention.ts +57 -0
- package/src/preservation/store.ts +185 -0
- package/src/review/README.md +16 -0
- package/src/review/store/README.md +3 -0
- package/src/review/store/comment-anchors.ts +70 -0
- package/src/review/store/comment-remapping.ts +154 -0
- package/src/review/store/comment-store.ts +331 -0
- package/src/review/store/comment-thread.ts +109 -0
- package/src/review/store/revision-actions.ts +394 -0
- package/src/review/store/revision-store.ts +303 -0
- package/src/review/store/revision-types.ts +168 -0
- package/src/review/store/runtime-comment-store.ts +43 -0
- package/src/runtime/README.md +3 -0
- package/src/runtime/ai-action-policy.ts +764 -0
- package/src/runtime/document-runtime.ts +967 -0
- package/src/runtime/read-only-diagnostics-runtime.ts +232 -0
- package/src/runtime/review-runtime.ts +44 -0
- package/src/runtime/revision-runtime.ts +107 -0
- package/src/runtime/session-capabilities.ts +138 -0
- package/src/runtime/surface-projection.ts +570 -0
- package/src/runtime/table-commands.ts +87 -0
- package/src/runtime/table-schema.ts +140 -0
- package/src/runtime/virtualized-rendering.ts +258 -0
- package/src/ui/README.md +30 -0
- package/src/ui/WordReviewEditor.tsx +1504 -0
- package/src/ui/comments/README.md +3 -0
- package/src/ui/compatibility/README.md +3 -0
- package/src/ui/editor-surface/README.md +3 -0
- package/src/ui/headless/comment-decoration-model.ts +124 -0
- package/src/ui/headless/revision-decoration-model.ts +128 -0
- package/src/ui/headless/selection-helpers.ts +34 -0
- package/src/ui/headless/use-editor-keyboard.ts +98 -0
- package/src/ui/review/README.md +3 -0
- package/src/ui/shared/revision-filters.ts +31 -0
- package/src/ui/status/README.md +3 -0
- package/src/ui/theme/README.md +3 -0
- package/src/ui/toolbar/README.md +3 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +48 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +44 -0
- package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
- package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +139 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +98 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +452 -0
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +327 -0
- package/src/ui-tailwind/editor-surface/search-plugin.ts +157 -0
- package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
- package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +118 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +52 -0
- package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +215 -0
- package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +122 -0
- package/src/ui-tailwind/index.ts +61 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +276 -0
- package/src/ui-tailwind/review/tw-health-panel.tsx +120 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +120 -0
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
- package/src/ui-tailwind/status/tw-status-bar.tsx +58 -0
- package/src/ui-tailwind/theme/editor-theme.css +190 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +48 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +231 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +140 -0
- package/src/validation/README.md +3 -0
- package/src/validation/compatibility-engine.ts +317 -0
- package/src/validation/compatibility-report.ts +160 -0
- package/src/validation/diagnostics.ts +203 -0
- package/src/validation/import-diagnostics.ts +128 -0
- package/src/validation/low-priority-word-surfaces.ts +373 -0
- package/dist/chunk-32W6IVQE.js +0 -7725
- package/dist/chunk-32W6IVQE.js.map +0 -1
- package/dist/index.cjs +0 -23722
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -7
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -16011
- package/dist/index.js.map +0 -1
- package/dist/public-types-DqCURAz8.d.cts +0 -1152
- package/dist/public-types-DqCURAz8.d.ts +0 -1152
- package/dist/tailwind.cjs +0 -8295
- package/dist/tailwind.cjs.map +0 -1
- package/dist/tailwind.d.cts +0 -323
- package/dist/tailwind.d.ts +0 -323
- package/dist/tailwind.js +0 -553
- package/dist/tailwind.js.map +0 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DocRange,
|
|
3
|
+
OpaqueFragmentRecord,
|
|
4
|
+
PreservationStore,
|
|
5
|
+
PreservedPackagePart,
|
|
6
|
+
} from "../model/canonical-document.ts";
|
|
7
|
+
|
|
8
|
+
export interface OpaqueFragmentDescriptor {
|
|
9
|
+
featureKey:
|
|
10
|
+
| "sections"
|
|
11
|
+
| "tables"
|
|
12
|
+
| "headers-footers"
|
|
13
|
+
| "fields"
|
|
14
|
+
| "content-controls"
|
|
15
|
+
| "custom-xml"
|
|
16
|
+
| "alt-chunk"
|
|
17
|
+
| "embedded-objects"
|
|
18
|
+
| "alternate-content"
|
|
19
|
+
| "unknown-ooxml";
|
|
20
|
+
label: string;
|
|
21
|
+
detail: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createPreservationStore(
|
|
25
|
+
seed?: Partial<PreservationStore>,
|
|
26
|
+
): PreservationStore {
|
|
27
|
+
return {
|
|
28
|
+
opaqueFragments: { ...(seed?.opaqueFragments ?? {}) },
|
|
29
|
+
packageParts: { ...(seed?.packageParts ?? {}) },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getOpaqueFragment(
|
|
34
|
+
store: PreservationStore,
|
|
35
|
+
fragmentId: string,
|
|
36
|
+
): OpaqueFragmentRecord | undefined {
|
|
37
|
+
return normalizeOpaqueFragmentMap(store)[fragmentId];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function listOpaqueFragments(
|
|
41
|
+
store: PreservationStore,
|
|
42
|
+
): OpaqueFragmentRecord[] {
|
|
43
|
+
return Object.values(normalizeOpaqueFragmentMap(store)).sort((left, right) => {
|
|
44
|
+
return (
|
|
45
|
+
left.lastKnownRange.from - right.lastKnownRange.from ||
|
|
46
|
+
left.fragmentId.localeCompare(right.fragmentId)
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function listPreservedPackageParts(
|
|
52
|
+
store: PreservationStore,
|
|
53
|
+
): PreservedPackagePart[] {
|
|
54
|
+
return Object.values(normalizePackagePartMap(store)).sort((left, right) =>
|
|
55
|
+
left.packagePartName.localeCompare(right.packagePartName),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function findOpaqueFragmentsIntersectingRange(
|
|
60
|
+
store: PreservationStore,
|
|
61
|
+
range: DocRange,
|
|
62
|
+
): OpaqueFragmentRecord[] {
|
|
63
|
+
return listOpaqueFragments(store).filter((fragment) =>
|
|
64
|
+
rangesIntersect(fragment.lastKnownRange, range),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function describeOpaqueFragment(
|
|
69
|
+
fragment: OpaqueFragmentRecord,
|
|
70
|
+
): OpaqueFragmentDescriptor {
|
|
71
|
+
const xml = fragment.payloadReference;
|
|
72
|
+
const detail = createDetail(fragment);
|
|
73
|
+
|
|
74
|
+
if (/\b(?:w:)?sectPr\b/u.test(xml)) {
|
|
75
|
+
return {
|
|
76
|
+
featureKey: "sections",
|
|
77
|
+
label: "Section properties",
|
|
78
|
+
detail,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (/\b(?:w:)?tbl\b/u.test(xml)) {
|
|
83
|
+
return {
|
|
84
|
+
featureKey: "tables",
|
|
85
|
+
label: "Preserved table structure",
|
|
86
|
+
detail,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (/\b(?:w:)?hdr\b|\b(?:w:)?ftr\b/u.test(xml)) {
|
|
91
|
+
return {
|
|
92
|
+
featureKey: "headers-footers",
|
|
93
|
+
label: "Header or footer content",
|
|
94
|
+
detail,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (/\b(?:w:)?fldSimple\b|\b(?:w:)?fldChar\b|\b(?:w:)?instrText\b/u.test(xml)) {
|
|
99
|
+
return {
|
|
100
|
+
featureKey: "fields",
|
|
101
|
+
label: "Word field content",
|
|
102
|
+
detail,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (/\b(?:w:)?sdt\b/u.test(xml)) {
|
|
107
|
+
return {
|
|
108
|
+
featureKey: "content-controls",
|
|
109
|
+
label: "Content control",
|
|
110
|
+
detail,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (/\b(?:w:)?customXml\b/u.test(xml)) {
|
|
115
|
+
return {
|
|
116
|
+
featureKey: "custom-xml",
|
|
117
|
+
label: "Custom XML wrapper",
|
|
118
|
+
detail,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (/\b(?:w:)?altChunk\b/u.test(xml)) {
|
|
123
|
+
return {
|
|
124
|
+
featureKey: "alt-chunk",
|
|
125
|
+
label: "Alternate content import",
|
|
126
|
+
detail,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (/\b(?:w:)?object\b|\b(?:o:)?OLEObject\b/u.test(xml)) {
|
|
131
|
+
return {
|
|
132
|
+
featureKey: "embedded-objects",
|
|
133
|
+
label: "Embedded object",
|
|
134
|
+
detail,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (/\b(?:mc:)?AlternateContent\b/u.test(xml)) {
|
|
139
|
+
return {
|
|
140
|
+
featureKey: "alternate-content",
|
|
141
|
+
label: "Markup compatibility block",
|
|
142
|
+
detail,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
featureKey: "unknown-ooxml",
|
|
148
|
+
label: "Unsupported OOXML fragment",
|
|
149
|
+
detail,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function createDetail(fragment: OpaqueFragmentRecord): string {
|
|
154
|
+
const detail = [
|
|
155
|
+
`Preserved whole-unit from ${fragment.lastKnownRange.from}-${fragment.lastKnownRange.to}.`,
|
|
156
|
+
fragment.packagePartName ? `Part ${fragment.packagePartName}.` : null,
|
|
157
|
+
fragment.relationshipId ? `Relationship ${fragment.relationshipId}.` : null,
|
|
158
|
+
]
|
|
159
|
+
.filter(Boolean)
|
|
160
|
+
.join(" ");
|
|
161
|
+
|
|
162
|
+
return detail.length > 0
|
|
163
|
+
? detail
|
|
164
|
+
: "Preserved whole-unit to keep unsupported OOXML intact.";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function rangesIntersect(left: DocRange, right: DocRange): boolean {
|
|
168
|
+
return left.from < right.to && right.from < left.to;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function normalizeOpaqueFragmentMap(
|
|
172
|
+
store: PreservationStore,
|
|
173
|
+
): Record<string, OpaqueFragmentRecord> {
|
|
174
|
+
return store && typeof store === "object" && store.opaqueFragments && typeof store.opaqueFragments === "object"
|
|
175
|
+
? (store.opaqueFragments as Record<string, OpaqueFragmentRecord>)
|
|
176
|
+
: {};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function normalizePackagePartMap(
|
|
180
|
+
store: PreservationStore,
|
|
181
|
+
): Record<string, PreservedPackagePart> {
|
|
182
|
+
return store && typeof store === "object" && store.packageParts && typeof store.packageParts === "object"
|
|
183
|
+
? (store.packageParts as Record<string, PreservedPackagePart>)
|
|
184
|
+
: {};
|
|
185
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Review
|
|
2
|
+
|
|
3
|
+
Comments, anchors, revisions, remap helpers, and accept/reject behavior belong here.
|
|
4
|
+
|
|
5
|
+
Frozen Wave 1 contract:
|
|
6
|
+
|
|
7
|
+
- comments and revisions live in the canonical `review` store, not in the content tree
|
|
8
|
+
- review anchors use canonical position ranges or detached-anchor payloads, never DOM paths
|
|
9
|
+
- all review mutations flow through runtime commands and transactions
|
|
10
|
+
- detached comments or revisions remain addressable with reason metadata; they are not silently dropped
|
|
11
|
+
- v1 authoring is limited to single-paragraph comments, thread replies, tracked insertions, tracked deletions, and accept/reject flows
|
|
12
|
+
- preserve-only review structures such as multi-paragraph editable comment ranges, tracked moves, and structural list/table revisions stay locked and warning-backed
|
|
13
|
+
|
|
14
|
+
Key subdirectories:
|
|
15
|
+
|
|
16
|
+
- `store/`
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNodeAnchor,
|
|
3
|
+
createRangeAnchor,
|
|
4
|
+
mapAnchor,
|
|
5
|
+
normalizeRange,
|
|
6
|
+
type Assoc,
|
|
7
|
+
type BoundaryAssoc,
|
|
8
|
+
type DocRange,
|
|
9
|
+
type EditorAnchorProjection,
|
|
10
|
+
type TransactionMapping,
|
|
11
|
+
} from "../../core/selection/mapping.ts";
|
|
12
|
+
|
|
13
|
+
export type CommentAnchor = EditorAnchorProjection;
|
|
14
|
+
export type CommentAnchorState = "active" | "detached";
|
|
15
|
+
|
|
16
|
+
export interface CommentAnchorSummary {
|
|
17
|
+
anchor: CommentAnchor;
|
|
18
|
+
state: CommentAnchorState;
|
|
19
|
+
range: DocRange;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createCommentRangeAnchor(
|
|
23
|
+
from: number,
|
|
24
|
+
to = from,
|
|
25
|
+
assoc?: BoundaryAssoc,
|
|
26
|
+
): CommentAnchor {
|
|
27
|
+
return createRangeAnchor(from, to, assoc);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createCommentNodeAnchor(at: number, assoc?: Assoc): CommentAnchor {
|
|
31
|
+
return createNodeAnchor(at, assoc);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function remapCommentAnchor(
|
|
35
|
+
anchor: CommentAnchor,
|
|
36
|
+
mapping: TransactionMapping,
|
|
37
|
+
): CommentAnchor {
|
|
38
|
+
return mapAnchor(anchor, mapping);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isDetachedCommentAnchor(anchor: CommentAnchor): boolean {
|
|
42
|
+
return anchor.kind === "detached";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function summarizeCommentAnchor(anchor: CommentAnchor): CommentAnchorSummary {
|
|
46
|
+
if (anchor.kind === "range") {
|
|
47
|
+
return {
|
|
48
|
+
anchor,
|
|
49
|
+
state: "active",
|
|
50
|
+
range: normalizeRange(anchor.range),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (anchor.kind === "node") {
|
|
55
|
+
return {
|
|
56
|
+
anchor,
|
|
57
|
+
state: "active",
|
|
58
|
+
range: {
|
|
59
|
+
from: anchor.at,
|
|
60
|
+
to: anchor.at,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
anchor,
|
|
67
|
+
state: "detached",
|
|
68
|
+
range: normalizeRange(anchor.lastKnownRange),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { CommentThreadRecord, EditorWarning } from "../../core/state/editor-state.ts";
|
|
2
|
+
import {
|
|
3
|
+
detachReviewAnchor,
|
|
4
|
+
getAnchorRange,
|
|
5
|
+
mapReviewAnchor,
|
|
6
|
+
mappingTouchesAnchorContent,
|
|
7
|
+
rangeStaysWithinSingleParagraph,
|
|
8
|
+
type ReviewAnchor,
|
|
9
|
+
} from "../../core/selection/review-anchors.ts";
|
|
10
|
+
import type { TransactionMapping } from "../../core/selection/mapping.ts";
|
|
11
|
+
|
|
12
|
+
export interface RemapCommentThreadsOptions {
|
|
13
|
+
comments: Record<string, CommentThreadRecord>;
|
|
14
|
+
mapping: TransactionMapping;
|
|
15
|
+
nextContent: unknown;
|
|
16
|
+
existingWarnings?: EditorWarning[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RemapCommentThreadsResult {
|
|
20
|
+
comments: Record<string, CommentThreadRecord>;
|
|
21
|
+
warnings: EditorWarning[];
|
|
22
|
+
detachedCommentIds: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function remapCommentThreads(
|
|
26
|
+
options: RemapCommentThreadsOptions,
|
|
27
|
+
): RemapCommentThreadsResult {
|
|
28
|
+
const comments = Object.fromEntries(
|
|
29
|
+
Object.entries(options.comments).map(([commentId, comment]) => [
|
|
30
|
+
commentId,
|
|
31
|
+
remapCommentThread(comment, options.mapping, options.nextContent),
|
|
32
|
+
]),
|
|
33
|
+
);
|
|
34
|
+
const detachedCommentIds = Object.values(comments)
|
|
35
|
+
.filter((comment) => comment.anchor.kind === "detached")
|
|
36
|
+
.map((comment) => comment.commentId);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
comments,
|
|
40
|
+
warnings: mergeDetachedAnchorWarnings(comments, options.existingWarnings ?? []),
|
|
41
|
+
detachedCommentIds,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function remapCommentThread(
|
|
46
|
+
comment: CommentThreadRecord,
|
|
47
|
+
mapping: TransactionMapping,
|
|
48
|
+
nextContent: unknown,
|
|
49
|
+
): CommentThreadRecord {
|
|
50
|
+
if (comment.anchor.kind === "detached") {
|
|
51
|
+
return comment;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const mappedAnchor = mapReviewAnchor(comment.anchor, mapping);
|
|
55
|
+
const anchor = normalizeCommentAnchor(comment.anchor, mappedAnchor, mapping, nextContent);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
...comment,
|
|
59
|
+
anchor,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeCommentAnchor(
|
|
64
|
+
previousAnchor: ReviewAnchor,
|
|
65
|
+
mappedAnchor: ReviewAnchor,
|
|
66
|
+
mapping: TransactionMapping,
|
|
67
|
+
nextContent: unknown,
|
|
68
|
+
): ReviewAnchor {
|
|
69
|
+
if (mappedAnchor.kind === "detached") {
|
|
70
|
+
return mappedAnchor;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const previousRange = getAnchorRange(previousAnchor);
|
|
74
|
+
const mappedRange = getAnchorRange(mappedAnchor);
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
previousAnchor.kind === "range" &&
|
|
78
|
+
previousRange.from < previousRange.to &&
|
|
79
|
+
mappedAnchor.kind === "range" &&
|
|
80
|
+
mappedRange.from === mappedRange.to &&
|
|
81
|
+
mappingTouchesAnchorContent(previousAnchor, mapping)
|
|
82
|
+
) {
|
|
83
|
+
return detachReviewAnchor(previousRange, detachReason(mapping));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
mappedAnchor.kind === "range" &&
|
|
88
|
+
!rangeStaysWithinSingleParagraph(nextContent, mappedAnchor.range)
|
|
89
|
+
) {
|
|
90
|
+
return detachReviewAnchor(previousRange, "invalidatedByStructureChange");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return mappedAnchor;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function detachReason(
|
|
97
|
+
mapping: TransactionMapping,
|
|
98
|
+
): "deleted" | "invalidatedByStructureChange" {
|
|
99
|
+
return mapping.metadata?.invalidatesStructures
|
|
100
|
+
? "invalidatedByStructureChange"
|
|
101
|
+
: "deleted";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function mergeDetachedAnchorWarnings(
|
|
105
|
+
comments: Record<string, CommentThreadRecord>,
|
|
106
|
+
existingWarnings: EditorWarning[],
|
|
107
|
+
): EditorWarning[] {
|
|
108
|
+
const retainedWarnings = existingWarnings.filter(
|
|
109
|
+
(warning) =>
|
|
110
|
+
warning.code !== "comment_anchor_detached" ||
|
|
111
|
+
!warning.details ||
|
|
112
|
+
typeof warning.details.commentId !== "string" ||
|
|
113
|
+
comments[warning.details.commentId]?.anchor.kind === "detached",
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const knownDetachedIds = new Set(
|
|
117
|
+
retainedWarnings
|
|
118
|
+
.filter((warning) => warning.code === "comment_anchor_detached")
|
|
119
|
+
.map((warning) =>
|
|
120
|
+
typeof warning.details?.commentId === "string"
|
|
121
|
+
? warning.details.commentId
|
|
122
|
+
: undefined,
|
|
123
|
+
)
|
|
124
|
+
.filter((value): value is string => Boolean(value)),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const detachedWarnings = Object.values(comments)
|
|
128
|
+
.filter(
|
|
129
|
+
(comment) =>
|
|
130
|
+
comment.anchor.kind === "detached" && !knownDetachedIds.has(comment.commentId),
|
|
131
|
+
)
|
|
132
|
+
.map((comment) => createDetachedCommentWarning(comment));
|
|
133
|
+
|
|
134
|
+
return [...retainedWarnings, ...detachedWarnings];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function createDetachedCommentWarning(
|
|
138
|
+
comment: CommentThreadRecord,
|
|
139
|
+
): EditorWarning {
|
|
140
|
+
const anchor = comment.anchor.kind === "detached" ? comment.anchor : undefined;
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
warningId: `warning:comment-anchor-detached:${comment.commentId}`,
|
|
144
|
+
code: "comment_anchor_detached",
|
|
145
|
+
severity: "warning",
|
|
146
|
+
message: `Comment ${comment.commentId} detached after edit remapping.`,
|
|
147
|
+
source: "review",
|
|
148
|
+
affectedAnchor: anchor,
|
|
149
|
+
details: {
|
|
150
|
+
commentId: comment.commentId,
|
|
151
|
+
reason: anchor?.reason,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|