@beyondwork/docx-react-component 1.0.42 → 1.0.45
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 +17 -0
- package/package.json +5 -4
- package/src/api/editor-state-types.ts +110 -0
- package/src/api/public-types.ts +333 -4
- package/src/core/commands/formatting-commands.ts +7 -1
- package/src/core/commands/index.ts +60 -10
- package/src/core/commands/text-commands.ts +59 -0
- package/src/core/search/search-text.ts +15 -2
- package/src/core/selection/review-anchors.ts +131 -21
- package/src/index.ts +29 -1
- package/src/io/chart-preview-resolver.ts +281 -0
- package/src/io/docx-session.ts +692 -2
- package/src/io/export/build-app-properties-xml.ts +1 -1
- package/src/io/export/serialize-comments.ts +38 -9
- package/src/io/export/twip.ts +1 -1
- package/src/io/load-scheduler.ts +230 -0
- package/src/io/normalize/normalize-text.ts +116 -0
- package/src/io/ooxml/parse-comments.ts +0 -33
- package/src/io/ooxml/parse-complex-content.ts +14 -0
- package/src/io/ooxml/parse-main-document.ts +4 -0
- package/src/io/ooxml/workflow-payload-validator.ts +97 -1
- package/src/io/ooxml/workflow-payload.ts +172 -1
- package/src/preservation/opaque-region.ts +5 -0
- package/src/review/store/comment-remapping.ts +2 -2
- package/src/runtime/collab-session.ts +1 -1
- package/src/runtime/document-runtime.ts +661 -42
- package/src/runtime/edit-dispatch/dispatch-text-command.ts +98 -0
- package/src/runtime/edit-dispatch/index.ts +2 -0
- package/src/runtime/edit-dispatch/list-aware-dispatch.ts +125 -0
- package/src/runtime/editor-state-channel.ts +544 -0
- package/src/runtime/editor-state-integration.ts +217 -0
- package/src/runtime/editor-surface/capabilities.ts +411 -0
- package/src/runtime/layout/index.ts +2 -0
- package/src/runtime/layout/inert-layout-facet.ts +4 -0
- package/src/runtime/layout/layout-engine-instance.ts +63 -2
- package/src/runtime/layout/layout-engine-version.ts +41 -0
- package/src/runtime/layout/paginated-layout-engine.ts +211 -14
- package/src/runtime/layout/public-facet.ts +430 -1
- package/src/runtime/perf-counters.ts +28 -0
- package/src/runtime/prerender/cache-envelope.ts +29 -0
- package/src/runtime/prerender/cache-key.ts +66 -0
- package/src/runtime/prerender/font-fingerprint.ts +17 -0
- package/src/runtime/prerender/graph-canonicalize.ts +121 -0
- package/src/runtime/prerender/indexeddb-cache.ts +184 -0
- package/src/runtime/prerender/prerender-document.ts +145 -0
- package/src/runtime/render/block-fragment-projection.ts +2 -0
- package/src/runtime/render/render-frame-types.ts +17 -0
- package/src/runtime/render/render-kernel.ts +172 -29
- package/src/runtime/selection/post-edit-validator.ts +77 -0
- package/src/runtime/surface-projection.ts +45 -7
- package/src/runtime/workflow-markup.ts +71 -16
- package/src/ui/WordReviewEditor.tsx +142 -237
- package/src/ui/editor-command-bag.ts +14 -0
- package/src/ui/editor-runtime-boundary.ts +115 -12
- package/src/ui/editor-shell-view.tsx +10 -0
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-helpers.ts +10 -0
- package/src/ui/runtime-shortcut-dispatch.ts +28 -68
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +48 -0
- package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +76 -165
- package/src/ui-tailwind/editor-surface/pm-schema.ts +170 -4
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +58 -7
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +8 -255
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +47 -0
- package/src/ui-tailwind/index.ts +5 -1
- package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
- package/src/ui-tailwind/page-stack/use-visible-block-range.ts +157 -0
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
- package/src/ui-tailwind/theme/editor-theme.css +47 -14
- package/src/ui-tailwind/tw-review-workspace.tsx +303 -123
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
insertHardBreak,
|
|
32
32
|
insertTab,
|
|
33
33
|
insertText,
|
|
34
|
+
outdentParagraphAtSelection,
|
|
34
35
|
splitParagraph,
|
|
35
36
|
} from "./text-commands.ts";
|
|
36
37
|
import type { RevisionRecord as CanonicalRevisionRecord } from "../../model/canonical-document.ts";
|
|
@@ -128,6 +129,10 @@ export type EditorCommand =
|
|
|
128
129
|
type: "text.insert-tab";
|
|
129
130
|
origin?: CommandOrigin;
|
|
130
131
|
}
|
|
132
|
+
| {
|
|
133
|
+
type: "text.outdent-tab";
|
|
134
|
+
origin?: CommandOrigin;
|
|
135
|
+
}
|
|
131
136
|
| {
|
|
132
137
|
type: "text.insert-hard-break";
|
|
133
138
|
origin?: CommandOrigin;
|
|
@@ -367,7 +372,7 @@ export interface TransactionEffects {
|
|
|
367
372
|
commentAdded?: { commentId: string; anchor: EditorAnchorProjection };
|
|
368
373
|
commentResolved?: { commentId: string };
|
|
369
374
|
commentReopened?: { commentId: string };
|
|
370
|
-
commentReplyAdded?: { commentId: string };
|
|
375
|
+
commentReplyAdded?: { commentId: string; entryId: string };
|
|
371
376
|
commentBodyEdited?: { commentId: string };
|
|
372
377
|
changeAccepted?: { changeId: string };
|
|
373
378
|
changeRejected?: { changeId: string };
|
|
@@ -513,6 +518,26 @@ export function executeEditorCommand(
|
|
|
513
518
|
insertTab(document, selection, context),
|
|
514
519
|
);
|
|
515
520
|
}
|
|
521
|
+
case "text.outdent-tab": {
|
|
522
|
+
// No suggesting-mode branch: outdent is a paragraph-format change, not a
|
|
523
|
+
// text-content change, so it bypasses the suggesting/track-changes pipeline.
|
|
524
|
+
// (If a tracked-changes test fails, copy the suggesting branch from
|
|
525
|
+
// text.insert-tab.)
|
|
526
|
+
//
|
|
527
|
+
// We use `buildDocumentReplaceTransaction` (not `applyTextCommand`) so the
|
|
528
|
+
// no-op cases — already at zero indent or list paragraph — skip the
|
|
529
|
+
// transaction entirely and do not bump revisionToken. This matches the
|
|
530
|
+
// pattern used by the formatting-indent path.
|
|
531
|
+
const result = outdentParagraphAtSelection(state.document, state.selection, {
|
|
532
|
+
timestamp: context.timestamp,
|
|
533
|
+
});
|
|
534
|
+
return buildDocumentReplaceTransaction(state, context, {
|
|
535
|
+
changed: result.changed,
|
|
536
|
+
document: result.document,
|
|
537
|
+
selection: result.selection,
|
|
538
|
+
mapping: result.mapping,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
516
541
|
case "text.insert-hard-break": {
|
|
517
542
|
const suggestingResult = context.documentMode === "suggesting"
|
|
518
543
|
? applySuggestingInsertUnit(state, "hard_break", context)
|
|
@@ -785,7 +810,7 @@ export function executeEditorCommand(
|
|
|
785
810
|
{
|
|
786
811
|
historyBoundary: "push",
|
|
787
812
|
markDirty: true,
|
|
788
|
-
effects: { commentReplyAdded: { commentId: command.commentId } },
|
|
813
|
+
effects: { commentReplyAdded: { commentId: command.commentId, entryId } },
|
|
789
814
|
},
|
|
790
815
|
);
|
|
791
816
|
}
|
|
@@ -1791,22 +1816,46 @@ function combineMappingSteps(
|
|
|
1791
1816
|
// Suggesting mode: creates revision records instead of (or alongside) text mutations
|
|
1792
1817
|
// ---------------------------------------------------------------------------
|
|
1793
1818
|
|
|
1794
|
-
|
|
1795
|
-
|
|
1819
|
+
/**
|
|
1820
|
+
* Builds a suggesting-mode revision id that is deterministic across
|
|
1821
|
+
* clients. The id is a pure function of `(existing, timestamp, authorId)`
|
|
1822
|
+
* so that origin and replica — both of whom see the same `existing`
|
|
1823
|
+
* revisions record after a Yjs-ordered replay and receive the same
|
|
1824
|
+
* `timestamp` + `authorId` on the broadcast command event — produce
|
|
1825
|
+
* the same id for the same authored revision.
|
|
1826
|
+
*
|
|
1827
|
+
* Previously this used a module-level counter; that counter was shared
|
|
1828
|
+
* across every `DocumentRuntime` in a single process and drifted between
|
|
1829
|
+
* origin and replica whenever either side had other suggesting-mode work
|
|
1830
|
+
* bump the counter before replay. Cross-client `change.accept(changeId)`
|
|
1831
|
+
* / `change.reject(changeId)` then missed because the target id only
|
|
1832
|
+
* existed on one side.
|
|
1833
|
+
*/
|
|
1796
1834
|
function createSuggestingRevisionId(
|
|
1797
1835
|
existing: Record<string, unknown>,
|
|
1798
1836
|
timestamp: string,
|
|
1837
|
+
authorId: string,
|
|
1799
1838
|
): string {
|
|
1800
|
-
suggestingRevisionCounter += 1;
|
|
1801
1839
|
const ts = timestamp.replace(/[^0-9]/gu, "");
|
|
1802
|
-
|
|
1840
|
+
const authorMarker = sanitizeAuthorMarker(authorId);
|
|
1841
|
+
const prefix = `change-${authorMarker}-${ts}-s`;
|
|
1842
|
+
let n = 1;
|
|
1843
|
+
let id = `${prefix}${n}`;
|
|
1803
1844
|
while (existing[id]) {
|
|
1804
|
-
|
|
1805
|
-
id =
|
|
1845
|
+
n += 1;
|
|
1846
|
+
id = `${prefix}${n}`;
|
|
1806
1847
|
}
|
|
1807
1848
|
return id;
|
|
1808
1849
|
}
|
|
1809
1850
|
|
|
1851
|
+
function sanitizeAuthorMarker(authorId: string): string {
|
|
1852
|
+
// OOXML `w:id` accepts alphanumerics plus `._-`; keep within that set so
|
|
1853
|
+
// the id survives round-trips unchanged (see `sanitizeRevisionId` on the
|
|
1854
|
+
// serializer side).
|
|
1855
|
+
const cleaned = authorId.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
1856
|
+
return cleaned.length > 0 ? cleaned : "u";
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1810
1859
|
function createAuthoredRevision(
|
|
1811
1860
|
existing: Record<string, unknown>,
|
|
1812
1861
|
kind: "insertion" | "deletion",
|
|
@@ -1816,7 +1865,7 @@ function createAuthoredRevision(
|
|
|
1816
1865
|
timestamp: string,
|
|
1817
1866
|
metadata: Partial<NonNullable<CanonicalRevisionRecord["metadata"]>> = {},
|
|
1818
1867
|
): CanonicalRevisionRecord {
|
|
1819
|
-
const changeId = createSuggestingRevisionId(existing, timestamp);
|
|
1868
|
+
const changeId = createSuggestingRevisionId(existing, timestamp, authorId);
|
|
1820
1869
|
return {
|
|
1821
1870
|
changeId,
|
|
1822
1871
|
kind,
|
|
@@ -2066,6 +2115,7 @@ function applySuggestingInsert(
|
|
|
2066
2115
|
const replacementSuggestionId = createSuggestingRevisionId(
|
|
2067
2116
|
reviewState.document.review.revisions,
|
|
2068
2117
|
context.timestamp,
|
|
2118
|
+
authorId,
|
|
2069
2119
|
);
|
|
2070
2120
|
|
|
2071
2121
|
// Step 3: Create deletion revision for the selected range (text stays in place).
|
|
@@ -2324,7 +2374,7 @@ function applySuggestingInsertUnit(
|
|
|
2324
2374
|
result.mapping,
|
|
2325
2375
|
);
|
|
2326
2376
|
const replacementSuggestionId = from !== to
|
|
2327
|
-
? createSuggestingRevisionId(reviewState.document.review.revisions, context.timestamp)
|
|
2377
|
+
? createSuggestingRevisionId(reviewState.document.review.revisions, context.timestamp, authorId)
|
|
2328
2378
|
: undefined;
|
|
2329
2379
|
|
|
2330
2380
|
// If non-collapsed, mark selected range as deletion (positions are pre-mapping, content preserved)
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
resolveParagraphScope,
|
|
24
24
|
type StructuralMutationResult,
|
|
25
25
|
} from "./structural-helpers.ts";
|
|
26
|
+
import { applyIndentation } from "./formatting-commands.ts";
|
|
27
|
+
import { createEmptyMapping, type TransactionMapping } from "../selection/mapping.ts";
|
|
26
28
|
import { createEditorSurfaceSnapshot } from "../../runtime/surface-projection.ts";
|
|
27
29
|
|
|
28
30
|
export interface TextCommandContext {
|
|
@@ -197,6 +199,63 @@ export function insertTab(
|
|
|
197
199
|
);
|
|
198
200
|
}
|
|
199
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Reduce the active paragraph's left indent by one half-inch step (720 twips).
|
|
204
|
+
* Mirrors Word's Shift+Tab behavior on a non-list paragraph.
|
|
205
|
+
*
|
|
206
|
+
* Defensive list-check: if the paragraph carries `numbering`, this is a no-op —
|
|
207
|
+
* list-level changes are owned by the list-aware dispatcher in
|
|
208
|
+
* `src/runtime/edit-dispatch/list-aware-dispatch.ts`, which intercepts before
|
|
209
|
+
* we are called. The runtime command itself never mutates list level.
|
|
210
|
+
*
|
|
211
|
+
* No text positions change, so `mapping` is empty and the selection is preserved.
|
|
212
|
+
*/
|
|
213
|
+
export function outdentParagraphAtSelection(
|
|
214
|
+
document: CanonicalDocumentEnvelope,
|
|
215
|
+
selection: SelectionSnapshot,
|
|
216
|
+
context: TextCommandContext,
|
|
217
|
+
): {
|
|
218
|
+
changed: boolean;
|
|
219
|
+
document: CanonicalDocumentEnvelope;
|
|
220
|
+
selection: SelectionSnapshot;
|
|
221
|
+
mapping: TransactionMapping;
|
|
222
|
+
} {
|
|
223
|
+
const noop = {
|
|
224
|
+
changed: false,
|
|
225
|
+
document,
|
|
226
|
+
selection,
|
|
227
|
+
mapping: createEmptyMapping(),
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const scope = resolveParagraphScope(document, selection);
|
|
231
|
+
if (!scope) {
|
|
232
|
+
return noop;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Defensive: list paragraphs are owned by the list-aware dispatcher.
|
|
236
|
+
if (scope.paragraph.numbering) {
|
|
237
|
+
return noop;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// `resolveParagraphScope` already returns a cloned paragraph, so it is
|
|
241
|
+
// safe to mutate in place.
|
|
242
|
+
const changed = applyIndentation(scope.paragraph, -1);
|
|
243
|
+
if (!changed) {
|
|
244
|
+
return noop;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const nextDocument = replaceParagraphScope(document, scope, [scope.paragraph]);
|
|
248
|
+
return {
|
|
249
|
+
changed: true,
|
|
250
|
+
document: {
|
|
251
|
+
...nextDocument,
|
|
252
|
+
updatedAt: context.timestamp,
|
|
253
|
+
},
|
|
254
|
+
selection,
|
|
255
|
+
mapping: createEmptyMapping(),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
200
259
|
export function insertHardBreak(
|
|
201
260
|
document: CanonicalDocumentEnvelope,
|
|
202
261
|
selection: SelectionSnapshot,
|
|
@@ -26,7 +26,7 @@ export interface SecondaryStorySearchResult extends SurfaceSearchResult {
|
|
|
26
26
|
storyTarget: EditorStoryTarget;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
interface ProjectedSurfaceText {
|
|
29
|
+
export interface ProjectedSurfaceText {
|
|
30
30
|
text: string;
|
|
31
31
|
offsetMap: Array<number | null>;
|
|
32
32
|
}
|
|
@@ -112,7 +112,20 @@ export function searchSurfaceBlocks(
|
|
|
112
112
|
query: string,
|
|
113
113
|
options: SearchTextOptions = {},
|
|
114
114
|
): SurfaceSearchResult[] {
|
|
115
|
-
|
|
115
|
+
return searchProjectedSurfaceText(projectSurfaceText(blocks), query, options);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Search a pre-projected surface text. Hoist `projectSurfaceText(blocks)` out
|
|
120
|
+
* of a per-query loop to avoid rebuilding the projection N times for N queries
|
|
121
|
+
* against the same surface — L7 Phase 1.5 discovered this cost dominates
|
|
122
|
+
* `collectFieldMarkup` on the CCEP large-tables fixture.
|
|
123
|
+
*/
|
|
124
|
+
export function searchProjectedSurfaceText(
|
|
125
|
+
projection: ProjectedSurfaceText,
|
|
126
|
+
query: string,
|
|
127
|
+
options: SearchTextOptions = {},
|
|
128
|
+
): SurfaceSearchResult[] {
|
|
116
129
|
return findSearchMatches(projection.text, query, options)
|
|
117
130
|
.map((match) => {
|
|
118
131
|
const range = resolveProjectedRuntimeRange(
|
|
@@ -116,12 +116,61 @@ export function canCreateDocxCommentAnchor(
|
|
|
116
116
|
return false;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
return
|
|
119
|
+
return rangeStaysWithinCommentableStory(content, normalized);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function rangeStaysWithinCommentableStory(
|
|
123
|
+
content: unknown,
|
|
124
|
+
range: DocRange,
|
|
125
|
+
): boolean {
|
|
126
|
+
const normalized = normalizeRange(range);
|
|
127
|
+
if (normalized.from === normalized.to) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const surfaceBlocks = readSurfaceBlocks(content);
|
|
132
|
+
if (surfaceBlocks) {
|
|
133
|
+
const fromOwner = findContainingParagraphForEndpoint(surfaceBlocks, normalized.from, "start");
|
|
134
|
+
const toOwner = findContainingParagraphForEndpoint(surfaceBlocks, normalized.to, "end");
|
|
135
|
+
if (!fromOwner || !toOwner) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (fromOwner.tableId !== toOwner.tableId) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (fromOwner.tableCellId !== toOwner.tableCellId) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return !rangeCrossesOpaqueOrTableBoundary(
|
|
145
|
+
surfaceBlocks,
|
|
146
|
+
normalized,
|
|
147
|
+
fromOwner,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const story = parseTextStory(content);
|
|
152
|
+
const upperBound = Math.min(normalized.to, story.units.length);
|
|
153
|
+
for (let index = Math.max(0, normalized.from); index < upperBound; index += 1) {
|
|
154
|
+
const unit = story.units[index];
|
|
155
|
+
if (!unit) continue;
|
|
156
|
+
if (unit.kind === "opaque_block") {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
interface FlattenedSurfaceBlock {
|
|
164
|
+
kind: string;
|
|
165
|
+
from: number;
|
|
166
|
+
to: number;
|
|
167
|
+
tableId: string | null;
|
|
168
|
+
tableCellId: string | null;
|
|
120
169
|
}
|
|
121
170
|
|
|
122
171
|
function readSurfaceBlocks(
|
|
123
172
|
content: unknown,
|
|
124
|
-
):
|
|
173
|
+
): FlattenedSurfaceBlock[] | undefined {
|
|
125
174
|
if (!content || typeof content !== "object" || !("blocks" in content)) {
|
|
126
175
|
return undefined;
|
|
127
176
|
}
|
|
@@ -131,17 +180,19 @@ function readSurfaceBlocks(
|
|
|
131
180
|
return undefined;
|
|
132
181
|
}
|
|
133
182
|
|
|
134
|
-
const normalized = flattenSurfaceBlocks(blocks);
|
|
183
|
+
const normalized = flattenSurfaceBlocks(blocks, null, null);
|
|
135
184
|
|
|
136
185
|
return normalized.length > 0 ? normalized : undefined;
|
|
137
186
|
}
|
|
138
187
|
|
|
139
188
|
function flattenSurfaceBlocks(
|
|
140
189
|
blocks: unknown[],
|
|
141
|
-
|
|
142
|
-
|
|
190
|
+
tableId: string | null,
|
|
191
|
+
tableCellId: string | null,
|
|
192
|
+
): FlattenedSurfaceBlock[] {
|
|
193
|
+
const flattened: FlattenedSurfaceBlock[] = [];
|
|
143
194
|
|
|
144
|
-
for (const block of blocks) {
|
|
195
|
+
for (const [blockIndex, block] of blocks.entries()) {
|
|
145
196
|
if (
|
|
146
197
|
!block ||
|
|
147
198
|
typeof block !== "object" ||
|
|
@@ -152,32 +203,91 @@ function flattenSurfaceBlocks(
|
|
|
152
203
|
continue;
|
|
153
204
|
}
|
|
154
205
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
to: (block as { to: number }).to,
|
|
159
|
-
});
|
|
206
|
+
const kind = (block as { kind: string }).kind;
|
|
207
|
+
const from = (block as { from: number }).from;
|
|
208
|
+
const to = (block as { to: number }).to;
|
|
160
209
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
210
|
+
flattened.push({ kind, from, to, tableId, tableCellId });
|
|
211
|
+
|
|
212
|
+
if (kind === "table" && Array.isArray((block as { rows?: unknown }).rows)) {
|
|
213
|
+
const nextTableId =
|
|
214
|
+
(block as { blockId?: string }).blockId ?? `table-${from}-${to}-${blockIndex}`;
|
|
215
|
+
const rows = (block as { rows: Array<{ cells?: unknown[] }> }).rows;
|
|
216
|
+
for (const [rowIdx, row] of rows.entries()) {
|
|
217
|
+
for (const [cellIdx, cell] of (row.cells ?? []).entries()) {
|
|
167
218
|
if (cell && typeof cell === "object" && Array.isArray((cell as { content?: unknown[] }).content)) {
|
|
168
|
-
|
|
219
|
+
const cellFingerprint = `${nextTableId}-r${rowIdx}-c${cellIdx}`;
|
|
220
|
+
flattened.push(
|
|
221
|
+
...flattenSurfaceBlocks(
|
|
222
|
+
(cell as { content: unknown[] }).content,
|
|
223
|
+
nextTableId,
|
|
224
|
+
cellFingerprint,
|
|
225
|
+
),
|
|
226
|
+
);
|
|
169
227
|
}
|
|
170
228
|
}
|
|
171
229
|
}
|
|
172
230
|
}
|
|
173
231
|
|
|
174
232
|
if (
|
|
175
|
-
|
|
176
|
-
Array.isArray((block as { children?: unknown
|
|
233
|
+
kind === "sdt_block" &&
|
|
234
|
+
Array.isArray((block as { children?: unknown }).children)
|
|
177
235
|
) {
|
|
178
|
-
flattened.push(
|
|
236
|
+
flattened.push(
|
|
237
|
+
...flattenSurfaceBlocks(
|
|
238
|
+
(block as { children: unknown[] }).children,
|
|
239
|
+
tableId,
|
|
240
|
+
tableCellId,
|
|
241
|
+
),
|
|
242
|
+
);
|
|
179
243
|
}
|
|
180
244
|
}
|
|
181
245
|
|
|
182
246
|
return flattened;
|
|
183
247
|
}
|
|
248
|
+
|
|
249
|
+
function findContainingParagraphForEndpoint(
|
|
250
|
+
blocks: readonly FlattenedSurfaceBlock[],
|
|
251
|
+
offset: number,
|
|
252
|
+
kind: "start" | "end",
|
|
253
|
+
): FlattenedSurfaceBlock | null {
|
|
254
|
+
const matches = blocks.filter(
|
|
255
|
+
(block) =>
|
|
256
|
+
block.kind === "paragraph" && offset >= block.from && offset <= block.to,
|
|
257
|
+
);
|
|
258
|
+
if (matches.length === 0) return null;
|
|
259
|
+
if (matches.length === 1) return matches[0]!;
|
|
260
|
+
// When an offset sits exactly on a paragraph boundary it matches the
|
|
261
|
+
// trailing paragraph as well as the leading one. For a start endpoint we
|
|
262
|
+
// prefer the later paragraph (the range extends forward from it); for an
|
|
263
|
+
// end endpoint we prefer the earlier paragraph (the range ends there).
|
|
264
|
+
if (kind === "start") {
|
|
265
|
+
return matches.reduce((a, b) => (b.from >= a.from ? b : a));
|
|
266
|
+
}
|
|
267
|
+
return matches.reduce((a, b) => (b.to <= a.to ? b : a));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function rangeCrossesOpaqueOrTableBoundary(
|
|
271
|
+
blocks: readonly FlattenedSurfaceBlock[],
|
|
272
|
+
range: DocRange,
|
|
273
|
+
origin: FlattenedSurfaceBlock,
|
|
274
|
+
): boolean {
|
|
275
|
+
for (const block of blocks) {
|
|
276
|
+
const overlapFrom = Math.max(block.from, range.from);
|
|
277
|
+
const overlapTo = Math.min(block.to, range.to);
|
|
278
|
+
if (overlapTo <= overlapFrom) continue;
|
|
279
|
+
|
|
280
|
+
if (block.kind === "opaque_block") {
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (
|
|
285
|
+
block.kind === "paragraph" &&
|
|
286
|
+
(block.tableId !== origin.tableId ||
|
|
287
|
+
block.tableCellId !== origin.tableCellId)
|
|
288
|
+
) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { ISSUE_METADATA_ID, REVIEW_ACTION_METADATA_ID } from "./api/public-types
|
|
|
13
13
|
// P17 — metadata persistence error class.
|
|
14
14
|
export { MetadataResolverMissingError } from "./api/public-types.ts";
|
|
15
15
|
|
|
16
|
-
// Collab substrate (P1 – P8f + P14). See docs/plans/collab-
|
|
16
|
+
// Collab substrate (P1 – P8f + P14). See docs/plans/lane-4-collab-clm-vallor.md
|
|
17
17
|
// for the shipped-slice table. Surfaces are stable for host integration;
|
|
18
18
|
// the chrome preset + markdown renderer land in P9 / P10.
|
|
19
19
|
export { createCollabSession } from "./runtime/collab-session.ts";
|
|
@@ -121,6 +121,7 @@ export type {
|
|
|
121
121
|
LoadSourcePolicy,
|
|
122
122
|
EditorSessionState,
|
|
123
123
|
EditorHostAdapter,
|
|
124
|
+
ChartPreviewResolveParams,
|
|
124
125
|
WordReviewEditorProps,
|
|
125
126
|
WordReviewEditorChromePreset,
|
|
126
127
|
WordReviewEditorChromeOptions,
|
|
@@ -185,6 +186,8 @@ export type {
|
|
|
185
186
|
SnapshotRefreshChangeKind,
|
|
186
187
|
SnapshotRefreshHints,
|
|
187
188
|
AddCommentParams,
|
|
189
|
+
AddCommentResult,
|
|
190
|
+
AddCommentReplyResult,
|
|
188
191
|
ExportDocxOptions,
|
|
189
192
|
ExportResult,
|
|
190
193
|
AutosaveConfig,
|
|
@@ -267,4 +270,29 @@ export type {
|
|
|
267
270
|
ScopeMetadataPersistence,
|
|
268
271
|
ScopeMetadataResolver,
|
|
269
272
|
ScopeMetadataStorageRef,
|
|
273
|
+
// Schema 1.2 — editor-state persistence types
|
|
274
|
+
EditorStateNamespace,
|
|
275
|
+
EditorStateLocation,
|
|
276
|
+
EditorStateResolveErrorMode,
|
|
277
|
+
EditorStatePolicyEntry,
|
|
278
|
+
EditorStatePolicy,
|
|
279
|
+
EditorStateStorageRef,
|
|
280
|
+
EditorStateBlob,
|
|
281
|
+
EditorStateResolver,
|
|
282
|
+
EditorStatePersister,
|
|
283
|
+
EditorStatePolicyMigration,
|
|
284
|
+
EditorStatePartLoadFailure,
|
|
285
|
+
EditorStatePartPersistFailure,
|
|
270
286
|
} from "./api/public-types.ts";
|
|
287
|
+
|
|
288
|
+
// L7 Phase 2.5 — prerender cache public API. Platforms / ingest workers
|
|
289
|
+
// call `prerenderDocument(buffer)` on template upload to populate the
|
|
290
|
+
// cache; end-user opens read the cache and skip the layout pass. See
|
|
291
|
+
// docs/plans/lane-2-render-performance.md §Phase 2.5.
|
|
292
|
+
export { prerenderDocument } from "./runtime/prerender/prerender-document.ts";
|
|
293
|
+
export type {
|
|
294
|
+
PrerenderOptions,
|
|
295
|
+
PrerenderResult,
|
|
296
|
+
PrerenderCounters,
|
|
297
|
+
} from "./runtime/prerender/prerender-document.ts";
|
|
298
|
+
export type { CacheEnvelope } from "./runtime/prerender/cache-envelope.ts";
|