superdoc 2.4.0 → 2.5.0
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 +12 -90
- package/dist/chunks/{create-super-doc-ui-CqGmoMMA.cjs → create-super-doc-ui-Bom7oDPv.cjs} +194 -29
- package/dist/chunks/{create-super-doc-ui-CmOSA_lN.es.js → create-super-doc-ui-DbzVC0rr.es.js} +194 -29
- package/dist/chunks/{eventemitter3-DEIiXiH2.es.js → eventemitter3-CMOjDs74.es.js} +1 -1
- package/dist/chunks/{eventemitter3-C_TAnXOl.cjs → eventemitter3-D38u8ta_.cjs} +1 -1
- package/dist/chunks/{jszip-D8mAFF-r.cjs → jszip-RC64TPzL.cjs} +1 -1
- package/dist/chunks/{jszip-BzJ3CyxR.es.js → jszip-VxCFV9YS.es.js} +1 -1
- package/dist/chunks/{rolldown-runtime-0pSA04fp.es.js → rolldown-runtime-B5TtN50X.es.js} +1 -1
- package/dist/chunks/{rolldown-runtime-74VwNCz7.cjs → rolldown-runtime-DtsSbkrh.cjs} +1 -1
- package/dist/collaboration-upgrade-engine.cjs +1 -1
- package/dist/collaboration-upgrade-engine.es.js +1 -1
- package/dist/document-api/src/types/track-changes.types.d.ts +17 -0
- package/dist/layout-engine/contracts/src/graphic-placement.d.ts +35 -6
- package/dist/layout-engine/contracts/src/index.d.ts +296 -13
- package/dist/layout-engine/contracts/src/resolved-layout.d.ts +5 -0
- package/dist/layout-engine/dom-contract/src/class-names.d.ts +2 -0
- package/dist/layout-engine/layout-bridge/src/footnote-scorer.d.ts +1 -1
- package/dist/layout-engine/layout-bridge/src/hydrateTableTextboxMeasures.d.ts +9 -0
- package/dist/layout-engine/layout-bridge/src/incrementalLayout.d.ts +40 -12
- package/dist/layout-engine/layout-bridge/src/index.d.ts +3 -2
- package/dist/layout-engine/layout-bridge/src/layoutHeaderFooter.d.ts +10 -7
- package/dist/layout-engine/layout-bridge/src/remeasure.d.ts +2 -2
- package/dist/layout-engine/layout-engine/src/anchors.d.ts +4 -0
- package/dist/layout-engine/layout-engine/src/execution.d.ts +21 -0
- package/dist/layout-engine/layout-engine/src/floating-objects.d.ts +15 -2
- package/dist/layout-engine/layout-engine/src/floating-table-anchor.d.ts +2 -0
- package/dist/layout-engine/layout-engine/src/footnote-anchor-index.d.ts +17 -0
- package/dist/layout-engine/layout-engine/src/index.d.ts +13 -10
- package/dist/layout-engine/layout-engine/src/inline-drawing-alignment.d.ts +9 -0
- package/dist/layout-engine/layout-engine/src/layout-drawing.d.ts +3 -3
- package/dist/layout-engine/layout-engine/src/layout-paragraph.d.ts +2 -2
- package/dist/layout-engine/layout-engine/src/layout-textbox.d.ts +3 -3
- package/dist/layout-engine/layout-engine/src/pageNumbering.d.ts +3 -41
- package/dist/layout-engine/layout-engine/src/paginator.d.ts +7 -2
- package/dist/layout-engine/layout-engine/src/resolvePageTokens.d.ts +2 -0
- package/dist/layout-engine/layout-engine/src/section-breaks.d.ts +1 -0
- package/dist/layout-engine/layout-engine/src/section-props.d.ts +5 -0
- package/dist/layout-engine/painters/dom/src/chart-renderer.d.ts +3 -3
- package/dist/layout-engine/painters/dom/src/images/image-outline.d.ts +7 -0
- package/dist/layout-engine/painters/dom/src/images/render-placeholder.d.ts +12 -0
- package/dist/layout-engine/painters/dom/src/renderer.d.ts +54 -0
- package/dist/layout-engine/painters/dom/src/runs/text-effects.d.ts +30 -0
- package/dist/layout-engine/painters/dom/src/runs/text-run.d.ts +6 -4
- package/dist/layout-engine/painters/dom/src/shapes/preset-text-warp-data.d.ts +32 -0
- package/dist/layout-engine/painters/dom/src/shapes/wordart-warp.d.ts +39 -0
- package/dist/layout-engine/painters/dom/src/table/border-utils.d.ts +20 -27
- package/dist/layout-engine/painters/dom/src/table/renderTableFragment.d.ts +1 -1
- package/dist/layout-engine/style-engine/src/ooxml/word-style-model/parse-theme.d.ts +7 -0
- package/dist/public/ui-react.cjs +1 -1
- package/dist/public/ui-react.es.js +1 -1
- package/dist/public/ui.cjs +1 -1
- package/dist/public/ui.es.js +1 -1
- package/dist/style.css +87 -87
- package/dist/style.layered.css +87 -87
- package/dist/superdoc/src/core/config/normalize-ui-config.d.ts +5 -0
- package/dist/superdoc/src/core/types/index.d.ts +24 -0
- package/dist/superdoc.cjs +88 -79
- package/dist/superdoc.es.js +88 -79
- package/dist-cdn/style.layered.css +1 -1
- package/dist-cdn/superdoc.min.css +1 -1
- package/dist-cdn/superdoc.min.js +37 -37
- package/package.json +2 -6
- package/AGENTS.md +0 -337
package/README.md
CHANGED
|
@@ -1,106 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# superdoc
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Browser editor for opening, editing, and rendering DOCX files.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[](https://www.gnu.org/licenses/agpl-3.0)
|
|
7
|
-
[](https://www.npmjs.com/package/superdoc)
|
|
8
|
-
[](https://codecov.io/gh/superdoc/docx-editor)
|
|
9
|
-
[](https://discord.com/invite/b9UuaZRyaB)
|
|
10
|
-
|
|
11
|
-
SuperDoc renders and edits DOCX files in the browser. Built on OOXML — not bolted onto HTML. As you type, you write directly to the XML. Import a document, edit it, export it. Nothing lost.
|
|
12
|
-
|
|
13
|
-
## Features
|
|
14
|
-
|
|
15
|
-
- **Real DOCX, not rich text** — Built on OOXML. Real pagination, section breaks, headers/footers, complex tables. Not a contenteditable wrapper with export bolted on.
|
|
16
|
-
- **Self-hosted** — Runs entirely in the browser. Your documents never leave your servers.
|
|
17
|
-
- **Any framework** — React, Vue, Angular, Svelte, vanilla JS. One component, zero lock-in.
|
|
18
|
-
- **Real-time collaboration** — Yjs-based CRDT. Multiplayer editing with comments, tracked changes, and automatic conflict resolution.
|
|
19
|
-
- **Built for agents** — [SDK](https://www.npmjs.com/package/@superdoc-dev/sdk), [CLI](https://www.npmjs.com/package/@superdoc-dev/cli), and [MCP server](https://www.npmjs.com/package/@superdoc-dev/mcp) let LLMs read, edit, and save .docx files programmatically.
|
|
20
|
-
- **Dual licensed** — AGPLv3 for community use. [Commercial license](https://www.superdocportal.dev/get-in-touch) for proprietary deployments.
|
|
21
|
-
|
|
22
|
-
## Quick start
|
|
5
|
+
## Install
|
|
23
6
|
|
|
24
7
|
```bash
|
|
25
8
|
npm install superdoc
|
|
26
9
|
```
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
import 'superdoc/style.css';
|
|
30
|
-
import { SuperDoc } from 'superdoc';
|
|
31
|
-
|
|
32
|
-
const superdoc = new SuperDoc({
|
|
33
|
-
selector: '#superdoc',
|
|
34
|
-
documentMode: 'editing',
|
|
35
|
-
documents: [
|
|
36
|
-
{
|
|
37
|
-
id: 'my-doc-id',
|
|
38
|
-
type: 'docx',
|
|
39
|
-
data: fileObject,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
});
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Supported document modes are `editing`, `viewing`, and `suggesting`. Do not use `edit`, `view`, or `suggest`.
|
|
46
|
-
|
|
47
|
-
### Browser Document API contract
|
|
48
|
-
|
|
49
|
-
`superdoc.activeEditor.doc` is the browser-facing v2 Document API facade. In the
|
|
50
|
-
browser it is intentionally async-capable, so custom UI code must tolerate
|
|
51
|
-
promise-returning reads and writes, including the default worker-backed runtime.
|
|
52
|
-
SDK/headless document-automation surfaces remain synchronous; this async
|
|
53
|
-
contract is specific to the browser `superdoc` package.
|
|
54
|
-
|
|
55
|
-
### Vanilla wrapper for a DOCX file
|
|
56
|
-
|
|
57
|
-
When an agent needs to create a small vanilla JS wrapper around a DOCX `File`, use this shape:
|
|
11
|
+
## Quick start
|
|
58
12
|
|
|
59
|
-
```
|
|
13
|
+
```js
|
|
60
14
|
import { SuperDoc } from 'superdoc';
|
|
61
15
|
import 'superdoc/style.css';
|
|
62
16
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
documents: [
|
|
68
|
-
{
|
|
69
|
-
id: 'contract',
|
|
70
|
-
type: 'docx',
|
|
71
|
-
data: file,
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
For React, Vue, and other frameworks, see the [documentation](https://docs.superdoc.dev).
|
|
79
|
-
|
|
80
|
-
### Using an AI coding agent?
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
claude mcp add superdoc -- npx @superdoc-dev/mcp # connect agent to DOCX files
|
|
17
|
+
const editor = new SuperDoc({
|
|
18
|
+
selector: '#editor',
|
|
19
|
+
document: '/document.docx',
|
|
20
|
+
});
|
|
84
21
|
```
|
|
85
22
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
[docs.superdoc.dev](https://docs.superdoc.dev) — installation, integration guides, collaboration setup, API reference, and more.
|
|
89
|
-
|
|
90
|
-
## Contributing
|
|
91
|
-
|
|
92
|
-
Check the [issue tracker](https://github.com/superdoc/docx-editor/issues) for open issues, or read the [Contributing Guide](../../CONTRIBUTING.md) to get started. Bug reports with reproduction .docx files are especially valuable.
|
|
93
|
-
|
|
94
|
-
## Community
|
|
95
|
-
|
|
96
|
-
- [Discord](https://discord.com/invite/b9UuaZRyaB) — Chat with the team and other contributors
|
|
97
|
-
- [Email](mailto:q@superdoc.dev) — Reach the team directly
|
|
23
|
+
See the [editor quick start](https://docs.superdoc.dev/editor/quickstart) for a complete example and the
|
|
24
|
+
[SuperDoc documentation](https://docs.superdoc.dev) for configuration and APIs.
|
|
98
25
|
|
|
99
26
|
## License
|
|
100
27
|
|
|
101
|
-
-
|
|
102
|
-
- Commercial: [Enterprise License](https://www.superdocportal.dev/get-in-touch)
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
Created and maintained by [Harbour](https://www.superdoc.dev) and the SuperDoc community.
|
|
28
|
+
AGPL-3.0. Commercial licenses are available from [SuperDoc](https://www.superdoc.dev).
|
|
@@ -13,6 +13,8 @@ var DOM_CLASS_NAMES = {
|
|
|
13
13
|
PAGE: "superdoc-page",
|
|
14
14
|
/** Fragment container (paragraph, table, image block, etc.). */
|
|
15
15
|
FRAGMENT: "superdoc-fragment",
|
|
16
|
+
/** Non-flowing fragment whose visual position must not define body flow geometry. */
|
|
17
|
+
FLOATING_FRAGMENT: "superdoc-floating-fragment",
|
|
16
18
|
/** Line container within a fragment. */
|
|
17
19
|
LINE: "superdoc-line",
|
|
18
20
|
/**
|
|
@@ -614,7 +616,6 @@ var SUPERDOC_UI_REASONS = {
|
|
|
614
616
|
* No descriptor imports a v1 editor internal or a private v2 runtime package,
|
|
615
617
|
* and no routed command mutates while the document is read-only / viewing.
|
|
616
618
|
*
|
|
617
|
-
* See `docs/architecture/custom-ui-command-matrix.md` for the per-id matrix.
|
|
618
619
|
*/
|
|
619
620
|
/**
|
|
620
621
|
* The 14 canonical v2-native command ids. These are the historically stable
|
|
@@ -1873,6 +1874,23 @@ INLINE_PROPERTY_REGISTRY.filter((entry) => entry.storage === "mark").map((entry)
|
|
|
1873
1874
|
* `false` when the surface is unavailable (view mode, a pre-ready editor, or a
|
|
1874
1875
|
* host that has not exposed a Document API facade) rather than throwing.
|
|
1875
1876
|
*/
|
|
1877
|
+
function uiBenchNowMs() {
|
|
1878
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
1879
|
+
}
|
|
1880
|
+
function readUiBenchRuntime() {
|
|
1881
|
+
const benchGlobal = globalThis;
|
|
1882
|
+
return {
|
|
1883
|
+
sink: typeof benchGlobal.__superdocV2BenchPipelineTiming === "function" ? benchGlobal.__superdocV2BenchPipelineTiming : null,
|
|
1884
|
+
workerContext: benchGlobal.__superdocV2BenchWorkerMessageContext ?? null
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
function emitUiBenchTiming(event) {
|
|
1888
|
+
const { sink } = readUiBenchRuntime();
|
|
1889
|
+
if (!sink) return;
|
|
1890
|
+
try {
|
|
1891
|
+
sink(event);
|
|
1892
|
+
} catch {}
|
|
1893
|
+
}
|
|
1876
1894
|
var sharedUiTrackedChangesCatalogByHost = /* @__PURE__ */ new WeakMap();
|
|
1877
1895
|
function acquireSharedUiTrackedChangesCatalog(host) {
|
|
1878
1896
|
let state = sharedUiTrackedChangesCatalogByHost.get(host);
|
|
@@ -3702,6 +3720,7 @@ function createSuperDocUI(options) {
|
|
|
3702
3720
|
if (options?.invalidateQueuedNavigation !== false) queuedTrackChangeNavigationInvalidation += 1;
|
|
3703
3721
|
};
|
|
3704
3722
|
const listeners = /* @__PURE__ */ new Set();
|
|
3723
|
+
let state;
|
|
3705
3724
|
let disposed = false;
|
|
3706
3725
|
let controllerRef = null;
|
|
3707
3726
|
const asyncReads = /* @__PURE__ */ new Map();
|
|
@@ -3714,12 +3733,13 @@ function createSuperDocUI(options) {
|
|
|
3714
3733
|
let foregroundAsyncRetryTimer = null;
|
|
3715
3734
|
let pendingSelectionSeedValidationToken = null;
|
|
3716
3735
|
let typingContentInvalidationTimer = null;
|
|
3736
|
+
const pendingAsyncReadSettlements = [];
|
|
3717
3737
|
const scheduleAsyncRefresh = () => {
|
|
3718
3738
|
if (asyncRefreshScheduled || disposed) return;
|
|
3719
3739
|
asyncRefreshScheduled = true;
|
|
3720
3740
|
const run = () => {
|
|
3721
3741
|
asyncRefreshScheduled = false;
|
|
3722
|
-
if (!disposed) recompute();
|
|
3742
|
+
if (!disposed) recompute("async-read-settled");
|
|
3723
3743
|
};
|
|
3724
3744
|
if (typeof queueMicrotask === "function") queueMicrotask(run);
|
|
3725
3745
|
else Promise.resolve().then(run);
|
|
@@ -4273,6 +4293,24 @@ function createSuperDocUI(options) {
|
|
|
4273
4293
|
failureCount: 0,
|
|
4274
4294
|
retryAtMs: 0
|
|
4275
4295
|
});
|
|
4296
|
+
const { sink, workerContext } = readUiBenchRuntime();
|
|
4297
|
+
if (sink) {
|
|
4298
|
+
const settledAtMs = uiBenchNowMs();
|
|
4299
|
+
pendingAsyncReadSettlements.push({
|
|
4300
|
+
key,
|
|
4301
|
+
settledAtMs,
|
|
4302
|
+
commandId: workerContext?.commandId ?? null,
|
|
4303
|
+
commandKind: workerContext?.commandKind ?? null
|
|
4304
|
+
});
|
|
4305
|
+
emitUiBenchTiming({
|
|
4306
|
+
stage: "superdoc-ui-async-read-settled",
|
|
4307
|
+
atMs: settledAtMs,
|
|
4308
|
+
key,
|
|
4309
|
+
settledAtMs,
|
|
4310
|
+
commandId: workerContext?.commandId ?? null,
|
|
4311
|
+
commandKind: workerContext?.commandKind ?? null
|
|
4312
|
+
});
|
|
4313
|
+
}
|
|
4276
4314
|
scheduleAsyncRefresh();
|
|
4277
4315
|
}, () => {
|
|
4278
4316
|
const entry = asyncReads.get(key);
|
|
@@ -6533,31 +6571,57 @@ function createSuperDocUI(options) {
|
|
|
6533
6571
|
diagnostics
|
|
6534
6572
|
};
|
|
6535
6573
|
};
|
|
6536
|
-
const computeState = () => {
|
|
6537
|
-
const
|
|
6538
|
-
const
|
|
6574
|
+
const computeState = (reason = "direct") => {
|
|
6575
|
+
const { sink } = readUiBenchRuntime();
|
|
6576
|
+
const startedAtMs = sink ? uiBenchNowMs() : 0;
|
|
6577
|
+
const phaseMs = {};
|
|
6578
|
+
const measure = (phase, run) => {
|
|
6579
|
+
if (!sink) return run();
|
|
6580
|
+
const phaseStartedAtMs = uiBenchNowMs();
|
|
6581
|
+
try {
|
|
6582
|
+
return run();
|
|
6583
|
+
} finally {
|
|
6584
|
+
phaseMs[phase] = uiBenchNowMs() - phaseStartedAtMs;
|
|
6585
|
+
}
|
|
6586
|
+
};
|
|
6587
|
+
const documentMode = measure("documentMode", readDocumentMode);
|
|
6588
|
+
const selection = measure("selection", computeSelection);
|
|
6539
6589
|
if (selection.status === "ready") pruneSelectionScopedAsyncReads(selection);
|
|
6540
6590
|
reconcilePendingInlineFormat(selection);
|
|
6541
6591
|
const selectionSignature = selectionInlineValueSignature(selection);
|
|
6542
6592
|
for (const [commandId, optimistic] of optimisticInlineToggles) if (!selectionSignature || optimistic.selectionSignature !== selectionSignature) optimisticInlineToggles.delete(commandId);
|
|
6543
6593
|
else if (optimistic.settled && selection.status === "ready") optimisticInlineToggles.delete(commandId);
|
|
6544
|
-
|
|
6594
|
+
const nextState = {
|
|
6545
6595
|
ready: getEditor() != null,
|
|
6546
6596
|
documentMode,
|
|
6547
|
-
document: computeDocument
|
|
6597
|
+
document: measure("document", computeDocument),
|
|
6548
6598
|
selection,
|
|
6549
6599
|
toolbar: {
|
|
6550
6600
|
context: documentMode,
|
|
6551
|
-
commands: computeCommandStates(selection),
|
|
6601
|
+
commands: measure("toolbarCommands", () => computeCommandStates(selection)),
|
|
6552
6602
|
copyFormatActive: painter.mode !== "idle"
|
|
6553
6603
|
},
|
|
6554
|
-
comments: computeComments(selection),
|
|
6555
|
-
trackChanges: computeTrackChanges(selection),
|
|
6556
|
-
contentControls: computeContentControls(selection),
|
|
6557
|
-
zoom: computeZoom
|
|
6558
|
-
fonts: computeFonts
|
|
6559
|
-
styles: computeStyles(selection)
|
|
6560
|
-
};
|
|
6604
|
+
comments: measure("comments", () => computeComments(selection)),
|
|
6605
|
+
trackChanges: measure("trackChanges", () => computeTrackChanges(selection)),
|
|
6606
|
+
contentControls: measure("contentControls", () => computeContentControls(selection)),
|
|
6607
|
+
zoom: measure("zoom", computeZoom),
|
|
6608
|
+
fonts: measure("fonts", computeFonts),
|
|
6609
|
+
styles: measure("styles", () => computeStyles(selection))
|
|
6610
|
+
};
|
|
6611
|
+
if (sink) {
|
|
6612
|
+
const completedAtMs = uiBenchNowMs();
|
|
6613
|
+
emitUiBenchTiming({
|
|
6614
|
+
stage: "superdoc-ui-compute-state",
|
|
6615
|
+
atMs: completedAtMs,
|
|
6616
|
+
reason,
|
|
6617
|
+
startedAtMs,
|
|
6618
|
+
completedAtMs,
|
|
6619
|
+
durationMs: completedAtMs - startedAtMs,
|
|
6620
|
+
phaseMs,
|
|
6621
|
+
asyncReadSettlements: pendingAsyncReadSettlements.splice(0)
|
|
6622
|
+
});
|
|
6623
|
+
}
|
|
6624
|
+
return nextState;
|
|
6561
6625
|
};
|
|
6562
6626
|
const syncOptimisticInlineSelection = (selection) => {
|
|
6563
6627
|
const nextSignature = selectionInlineValueSignature(selection);
|
|
@@ -6642,7 +6706,7 @@ function createSuperDocUI(options) {
|
|
|
6642
6706
|
const unsubscribe = next.subscribe((snapshot) => {
|
|
6643
6707
|
selectionEpoch += 1;
|
|
6644
6708
|
seedCaretSelectionFromHost(snapshot);
|
|
6645
|
-
recompute();
|
|
6709
|
+
recompute("host-selection");
|
|
6646
6710
|
});
|
|
6647
6711
|
if (typeof unsubscribe === "function") detachHostSelection = unsubscribe;
|
|
6648
6712
|
} catch {
|
|
@@ -6667,15 +6731,24 @@ function createSuperDocUI(options) {
|
|
|
6667
6731
|
const target = rawTarget && typeof rawTarget === "object" ? rawTarget : null;
|
|
6668
6732
|
let next = null;
|
|
6669
6733
|
if (target?.entityType === "trackedChange" && typeof target.entityId === "string" && target.entityId.length > 0) {
|
|
6670
|
-
const paintedEntityId = target.entityId;
|
|
6671
6734
|
const targetStory = target.story && typeof target.story === "object" ? target.story : void 0;
|
|
6672
|
-
const allStoryItems = readAllStoryTrackChanges();
|
|
6673
|
-
const entityId = (allStoryItems ? targetStory ? buildStoryScopedTrackedChangeIdContext(allStoryItems, targetStory).toPublicId(paintedEntityId) : buildTrackedChangeIdContext(allStoryItems).toPublicId(paintedEntityId) : null) ?? paintedEntityId;
|
|
6674
6735
|
const story = targetStory && storyLocatorSignature(targetStory) !== storyLocatorSignature(null) ? targetStory : void 0;
|
|
6736
|
+
const rawPaintedEntityId = typeof target.paintedEntityId === "string" && target.paintedEntityId.length > 0 ? target.paintedEntityId : null;
|
|
6737
|
+
const lookupEntityId = rawPaintedEntityId ?? target.entityId;
|
|
6738
|
+
const publicId = (() => {
|
|
6739
|
+
if (story) {
|
|
6740
|
+
const allStoryItems = readAllStoryTrackChanges();
|
|
6741
|
+
const storyContext = allStoryItems ? buildStoryScopedTrackedChangeIdContext(allStoryItems, story) : null;
|
|
6742
|
+
return storyContext?.toPublicId(lookupEntityId) ?? storyContext?.toPublicId(target.entityId) ?? target.entityId;
|
|
6743
|
+
}
|
|
6744
|
+
const bodyContext = buildTrackedChangeIdContext(Array.isArray(state?.trackChanges.items) ? state.trackChanges.items : []);
|
|
6745
|
+
return bodyContext.toPublicId(lookupEntityId) ?? bodyContext.toPublicId(target.entityId) ?? target.entityId;
|
|
6746
|
+
})();
|
|
6747
|
+
const paintedEntityId = rawPaintedEntityId ?? (lookupEntityId !== publicId ? lookupEntityId : null);
|
|
6675
6748
|
next = {
|
|
6676
|
-
id:
|
|
6749
|
+
id: publicId,
|
|
6677
6750
|
...story ? { story } : {},
|
|
6678
|
-
...
|
|
6751
|
+
...paintedEntityId && paintedEntityId !== publicId ? { paintedEntityId } : {}
|
|
6679
6752
|
};
|
|
6680
6753
|
}
|
|
6681
6754
|
if (rawTarget == null && next == null && explicitActiveChange != null && hasPendingTrackChangeRevealFocus(explicitActiveChange)) return false;
|
|
@@ -6741,7 +6814,7 @@ function createSuperDocUI(options) {
|
|
|
6741
6814
|
};
|
|
6742
6815
|
syncCoordinatorEditor();
|
|
6743
6816
|
syncTrackedChangeFocusFromHostReviewTarget(readHostActiveReviewTarget(readHostReviewSource()));
|
|
6744
|
-
|
|
6817
|
+
state = computeState("initial");
|
|
6745
6818
|
lastOptimisticInlineSelectionSignature = selectionInlineValueSignature(state.selection);
|
|
6746
6819
|
const syncHostEventsSubscription = () => {
|
|
6747
6820
|
const events = (getEditor()?.host)?.events;
|
|
@@ -6809,8 +6882,10 @@ function createSuperDocUI(options) {
|
|
|
6809
6882
|
currentHostEventsSource = null;
|
|
6810
6883
|
}
|
|
6811
6884
|
};
|
|
6812
|
-
const recompute = () => {
|
|
6885
|
+
const recompute = (reason = "unspecified") => {
|
|
6813
6886
|
if (disposed) return;
|
|
6887
|
+
const { sink } = readUiBenchRuntime();
|
|
6888
|
+
const startedAtMs = sink ? uiBenchNowMs() : 0;
|
|
6814
6889
|
editCommandsRecomputeQueued = false;
|
|
6815
6890
|
syncCoordinatorEditor();
|
|
6816
6891
|
syncHostSelectionSubscription();
|
|
@@ -6819,10 +6894,36 @@ function createSuperDocUI(options) {
|
|
|
6819
6894
|
syncHostReviewSubscription();
|
|
6820
6895
|
syncDocumentSelectionFallbackSubscription();
|
|
6821
6896
|
syncHostEventsSubscription();
|
|
6822
|
-
const nextState = computeState();
|
|
6897
|
+
const nextState = computeState(reason);
|
|
6823
6898
|
syncOptimisticInlineSelection(nextState.selection);
|
|
6824
6899
|
state = nextState;
|
|
6825
|
-
|
|
6900
|
+
let listenerTotalMs = 0;
|
|
6901
|
+
let listenerMaxMs = 0;
|
|
6902
|
+
for (const listener of [...listeners]) {
|
|
6903
|
+
if (!sink) {
|
|
6904
|
+
listener(state);
|
|
6905
|
+
continue;
|
|
6906
|
+
}
|
|
6907
|
+
const listenerStartedAtMs = uiBenchNowMs();
|
|
6908
|
+
listener(state);
|
|
6909
|
+
const listenerMs = uiBenchNowMs() - listenerStartedAtMs;
|
|
6910
|
+
listenerTotalMs += listenerMs;
|
|
6911
|
+
listenerMaxMs = Math.max(listenerMaxMs, listenerMs);
|
|
6912
|
+
}
|
|
6913
|
+
if (sink) {
|
|
6914
|
+
const completedAtMs = uiBenchNowMs();
|
|
6915
|
+
emitUiBenchTiming({
|
|
6916
|
+
stage: "superdoc-ui-recompute",
|
|
6917
|
+
atMs: completedAtMs,
|
|
6918
|
+
reason,
|
|
6919
|
+
startedAtMs,
|
|
6920
|
+
completedAtMs,
|
|
6921
|
+
durationMs: completedAtMs - startedAtMs,
|
|
6922
|
+
listenerCount: listeners.size,
|
|
6923
|
+
listenerTotalMs,
|
|
6924
|
+
listenerMaxMs
|
|
6925
|
+
});
|
|
6926
|
+
}
|
|
6826
6927
|
};
|
|
6827
6928
|
/**
|
|
6828
6929
|
* Work that is bound to one specific active editor and cannot survive a swap.
|
|
@@ -7412,7 +7513,7 @@ function createSuperDocUI(options) {
|
|
|
7412
7513
|
if (options && options.success === false) return options;
|
|
7413
7514
|
if (!selection.selectionTarget) return options ? op(input, options) : op(input);
|
|
7414
7515
|
return op(input, {
|
|
7415
|
-
...options
|
|
7516
|
+
...options,
|
|
7416
7517
|
offsetSpace: "selection"
|
|
7417
7518
|
});
|
|
7418
7519
|
};
|
|
@@ -8743,6 +8844,66 @@ function createSuperDocUI(options) {
|
|
|
8743
8844
|
}
|
|
8744
8845
|
};
|
|
8745
8846
|
/**
|
|
8847
|
+
* Derive the browser-shell's `importedId` carrier-lookup alias from a
|
|
8848
|
+
* public list row's `wordRevisionIds` / `sourceIds` provenance fields,
|
|
8849
|
+
* mirroring `create-v2-tracked-changes-adapter.js`'s own derivation
|
|
8850
|
+
* (`wordRevisionIds.insert ?? .delete ?? .format ?? sourceIds.wordIdInsert
|
|
8851
|
+
* ?? .wordIdDelete ?? .wordIdOther[0]`). A carrier imported from a legacy
|
|
8852
|
+
* Word revision id is painted with THIS id, not the canonical public id —
|
|
8853
|
+
* `focusTrackedChange`'s carrier lookup only tries it when explicitly
|
|
8854
|
+
* passed as `importedId` on an object input (see `scrollTrackChangeIntoView`).
|
|
8855
|
+
*/
|
|
8856
|
+
const deriveTrackedChangeImportedId = (row) => {
|
|
8857
|
+
if (!row || typeof row !== "object") return null;
|
|
8858
|
+
const record = row;
|
|
8859
|
+
const wordRevisionIds = record.wordRevisionIds && typeof record.wordRevisionIds === "object" ? record.wordRevisionIds : null;
|
|
8860
|
+
const sourceIds = record.sourceIds && typeof record.sourceIds === "object" ? record.sourceIds : null;
|
|
8861
|
+
const wordIdOther = Array.isArray(sourceIds?.wordIdOther) ? sourceIds.wordIdOther : void 0;
|
|
8862
|
+
const candidate = wordRevisionIds?.insert ?? wordRevisionIds?.delete ?? wordRevisionIds?.format ?? sourceIds?.wordIdInsert ?? sourceIds?.wordIdDelete ?? wordIdOther?.[0];
|
|
8863
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : null;
|
|
8864
|
+
};
|
|
8865
|
+
/**
|
|
8866
|
+
* Scroll a tracked-change target into view, falling back to the v2
|
|
8867
|
+
* browser-shell's `focusTrackedChange` (the same mechanism the built-in
|
|
8868
|
+
* comments/tracked-changes sidebar uses via `CommentDialog.vue`'s
|
|
8869
|
+
* `setFocus`) when the shared host geometry path can't resolve the target.
|
|
8870
|
+
* The host's `normalizeGeometryTarget` only understands `text`/`replacement`
|
|
8871
|
+
* targets (enriched with painted `segments`) and never `structural`/
|
|
8872
|
+
* `formatting` targets, so those always fail the primary path and need this
|
|
8873
|
+
* fallback.
|
|
8874
|
+
*
|
|
8875
|
+
* `story`, when present, is threaded into the fallback input as
|
|
8876
|
+
* `trackedChangeStory` — a bare id always resolves to the body story in
|
|
8877
|
+
* `focusTrackedChange`'s target resolver, which would collapse a non-body
|
|
8878
|
+
* (footnote/header/footer) occurrence onto its body counterpart sharing the
|
|
8879
|
+
* same raw id. `importedId`, when present, is threaded in too: the carrier
|
|
8880
|
+
* lookup (`buildTrackChangeCarrierLookupIds`) tries it as an ADDITIONAL alias
|
|
8881
|
+
* alongside the canonical id, for rows whose painted carrier only matches
|
|
8882
|
+
* through a legacy-imported Word revision id, not the canonical public id
|
|
8883
|
+
* (`create-v2-tracked-changes-adapter.test.js` pins this alias path as
|
|
8884
|
+
* necessary for some rows).
|
|
8885
|
+
*/
|
|
8886
|
+
const scrollTrackChangeIntoView = async (changeId, target, story, importedId, options) => {
|
|
8887
|
+
const primary = await scrollTargetIntoView(target, options);
|
|
8888
|
+
if (primary.ok) return primary;
|
|
8889
|
+
const trackedChanges = getV2TrackedChanges();
|
|
8890
|
+
const focusTrackedChange = trackedChanges?.focusTrackedChange;
|
|
8891
|
+
if (typeof focusTrackedChange !== "function") return primary;
|
|
8892
|
+
const fallbackInput = story || importedId ? {
|
|
8893
|
+
commentId: changeId,
|
|
8894
|
+
...story ? { trackedChangeStory: story } : {},
|
|
8895
|
+
...importedId ? { importedId } : {}
|
|
8896
|
+
} : changeId;
|
|
8897
|
+
try {
|
|
8898
|
+
const fallbackResult = await Promise.resolve(focusTrackedChange.call(trackedChanges, fallbackInput));
|
|
8899
|
+
if (fallbackResult && typeof fallbackResult === "object" && fallbackResult.ok === true) return {
|
|
8900
|
+
success: true,
|
|
8901
|
+
ok: true
|
|
8902
|
+
};
|
|
8903
|
+
} catch {}
|
|
8904
|
+
return primary;
|
|
8905
|
+
};
|
|
8906
|
+
/**
|
|
8746
8907
|
* Resolve a document entity (comment / tracked change) to its anchor target.
|
|
8747
8908
|
* Prefers the already-loaded list row, falling back to a live `get({ id })`
|
|
8748
8909
|
* read when the list does not carry the row.
|
|
@@ -9004,9 +9165,11 @@ function createSuperDocUI(options) {
|
|
|
9004
9165
|
rollback();
|
|
9005
9166
|
return { success: false };
|
|
9006
9167
|
}
|
|
9168
|
+
const activeRow = trackChangesSub.get().items.find((item) => entityRowMatchesRequest(item, activeId, optimisticActiveChange?.story)) ?? trackChangesSub.get().items.find((item) => readEntityId(item) === activeId);
|
|
9169
|
+
const importedId = deriveTrackedChangeImportedId(activeRow);
|
|
9007
9170
|
trackedChangeNavigationInFlight += 1;
|
|
9008
9171
|
try {
|
|
9009
|
-
const result = await
|
|
9172
|
+
const result = await scrollTrackChangeIntoView(activeId, target, optimisticActiveChange?.story, importedId, {
|
|
9010
9173
|
behavior: "auto",
|
|
9011
9174
|
shouldContinue: isCurrent
|
|
9012
9175
|
});
|
|
@@ -9130,7 +9293,9 @@ function createSuperDocUI(options) {
|
|
|
9130
9293
|
const requestedAtInvalidation = trackChangeRevealInvalidation;
|
|
9131
9294
|
const loadedItems = trackChangesSub.get().items;
|
|
9132
9295
|
const publicId = buildTrackedChangeIdContext(loadedItems).toPublicId(changeId) ?? changeId;
|
|
9133
|
-
const
|
|
9296
|
+
const matchingRow = loadedItems.find((item) => readEntityId(item) === publicId);
|
|
9297
|
+
const story = readEntityRequestStory(matchingRow);
|
|
9298
|
+
const importedId = deriveTrackedChangeImportedId(matchingRow);
|
|
9134
9299
|
const previousActiveChange = explicitActiveChange;
|
|
9135
9300
|
const requestedActiveChange = {
|
|
9136
9301
|
id: publicId,
|
|
@@ -9162,7 +9327,7 @@ function createSuperDocUI(options) {
|
|
|
9162
9327
|
recompute();
|
|
9163
9328
|
trackedChangeNavigationInFlight += 1;
|
|
9164
9329
|
try {
|
|
9165
|
-
const result = await
|
|
9330
|
+
const result = await scrollTrackChangeIntoView(publicId, target, story, importedId, {
|
|
9166
9331
|
behavior: "auto",
|
|
9167
9332
|
shouldContinue: isCurrent
|
|
9168
9333
|
});
|