docxodus 9.1.0 → 9.2.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 +39 -18
- package/dist/editor-reconcile.d.ts.map +1 -1
- package/dist/editor-reconcile.js +33 -5
- package/dist/editor-reconcile.js.map +1 -1
- package/dist/editor.bundle.js +1912 -24
- package/dist/editor.d.ts +52 -3
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +261 -18
- package/dist/editor.js.map +1 -1
- package/dist/embed.bundle.js +1955 -22
- package/dist/embed.d.ts +26 -1
- package/dist/embed.d.ts.map +1 -1
- package/dist/embed.iife.js +1955 -22
- package/dist/embed.js +70 -1
- package/dist/embed.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/ribbon-chrome.d.ts +45 -0
- package/dist/ribbon-chrome.d.ts.map +1 -0
- package/dist/ribbon-chrome.js +965 -0
- package/dist/ribbon-chrome.js.map +1 -0
- package/dist/ribbon.d.ts +153 -0
- package/dist/ribbon.d.ts.map +1 -0
- package/dist/ribbon.js +830 -0
- package/dist/ribbon.js.map +1 -0
- package/dist/wasm/_framework/Docxodus.wasm +0 -0
- package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +3 -3
- package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
- package/package.json +3 -3
package/dist/embed.iife.js
CHANGED
|
@@ -61,6 +61,7 @@ var Docxodus = (() => {
|
|
|
61
61
|
createBlankDocx: () => createBlankDocx,
|
|
62
62
|
createEditor: () => createEditor,
|
|
63
63
|
createExternalAnnotationSet: () => createExternalAnnotationSet,
|
|
64
|
+
createRibbonEditor: () => createRibbonEditor,
|
|
64
65
|
createViewer: () => createViewer,
|
|
65
66
|
docxDiffAcceptRevisions: () => docxDiffAcceptRevisions,
|
|
66
67
|
docxDiffCompare: () => docxDiffCompare,
|
|
@@ -96,6 +97,7 @@ var Docxodus = (() => {
|
|
|
96
97
|
isMove: () => isMove,
|
|
97
98
|
isMoveDestination: () => isMoveDestination,
|
|
98
99
|
isMoveSource: () => isMoveSource,
|
|
100
|
+
mountRibbon: () => mountRibbon,
|
|
99
101
|
openDocxSession: () => openDocxSession2,
|
|
100
102
|
paginateHtml: () => paginateHtml,
|
|
101
103
|
projectAnnotationsOntoHtml: () => projectAnnotationsOntoHtml,
|
|
@@ -3166,6 +3168,28 @@ var Docxodus = (() => {
|
|
|
3166
3168
|
}
|
|
3167
3169
|
while (i < n) removed.push(i++);
|
|
3168
3170
|
while (j < m) added.push(j++);
|
|
3171
|
+
const bareUnid = (token) => {
|
|
3172
|
+
const bar = token.indexOf("|");
|
|
3173
|
+
return bar < 0 ? token : token.slice(0, bar);
|
|
3174
|
+
};
|
|
3175
|
+
const substituted = [];
|
|
3176
|
+
const usedRemoved = /* @__PURE__ */ new Set();
|
|
3177
|
+
const removedByUnid = /* @__PURE__ */ new Map();
|
|
3178
|
+
for (const oi of removed) {
|
|
3179
|
+
const u = bareUnid(oldUnids[oi]);
|
|
3180
|
+
(removedByUnid.get(u) ?? removedByUnid.set(u, []).get(u)).push(oi);
|
|
3181
|
+
}
|
|
3182
|
+
const unmatchedAdded = [];
|
|
3183
|
+
for (const nj of added) {
|
|
3184
|
+
const candidates = removedByUnid.get(bareUnid(newUnids[nj]));
|
|
3185
|
+
const oi = candidates?.find((i2) => !usedRemoved.has(i2));
|
|
3186
|
+
if (oi !== void 0) {
|
|
3187
|
+
substituted.push({ oldIndex: oi, newIndex: nj });
|
|
3188
|
+
usedRemoved.add(oi);
|
|
3189
|
+
} else {
|
|
3190
|
+
unmatchedAdded.push(nj);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3169
3193
|
const keptBeforeOld = (oi) => {
|
|
3170
3194
|
let c = 0;
|
|
3171
3195
|
for (const v of keep.values()) if (v < oi) c++;
|
|
@@ -3176,9 +3200,7 @@ var Docxodus = (() => {
|
|
|
3176
3200
|
for (const k of keep.keys()) if (k < nj) c++;
|
|
3177
3201
|
return c;
|
|
3178
3202
|
};
|
|
3179
|
-
const
|
|
3180
|
-
const usedRemoved = /* @__PURE__ */ new Set();
|
|
3181
|
-
for (const nj of added) {
|
|
3203
|
+
for (const nj of unmatchedAdded) {
|
|
3182
3204
|
const target = keptBeforeNew(nj);
|
|
3183
3205
|
for (const oi of removed) {
|
|
3184
3206
|
if (usedRemoved.has(oi)) continue;
|
|
@@ -3382,6 +3404,33 @@ var Docxodus = (() => {
|
|
|
3382
3404
|
if (lastText) return { node: lastText, offset: (lastText.textContent ?? "").length };
|
|
3383
3405
|
return { node: el, offset: el.childNodes.length };
|
|
3384
3406
|
}
|
|
3407
|
+
function caretPointFromClient(doc, clientX, clientY) {
|
|
3408
|
+
const caretDoc = doc;
|
|
3409
|
+
const position = caretDoc.caretPositionFromPoint?.(clientX, clientY);
|
|
3410
|
+
if (position) return { node: position.offsetNode, offset: position.offset };
|
|
3411
|
+
const range = caretDoc.caretRangeFromPoint?.(clientX, clientY);
|
|
3412
|
+
return range ? { node: range.startContainer, offset: range.startOffset } : null;
|
|
3413
|
+
}
|
|
3414
|
+
function setSelectionBetween(anchor, focus) {
|
|
3415
|
+
const sel = typeof window !== "undefined" ? window.getSelection() : null;
|
|
3416
|
+
if (!sel || !anchor.node.isConnected || !focus.node.isConnected) return false;
|
|
3417
|
+
try {
|
|
3418
|
+
const probe = document.createRange();
|
|
3419
|
+
probe.setStart(anchor.node, anchor.offset);
|
|
3420
|
+
probe.collapse(true);
|
|
3421
|
+
const focusIsBefore = probe.comparePoint(focus.node, focus.offset) < 0;
|
|
3422
|
+
const range = document.createRange();
|
|
3423
|
+
const start = focusIsBefore ? focus : anchor;
|
|
3424
|
+
const end = focusIsBefore ? anchor : focus;
|
|
3425
|
+
range.setStart(start.node, start.offset);
|
|
3426
|
+
range.setEnd(end.node, end.offset);
|
|
3427
|
+
sel.removeAllRanges();
|
|
3428
|
+
sel.addRange(range);
|
|
3429
|
+
return true;
|
|
3430
|
+
} catch {
|
|
3431
|
+
return false;
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3385
3434
|
function placeCaretAtOffset(el, offset) {
|
|
3386
3435
|
const sel = typeof window !== "undefined" ? window.getSelection() : null;
|
|
3387
3436
|
if (!sel) return;
|
|
@@ -3547,6 +3596,15 @@ var Docxodus = (() => {
|
|
|
3547
3596
|
* block, and cleared when a caret is collapsed inside a block (so it never goes stale).
|
|
3548
3597
|
*/
|
|
3549
3598
|
this.lastSelection = null;
|
|
3599
|
+
/**
|
|
3600
|
+
* Stable bookmark for a selection spanning independent editable blocks. Native controls such as
|
|
3601
|
+
* a font-size combobox can take focus and collapse the live DOM selection; block ids + content
|
|
3602
|
+
* offsets let the command restore that same range before applying. This is the multi-block
|
|
3603
|
+
* counterpart to lastSelection above.
|
|
3604
|
+
*/
|
|
3605
|
+
this.lastCrossBlockSelection = null;
|
|
3606
|
+
/** State for the mouse-selection bridge between independent contenteditable block hosts. */
|
|
3607
|
+
this.dragSelection = null;
|
|
3550
3608
|
/** The docked header/footer bands, when `options.headerFooter` is on. */
|
|
3551
3609
|
this.region = null;
|
|
3552
3610
|
/** Why the last reconcile() fell back to a full remount (null = it patched). For
|
|
@@ -3558,24 +3616,128 @@ var Docxodus = (() => {
|
|
|
3558
3616
|
const sel = typeof window !== "undefined" ? window.getSelection() : null;
|
|
3559
3617
|
if (!sel || sel.rangeCount === 0) return;
|
|
3560
3618
|
const range = sel.getRangeAt(0);
|
|
3561
|
-
const
|
|
3619
|
+
const startBlock = this.editableBlockOf(range.startContainer);
|
|
3620
|
+
const endBlock = this.editableBlockOf(range.endContainer);
|
|
3621
|
+
const block = startBlock ?? endBlock;
|
|
3562
3622
|
if (!block) return;
|
|
3563
|
-
const unid = block.getAttribute("data-anchor");
|
|
3564
|
-
if (!unid) return;
|
|
3565
3623
|
if (range.collapsed) {
|
|
3566
3624
|
this.lastSelection = null;
|
|
3625
|
+
this.lastCrossBlockSelection = null;
|
|
3626
|
+
return;
|
|
3627
|
+
}
|
|
3628
|
+
if (startBlock && endBlock && startBlock !== endBlock && this.ownerRoot(startBlock) === this.ownerRoot(endBlock)) {
|
|
3629
|
+
const startUnid = startBlock.getAttribute("data-anchor");
|
|
3630
|
+
const endUnid = endBlock.getAttribute("data-anchor");
|
|
3631
|
+
if (startUnid && endUnid) {
|
|
3632
|
+
this.lastSelection = null;
|
|
3633
|
+
this.lastCrossBlockSelection = {
|
|
3634
|
+
startUnid,
|
|
3635
|
+
startOffset: contentOffsetOf(startBlock, range.startContainer, range.startOffset),
|
|
3636
|
+
endUnid,
|
|
3637
|
+
endOffset: contentOffsetOf(endBlock, range.endContainer, range.endOffset)
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3567
3640
|
return;
|
|
3568
3641
|
}
|
|
3642
|
+
const unid = block.getAttribute("data-anchor");
|
|
3643
|
+
if (!unid) return;
|
|
3644
|
+
this.lastCrossBlockSelection = null;
|
|
3569
3645
|
const span = selectionSpanIn(block);
|
|
3570
3646
|
if (span) this.lastSelection = { unid, span };
|
|
3571
3647
|
};
|
|
3648
|
+
/** Start tracking a normal primary-button text drag inside one editable block. */
|
|
3649
|
+
this.onMouseDown = (event) => {
|
|
3650
|
+
if (this.closed || !this.options.editable || event.button !== 0) return;
|
|
3651
|
+
const target = event.target instanceof Node ? event.target : null;
|
|
3652
|
+
const origin = this.editableBlockOf(target);
|
|
3653
|
+
if (!origin || isInMarker(target)) return;
|
|
3654
|
+
const anchor = caretPointFromClient(document, event.clientX, event.clientY);
|
|
3655
|
+
if (!anchor || this.editableBlockOf(anchor.node) !== origin) return;
|
|
3656
|
+
this.clearDragSelection();
|
|
3657
|
+
this.dragSelection = {
|
|
3658
|
+
anchor,
|
|
3659
|
+
origin,
|
|
3660
|
+
crossedBlockBoundary: false,
|
|
3661
|
+
focus: null,
|
|
3662
|
+
frame: null
|
|
3663
|
+
};
|
|
3664
|
+
};
|
|
3665
|
+
/**
|
|
3666
|
+
* Browsers fence native mouse selection at a contenteditable host boundary. Once a drag reaches
|
|
3667
|
+
* another block in the same OOXML story, take over just that gesture and create the cross-block
|
|
3668
|
+
* Selection the editor's existing multi-block command path consumes. Intra-block selection stays
|
|
3669
|
+
* entirely native, and separate hosts remain intact for safe per-anchor commits.
|
|
3670
|
+
*/
|
|
3671
|
+
this.onMouseMove = (event) => {
|
|
3672
|
+
const drag = this.dragSelection;
|
|
3673
|
+
if (!drag) return;
|
|
3674
|
+
if ((event.buttons & 1) === 0) {
|
|
3675
|
+
this.clearDragSelection();
|
|
3676
|
+
return;
|
|
3677
|
+
}
|
|
3678
|
+
const focus = caretPointFromClient(document, event.clientX, event.clientY);
|
|
3679
|
+
if (!focus || isInMarker(focus.node)) return;
|
|
3680
|
+
const focusBlock = this.editableBlockOf(focus.node);
|
|
3681
|
+
if (!focusBlock || this.ownerRoot(focusBlock) !== this.ownerRoot(drag.origin)) return;
|
|
3682
|
+
if (focusBlock !== drag.origin) drag.crossedBlockBoundary = true;
|
|
3683
|
+
if (!drag.crossedBlockBoundary) return;
|
|
3684
|
+
event.preventDefault();
|
|
3685
|
+
this.queueDragSelection(drag, focus);
|
|
3686
|
+
};
|
|
3687
|
+
/** Commit the final cross-block endpoint before releasing the gesture state. */
|
|
3688
|
+
this.onMouseUp = (event) => {
|
|
3689
|
+
const drag = this.dragSelection;
|
|
3690
|
+
if (!drag) return;
|
|
3691
|
+
if (drag.crossedBlockBoundary) {
|
|
3692
|
+
const focus = caretPointFromClient(document, event.clientX, event.clientY);
|
|
3693
|
+
const focusBlock = focus ? this.editableBlockOf(focus.node) : null;
|
|
3694
|
+
if (focus && focusBlock && this.ownerRoot(focusBlock) === this.ownerRoot(drag.origin)) {
|
|
3695
|
+
event.preventDefault();
|
|
3696
|
+
setSelectionBetween(drag.anchor, focus);
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
this.clearDragSelection();
|
|
3700
|
+
};
|
|
3572
3701
|
this.container = container;
|
|
3573
3702
|
this.exports = exports;
|
|
3574
3703
|
this.handle = handle;
|
|
3575
3704
|
this.options = options;
|
|
3576
3705
|
this.editRoot = container;
|
|
3577
|
-
if (typeof document !== "undefined")
|
|
3706
|
+
if (typeof document !== "undefined") {
|
|
3578
3707
|
document.addEventListener("selectionchange", this.onSelectionChange);
|
|
3708
|
+
document.addEventListener("mousedown", this.onMouseDown, true);
|
|
3709
|
+
document.addEventListener("mousemove", this.onMouseMove, true);
|
|
3710
|
+
document.addEventListener("mouseup", this.onMouseUp, true);
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Apply the latest cross-block endpoint after the browser finishes its native mousemove
|
|
3715
|
+
* selection update. Firefox rewrites Selection back into the originating contenteditable after
|
|
3716
|
+
* event dispatch even when mousemove is cancelled; writing in requestAnimationFrame wins that
|
|
3717
|
+
* race and happens before paint. Coalescing also avoids rebuilding a Range for every raw pointer
|
|
3718
|
+
* event when the mouse is moving faster than the display can refresh.
|
|
3719
|
+
*/
|
|
3720
|
+
queueDragSelection(drag, focus) {
|
|
3721
|
+
drag.focus = focus;
|
|
3722
|
+
if (drag.frame !== null) return;
|
|
3723
|
+
const view = this.container.ownerDocument.defaultView;
|
|
3724
|
+
if (!view) {
|
|
3725
|
+
setSelectionBetween(drag.anchor, focus);
|
|
3726
|
+
return;
|
|
3727
|
+
}
|
|
3728
|
+
drag.frame = view.requestAnimationFrame(() => {
|
|
3729
|
+
drag.frame = null;
|
|
3730
|
+
if (this.dragSelection !== drag || !drag.focus) return;
|
|
3731
|
+
setSelectionBetween(drag.anchor, drag.focus);
|
|
3732
|
+
});
|
|
3733
|
+
}
|
|
3734
|
+
/** Cancel a queued repaint and discard the current gesture. */
|
|
3735
|
+
clearDragSelection() {
|
|
3736
|
+
const drag = this.dragSelection;
|
|
3737
|
+
if (drag && drag.frame !== null) {
|
|
3738
|
+
this.container.ownerDocument.defaultView?.cancelAnimationFrame(drag.frame);
|
|
3739
|
+
}
|
|
3740
|
+
this.dragSelection = null;
|
|
3579
3741
|
}
|
|
3580
3742
|
/** The editable block (contenteditable [data-anchor]) containing `node`, if any, within this editor.
|
|
3581
3743
|
* Fenced by `container`, not `editRoot`, so header/footer band blocks — which live outside the
|
|
@@ -3655,8 +3817,13 @@ var Docxodus = (() => {
|
|
|
3655
3817
|
close() {
|
|
3656
3818
|
if (this.closed) return;
|
|
3657
3819
|
this.closed = true;
|
|
3658
|
-
if (typeof document !== "undefined")
|
|
3820
|
+
if (typeof document !== "undefined") {
|
|
3659
3821
|
document.removeEventListener("selectionchange", this.onSelectionChange);
|
|
3822
|
+
document.removeEventListener("mousedown", this.onMouseDown, true);
|
|
3823
|
+
document.removeEventListener("mousemove", this.onMouseMove, true);
|
|
3824
|
+
document.removeEventListener("mouseup", this.onMouseUp, true);
|
|
3825
|
+
}
|
|
3826
|
+
this.clearDragSelection();
|
|
3660
3827
|
this.exports.DocxSessionBridge.CloseSession(this.handle);
|
|
3661
3828
|
}
|
|
3662
3829
|
/**
|
|
@@ -3674,6 +3841,16 @@ var Docxodus = (() => {
|
|
|
3674
3841
|
get root() {
|
|
3675
3842
|
return this.container;
|
|
3676
3843
|
}
|
|
3844
|
+
/**
|
|
3845
|
+
* The live `DocxSession` handle backing this editor — the model of record.
|
|
3846
|
+
*
|
|
3847
|
+
* Surfaced because chrome around the editor (the anchor rail) reports it as engine
|
|
3848
|
+
* state, and reaching into the private field from a host page only worked because
|
|
3849
|
+
* the bundle erases TypeScript's visibility.
|
|
3850
|
+
*/
|
|
3851
|
+
get sessionHandle() {
|
|
3852
|
+
return this.handle;
|
|
3853
|
+
}
|
|
3677
3854
|
// ─── internals ───────────────────────────────────────────────────────
|
|
3678
3855
|
assertOpen() {
|
|
3679
3856
|
if (this.closed) throw new Error("DocxEditor is closed");
|
|
@@ -3964,8 +4141,7 @@ var Docxodus = (() => {
|
|
|
3964
4141
|
this.options.onEdit?.({ anchorId: second.id, unid: second.unid });
|
|
3965
4142
|
return;
|
|
3966
4143
|
}
|
|
3967
|
-
const firstEl = this.
|
|
3968
|
-
const secondEl = this.renderInto(second.id);
|
|
4144
|
+
const [firstEl, secondEl] = this.renderTwo(first.id, second.id);
|
|
3969
4145
|
if (!firstEl || !secondEl) return;
|
|
3970
4146
|
const inBand = this.isBandBlock(el);
|
|
3971
4147
|
if (!this.replaceNode(el, firstEl, secondEl)) return;
|
|
@@ -4078,6 +4254,34 @@ var Docxodus = (() => {
|
|
|
4078
4254
|
if (html.charCodeAt(0) === 123) return null;
|
|
4079
4255
|
return new DOMParser().parseFromString(html, "text/html").body.firstElementChild;
|
|
4080
4256
|
}
|
|
4257
|
+
/** Render two blocks in ONE batched bridge call when the bundle carries RenderBlocksHtml —
|
|
4258
|
+
* the per-render shell/converter setup is paid once instead of twice, which matters on the
|
|
4259
|
+
* Enter path (split renders both halves synchronously under the keystroke). Falls back to
|
|
4260
|
+
* two per-block renders on older bundles. Output is renderInto-identical per block (both
|
|
4261
|
+
* routes share the same extraction). */
|
|
4262
|
+
renderTwo(a, b) {
|
|
4263
|
+
const bridge = this.exports.DocxSessionBridge;
|
|
4264
|
+
if (typeof bridge.RenderBlocksHtml === "function") {
|
|
4265
|
+
try {
|
|
4266
|
+
const map = JSON.parse(
|
|
4267
|
+
bridge.RenderBlocksHtml(
|
|
4268
|
+
this.handle,
|
|
4269
|
+
JSON.stringify([a, b]),
|
|
4270
|
+
this.options.cssPrefix,
|
|
4271
|
+
this.options.fabricateClasses
|
|
4272
|
+
)
|
|
4273
|
+
);
|
|
4274
|
+
if (!map.error) {
|
|
4275
|
+
const parse = (h) => h ? new DOMParser().parseFromString(h, "text/html").body.firstElementChild : null;
|
|
4276
|
+
const fa = parse(map[a]);
|
|
4277
|
+
const fb = parse(map[b]);
|
|
4278
|
+
if (fa && fb) return [fa, fb];
|
|
4279
|
+
}
|
|
4280
|
+
} catch {
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
return [this.renderInto(a), this.renderInto(b)];
|
|
4284
|
+
}
|
|
4081
4285
|
/** The editable block immediately before `el` within its own root, or null. */
|
|
4082
4286
|
previousEditable(el) {
|
|
4083
4287
|
const all = Array.from(
|
|
@@ -4095,12 +4299,34 @@ var Docxodus = (() => {
|
|
|
4095
4299
|
}
|
|
4096
4300
|
// ─── M5: formatting commands (ribbon) ────────────────────────────────
|
|
4097
4301
|
// ─── Multi-block selection helpers (format a whole stack of paragraphs at once) ──────
|
|
4302
|
+
/**
|
|
4303
|
+
* Restore a cross-block selection after a native toolbar control took focus. The bookmark uses
|
|
4304
|
+
* stable anchor ids and content offsets, so it also survives incremental block swaps.
|
|
4305
|
+
*/
|
|
4306
|
+
restoreCrossBlockSelection() {
|
|
4307
|
+
const bookmark = this.lastCrossBlockSelection;
|
|
4308
|
+
const active = this.activeBlock;
|
|
4309
|
+
if (!bookmark || !active) return false;
|
|
4310
|
+
const all = Array.from(
|
|
4311
|
+
this.ownerRoot(active).querySelectorAll('[data-anchor][contenteditable="true"]')
|
|
4312
|
+
);
|
|
4313
|
+
const first = all.find((block) => block.getAttribute("data-anchor") === bookmark.startUnid);
|
|
4314
|
+
const last = all.find((block) => block.getAttribute("data-anchor") === bookmark.endUnid);
|
|
4315
|
+
if (!first || !last || first === last || all.indexOf(first) > all.indexOf(last)) return false;
|
|
4316
|
+
const start = contentPositionIn(first, bookmark.startOffset);
|
|
4317
|
+
const end = contentPositionIn(last, bookmark.endOffset);
|
|
4318
|
+
return setSelectionBetween(start, end);
|
|
4319
|
+
}
|
|
4098
4320
|
/** Editable blocks the current selection covers, in document order. Uses Range.comparePoint
|
|
4099
4321
|
* (robust to a selection boundary that normalized onto a wrapper element rather than a block
|
|
4100
4322
|
* or text node — Range.intersectsNode misses the end block at a `(block, childCount)` boundary).
|
|
4101
4323
|
* A collapsed or single-block selection yields just the active block. */
|
|
4102
4324
|
selectedBlocks() {
|
|
4103
|
-
|
|
4325
|
+
let sel = typeof window !== "undefined" ? window.getSelection() : null;
|
|
4326
|
+
if ((!sel || sel.rangeCount === 0 || sel.isCollapsed) && this.lastCrossBlockSelection) {
|
|
4327
|
+
this.restoreCrossBlockSelection();
|
|
4328
|
+
sel = typeof window !== "undefined" ? window.getSelection() : null;
|
|
4329
|
+
}
|
|
4104
4330
|
const root = this.activeBlock ? this.ownerRoot(this.activeBlock) : this.editRoot;
|
|
4105
4331
|
const all = Array.from(
|
|
4106
4332
|
root.querySelectorAll('[data-anchor][contenteditable="true"]')
|
|
@@ -4879,7 +5105,8 @@ var Docxodus = (() => {
|
|
|
4879
5105
|
);
|
|
4880
5106
|
if (oldMarker !== newMarker) return this.bail("substituted li marker drift");
|
|
4881
5107
|
}
|
|
4882
|
-
|
|
5108
|
+
const bodyApply = this.applyBodyDiff(oldNodes, plan.body, bodyDiff, freshBody);
|
|
5109
|
+
if (bodyApply !== true) return this.bail(`applyBodyDiff bail: ${bodyApply}`);
|
|
4883
5110
|
this.applyNotesDiff("footnotes", plan.footnotes, fnState, rendered);
|
|
4884
5111
|
this.applyNotesDiff("endnotes", plan.endnotes, enState, rendered);
|
|
4885
5112
|
const freshHasMarker = [...freshBody.values()].some(
|
|
@@ -4911,15 +5138,15 @@ var Docxodus = (() => {
|
|
|
4911
5138
|
static anchorElOf(root) {
|
|
4912
5139
|
return root.hasAttribute("data-anchor") ? root : root.querySelector("[data-anchor]");
|
|
4913
5140
|
}
|
|
4914
|
-
/** Insert/remove/swap body unit nodes per the diff. Returns
|
|
4915
|
-
* ambiguity, order violation, wrapper semantics) — the
|
|
4916
|
-
* so bailing just means a full repaint. */
|
|
5141
|
+
/** Insert/remove/swap body unit nodes per the diff. Returns `true` on success or a
|
|
5142
|
+
* bail-reason string (parent ambiguity, order violation, wrapper semantics) — the
|
|
5143
|
+
* session is already correct, so bailing just means a full repaint. */
|
|
4917
5144
|
applyBodyDiff(oldNodes, units, diff, fresh) {
|
|
4918
5145
|
let lastOld = -1;
|
|
4919
5146
|
for (let j = 0; j < units.length; j++) {
|
|
4920
5147
|
const oi = diff.keep.get(j);
|
|
4921
5148
|
if (oi === void 0) continue;
|
|
4922
|
-
if (oi < lastOld) return
|
|
5149
|
+
if (oi < lastOld) return "kept order violation";
|
|
4923
5150
|
lastOld = oi;
|
|
4924
5151
|
}
|
|
4925
5152
|
const subOldByNew = new Map(diff.substituted.map((s) => [s.newIndex, s.oldIndex]));
|
|
@@ -4930,8 +5157,10 @@ var Docxodus = (() => {
|
|
|
4930
5157
|
oldWrapper.replaceWith(freshRoot);
|
|
4931
5158
|
} else if (oldWrapper === oldNodes[oi]) {
|
|
4932
5159
|
oldNodes[oi].replaceWith(freshRoot);
|
|
5160
|
+
} else if (!oldNodes[oi].hasAttribute("data-render-sig") && unidOf(units[nj].id) === oldNodes[oi].getAttribute("data-anchor")) {
|
|
5161
|
+
oldNodes[oi].replaceWith(freshRoot);
|
|
4933
5162
|
} else {
|
|
4934
|
-
return
|
|
5163
|
+
return `leaf render into wrapped slot (unit ${units[nj].id.slice(0, 24)})`;
|
|
4935
5164
|
}
|
|
4936
5165
|
this.wireUnit(freshRoot, units[nj]);
|
|
4937
5166
|
}
|
|
@@ -4958,10 +5187,11 @@ var Docxodus = (() => {
|
|
|
4958
5187
|
}
|
|
4959
5188
|
const prevW = prev ? this.unitWrapperOf(prev) : null;
|
|
4960
5189
|
const nextW = next ? this.unitWrapperOf(next) : null;
|
|
4961
|
-
if (prevW && nextW && prevW.parentElement !== nextW.parentElement)
|
|
5190
|
+
if (prevW && nextW && prevW.parentElement !== nextW.parentElement)
|
|
5191
|
+
return "insert neighbors in different parents";
|
|
4962
5192
|
if (prevW) prevW.after(el);
|
|
4963
5193
|
else if (nextW) nextW.before(el);
|
|
4964
|
-
else return
|
|
5194
|
+
else return "insert with no anchored neighbor";
|
|
4965
5195
|
this.wireUnit(el, units[j]);
|
|
4966
5196
|
}
|
|
4967
5197
|
for (const i of pureRemoved) {
|
|
@@ -5136,6 +5366,1666 @@ var Docxodus = (() => {
|
|
|
5136
5366
|
}
|
|
5137
5367
|
};
|
|
5138
5368
|
|
|
5369
|
+
// src/ribbon-chrome.ts
|
|
5370
|
+
var RIBBON_STYLE_VERSION = "5";
|
|
5371
|
+
var RIBBON_STYLE_ATTR = "data-docxodus-ribbon-styles";
|
|
5372
|
+
var RIBBON_CSS = `
|
|
5373
|
+
.dxr {
|
|
5374
|
+
--dxr-ink: #101418;
|
|
5375
|
+
--dxr-sheet: #ffffff;
|
|
5376
|
+
--dxr-chrome: #eef1f6;
|
|
5377
|
+
--dxr-chrome-sunk: #e3e8f0;
|
|
5378
|
+
--dxr-rule: #d3dae4;
|
|
5379
|
+
--dxr-accent: #1f5fd0;
|
|
5380
|
+
--dxr-wash: #dde8fb;
|
|
5381
|
+
--dxr-muted: #5d6975;
|
|
5382
|
+
--dxr-data: #0b6f6a;
|
|
5383
|
+
--dxr-danger: #a3341f;
|
|
5384
|
+
--dxr-desk: #dfe3ea;
|
|
5385
|
+
--dxr-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
5386
|
+
--dxr-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;
|
|
5387
|
+
--dxr-tap: 30px;
|
|
5388
|
+
|
|
5389
|
+
position: relative;
|
|
5390
|
+
display: flex;
|
|
5391
|
+
flex-direction: column;
|
|
5392
|
+
height: 100%;
|
|
5393
|
+
min-height: 0;
|
|
5394
|
+
isolation: isolate;
|
|
5395
|
+
font-family: var(--dxr-ui);
|
|
5396
|
+
color: var(--dxr-ink);
|
|
5397
|
+
background: var(--dxr-desk);
|
|
5398
|
+
-webkit-text-size-adjust: 100%;
|
|
5399
|
+
}
|
|
5400
|
+
.dxr *, .dxr *::before, .dxr *::after { box-sizing: border-box; }
|
|
5401
|
+
|
|
5402
|
+
/* Touch devices get bigger hit targets everywhere the token is used. */
|
|
5403
|
+
@media (pointer: coarse) { .dxr { --dxr-tap: 40px; } }
|
|
5404
|
+
|
|
5405
|
+
/* \u2500\u2500 Chrome shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5406
|
+
.dxr-chrome {
|
|
5407
|
+
position: sticky;
|
|
5408
|
+
top: 0;
|
|
5409
|
+
z-index: 10;
|
|
5410
|
+
flex: 0 0 auto;
|
|
5411
|
+
background: var(--dxr-chrome);
|
|
5412
|
+
border-bottom: 1px solid var(--dxr-rule);
|
|
5413
|
+
}
|
|
5414
|
+
|
|
5415
|
+
.dxr-titlebar {
|
|
5416
|
+
display: flex;
|
|
5417
|
+
align-items: center;
|
|
5418
|
+
gap: 10px;
|
|
5419
|
+
padding: 6px 10px 0;
|
|
5420
|
+
}
|
|
5421
|
+
.dxr-brand {
|
|
5422
|
+
display: flex;
|
|
5423
|
+
align-items: baseline;
|
|
5424
|
+
gap: 7px;
|
|
5425
|
+
min-width: 0;
|
|
5426
|
+
font-size: 13px;
|
|
5427
|
+
font-weight: 600;
|
|
5428
|
+
letter-spacing: -.01em;
|
|
5429
|
+
}
|
|
5430
|
+
.dxr-brand .dxr-mark {
|
|
5431
|
+
flex: 0 0 auto;
|
|
5432
|
+
width: 9px;
|
|
5433
|
+
height: 9px;
|
|
5434
|
+
background: var(--dxr-accent);
|
|
5435
|
+
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
|
|
5436
|
+
}
|
|
5437
|
+
.dxr-brand .dxr-docname {
|
|
5438
|
+
overflow: hidden;
|
|
5439
|
+
max-width: 22ch;
|
|
5440
|
+
color: var(--dxr-muted);
|
|
5441
|
+
font-size: 12px;
|
|
5442
|
+
font-weight: 400;
|
|
5443
|
+
text-overflow: ellipsis;
|
|
5444
|
+
white-space: nowrap;
|
|
5445
|
+
}
|
|
5446
|
+
.dxr-quick { flex: 0 0 auto; display: flex; align-items: center; gap: 3px; }
|
|
5447
|
+
.dxr-titlebar .dxr-spacer { flex: 1; }
|
|
5448
|
+
.dxr-status {
|
|
5449
|
+
overflow: hidden;
|
|
5450
|
+
max-width: 42ch;
|
|
5451
|
+
color: var(--dxr-muted);
|
|
5452
|
+
font-family: var(--dxr-mono);
|
|
5453
|
+
font-size: 11.5px;
|
|
5454
|
+
text-overflow: ellipsis;
|
|
5455
|
+
white-space: nowrap;
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5458
|
+
/* \u2500\u2500 Tab strip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5459
|
+
.dxr-tabs {
|
|
5460
|
+
display: flex;
|
|
5461
|
+
gap: 1px;
|
|
5462
|
+
padding: 6px 10px 0;
|
|
5463
|
+
overflow-x: auto;
|
|
5464
|
+
scrollbar-width: none;
|
|
5465
|
+
}
|
|
5466
|
+
.dxr-tabs::-webkit-scrollbar { display: none; }
|
|
5467
|
+
.dxr-tab {
|
|
5468
|
+
flex: 0 0 auto;
|
|
5469
|
+
padding: 6px 15px 7px;
|
|
5470
|
+
border: 1px solid transparent;
|
|
5471
|
+
border-bottom: none;
|
|
5472
|
+
border-radius: 5px 5px 0 0;
|
|
5473
|
+
background: none;
|
|
5474
|
+
color: var(--dxr-muted);
|
|
5475
|
+
font: inherit;
|
|
5476
|
+
font-size: 12.5px;
|
|
5477
|
+
cursor: pointer;
|
|
5478
|
+
}
|
|
5479
|
+
.dxr-tab:hover { color: var(--dxr-ink); background: #e6ebf3; }
|
|
5480
|
+
.dxr-tab[aria-selected="true"] {
|
|
5481
|
+
color: var(--dxr-ink);
|
|
5482
|
+
font-weight: 600;
|
|
5483
|
+
background: var(--dxr-chrome-sunk);
|
|
5484
|
+
border-color: var(--dxr-rule);
|
|
5485
|
+
box-shadow: inset 0 2px 0 var(--dxr-accent);
|
|
5486
|
+
}
|
|
5487
|
+
/* Contextual tab \u2014 present only while the caret is inside a table. */
|
|
5488
|
+
.dxr-tab[data-contextual] { color: var(--dxr-accent); }
|
|
5489
|
+
.dxr-tab[hidden] { display: none; }
|
|
5490
|
+
|
|
5491
|
+
/* \u2500\u2500 Ribbon panels \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5492
|
+
.dxr-ribbon { background: var(--dxr-chrome-sunk); border-top: 1px solid var(--dxr-rule); }
|
|
5493
|
+
.dxr-ribbon[aria-disabled="true"] { opacity: .45; pointer-events: none; }
|
|
5494
|
+
.dxr-panel {
|
|
5495
|
+
display: none;
|
|
5496
|
+
align-items: stretch;
|
|
5497
|
+
padding: 7px 10px 8px;
|
|
5498
|
+
overflow-x: auto;
|
|
5499
|
+
overscroll-behavior-x: contain;
|
|
5500
|
+
}
|
|
5501
|
+
.dxr-panel[data-active] { display: flex; }
|
|
5502
|
+
/* Group label sits ABOVE its controls (a spec-sheet reading), with hairline
|
|
5503
|
+
dividers rather than boxes \u2014 lighter than the boxed, label-under convention. */
|
|
5504
|
+
.dxr-group {
|
|
5505
|
+
flex: 0 0 auto;
|
|
5506
|
+
display: flex;
|
|
5507
|
+
flex-direction: column;
|
|
5508
|
+
gap: 5px;
|
|
5509
|
+
padding: 0 13px;
|
|
5510
|
+
border-right: 1px solid var(--dxr-rule);
|
|
5511
|
+
}
|
|
5512
|
+
.dxr-group:last-child { border-right: none; }
|
|
5513
|
+
.dxr-group:first-child { padding-left: 0; }
|
|
5514
|
+
.dxr-glabel {
|
|
5515
|
+
color: var(--dxr-muted);
|
|
5516
|
+
font-size: 9.5px;
|
|
5517
|
+
font-weight: 600;
|
|
5518
|
+
letter-spacing: .11em;
|
|
5519
|
+
text-transform: uppercase;
|
|
5520
|
+
}
|
|
5521
|
+
.dxr-row { display: flex; gap: 3px; align-items: center; }
|
|
5522
|
+
|
|
5523
|
+
/* \u2500\u2500 Controls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5524
|
+
.dxr button, .dxr label.dxr-btn {
|
|
5525
|
+
min-height: var(--dxr-tap);
|
|
5526
|
+
padding: 5px 9px;
|
|
5527
|
+
border: 1px solid transparent;
|
|
5528
|
+
border-radius: 4px;
|
|
5529
|
+
background: none;
|
|
5530
|
+
color: var(--dxr-ink);
|
|
5531
|
+
font: inherit;
|
|
5532
|
+
font-size: 13px;
|
|
5533
|
+
line-height: 1.15;
|
|
5534
|
+
cursor: pointer;
|
|
5535
|
+
}
|
|
5536
|
+
.dxr label.dxr-btn { display: inline-flex; align-items: center; }
|
|
5537
|
+
.dxr button:hover, .dxr label.dxr-btn:hover { background: #d6deea; }
|
|
5538
|
+
.dxr button:active { background: #c8d3e3; }
|
|
5539
|
+
.dxr button:disabled { opacity: .38; cursor: default; background: none; }
|
|
5540
|
+
.dxr button.dxr-on { color: var(--dxr-accent); background: var(--dxr-wash); border-color: #b3ccf2; }
|
|
5541
|
+
.dxr-quick button, .dxr-quick label.dxr-btn {
|
|
5542
|
+
padding: 4px 10px;
|
|
5543
|
+
border-color: var(--dxr-rule);
|
|
5544
|
+
background: #f7f9fc;
|
|
5545
|
+
font-size: 12.5px;
|
|
5546
|
+
}
|
|
5547
|
+
.dxr-quick button:hover, .dxr-quick label.dxr-btn:hover { background: #fff; border-color: #b9c4d4; }
|
|
5548
|
+
.dxr-quick button:disabled:hover { background: #f7f9fc; border-color: var(--dxr-rule); }
|
|
5549
|
+
.dxr button.dxr-icon { min-width: var(--dxr-tap); text-align: center; }
|
|
5550
|
+
/* Icons are inline SVG drawn from the same shapes the control acts on (text lines,
|
|
5551
|
+
rules), so alignment and indent read at a glance instead of relying on arrow
|
|
5552
|
+
glyphs that collide with undo/redo. currentColor keeps them in step with state. */
|
|
5553
|
+
.dxr button svg { display: block; margin: 0 auto; fill: currentColor; }
|
|
5554
|
+
.dxr button.dxr-wide { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
|
|
5555
|
+
.dxr button.dxr-danger:hover { color: var(--dxr-danger); background: #f6dcd6; }
|
|
5556
|
+
.dxr select, .dxr input[type="number"], .dxr input[type="text"] {
|
|
5557
|
+
min-height: var(--dxr-tap);
|
|
5558
|
+
padding: 4px 6px;
|
|
5559
|
+
border: 1px solid var(--dxr-rule);
|
|
5560
|
+
border-radius: 4px;
|
|
5561
|
+
background: #f7f9fc;
|
|
5562
|
+
color: var(--dxr-ink);
|
|
5563
|
+
font: inherit;
|
|
5564
|
+
font-size: 12.5px;
|
|
5565
|
+
}
|
|
5566
|
+
.dxr select:focus-visible, .dxr input:focus-visible, .dxr button:focus-visible,
|
|
5567
|
+
.dxr .dxr-tab:focus-visible, .dxr label.dxr-btn:focus-within {
|
|
5568
|
+
outline: 2px solid var(--dxr-accent);
|
|
5569
|
+
outline-offset: 1px;
|
|
5570
|
+
}
|
|
5571
|
+
.dxr [data-dxr="fontsize"] { width: 62px; }
|
|
5572
|
+
.dxr [data-dxr="fontfamily"] { max-width: 132px; }
|
|
5573
|
+
.dxr [data-dxr="pgstart"] { width: 64px; }
|
|
5574
|
+
.dxr-toggle {
|
|
5575
|
+
display: inline-flex;
|
|
5576
|
+
gap: 5px;
|
|
5577
|
+
align-items: center;
|
|
5578
|
+
color: var(--dxr-ink);
|
|
5579
|
+
font-size: 12.5px;
|
|
5580
|
+
white-space: nowrap;
|
|
5581
|
+
cursor: pointer;
|
|
5582
|
+
}
|
|
5583
|
+
.dxr-note { color: var(--dxr-muted); font-size: 11.5px; }
|
|
5584
|
+
|
|
5585
|
+
/* \u2500\u2500 Anchor rail \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5586
|
+
The addressing spine made permanent chrome. Every block in this editor is
|
|
5587
|
+
addressable as kind:scope:unid, and the model of record is a live WASM session \u2014
|
|
5588
|
+
so the surface states both, live, instead of hiding them behind devtools. It also
|
|
5589
|
+
reports each command's real cost, which is what a smoke test needs to see. */
|
|
5590
|
+
.dxr-rail {
|
|
5591
|
+
display: flex;
|
|
5592
|
+
align-items: center;
|
|
5593
|
+
height: 25px;
|
|
5594
|
+
padding: 0 10px;
|
|
5595
|
+
overflow-x: auto;
|
|
5596
|
+
background: #f7f9fc;
|
|
5597
|
+
border-top: 1px solid var(--dxr-rule);
|
|
5598
|
+
color: var(--dxr-muted);
|
|
5599
|
+
font-family: var(--dxr-mono);
|
|
5600
|
+
font-size: 11.5px;
|
|
5601
|
+
scrollbar-width: none;
|
|
5602
|
+
}
|
|
5603
|
+
.dxr-rail::-webkit-scrollbar { display: none; }
|
|
5604
|
+
.dxr-rail .dxr-cell {
|
|
5605
|
+
display: flex;
|
|
5606
|
+
align-items: baseline;
|
|
5607
|
+
gap: 6px;
|
|
5608
|
+
padding: 0 13px;
|
|
5609
|
+
border-right: 1px solid var(--dxr-rule);
|
|
5610
|
+
white-space: nowrap;
|
|
5611
|
+
}
|
|
5612
|
+
.dxr-rail .dxr-cell:first-child { padding-left: 0; }
|
|
5613
|
+
.dxr-rail .dxr-cell:last-child { border-right: none; }
|
|
5614
|
+
.dxr-rail .dxr-k {
|
|
5615
|
+
color: #8b96a3;
|
|
5616
|
+
font-family: var(--dxr-ui);
|
|
5617
|
+
font-size: 9.5px;
|
|
5618
|
+
letter-spacing: .1em;
|
|
5619
|
+
text-transform: uppercase;
|
|
5620
|
+
}
|
|
5621
|
+
.dxr-rail .dxr-v { color: var(--dxr-data); }
|
|
5622
|
+
.dxr-rail .dxr-v.dxr-flash { animation: dxr-railflash .45s ease-out; }
|
|
5623
|
+
@keyframes dxr-railflash { from { background: #b9ecdf; } to { background: transparent; } }
|
|
5624
|
+
|
|
5625
|
+
/* \u2500\u2500 Hint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5626
|
+
.dxr-hint {
|
|
5627
|
+
flex: 0 0 auto;
|
|
5628
|
+
max-width: 920px;
|
|
5629
|
+
margin: 0 auto;
|
|
5630
|
+
padding: 9px 16px 0;
|
|
5631
|
+
color: var(--dxr-muted);
|
|
5632
|
+
font-size: 12px;
|
|
5633
|
+
line-height: 1.5;
|
|
5634
|
+
}
|
|
5635
|
+
.dxr-hint kbd {
|
|
5636
|
+
padding: 0 4px;
|
|
5637
|
+
border: 1px solid var(--dxr-rule);
|
|
5638
|
+
border-radius: 3px;
|
|
5639
|
+
background: #e8ecf3;
|
|
5640
|
+
font-family: var(--dxr-mono);
|
|
5641
|
+
font-size: 11px;
|
|
5642
|
+
}
|
|
5643
|
+
|
|
5644
|
+
/* \u2500\u2500 Document surface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5645
|
+
The surface is also the element the converter's own stylesheet treats as the
|
|
5646
|
+
document body: in an embed, its "body { margin: 20px }" is rewritten to
|
|
5647
|
+
[data-docxodus-embed-root="dN"] [data-dxr-surface] \u2014 (0,2,0), and inserted AFTER
|
|
5648
|
+
this sheet, so it wins a tie. Rules that own the SHEET BOX (centering, page
|
|
5649
|
+
padding, the paper itself) therefore qualify with the root's data-chrome to reach
|
|
5650
|
+
(0,3,0). Rules that only style CONTENT stay unqualified \u2014 there the converter
|
|
5651
|
+
SHOULD win.
|
|
5652
|
+
(No backticks in this file's comments: the stylesheet is a template literal.) */
|
|
5653
|
+
.dxr-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; -webkit-overflow-scrolling: touch; }
|
|
5654
|
+
.dxr[data-chrome] .dxr-surface { max-width: 920px; margin: 26px auto; padding: 0 16px 96px; }
|
|
5655
|
+
.dxr-surface[data-view="continuous"] > * { background: var(--dxr-sheet); }
|
|
5656
|
+
.dxr[data-chrome] .dxr-surface[data-view="continuous"] {
|
|
5657
|
+
padding: 56px 72px;
|
|
5658
|
+
border-radius: 3px;
|
|
5659
|
+
background: var(--dxr-sheet);
|
|
5660
|
+
box-shadow: 0 1px 3px rgba(16, 20, 24, .14), 0 8px 24px rgba(16, 20, 24, .05);
|
|
5661
|
+
}
|
|
5662
|
+
.dxr-surface [contenteditable="true"]:focus {
|
|
5663
|
+
outline: 2px solid var(--dxr-accent);
|
|
5664
|
+
outline-offset: 2px;
|
|
5665
|
+
border-radius: 2px;
|
|
5666
|
+
}
|
|
5667
|
+
@media (hover: hover) { .dxr-surface [contenteditable="true"]:hover { background: #f2f7ff; } }
|
|
5668
|
+
|
|
5669
|
+
/* Header/footer bands: stories live in their own OOXML parts outside the body,
|
|
5670
|
+
so they dock as their own regions. Styled from the same tokens as the ribbon
|
|
5671
|
+
so the surface reads as one instrument rather than two apps. */
|
|
5672
|
+
.dxr-surface .docx-hf-band {
|
|
5673
|
+
margin: 0 0 14px;
|
|
5674
|
+
padding: 9px 72px 13px;
|
|
5675
|
+
border: 1px solid var(--dxr-rule);
|
|
5676
|
+
border-left: 2px solid #c2ccd9;
|
|
5677
|
+
border-radius: 3px;
|
|
5678
|
+
background: var(--dxr-sheet);
|
|
5679
|
+
}
|
|
5680
|
+
.dxr-surface .docx-hf-band + .docx-body-flow { margin-top: 0; }
|
|
5681
|
+
.dxr-surface .docx-hf-band[data-hf-band="footer"] { margin: 14px 0 0; }
|
|
5682
|
+
.dxr-surface .docx-hf-chrome {
|
|
5683
|
+
display: flex;
|
|
5684
|
+
gap: 8px;
|
|
5685
|
+
align-items: center;
|
|
5686
|
+
flex-wrap: wrap;
|
|
5687
|
+
margin: 0 0 7px -60px;
|
|
5688
|
+
color: var(--dxr-muted);
|
|
5689
|
+
font-size: 9.5px;
|
|
5690
|
+
font-weight: 600;
|
|
5691
|
+
letter-spacing: .11em;
|
|
5692
|
+
text-transform: uppercase;
|
|
5693
|
+
}
|
|
5694
|
+
.dxr-surface .docx-hf-chrome select, .dxr-surface .docx-hf-chrome input {
|
|
5695
|
+
padding: 3px 5px;
|
|
5696
|
+
border: 1px solid var(--dxr-rule);
|
|
5697
|
+
border-radius: 4px;
|
|
5698
|
+
background: #f7f9fc;
|
|
5699
|
+
color: var(--dxr-ink);
|
|
5700
|
+
font: inherit;
|
|
5701
|
+
font-family: var(--dxr-ui);
|
|
5702
|
+
font-size: 12.5px;
|
|
5703
|
+
font-weight: 400;
|
|
5704
|
+
letter-spacing: normal;
|
|
5705
|
+
text-transform: none;
|
|
5706
|
+
}
|
|
5707
|
+
.dxr-surface .docx-hf-chrome input[data-hf-pagestart] { width: 64px; }
|
|
5708
|
+
.dxr-surface .docx-hf-label { font-weight: 600; }
|
|
5709
|
+
.dxr-surface .docx-hf-warning {
|
|
5710
|
+
margin: 0 0 8px -60px;
|
|
5711
|
+
padding: 7px 9px;
|
|
5712
|
+
border: 1px solid #e8d9a8;
|
|
5713
|
+
border-left: 2px solid #c9a227;
|
|
5714
|
+
border-radius: 3px;
|
|
5715
|
+
background: #fdf6e3;
|
|
5716
|
+
color: #6b5310;
|
|
5717
|
+
font-size: 12px;
|
|
5718
|
+
line-height: 1.45;
|
|
5719
|
+
}
|
|
5720
|
+
.dxr-surface .docx-hf-warning button {
|
|
5721
|
+
margin-left: 6px;
|
|
5722
|
+
padding: 2px 8px;
|
|
5723
|
+
border-color: #e8d9a8;
|
|
5724
|
+
background: #fff;
|
|
5725
|
+
font-size: 12px;
|
|
5726
|
+
}
|
|
5727
|
+
.dxr-surface .docx-hf-placeholder { color: #9aa4b1; font-style: italic; }
|
|
5728
|
+
.dxr-surface .docx-hf-inherited {
|
|
5729
|
+
color: var(--dxr-muted);
|
|
5730
|
+
font-style: italic;
|
|
5731
|
+
font-weight: 400;
|
|
5732
|
+
letter-spacing: normal;
|
|
5733
|
+
text-transform: none;
|
|
5734
|
+
}
|
|
5735
|
+
.dxr-surface .docx-hf-band[data-hf-inherited] { border-style: dashed; }
|
|
5736
|
+
.dxr[data-chrome] .dxr-surface[data-view="continuous"]:has(.docx-body-flow) {
|
|
5737
|
+
padding: 0;
|
|
5738
|
+
background: transparent;
|
|
5739
|
+
box-shadow: none;
|
|
5740
|
+
}
|
|
5741
|
+
.dxr[data-chrome] .dxr-surface[data-view="continuous"] .docx-body-flow {
|
|
5742
|
+
padding: 56px 72px;
|
|
5743
|
+
border-radius: 3px;
|
|
5744
|
+
background: var(--dxr-sheet);
|
|
5745
|
+
box-shadow: 0 1px 3px rgba(16, 20, 24, .14), 0 8px 24px rgba(16, 20, 24, .05);
|
|
5746
|
+
}
|
|
5747
|
+
|
|
5748
|
+
/* \u2500\u2500 Table size picker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
5749
|
+
.dxr-pop {
|
|
5750
|
+
display: none;
|
|
5751
|
+
position: absolute;
|
|
5752
|
+
z-index: 30;
|
|
5753
|
+
padding: 9px;
|
|
5754
|
+
border: 1px solid var(--dxr-rule);
|
|
5755
|
+
border-radius: 6px;
|
|
5756
|
+
background: #fff;
|
|
5757
|
+
box-shadow: 0 4px 16px rgba(16, 20, 24, .18);
|
|
5758
|
+
}
|
|
5759
|
+
.dxr-pop[data-open] { display: block; }
|
|
5760
|
+
.dxr-gridcells { display: grid; grid-template-columns: repeat(10, 16px); gap: 2px; }
|
|
5761
|
+
.dxr-gridcells div {
|
|
5762
|
+
width: 16px;
|
|
5763
|
+
height: 16px;
|
|
5764
|
+
border: 1px solid var(--dxr-rule);
|
|
5765
|
+
border-radius: 2px;
|
|
5766
|
+
background: #f2f5f9;
|
|
5767
|
+
cursor: pointer;
|
|
5768
|
+
}
|
|
5769
|
+
.dxr-gridcells div[data-on] { background: var(--dxr-wash); border-color: var(--dxr-accent); }
|
|
5770
|
+
.dxr-popfoot {
|
|
5771
|
+
display: flex;
|
|
5772
|
+
justify-content: space-between;
|
|
5773
|
+
align-items: center;
|
|
5774
|
+
flex-wrap: wrap;
|
|
5775
|
+
gap: 10px;
|
|
5776
|
+
margin-top: 8px;
|
|
5777
|
+
font-size: 12px;
|
|
5778
|
+
}
|
|
5779
|
+
|
|
5780
|
+
/* \u2500\u2500 Compact chrome (the mobile-first base) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5781
|
+
One scrolling strip per tab instead of a multi-row ribbon: group labels turn
|
|
5782
|
+
into inline dividers, the rail and hint step aside, and the picker becomes a
|
|
5783
|
+
bottom sheet because there is no room to hang a popover off a button. */
|
|
5784
|
+
/* A 320px phone cannot hold the product name AND the file actions, and the file
|
|
5785
|
+
actions are what a user came for. The strip also scrolls, so nothing is stranded. */
|
|
5786
|
+
.dxr[data-chrome="compact"] .dxr-titlebar {
|
|
5787
|
+
gap: 6px;
|
|
5788
|
+
padding: 5px 8px 0;
|
|
5789
|
+
overflow-x: auto;
|
|
5790
|
+
scrollbar-width: none;
|
|
5791
|
+
}
|
|
5792
|
+
.dxr[data-chrome="compact"] .dxr-titlebar::-webkit-scrollbar { display: none; }
|
|
5793
|
+
.dxr[data-chrome="compact"] .dxr-brandname { display: none; }
|
|
5794
|
+
.dxr[data-chrome="compact"] .dxr-brand { flex: 0 1 auto; }
|
|
5795
|
+
/* A filename clipped to "do..." tells you less than no filename at all, so it keeps a
|
|
5796
|
+
readable floor and the strip scrolls instead. The tighter file-action padding is what
|
|
5797
|
+
buys that floor back on a 390px screen. */
|
|
5798
|
+
.dxr[data-chrome="compact"] .dxr-brand .dxr-docname { min-width: 6ch; max-width: 14ch; }
|
|
5799
|
+
.dxr[data-chrome="compact"] .dxr-quick button,
|
|
5800
|
+
.dxr[data-chrome="compact"] .dxr-quick label.dxr-btn { padding: 4px 8px; }
|
|
5801
|
+
.dxr[data-chrome="compact"] .dxr-status { display: none; }
|
|
5802
|
+
.dxr[data-chrome="compact"] .dxr-tabs { padding: 5px 8px 0; }
|
|
5803
|
+
.dxr[data-chrome="compact"] .dxr-tab { padding: 6px 12px 7px; font-size: 12px; }
|
|
5804
|
+
.dxr[data-chrome="compact"] .dxr-panel {
|
|
5805
|
+
align-items: center;
|
|
5806
|
+
gap: 4px;
|
|
5807
|
+
padding: 5px 8px;
|
|
5808
|
+
scroll-snap-type: x proximity;
|
|
5809
|
+
}
|
|
5810
|
+
.dxr[data-chrome="compact"] .dxr-group {
|
|
5811
|
+
flex-direction: row;
|
|
5812
|
+
align-items: center;
|
|
5813
|
+
gap: 4px;
|
|
5814
|
+
padding: 0 8px;
|
|
5815
|
+
scroll-snap-align: start;
|
|
5816
|
+
}
|
|
5817
|
+
.dxr[data-chrome="compact"] .dxr-glabel { display: none; }
|
|
5818
|
+
.dxr[data-chrome="compact"] .dxr-note { display: none; }
|
|
5819
|
+
.dxr[data-chrome="compact"] .dxr-rail { display: none; }
|
|
5820
|
+
.dxr[data-chrome="compact"] .dxr-hint { display: none; }
|
|
5821
|
+
.dxr[data-chrome="compact"] .dxr-surface { margin: 12px auto; padding: 0 10px 64px; }
|
|
5822
|
+
.dxr[data-chrome="compact"] .dxr-surface[data-view="continuous"] { padding: 22px 18px; }
|
|
5823
|
+
.dxr[data-chrome="compact"] .dxr-surface[data-view="continuous"] .docx-body-flow { padding: 22px 18px; }
|
|
5824
|
+
.dxr[data-chrome="compact"] .dxr-surface .docx-hf-band { padding: 9px 18px 13px; }
|
|
5825
|
+
.dxr[data-chrome="compact"] .dxr-surface .docx-hf-chrome,
|
|
5826
|
+
.dxr[data-chrome="compact"] .dxr-surface .docx-hf-warning { margin-left: 0; }
|
|
5827
|
+
/* A popover anchored to a button has nowhere to go on a narrow surface, so the
|
|
5828
|
+
picker docks to the bottom edge where a thumb already is. */
|
|
5829
|
+
.dxr[data-chrome="compact"] .dxr-pop[data-open] {
|
|
5830
|
+
position: fixed;
|
|
5831
|
+
left: 50%;
|
|
5832
|
+
right: auto;
|
|
5833
|
+
bottom: 12px;
|
|
5834
|
+
top: auto !important;
|
|
5835
|
+
transform: translateX(-50%);
|
|
5836
|
+
max-width: calc(100vw - 20px);
|
|
5837
|
+
}
|
|
5838
|
+
|
|
5839
|
+
/* \u2500\u2500 Loading overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5840
|
+
Kept from the shipped demo: the wait is real (a .NET runtime is streaming), so
|
|
5841
|
+
the surface spends it explaining what is being built rather than showing a
|
|
5842
|
+
spinner. It covers the whole instrument so half-built chrome never flashes. */
|
|
5843
|
+
.dxr-loader {
|
|
5844
|
+
position: absolute;
|
|
5845
|
+
inset: 0;
|
|
5846
|
+
z-index: 40;
|
|
5847
|
+
display: grid;
|
|
5848
|
+
place-items: center;
|
|
5849
|
+
padding: 24px;
|
|
5850
|
+
color: #f4f9ff;
|
|
5851
|
+
background:
|
|
5852
|
+
radial-gradient(circle at 20% 20%, rgba(63, 128, 255, .24), transparent 22rem),
|
|
5853
|
+
radial-gradient(circle at 82% 80%, rgba(176, 91, 255, .22), transparent 24rem),
|
|
5854
|
+
linear-gradient(145deg, #071221 0%, #0b1930 52%, #101426 100%);
|
|
5855
|
+
transition: opacity .55s ease, visibility .55s ease;
|
|
5856
|
+
}
|
|
5857
|
+
.dxr-loader[hidden] { display: none; }
|
|
5858
|
+
/* pointer-events drops the instant the fade starts: the surface underneath is already
|
|
5859
|
+
live, so a click during the half-second fade should reach it rather than be eaten. */
|
|
5860
|
+
.dxr-loader[data-done] { opacity: 0; visibility: hidden; pointer-events: none; }
|
|
5861
|
+
.dxr-loader-grid {
|
|
5862
|
+
width: min(850px, 100%);
|
|
5863
|
+
display: grid;
|
|
5864
|
+
grid-template-columns: 1fr;
|
|
5865
|
+
align-items: center;
|
|
5866
|
+
gap: 22px;
|
|
5867
|
+
text-align: center;
|
|
5868
|
+
}
|
|
5869
|
+
.dxr-visual { position: relative; width: min(170px, 52vw); aspect-ratio: 1; margin: 0 auto; }
|
|
5870
|
+
.dxr-orbit {
|
|
5871
|
+
position: absolute;
|
|
5872
|
+
inset: 7%;
|
|
5873
|
+
border: 1px solid rgba(115, 204, 255, .22);
|
|
5874
|
+
border-radius: 50%;
|
|
5875
|
+
animation: dxr-spin 9s linear infinite;
|
|
5876
|
+
}
|
|
5877
|
+
.dxr-orbit.dxr-two {
|
|
5878
|
+
inset: 20%;
|
|
5879
|
+
border-style: dashed;
|
|
5880
|
+
border-color: rgba(196, 128, 255, .32);
|
|
5881
|
+
animation-duration: 6s;
|
|
5882
|
+
animation-direction: reverse;
|
|
5883
|
+
}
|
|
5884
|
+
.dxr-orbit::before, .dxr-orbit::after {
|
|
5885
|
+
position: absolute;
|
|
5886
|
+
width: 10px;
|
|
5887
|
+
height: 10px;
|
|
5888
|
+
border-radius: 50%;
|
|
5889
|
+
content: "";
|
|
5890
|
+
background: #52e5ff;
|
|
5891
|
+
box-shadow: 0 0 18px #52e5ff;
|
|
5892
|
+
}
|
|
5893
|
+
.dxr-orbit::before { top: -5px; left: 50%; }
|
|
5894
|
+
.dxr-orbit::after { right: 7%; bottom: 12%; background: #b26cff; box-shadow: 0 0 18px #b26cff; }
|
|
5895
|
+
.dxr-card {
|
|
5896
|
+
position: absolute;
|
|
5897
|
+
inset: 24% 28%;
|
|
5898
|
+
padding: 20px 15px;
|
|
5899
|
+
border: 1px solid rgba(255, 255, 255, .33);
|
|
5900
|
+
border-radius: 14px;
|
|
5901
|
+
background: linear-gradient(150deg, rgba(255, 255, 255, .18), rgba(255, 255, 255, .06));
|
|
5902
|
+
box-shadow: 0 22px 50px rgba(0, 0, 0, .35), 0 0 45px rgba(71, 150, 255, .13);
|
|
5903
|
+
backdrop-filter: blur(10px);
|
|
5904
|
+
animation: dxr-float 3.6s ease-in-out infinite;
|
|
5905
|
+
}
|
|
5906
|
+
.dxr-card::before {
|
|
5907
|
+
position: absolute;
|
|
5908
|
+
top: -9px;
|
|
5909
|
+
right: -9px;
|
|
5910
|
+
padding: 4px 7px;
|
|
5911
|
+
border-radius: 6px;
|
|
5912
|
+
background: #52e5ff;
|
|
5913
|
+
color: #06111d;
|
|
5914
|
+
content: "DOCX";
|
|
5915
|
+
font-size: 8px;
|
|
5916
|
+
font-weight: 900;
|
|
5917
|
+
letter-spacing: .12em;
|
|
5918
|
+
}
|
|
5919
|
+
.dxr-card i {
|
|
5920
|
+
display: block;
|
|
5921
|
+
height: 4px;
|
|
5922
|
+
margin-bottom: 10px;
|
|
5923
|
+
border-radius: 4px;
|
|
5924
|
+
background: rgba(255, 255, 255, .56);
|
|
5925
|
+
transform-origin: left;
|
|
5926
|
+
animation: dxr-pulse 2.2s ease-in-out infinite;
|
|
5927
|
+
}
|
|
5928
|
+
.dxr-card i:nth-child(2) { width: 76%; animation-delay: -.45s; }
|
|
5929
|
+
.dxr-card i:nth-child(3) { width: 88%; animation-delay: -.85s; }
|
|
5930
|
+
.dxr-card i:nth-child(4) { width: 58%; background: rgba(255, 111, 139, .75); animation-delay: -1.2s; }
|
|
5931
|
+
.dxr-chip {
|
|
5932
|
+
position: absolute;
|
|
5933
|
+
display: grid;
|
|
5934
|
+
width: 34px;
|
|
5935
|
+
height: 34px;
|
|
5936
|
+
place-items: center;
|
|
5937
|
+
border: 1px solid rgba(255, 255, 255, .17);
|
|
5938
|
+
border-radius: 11px;
|
|
5939
|
+
background: rgba(12, 28, 49, .88);
|
|
5940
|
+
box-shadow: 0 12px 30px rgba(0, 0, 0, .28);
|
|
5941
|
+
font-size: 11px;
|
|
5942
|
+
font-weight: 850;
|
|
5943
|
+
}
|
|
5944
|
+
.dxr-chip.dxr-b { left: 2%; top: 30%; color: #52e5ff; animation: dxr-chip 3s ease-in-out infinite; }
|
|
5945
|
+
.dxr-chip.dxr-r { right: -2%; bottom: 24%; color: #ff8da0; animation: dxr-chip 3s -1.4s ease-in-out infinite; }
|
|
5946
|
+
.dxr-chip.dxr-s { left: 20%; bottom: 0; color: #52e0a2; animation: dxr-chip 3s -.7s ease-in-out infinite; }
|
|
5947
|
+
.dxr-eyebrow { color: #52e5ff; font-size: 10px; font-weight: 850; letter-spacing: .16em; text-transform: uppercase; }
|
|
5948
|
+
.dxr-loader h2 {
|
|
5949
|
+
margin: 10px auto 10px;
|
|
5950
|
+
max-width: 520px;
|
|
5951
|
+
font-size: clamp(23px, 5vw, 40px);
|
|
5952
|
+
line-height: 1.05;
|
|
5953
|
+
letter-spacing: -.045em;
|
|
5954
|
+
}
|
|
5955
|
+
.dxr-loader-copy > p { margin: 0 auto; max-width: 46ch; color: #9eb2c9; font-size: 13.5px; line-height: 1.6; }
|
|
5956
|
+
.dxr-ad {
|
|
5957
|
+
display: flex;
|
|
5958
|
+
gap: 12px;
|
|
5959
|
+
margin: 20px auto 0;
|
|
5960
|
+
max-width: 420px;
|
|
5961
|
+
padding: 13px;
|
|
5962
|
+
border: 1px solid rgba(126, 160, 200, .16);
|
|
5963
|
+
border-radius: 13px;
|
|
5964
|
+
background: rgba(255, 255, 255, .04);
|
|
5965
|
+
text-align: left;
|
|
5966
|
+
}
|
|
5967
|
+
.dxr-ad .dxr-num { color: #b26cff; font: 800 10px/1.4 var(--dxr-mono); }
|
|
5968
|
+
.dxr-ad strong { display: block; font-size: 12.5px; }
|
|
5969
|
+
.dxr-ad .dxr-adcopy { display: block; margin-top: 4px; color: #849bb5; font-size: 11.5px; line-height: 1.45; }
|
|
5970
|
+
.dxr-ad[data-swap] { animation: dxr-swap .4s ease; }
|
|
5971
|
+
.dxr-track {
|
|
5972
|
+
height: 3px;
|
|
5973
|
+
margin: 22px auto 0;
|
|
5974
|
+
max-width: 420px;
|
|
5975
|
+
overflow: hidden;
|
|
5976
|
+
border-radius: 999px;
|
|
5977
|
+
background: rgba(255, 255, 255, .09);
|
|
5978
|
+
}
|
|
5979
|
+
.dxr-bar {
|
|
5980
|
+
width: 12%;
|
|
5981
|
+
height: 100%;
|
|
5982
|
+
border-radius: inherit;
|
|
5983
|
+
background: linear-gradient(90deg, #52e5ff, #5c7cff, #b26cff);
|
|
5984
|
+
box-shadow: 0 0 16px rgba(82, 229, 255, .55);
|
|
5985
|
+
transition: width .65s cubic-bezier(.22, .8, .28, 1);
|
|
5986
|
+
}
|
|
5987
|
+
.dxr-meta {
|
|
5988
|
+
display: flex;
|
|
5989
|
+
justify-content: space-between;
|
|
5990
|
+
gap: 14px;
|
|
5991
|
+
margin: 9px auto 0;
|
|
5992
|
+
max-width: 420px;
|
|
5993
|
+
color: #647e9c;
|
|
5994
|
+
font: 700 9px/1 var(--dxr-mono);
|
|
5995
|
+
letter-spacing: .09em;
|
|
5996
|
+
text-transform: uppercase;
|
|
5997
|
+
}
|
|
5998
|
+
.dxr-retry {
|
|
5999
|
+
display: none;
|
|
6000
|
+
margin-top: 18px;
|
|
6001
|
+
padding: 9px 14px;
|
|
6002
|
+
border: 1px solid rgba(255, 127, 145, .4);
|
|
6003
|
+
border-radius: 9px;
|
|
6004
|
+
background: rgba(255, 127, 145, .12);
|
|
6005
|
+
color: #fff;
|
|
6006
|
+
cursor: pointer;
|
|
6007
|
+
}
|
|
6008
|
+
.dxr-loader[data-error] .dxr-retry { display: inline-flex; }
|
|
6009
|
+
.dxr-loader[data-error] .dxr-visual { opacity: .35; }
|
|
6010
|
+
|
|
6011
|
+
/* Two columns once there is room \u2014 the visual earns its space beside the copy. */
|
|
6012
|
+
@media (min-width: 760px) {
|
|
6013
|
+
.dxr-loader-grid {
|
|
6014
|
+
grid-template-columns: minmax(220px, .8fr) minmax(300px, 1.2fr);
|
|
6015
|
+
gap: clamp(28px, 6vw, 72px);
|
|
6016
|
+
text-align: left;
|
|
6017
|
+
}
|
|
6018
|
+
.dxr-visual { width: min(280px, 30vw); }
|
|
6019
|
+
.dxr-loader h2 { margin-left: 0; }
|
|
6020
|
+
.dxr-loader-copy > p { margin-left: 0; min-height: 44px; }
|
|
6021
|
+
.dxr-ad, .dxr-track, .dxr-meta { margin-left: 0; }
|
|
6022
|
+
}
|
|
6023
|
+
|
|
6024
|
+
@keyframes dxr-spin { to { transform: rotate(360deg); } }
|
|
6025
|
+
@keyframes dxr-float { 0%, 100% { transform: translateY(-5px) rotate(-2deg); } 50% { transform: translateY(7px) rotate(1deg); } }
|
|
6026
|
+
@keyframes dxr-chip { 0%, 100% { transform: translateY(-4px); } 50% { transform: translateY(5px); } }
|
|
6027
|
+
@keyframes dxr-pulse { 0%, 100% { transform: scaleX(.65); opacity: .45; } 50% { transform: scaleX(1); opacity: .95; } }
|
|
6028
|
+
@keyframes dxr-swap { from { opacity: .1; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
|
|
6029
|
+
|
|
6030
|
+
@media (prefers-reduced-motion: reduce) {
|
|
6031
|
+
.dxr *, .dxr *::before, .dxr *::after {
|
|
6032
|
+
animation-duration: .01ms !important;
|
|
6033
|
+
animation-iteration-count: 1 !important;
|
|
6034
|
+
transition-duration: .01ms !important;
|
|
6035
|
+
}
|
|
6036
|
+
}
|
|
6037
|
+
`;
|
|
6038
|
+
var ICON_ALIGN = (bars) => `<svg width="15" height="13" viewBox="0 0 15 13" aria-hidden="true">${bars}</svg>`;
|
|
6039
|
+
var RIBBON_HTML = `
|
|
6040
|
+
<div class="dxr-chrome">
|
|
6041
|
+
<div class="dxr-titlebar">
|
|
6042
|
+
<span class="dxr-brand"><span class="dxr-mark"></span><span class="dxr-brandname">Docxodus</span>
|
|
6043
|
+
<span class="dxr-docname" data-dxr="docname">no document</span></span>
|
|
6044
|
+
<div class="dxr-quick" data-dxr-files>
|
|
6045
|
+
<button type="button" data-dxr="new" title="Start a new blank document">New</button>
|
|
6046
|
+
<label class="dxr-btn" tabindex="0">Open<input data-dxr="file" type="file" accept=".docx" hidden /></label>
|
|
6047
|
+
<button type="button" data-dxr="save" disabled>Save</button>
|
|
6048
|
+
</div>
|
|
6049
|
+
<div class="dxr-quick">
|
|
6050
|
+
<button type="button" class="dxr-icon" data-dxr="undo" title="Undo (Ctrl+Z)" aria-label="Undo">↶</button>
|
|
6051
|
+
<button type="button" class="dxr-icon" data-dxr="redo" title="Redo (Ctrl+Shift+Z)" aria-label="Redo">↷</button>
|
|
6052
|
+
</div>
|
|
6053
|
+
<span class="dxr-spacer"></span>
|
|
6054
|
+
<span class="dxr-status" data-dxr="status" role="status" aria-live="polite">Booting WASM…</span>
|
|
6055
|
+
</div>
|
|
6056
|
+
|
|
6057
|
+
<div class="dxr-tabs" role="tablist">
|
|
6058
|
+
<button type="button" class="dxr-tab" role="tab" data-tab="home" aria-selected="true">Home</button>
|
|
6059
|
+
<button type="button" class="dxr-tab" role="tab" data-tab="insert" aria-selected="false">Insert</button>
|
|
6060
|
+
<button type="button" class="dxr-tab" role="tab" data-tab="layout" aria-selected="false">Layout</button>
|
|
6061
|
+
<button type="button" class="dxr-tab" role="tab" data-tab="table" data-contextual aria-selected="false" hidden>Table</button>
|
|
6062
|
+
</div>
|
|
6063
|
+
|
|
6064
|
+
<div class="dxr-ribbon" data-dxr="ribbon" aria-disabled="true">
|
|
6065
|
+
<!-- HOME \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
|
|
6066
|
+
<div class="dxr-panel" data-panel="home" data-active>
|
|
6067
|
+
<div class="dxr-group">
|
|
6068
|
+
<span class="dxr-glabel">Text</span>
|
|
6069
|
+
<div class="dxr-row">
|
|
6070
|
+
<button type="button" class="dxr-icon" data-cmd="bold" title="Bold (Ctrl+B)"><b>B</b></button>
|
|
6071
|
+
<button type="button" class="dxr-icon" data-cmd="italic" title="Italic (Ctrl+I)"><i>I</i></button>
|
|
6072
|
+
<button type="button" class="dxr-icon" data-cmd="underline" title="Underline (Ctrl+U)"><u>U</u></button>
|
|
6073
|
+
<button type="button" class="dxr-icon" data-cmd="strike" title="Strikethrough"><s>S</s></button>
|
|
6074
|
+
<button type="button" class="dxr-icon" data-cmd="code" title="Inline code"></></button>
|
|
6075
|
+
<button type="button" class="dxr-icon" data-cmd="superscript" title="Superscript">x²</button>
|
|
6076
|
+
<button type="button" class="dxr-icon" data-cmd="subscript" title="Subscript">x₂</button>
|
|
6077
|
+
</div>
|
|
6078
|
+
<div class="dxr-row">
|
|
6079
|
+
<input data-dxr="fontsize" data-dxr-list="fontsizes" type="number" min="1" max="1638" step="0.5"
|
|
6080
|
+
placeholder="pt" title="Font size in points \u2014 type any value or pick a preset" />
|
|
6081
|
+
<datalist data-dxr="fontsizes">
|
|
6082
|
+
<option>8</option><option>9</option><option>10</option><option>11</option>
|
|
6083
|
+
<option>12</option><option>14</option><option>16</option><option>18</option>
|
|
6084
|
+
<option>20</option><option>24</option><option>28</option><option>36</option>
|
|
6085
|
+
<option>48</option><option>72</option><option>96</option>
|
|
6086
|
+
</datalist>
|
|
6087
|
+
<select data-dxr="fontfamily" title="Font family \u2014 applies to the selection">
|
|
6088
|
+
<option value="">Font…</option>
|
|
6089
|
+
<option>Calibri</option><option>Times New Roman</option><option>Arial</option>
|
|
6090
|
+
<option>Georgia</option><option>Cambria</option><option>Courier New</option>
|
|
6091
|
+
<option>Verdana</option><option>Garamond</option>
|
|
6092
|
+
</select>
|
|
6093
|
+
</div>
|
|
6094
|
+
</div>
|
|
6095
|
+
|
|
6096
|
+
<div class="dxr-group">
|
|
6097
|
+
<span class="dxr-glabel">Paragraph</span>
|
|
6098
|
+
<div class="dxr-row">
|
|
6099
|
+
<button type="button" class="dxr-icon" data-align="left" title="Align left">${ICON_ALIGN(
|
|
6100
|
+
'<rect x="0" y="0" width="15" height="1.6"/><rect x="0" y="3.8" width="9" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="0" y="11.4" width="9" height="1.6"/>'
|
|
6101
|
+
)}</button>
|
|
6102
|
+
<button type="button" class="dxr-icon" data-align="center" title="Align center">${ICON_ALIGN(
|
|
6103
|
+
'<rect x="0" y="0" width="15" height="1.6"/><rect x="3" y="3.8" width="9" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="3" y="11.4" width="9" height="1.6"/>'
|
|
6104
|
+
)}</button>
|
|
6105
|
+
<button type="button" class="dxr-icon" data-align="right" title="Align right">${ICON_ALIGN(
|
|
6106
|
+
'<rect x="0" y="0" width="15" height="1.6"/><rect x="6" y="3.8" width="9" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="6" y="11.4" width="9" height="1.6"/>'
|
|
6107
|
+
)}</button>
|
|
6108
|
+
<button type="button" class="dxr-icon" data-align="justify" title="Justify">${ICON_ALIGN(
|
|
6109
|
+
'<rect x="0" y="0" width="15" height="1.6"/><rect x="0" y="3.8" width="15" height="1.6"/><rect x="0" y="7.6" width="15" height="1.6"/><rect x="0" y="11.4" width="15" height="1.6"/>'
|
|
6110
|
+
)}</button>
|
|
6111
|
+
<button type="button" class="dxr-icon" data-indent="-720" title="Decrease indent">${ICON_ALIGN(
|
|
6112
|
+
'<rect x="0" y="0" width="15" height="1.6"/><rect x="6" y="3.8" width="9" height="1.6"/><rect x="6" y="7.6" width="9" height="1.6"/><rect x="0" y="11.4" width="15" height="1.6"/><path d="M4.6 4.2v4.6L0.6 6.5z"/>'
|
|
6113
|
+
)}</button>
|
|
6114
|
+
<button type="button" class="dxr-icon" data-indent="720" title="Increase indent">${ICON_ALIGN(
|
|
6115
|
+
'<rect x="0" y="0" width="15" height="1.6"/><rect x="6" y="3.8" width="9" height="1.6"/><rect x="6" y="7.6" width="9" height="1.6"/><rect x="0" y="11.4" width="15" height="1.6"/><path d="M0.6 4.2v4.6l4-2.3z"/>'
|
|
6116
|
+
)}</button>
|
|
6117
|
+
</div>
|
|
6118
|
+
<div class="dxr-row">
|
|
6119
|
+
<button type="button" data-list="bullet" title="Bullet list">• List</button>
|
|
6120
|
+
<button type="button" data-list="decimal" title="Numbered list">1. List</button>
|
|
6121
|
+
<button type="button" data-pagebreak title="Start this block on a new page">Page break</button>
|
|
6122
|
+
</div>
|
|
6123
|
+
</div>
|
|
6124
|
+
|
|
6125
|
+
<div class="dxr-group">
|
|
6126
|
+
<span class="dxr-glabel">Block</span>
|
|
6127
|
+
<div class="dxr-row">
|
|
6128
|
+
<select data-dxr="style" title="Paragraph style">
|
|
6129
|
+
<option value="">Style…</option>
|
|
6130
|
+
<option value="Normal">Normal</option>
|
|
6131
|
+
<option value="Heading1">Heading 1</option>
|
|
6132
|
+
<option value="Heading2">Heading 2</option>
|
|
6133
|
+
<option value="Heading3">Heading 3</option>
|
|
6134
|
+
<option value="Title">Title</option>
|
|
6135
|
+
</select>
|
|
6136
|
+
</div>
|
|
6137
|
+
<div class="dxr-row">
|
|
6138
|
+
<button type="button" data-dxr="delblock" class="dxr-danger" title="Delete the block the caret is in">Delete block</button>
|
|
6139
|
+
</div>
|
|
6140
|
+
</div>
|
|
6141
|
+
</div>
|
|
6142
|
+
|
|
6143
|
+
<!-- INSERT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
|
|
6144
|
+
<div class="dxr-panel" data-panel="insert">
|
|
6145
|
+
<div class="dxr-group">
|
|
6146
|
+
<span class="dxr-glabel">Table</span>
|
|
6147
|
+
<div class="dxr-row">
|
|
6148
|
+
<button type="button" data-dxr="table" title="Insert a table \u2014 pick its size on the grid">▦ Table</button>
|
|
6149
|
+
</div>
|
|
6150
|
+
</div>
|
|
6151
|
+
|
|
6152
|
+
<div class="dxr-group">
|
|
6153
|
+
<span class="dxr-glabel">Rules</span>
|
|
6154
|
+
<div class="dxr-row">
|
|
6155
|
+
<button type="button" data-dxr="hr" class="dxr-wide" title="Insert a single rule"><svg width="22" height="8" viewBox="0 0 22 8" aria-hidden="true"><rect x="0" y="3.2" width="22" height="1.4"/></svg>Single</button>
|
|
6156
|
+
<button type="button" data-dxr="hrThick" class="dxr-wide" title="Insert a thick rule"><svg width="22" height="8" viewBox="0 0 22 8" aria-hidden="true"><rect x="0" y="2.4" width="22" height="3.2"/></svg>Thick</button>
|
|
6157
|
+
<button type="button" data-dxr="hrDouble" class="dxr-wide" title="Insert a double rule"><svg width="22" height="8" viewBox="0 0 22 8" aria-hidden="true"><rect x="0" y="1.6" width="22" height="1.3"/><rect x="0" y="5" width="22" height="1.3"/></svg>Double</button>
|
|
6158
|
+
</div>
|
|
6159
|
+
<div class="dxr-row">
|
|
6160
|
+
<select data-dxr="rulepos" title="Where the rule lands relative to the current block">
|
|
6161
|
+
<option value="below">Below block</option>
|
|
6162
|
+
<option value="above">Above block</option>
|
|
6163
|
+
</select>
|
|
6164
|
+
<button type="button" data-dxr="hrClear" title="Remove the rule or paragraph border">Clear</button>
|
|
6165
|
+
</div>
|
|
6166
|
+
</div>
|
|
6167
|
+
|
|
6168
|
+
<div class="dxr-group">
|
|
6169
|
+
<span class="dxr-glabel">References</span>
|
|
6170
|
+
<div class="dxr-row">
|
|
6171
|
+
<button type="button" data-dxr="footnote" title="Cite a new footnote at the caret">Footnote</button>
|
|
6172
|
+
<button type="button" data-dxr="endnote" title="Cite a new endnote at the caret">Endnote</button>
|
|
6173
|
+
</div>
|
|
6174
|
+
</div>
|
|
6175
|
+
</div>
|
|
6176
|
+
|
|
6177
|
+
<!-- LAYOUT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->
|
|
6178
|
+
<div class="dxr-panel" data-panel="layout">
|
|
6179
|
+
<div class="dxr-group">
|
|
6180
|
+
<span class="dxr-glabel">View</span>
|
|
6181
|
+
<div class="dxr-row">
|
|
6182
|
+
<label class="dxr-toggle"><input data-dxr="paginated" type="checkbox" /> Page view</label>
|
|
6183
|
+
</div>
|
|
6184
|
+
<div class="dxr-row">
|
|
6185
|
+
<label class="dxr-toggle"><input data-dxr="headerfooter" type="checkbox" /> Header & footer bands</label>
|
|
6186
|
+
</div>
|
|
6187
|
+
</div>
|
|
6188
|
+
|
|
6189
|
+
<div class="dxr-group">
|
|
6190
|
+
<span class="dxr-glabel">Page numbers</span>
|
|
6191
|
+
<div class="dxr-row">
|
|
6192
|
+
<select data-dxr="pgfmt" title="Number format for this section">
|
|
6193
|
+
<option value="">Format…</option>
|
|
6194
|
+
<option value="decimal">1, 2, 3</option>
|
|
6195
|
+
<option value="lowerLetter">a, b, c</option>
|
|
6196
|
+
<option value="upperLetter">A, B, C</option>
|
|
6197
|
+
<option value="lowerRoman">i, ii, iii</option>
|
|
6198
|
+
<option value="upperRoman">I, II, III</option>
|
|
6199
|
+
</select>
|
|
6200
|
+
<label class="dxr-toggle">Start at
|
|
6201
|
+
<input data-dxr="pgstart" type="number" min="1" step="1"
|
|
6202
|
+
title="Restart this section's numbering at this value" />
|
|
6203
|
+
</label>
|
|
6204
|
+
<button type="button" data-dxr="pgclear" title="Continue the previous section's numbering">Clear</button>
|
|
6205
|
+
</div>
|
|
6206
|
+
<div class="dxr-row">
|
|
6207
|
+
<span class="dxr-note">Applies to the section holding the caret.</span>
|
|
6208
|
+
</div>
|
|
6209
|
+
</div>
|
|
6210
|
+
|
|
6211
|
+
<!-- The field lands in the running footer (Word's convention, and where the band
|
|
6212
|
+
puts it), so it belongs with the section's numbering rather than under Insert,
|
|
6213
|
+
whose controls all act at the caret. -->
|
|
6214
|
+
<div class="dxr-group">
|
|
6215
|
+
<span class="dxr-glabel">Footer fields</span>
|
|
6216
|
+
<div class="dxr-row">
|
|
6217
|
+
<button type="button" data-dxr="pagenum" title="Add a page-number field to the footer">Page number</button>
|
|
6218
|
+
<button type="button" data-dxr="totalpages" title="Add a total-pages field to the footer">Total pages</button>
|
|
6219
|
+
</div>
|
|
6220
|
+
<div class="dxr-row">
|
|
6221
|
+
<span class="dxr-note">Added to the footer story.</span>
|
|
6222
|
+
</div>
|
|
6223
|
+
</div>
|
|
6224
|
+
</div>
|
|
6225
|
+
|
|
6226
|
+
<!-- TABLE (contextual) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6227
|
+
Row/column editing lives here rather than in a floating toolbar: a docked
|
|
6228
|
+
contextual tab cannot overlap the cell you are editing. -->
|
|
6229
|
+
<div class="dxr-panel" data-panel="table">
|
|
6230
|
+
<div class="dxr-group">
|
|
6231
|
+
<span class="dxr-glabel">Rows</span>
|
|
6232
|
+
<div class="dxr-row">
|
|
6233
|
+
<button type="button" data-tt="rowAbove" title="Insert a row above this one">Insert above</button>
|
|
6234
|
+
<button type="button" data-tt="rowBelow" title="Insert a row below this one">Insert below</button>
|
|
6235
|
+
<button type="button" data-tt="delRow" class="dxr-danger" title="Delete this row">Delete row</button>
|
|
6236
|
+
</div>
|
|
6237
|
+
</div>
|
|
6238
|
+
<div class="dxr-group">
|
|
6239
|
+
<span class="dxr-glabel">Columns</span>
|
|
6240
|
+
<div class="dxr-row">
|
|
6241
|
+
<button type="button" data-tt="colLeft" title="Insert a column to the left">Insert left</button>
|
|
6242
|
+
<button type="button" data-tt="colRight" title="Insert a column to the right">Insert right</button>
|
|
6243
|
+
<button type="button" data-tt="delCol" class="dxr-danger" title="Delete this column">Delete column</button>
|
|
6244
|
+
</div>
|
|
6245
|
+
</div>
|
|
6246
|
+
</div>
|
|
6247
|
+
</div>
|
|
6248
|
+
|
|
6249
|
+
<!-- Anchor rail \u2014 live engine state, not decoration. -->
|
|
6250
|
+
<div class="dxr-rail" data-dxr-rail>
|
|
6251
|
+
<span class="dxr-cell"><span class="dxr-k">anchor</span><span class="dxr-v" data-dxr="railAnchor">—</span></span>
|
|
6252
|
+
<span class="dxr-cell"><span class="dxr-k">blocks</span><span class="dxr-v" data-dxr="railBlocks">—</span></span>
|
|
6253
|
+
<span class="dxr-cell"><span class="dxr-k">session</span><span class="dxr-v" data-dxr="railSession">—</span></span>
|
|
6254
|
+
<span class="dxr-cell"><span class="dxr-k">last op</span><span class="dxr-v" data-dxr="railOp">—</span></span>
|
|
6255
|
+
</div>
|
|
6256
|
+
</div>
|
|
6257
|
+
|
|
6258
|
+
<div class="dxr-scroll" data-dxr-scroll>
|
|
6259
|
+
<p class="dxr-hint" data-dxr-hint></p>
|
|
6260
|
+
<div class="dxr-surface" data-dxr="editor" data-dxr-surface data-view="continuous"></div>
|
|
6261
|
+
</div>
|
|
6262
|
+
|
|
6263
|
+
<!-- Table size picker, anchored to the Insert tab's Table button. -->
|
|
6264
|
+
<div class="dxr-pop" data-dxr="gridpicker">
|
|
6265
|
+
<div class="dxr-gridcells" data-dxr="gridcells"></div>
|
|
6266
|
+
<div class="dxr-popfoot">
|
|
6267
|
+
<span data-dxr="gridlabel">0 × 0</span>
|
|
6268
|
+
<span style="display:inline-flex; align-items:center; gap:10px;">
|
|
6269
|
+
<label class="dxr-toggle">Align
|
|
6270
|
+
<select data-dxr="gridalign">
|
|
6271
|
+
<option value="left">Left</option>
|
|
6272
|
+
<option value="center">Center</option>
|
|
6273
|
+
<option value="right">Right</option>
|
|
6274
|
+
</select>
|
|
6275
|
+
</label>
|
|
6276
|
+
<label class="dxr-toggle"><input data-dxr="gridborderless" type="checkbox" checked /> Borderless</label>
|
|
6277
|
+
</span>
|
|
6278
|
+
</div>
|
|
6279
|
+
</div>
|
|
6280
|
+
|
|
6281
|
+
<div class="dxr-loader" data-dxr="loader" aria-live="polite" hidden>
|
|
6282
|
+
<div class="dxr-loader-grid">
|
|
6283
|
+
<div class="dxr-visual" aria-hidden="true">
|
|
6284
|
+
<div class="dxr-orbit"></div><div class="dxr-orbit dxr-two"></div>
|
|
6285
|
+
<div class="dxr-card"><i></i><i></i><i></i><i></i></div>
|
|
6286
|
+
<span class="dxr-chip dxr-b">B</span><span class="dxr-chip dxr-r">±</span><span class="dxr-chip dxr-s">↓</span>
|
|
6287
|
+
</div>
|
|
6288
|
+
<div class="dxr-loader-copy">
|
|
6289
|
+
<div class="dxr-eyebrow" data-dxr="loaderEyebrow">Running locally in this tab</div>
|
|
6290
|
+
<h2 data-dxr="loaderTitle">Booting .NET inside your browser</h2>
|
|
6291
|
+
<p data-dxr="loaderCopy">Streaming the trimmed WebAssembly runtime.</p>
|
|
6292
|
+
<div class="dxr-ad" data-dxr="loaderAd">
|
|
6293
|
+
<span class="dxr-num" data-dxr="loaderNumber">01</span>
|
|
6294
|
+
<div><strong data-dxr="loaderAdTitle"></strong><span class="dxr-adcopy" data-dxr="loaderAdCopy"></span></div>
|
|
6295
|
+
</div>
|
|
6296
|
+
<div class="dxr-track" aria-hidden="true"><div class="dxr-bar" data-dxr="loaderBar"></div></div>
|
|
6297
|
+
<div class="dxr-meta"><span data-dxr="loaderLabel">Loading engine</span><span data-dxr="loaderMeta">DOCX → WASM → DOCX</span></div>
|
|
6298
|
+
<button type="button" class="dxr-retry" data-dxr="loaderRetry">Retry loading</button>
|
|
6299
|
+
</div>
|
|
6300
|
+
</div>
|
|
6301
|
+
</div>
|
|
6302
|
+
`;
|
|
6303
|
+
var RIBBON_HINT_HTML = "Click any paragraph, heading, table cell, footnote or header/footer line to edit it. <kbd>Enter</kbd> splits a block, <kbd>Backspace</kbd> at the start merges it into the previous one, <kbd>Ctrl</kbd>+<kbd>Z</kbd> undoes. Only the block you changed re-renders \u2014 everything else keeps full fidelity, and <b>Save</b> writes a lossless .docx.";
|
|
6304
|
+
|
|
6305
|
+
// src/ribbon.ts
|
|
6306
|
+
var DEFAULT_STAGES = [
|
|
6307
|
+
{
|
|
6308
|
+
title: "Booting .NET inside your browser",
|
|
6309
|
+
copy: "Streaming the trimmed WebAssembly runtime. No document bytes are sent to a server.",
|
|
6310
|
+
progress: 16,
|
|
6311
|
+
label: "Loading engine"
|
|
6312
|
+
},
|
|
6313
|
+
{
|
|
6314
|
+
title: "Opening the document",
|
|
6315
|
+
copy: "Parsing OOXML parts, styles, numbering, tables, notes, and tracked revisions locally.",
|
|
6316
|
+
progress: 54,
|
|
6317
|
+
label: "Reading document"
|
|
6318
|
+
},
|
|
6319
|
+
{
|
|
6320
|
+
title: "Wiring lossless editing",
|
|
6321
|
+
copy: "Connecting every editable block to its native WordprocessingML anchor.",
|
|
6322
|
+
progress: 84,
|
|
6323
|
+
label: "Mounting editor"
|
|
6324
|
+
},
|
|
6325
|
+
{
|
|
6326
|
+
title: "Your local editor is ready",
|
|
6327
|
+
copy: "Select text, use the ribbon, switch page view, and save a real DOCX.",
|
|
6328
|
+
progress: 100,
|
|
6329
|
+
label: "Ready"
|
|
6330
|
+
}
|
|
6331
|
+
];
|
|
6332
|
+
var DEFAULT_FEATURES = [
|
|
6333
|
+
{ number: "01", title: "Zero-upload architecture", copy: "Your source file and edits never leave this browser session." },
|
|
6334
|
+
{ number: "02", title: "Word-grade OOXML fidelity", copy: "Tables, numbering, footnotes, redlines, comments, and styles stay native." },
|
|
6335
|
+
{ number: "03", title: "Surgical editing", copy: "Formatting and text edits target real document anchors instead of flattening the file." },
|
|
6336
|
+
{ number: "04", title: "Lossless DOCX out", copy: "Undo, redo, edit, and save a Word document that remains a Word document." }
|
|
6337
|
+
];
|
|
6338
|
+
var CONTROL_NAMES = [
|
|
6339
|
+
"docname",
|
|
6340
|
+
"new",
|
|
6341
|
+
"file",
|
|
6342
|
+
"save",
|
|
6343
|
+
"undo",
|
|
6344
|
+
"redo",
|
|
6345
|
+
"status",
|
|
6346
|
+
"ribbon",
|
|
6347
|
+
"fontsize",
|
|
6348
|
+
"fontsizes",
|
|
6349
|
+
"fontfamily",
|
|
6350
|
+
"style",
|
|
6351
|
+
"delblock",
|
|
6352
|
+
"table",
|
|
6353
|
+
"hr",
|
|
6354
|
+
"hrThick",
|
|
6355
|
+
"hrDouble",
|
|
6356
|
+
"rulepos",
|
|
6357
|
+
"hrClear",
|
|
6358
|
+
"footnote",
|
|
6359
|
+
"endnote",
|
|
6360
|
+
"paginated",
|
|
6361
|
+
"headerfooter",
|
|
6362
|
+
"pgfmt",
|
|
6363
|
+
"pgstart",
|
|
6364
|
+
"pgclear",
|
|
6365
|
+
"pagenum",
|
|
6366
|
+
"totalpages",
|
|
6367
|
+
"railAnchor",
|
|
6368
|
+
"railBlocks",
|
|
6369
|
+
"railSession",
|
|
6370
|
+
"railOp",
|
|
6371
|
+
"gridpicker",
|
|
6372
|
+
"gridcells",
|
|
6373
|
+
"gridlabel",
|
|
6374
|
+
"gridalign",
|
|
6375
|
+
"gridborderless",
|
|
6376
|
+
"editor",
|
|
6377
|
+
"loader",
|
|
6378
|
+
"loaderEyebrow",
|
|
6379
|
+
"loaderTitle",
|
|
6380
|
+
"loaderCopy",
|
|
6381
|
+
"loaderAd",
|
|
6382
|
+
"loaderNumber",
|
|
6383
|
+
"loaderAdTitle",
|
|
6384
|
+
"loaderAdCopy",
|
|
6385
|
+
"loaderBar",
|
|
6386
|
+
"loaderLabel",
|
|
6387
|
+
"loaderMeta",
|
|
6388
|
+
"loaderRetry"
|
|
6389
|
+
];
|
|
6390
|
+
var GRID_ROWS = 8;
|
|
6391
|
+
var GRID_COLS = 10;
|
|
6392
|
+
var nextIdPrefixSeed = 0;
|
|
6393
|
+
function ensureStyles(doc) {
|
|
6394
|
+
const existing = doc.querySelector(`style[${RIBBON_STYLE_ATTR}]`);
|
|
6395
|
+
if (existing?.getAttribute(RIBBON_STYLE_ATTR) === RIBBON_STYLE_VERSION) return;
|
|
6396
|
+
existing?.remove();
|
|
6397
|
+
const style = doc.createElement("style");
|
|
6398
|
+
style.setAttribute(RIBBON_STYLE_ATTR, RIBBON_STYLE_VERSION);
|
|
6399
|
+
style.textContent = RIBBON_CSS;
|
|
6400
|
+
(doc.head ?? doc.documentElement).appendChild(style);
|
|
6401
|
+
}
|
|
6402
|
+
function resolveIdPrefix(explicit, doc) {
|
|
6403
|
+
if (explicit !== void 0) return explicit;
|
|
6404
|
+
if (!CONTROL_NAMES.some((name) => doc.getElementById(name))) return "";
|
|
6405
|
+
for (; ; ) {
|
|
6406
|
+
const candidate = `dxr${++nextIdPrefixSeed}-`;
|
|
6407
|
+
if (!CONTROL_NAMES.some((name) => doc.getElementById(candidate + name))) return candidate;
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
function resolveContainer(container) {
|
|
6411
|
+
if (typeof container !== "string") return container;
|
|
6412
|
+
const el = document.querySelector(container);
|
|
6413
|
+
if (!el) throw new Error(`Docxodus ribbon: no element matches "${container}"`);
|
|
6414
|
+
return el;
|
|
6415
|
+
}
|
|
6416
|
+
function mountRibbon(container, options = {}) {
|
|
6417
|
+
return new RibbonSurface(resolveContainer(container), options);
|
|
6418
|
+
}
|
|
6419
|
+
var RibbonSurface = class {
|
|
6420
|
+
constructor(container, options) {
|
|
6421
|
+
this.live = null;
|
|
6422
|
+
this.density = "full";
|
|
6423
|
+
this.destroyed = false;
|
|
6424
|
+
this.featureTimer = null;
|
|
6425
|
+
this.featureIndex = 0;
|
|
6426
|
+
this.resizeObserver = null;
|
|
6427
|
+
this.lastAnchorText = "";
|
|
6428
|
+
this.selectionFrame = null;
|
|
6429
|
+
this.options = options;
|
|
6430
|
+
this.exports = options.exports ?? null;
|
|
6431
|
+
this.documentName = options.documentName ?? "untitled.docx";
|
|
6432
|
+
this.chromeMode = options.chrome ?? "auto";
|
|
6433
|
+
this.headerFooter = options.headerFooter ?? false;
|
|
6434
|
+
this.loaderOptions = options.loader === false ? null : typeof options.loader === "object" ? options.loader : {};
|
|
6435
|
+
this.stages = this.loaderOptions?.stages ?? DEFAULT_STAGES;
|
|
6436
|
+
this.features = this.loaderOptions?.features ?? DEFAULT_FEATURES;
|
|
6437
|
+
const doc = container.ownerDocument ?? document;
|
|
6438
|
+
ensureStyles(doc);
|
|
6439
|
+
this.idPrefix = resolveIdPrefix(options.idPrefix, doc);
|
|
6440
|
+
const root = doc.createElement("div");
|
|
6441
|
+
root.className = "dxr";
|
|
6442
|
+
root.dataset.state = "idle";
|
|
6443
|
+
root.innerHTML = RIBBON_HTML;
|
|
6444
|
+
for (const el of Array.from(root.querySelectorAll("[data-dxr]"))) {
|
|
6445
|
+
el.id = this.idPrefix + el.dataset.dxr;
|
|
6446
|
+
}
|
|
6447
|
+
for (const el of Array.from(root.querySelectorAll("[data-dxr-list]"))) {
|
|
6448
|
+
el.setAttribute("list", this.idPrefix + el.dataset.dxrList);
|
|
6449
|
+
}
|
|
6450
|
+
container.replaceChildren(root);
|
|
6451
|
+
this.element = root;
|
|
6452
|
+
this.surface = this.require("editor");
|
|
6453
|
+
this.applyStaticOptions();
|
|
6454
|
+
this.buildGrid();
|
|
6455
|
+
this.wire();
|
|
6456
|
+
this.applyChrome();
|
|
6457
|
+
this.loader = this.createLoaderController();
|
|
6458
|
+
if (this.loaderOptions) this.loader.show();
|
|
6459
|
+
this.onSelectionChange = () => {
|
|
6460
|
+
if (this.selectionFrame != null) cancelAnimationFrame(this.selectionFrame);
|
|
6461
|
+
this.selectionFrame = requestAnimationFrame(() => {
|
|
6462
|
+
this.selectionFrame = null;
|
|
6463
|
+
this.syncSelection();
|
|
6464
|
+
});
|
|
6465
|
+
};
|
|
6466
|
+
doc.addEventListener("selectionchange", this.onSelectionChange);
|
|
6467
|
+
this.onDocumentMouseDown = (event) => this.maybeClosePicker(event);
|
|
6468
|
+
doc.addEventListener("mousedown", this.onDocumentMouseDown);
|
|
6469
|
+
if (this.chromeMode === "auto" && typeof ResizeObserver !== "undefined") {
|
|
6470
|
+
this.resizeObserver = new ResizeObserver(() => this.applyChrome());
|
|
6471
|
+
this.resizeObserver.observe(root);
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6474
|
+
// ── element lookup ──────────────────────────────────────────────────────────
|
|
6475
|
+
control(name) {
|
|
6476
|
+
return this.element.querySelector(`[data-dxr="${name}"]`);
|
|
6477
|
+
}
|
|
6478
|
+
require(name) {
|
|
6479
|
+
const el = this.control(name);
|
|
6480
|
+
if (!el) throw new Error(`Docxodus ribbon: template is missing "${name}"`);
|
|
6481
|
+
return el;
|
|
6482
|
+
}
|
|
6483
|
+
// ── mount-time configuration ────────────────────────────────────────────────
|
|
6484
|
+
applyStaticOptions() {
|
|
6485
|
+
const hintEl = this.element.querySelector("[data-dxr-hint]");
|
|
6486
|
+
if (hintEl) {
|
|
6487
|
+
if (this.options.hint === false) hintEl.remove();
|
|
6488
|
+
else hintEl.innerHTML = typeof this.options.hint === "string" ? this.options.hint : RIBBON_HINT_HTML;
|
|
6489
|
+
}
|
|
6490
|
+
if (this.options.rail === false) {
|
|
6491
|
+
this.element.querySelector("[data-dxr-rail]")?.remove();
|
|
6492
|
+
}
|
|
6493
|
+
if (this.options.fileActions === false) {
|
|
6494
|
+
this.element.querySelector("[data-dxr-files]")?.remove();
|
|
6495
|
+
}
|
|
6496
|
+
if (!this.loaderOptions) this.control("loader")?.remove();
|
|
6497
|
+
this.require("docname").textContent = this.documentName;
|
|
6498
|
+
this.require("paginated").checked = this.options.paginated ?? false;
|
|
6499
|
+
this.require("headerfooter").checked = this.headerFooter;
|
|
6500
|
+
this.surface.dataset.view = this.options.paginated ? "paginated" : "continuous";
|
|
6501
|
+
}
|
|
6502
|
+
buildGrid() {
|
|
6503
|
+
const cells = this.require("gridcells");
|
|
6504
|
+
const fragment = document.createDocumentFragment();
|
|
6505
|
+
for (let r = 0; r < GRID_ROWS; r++) {
|
|
6506
|
+
for (let c = 0; c < GRID_COLS; c++) {
|
|
6507
|
+
const cell = document.createElement("div");
|
|
6508
|
+
cell.dataset.r = String(r);
|
|
6509
|
+
cell.dataset.c = String(c);
|
|
6510
|
+
fragment.appendChild(cell);
|
|
6511
|
+
}
|
|
6512
|
+
}
|
|
6513
|
+
cells.replaceChildren(fragment);
|
|
6514
|
+
}
|
|
6515
|
+
// ── chrome density ──────────────────────────────────────────────────────────
|
|
6516
|
+
get chrome() {
|
|
6517
|
+
return this.density;
|
|
6518
|
+
}
|
|
6519
|
+
setChrome(mode) {
|
|
6520
|
+
this.chromeMode = mode;
|
|
6521
|
+
if (mode === "auto" && !this.resizeObserver && typeof ResizeObserver !== "undefined") {
|
|
6522
|
+
this.resizeObserver = new ResizeObserver(() => this.applyChrome());
|
|
6523
|
+
this.resizeObserver.observe(this.element);
|
|
6524
|
+
}
|
|
6525
|
+
this.applyChrome();
|
|
6526
|
+
}
|
|
6527
|
+
applyChrome() {
|
|
6528
|
+
const breakpoint = this.options.compactBreakpoint ?? 720;
|
|
6529
|
+
const width = this.element.clientWidth || this.element.getBoundingClientRect().width;
|
|
6530
|
+
const next = this.chromeMode === "auto" ? width > 0 && width < breakpoint ? "compact" : "full" : this.chromeMode;
|
|
6531
|
+
if (next === this.density && this.element.dataset.chrome) return;
|
|
6532
|
+
this.density = next;
|
|
6533
|
+
this.element.dataset.chrome = next;
|
|
6534
|
+
this.closePicker();
|
|
6535
|
+
}
|
|
6536
|
+
// ── status ──────────────────────────────────────────────────────────────────
|
|
6537
|
+
/** Publish the lifecycle on the root, where CSS and host pages can key off it. */
|
|
6538
|
+
setState(state) {
|
|
6539
|
+
this.element.dataset.state = state;
|
|
6540
|
+
}
|
|
6541
|
+
setStatus(text) {
|
|
6542
|
+
const el = this.control("status");
|
|
6543
|
+
if (el) el.textContent = text;
|
|
6544
|
+
this.options.onStatus?.(text);
|
|
6545
|
+
}
|
|
6546
|
+
// ── document lifecycle ──────────────────────────────────────────────────────
|
|
6547
|
+
get editor() {
|
|
6548
|
+
return this.live;
|
|
6549
|
+
}
|
|
6550
|
+
setExports(exports) {
|
|
6551
|
+
this.exports = exports;
|
|
6552
|
+
}
|
|
6553
|
+
open(bytes, name) {
|
|
6554
|
+
if (!this.exports) throw new Error("Docxodus ribbon: WASM exports are not set yet");
|
|
6555
|
+
if (this.live) {
|
|
6556
|
+
try {
|
|
6557
|
+
this.live.close();
|
|
6558
|
+
} catch {
|
|
6559
|
+
}
|
|
6560
|
+
this.live = null;
|
|
6561
|
+
}
|
|
6562
|
+
if (name) this.documentName = name;
|
|
6563
|
+
this.require("docname").textContent = this.documentName;
|
|
6564
|
+
const paginated = this.require("paginated").checked;
|
|
6565
|
+
this.surface.dataset.view = paginated ? "paginated" : "continuous";
|
|
6566
|
+
this.surface.replaceChildren();
|
|
6567
|
+
const started = performance.now();
|
|
6568
|
+
this.live = DocxEditor.open(this.surface, bytes, this.exports, {
|
|
6569
|
+
cssPrefix: this.options.cssPrefix,
|
|
6570
|
+
fabricateClasses: this.options.fabricateClasses,
|
|
6571
|
+
editable: this.options.editable,
|
|
6572
|
+
scale: this.options.scale,
|
|
6573
|
+
onEdit: this.options.onEdit,
|
|
6574
|
+
paginated,
|
|
6575
|
+
headerFooter: this.headerFooter
|
|
6576
|
+
});
|
|
6577
|
+
this.require("save").disabled = false;
|
|
6578
|
+
this.require("ribbon").setAttribute("aria-disabled", "false");
|
|
6579
|
+
this.setState("ready");
|
|
6580
|
+
this.setStatus(`Rendered in ${Math.round(performance.now() - started)} ms`);
|
|
6581
|
+
this.syncPageNumbering();
|
|
6582
|
+
this.refreshRailCounts();
|
|
6583
|
+
this.refreshRailAnchor();
|
|
6584
|
+
this.options.onOpen?.(this.live);
|
|
6585
|
+
return this.live;
|
|
6586
|
+
}
|
|
6587
|
+
openBlank(name = "untitled.docx") {
|
|
6588
|
+
if (!this.exports) throw new Error("Docxodus ribbon: WASM exports are not set yet");
|
|
6589
|
+
return this.open(this.exports.DocxSessionBridge.CreateBlankDocx(), name);
|
|
6590
|
+
}
|
|
6591
|
+
save() {
|
|
6592
|
+
return this.live ? this.live.save() : null;
|
|
6593
|
+
}
|
|
6594
|
+
download(name) {
|
|
6595
|
+
const bytes = this.save();
|
|
6596
|
+
if (!bytes) return;
|
|
6597
|
+
const filename = name ?? this.documentName ?? "edited.docx";
|
|
6598
|
+
if (this.options.onSave) {
|
|
6599
|
+
this.options.onSave(bytes, filename);
|
|
6600
|
+
return;
|
|
6601
|
+
}
|
|
6602
|
+
const url = URL.createObjectURL(
|
|
6603
|
+
new Blob([bytes], {
|
|
6604
|
+
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
6605
|
+
})
|
|
6606
|
+
);
|
|
6607
|
+
const link = document.createElement("a");
|
|
6608
|
+
link.href = url;
|
|
6609
|
+
link.download = filename;
|
|
6610
|
+
link.click();
|
|
6611
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
6612
|
+
this.setStatus(`Saved ${filename}`);
|
|
6613
|
+
}
|
|
6614
|
+
destroy() {
|
|
6615
|
+
if (this.destroyed) return;
|
|
6616
|
+
this.destroyed = true;
|
|
6617
|
+
const doc = this.element.ownerDocument ?? document;
|
|
6618
|
+
doc.removeEventListener("selectionchange", this.onSelectionChange);
|
|
6619
|
+
doc.removeEventListener("mousedown", this.onDocumentMouseDown);
|
|
6620
|
+
this.resizeObserver?.disconnect();
|
|
6621
|
+
this.resizeObserver = null;
|
|
6622
|
+
if (this.selectionFrame != null) cancelAnimationFrame(this.selectionFrame);
|
|
6623
|
+
this.selectionFrame = null;
|
|
6624
|
+
this.stopRotation();
|
|
6625
|
+
try {
|
|
6626
|
+
this.live?.close();
|
|
6627
|
+
} catch {
|
|
6628
|
+
}
|
|
6629
|
+
this.live = null;
|
|
6630
|
+
this.element.remove();
|
|
6631
|
+
}
|
|
6632
|
+
// ── command plumbing ────────────────────────────────────────────────────────
|
|
6633
|
+
/**
|
|
6634
|
+
* Run one ribbon command and report its real cost on the rail.
|
|
6635
|
+
*
|
|
6636
|
+
* Every control routes through here, which is what makes the rail's "last op"
|
|
6637
|
+
* an honest measurement rather than a label the surface makes up.
|
|
6638
|
+
*/
|
|
6639
|
+
run(label, fn) {
|
|
6640
|
+
if (!this.live) return;
|
|
6641
|
+
const started = performance.now();
|
|
6642
|
+
try {
|
|
6643
|
+
fn();
|
|
6644
|
+
} finally {
|
|
6645
|
+
const ms = performance.now() - started;
|
|
6646
|
+
const el = this.control("railOp");
|
|
6647
|
+
if (el) el.textContent = `${label} ${ms >= 1e3 ? `${(ms / 1e3).toFixed(2)} s` : `${Math.round(ms)} ms`}`;
|
|
6648
|
+
this.options.onCommand?.(label, ms);
|
|
6649
|
+
this.refreshRailCounts();
|
|
6650
|
+
this.refreshRailAnchor();
|
|
6651
|
+
}
|
|
6652
|
+
}
|
|
6653
|
+
/** Format controls must not steal the document selection they are about to act on. */
|
|
6654
|
+
keepSelection(el) {
|
|
6655
|
+
el.addEventListener("mousedown", (event) => event.preventDefault());
|
|
6656
|
+
}
|
|
6657
|
+
wire() {
|
|
6658
|
+
const ribbon = this.require("ribbon");
|
|
6659
|
+
for (const tab of Array.from(this.element.querySelectorAll(".dxr-tab"))) {
|
|
6660
|
+
this.keepSelection(tab);
|
|
6661
|
+
tab.addEventListener("click", () => this.selectTab(tab.dataset.tab ?? "home"));
|
|
6662
|
+
}
|
|
6663
|
+
const delegate = (selector, label, fn) => {
|
|
6664
|
+
for (const el of Array.from(ribbon.querySelectorAll(selector))) {
|
|
6665
|
+
this.keepSelection(el);
|
|
6666
|
+
el.addEventListener("click", () => this.run(label(el), () => fn(el)));
|
|
6667
|
+
}
|
|
6668
|
+
};
|
|
6669
|
+
delegate("button[data-cmd]", (b) => b.dataset.cmd, (b) => this.live.format(b.dataset.cmd));
|
|
6670
|
+
delegate("button[data-align]", (b) => `align ${b.dataset.align}`, (b) => this.live.setAlignment(b.dataset.align));
|
|
6671
|
+
delegate("button[data-indent]", () => "indent", (b) => this.live.indent(parseInt(b.dataset.indent ?? "720", 10)));
|
|
6672
|
+
delegate("button[data-list]", (b) => `list ${b.dataset.list}`, (b) => this.live.toggleList(b.dataset.list));
|
|
6673
|
+
delegate("button[data-pagebreak]", () => "page break", () => this.live.pageBreakBefore(true));
|
|
6674
|
+
delegate('.dxr-panel[data-panel="table"] button[data-tt]', (b) => b.dataset.tt, (b) => {
|
|
6675
|
+
const ops = {
|
|
6676
|
+
rowAbove: () => this.live.insertTableRow("above"),
|
|
6677
|
+
rowBelow: () => this.live.insertTableRow("below"),
|
|
6678
|
+
colLeft: () => this.live.insertTableColumn("left"),
|
|
6679
|
+
colRight: () => this.live.insertTableColumn("right"),
|
|
6680
|
+
delRow: () => this.live.deleteTableRow(),
|
|
6681
|
+
delCol: () => this.live.deleteTableColumn()
|
|
6682
|
+
};
|
|
6683
|
+
ops[b.dataset.tt ?? ""]?.();
|
|
6684
|
+
});
|
|
6685
|
+
const rulepos = () => this.require("rulepos").value === "above" ? "above" : "below";
|
|
6686
|
+
const simple = [
|
|
6687
|
+
["undo", "undo", () => this.live.undo()],
|
|
6688
|
+
["redo", "redo", () => this.live.redo()],
|
|
6689
|
+
["hr", "rule", () => this.live.insertHorizontalRule(12, "single", rulepos())],
|
|
6690
|
+
["hrThick", "thick rule", () => this.live.insertHorizontalRule(24, "single", rulepos())],
|
|
6691
|
+
["hrDouble", "double rule", () => this.live.insertHorizontalRule(12, "double", rulepos())],
|
|
6692
|
+
["hrClear", "clear border", () => this.live.clearParagraphBorders()],
|
|
6693
|
+
["delblock", "delete block", () => this.live.deleteBlock()],
|
|
6694
|
+
["footnote", "footnote", () => this.live.insertFootnote()],
|
|
6695
|
+
["endnote", "endnote", () => this.live.insertEndnote()],
|
|
6696
|
+
["pagenum", "page number", () => this.live.insertPageNumber("currentPage")],
|
|
6697
|
+
["totalpages", "total pages", () => this.live.insertPageNumber("totalPages")]
|
|
6698
|
+
];
|
|
6699
|
+
for (const [name, label, fn] of simple) {
|
|
6700
|
+
const el = this.control(name);
|
|
6701
|
+
if (!el) continue;
|
|
6702
|
+
this.keepSelection(el);
|
|
6703
|
+
el.addEventListener("click", () => this.run(label, fn));
|
|
6704
|
+
}
|
|
6705
|
+
const fontsize = this.require("fontsize");
|
|
6706
|
+
fontsize.addEventListener("change", () => {
|
|
6707
|
+
const pts = parseFloat(fontsize.value);
|
|
6708
|
+
if (this.live && pts > 0) this.run("font size", () => this.live.setFontSize(pts));
|
|
6709
|
+
});
|
|
6710
|
+
fontsize.addEventListener("keydown", (event) => {
|
|
6711
|
+
if (event.key === "Enter") {
|
|
6712
|
+
event.preventDefault();
|
|
6713
|
+
fontsize.blur();
|
|
6714
|
+
}
|
|
6715
|
+
});
|
|
6716
|
+
const fontfamily = this.require("fontfamily");
|
|
6717
|
+
fontfamily.addEventListener("change", () => {
|
|
6718
|
+
if (this.live && fontfamily.value) this.run("font family", () => this.live.setFontFamily(fontfamily.value));
|
|
6719
|
+
fontfamily.value = "";
|
|
6720
|
+
});
|
|
6721
|
+
const style = this.require("style");
|
|
6722
|
+
style.addEventListener("change", () => {
|
|
6723
|
+
if (this.live && style.value) this.run("style", () => this.live.setParagraphStyle(style.value));
|
|
6724
|
+
style.value = "";
|
|
6725
|
+
});
|
|
6726
|
+
this.wireFileActions();
|
|
6727
|
+
this.wireLayout();
|
|
6728
|
+
this.wirePicker();
|
|
6729
|
+
}
|
|
6730
|
+
wireFileActions() {
|
|
6731
|
+
const file = this.control("file");
|
|
6732
|
+
file?.addEventListener("change", async () => {
|
|
6733
|
+
const chosen = file.files?.[0];
|
|
6734
|
+
if (!chosen) return;
|
|
6735
|
+
this.setStatus(`Loading ${chosen.name}\u2026`);
|
|
6736
|
+
this.open(new Uint8Array(await chosen.arrayBuffer()), chosen.name);
|
|
6737
|
+
file.value = "";
|
|
6738
|
+
});
|
|
6739
|
+
this.control("new")?.addEventListener("click", () => {
|
|
6740
|
+
if (this.exports) this.openBlank("untitled.docx");
|
|
6741
|
+
});
|
|
6742
|
+
this.control("save")?.addEventListener("click", () => this.download());
|
|
6743
|
+
}
|
|
6744
|
+
wireLayout() {
|
|
6745
|
+
const paginated = this.require("paginated");
|
|
6746
|
+
paginated.addEventListener("change", () => {
|
|
6747
|
+
this.surface.dataset.view = paginated.checked ? "paginated" : "continuous";
|
|
6748
|
+
if (!this.live) return;
|
|
6749
|
+
this.run(paginated.checked ? "page view" : "continuous view", () => this.live.setPaginated(paginated.checked));
|
|
6750
|
+
});
|
|
6751
|
+
const headerFooter = this.require("headerfooter");
|
|
6752
|
+
headerFooter.addEventListener("change", () => {
|
|
6753
|
+
this.headerFooter = headerFooter.checked;
|
|
6754
|
+
if (!this.live) return;
|
|
6755
|
+
this.open(this.live.save(), this.documentName);
|
|
6756
|
+
});
|
|
6757
|
+
const pgfmt = this.require("pgfmt");
|
|
6758
|
+
pgfmt.addEventListener("change", () => {
|
|
6759
|
+
if (!this.live || !pgfmt.value) return;
|
|
6760
|
+
this.run("page format", () => this.live.setPageNumbering({ format: pgfmt.value }));
|
|
6761
|
+
this.syncPageNumbering();
|
|
6762
|
+
});
|
|
6763
|
+
const pgstart = this.require("pgstart");
|
|
6764
|
+
pgstart.addEventListener("change", () => {
|
|
6765
|
+
const value = parseInt(pgstart.value, 10);
|
|
6766
|
+
if (!this.live || !(value > 0)) return;
|
|
6767
|
+
this.run("page start", () => this.live.setPageNumbering({ start: value }));
|
|
6768
|
+
this.syncPageNumbering();
|
|
6769
|
+
});
|
|
6770
|
+
this.control("pgclear")?.addEventListener("click", () => {
|
|
6771
|
+
this.run("clear numbering", () => this.live.clearPageNumbering());
|
|
6772
|
+
this.syncPageNumbering();
|
|
6773
|
+
});
|
|
6774
|
+
}
|
|
6775
|
+
/** The bands own the same setting and read the live session, so both stay in step. */
|
|
6776
|
+
syncPageNumbering() {
|
|
6777
|
+
if (!this.live) return;
|
|
6778
|
+
const numbering = this.live.pageNumbering() ?? {};
|
|
6779
|
+
this.require("pgfmt").value = numbering.format ?? "";
|
|
6780
|
+
this.require("pgstart").value = numbering.start != null ? String(numbering.start) : "";
|
|
6781
|
+
}
|
|
6782
|
+
// ── table size picker ───────────────────────────────────────────────────────
|
|
6783
|
+
wirePicker() {
|
|
6784
|
+
const button = this.require("table");
|
|
6785
|
+
const picker = this.require("gridpicker");
|
|
6786
|
+
const cells = this.require("gridcells");
|
|
6787
|
+
const highlight = (rows, cols) => {
|
|
6788
|
+
for (const cell of Array.from(cells.children)) {
|
|
6789
|
+
const on = Number(cell.dataset.r) < rows && Number(cell.dataset.c) < cols;
|
|
6790
|
+
if (on) cell.setAttribute("data-on", "");
|
|
6791
|
+
else cell.removeAttribute("data-on");
|
|
6792
|
+
}
|
|
6793
|
+
this.require("gridlabel").textContent = `${rows} \xD7 ${cols}`;
|
|
6794
|
+
};
|
|
6795
|
+
cells.addEventListener("pointerover", (event) => {
|
|
6796
|
+
const cell = event.target.closest("[data-r]");
|
|
6797
|
+
if (cell) highlight(Number(cell.dataset.r) + 1, Number(cell.dataset.c) + 1);
|
|
6798
|
+
});
|
|
6799
|
+
cells.addEventListener("mousedown", (event) => {
|
|
6800
|
+
const cell = event.target.closest("[data-r]");
|
|
6801
|
+
if (!cell || !this.live) return;
|
|
6802
|
+
event.preventDefault();
|
|
6803
|
+
const rows = Number(cell.dataset.r) + 1;
|
|
6804
|
+
const cols = Number(cell.dataset.c) + 1;
|
|
6805
|
+
this.closePicker();
|
|
6806
|
+
this.run(`table ${rows}\xD7${cols}`, () => this.live.insertTable(rows, cols, {
|
|
6807
|
+
borderless: this.require("gridborderless").checked,
|
|
6808
|
+
cellAlignment: this.require("gridalign").value
|
|
6809
|
+
}));
|
|
6810
|
+
});
|
|
6811
|
+
this.keepSelection(button);
|
|
6812
|
+
button.addEventListener("click", () => {
|
|
6813
|
+
if (!this.live) return;
|
|
6814
|
+
if (picker.hasAttribute("data-open")) {
|
|
6815
|
+
this.closePicker();
|
|
6816
|
+
return;
|
|
6817
|
+
}
|
|
6818
|
+
highlight(0, 0);
|
|
6819
|
+
picker.setAttribute("data-open", "");
|
|
6820
|
+
if (this.density === "full") {
|
|
6821
|
+
const rect = button.getBoundingClientRect();
|
|
6822
|
+
const host = this.element.getBoundingClientRect();
|
|
6823
|
+
picker.style.left = `${rect.left - host.left}px`;
|
|
6824
|
+
picker.style.top = `${rect.bottom - host.top + 5}px`;
|
|
6825
|
+
}
|
|
6826
|
+
});
|
|
6827
|
+
}
|
|
6828
|
+
closePicker() {
|
|
6829
|
+
this.control("gridpicker")?.removeAttribute("data-open");
|
|
6830
|
+
}
|
|
6831
|
+
maybeClosePicker(event) {
|
|
6832
|
+
const picker = this.control("gridpicker");
|
|
6833
|
+
if (!picker?.hasAttribute("data-open")) return;
|
|
6834
|
+
const target = event.target;
|
|
6835
|
+
if (picker.contains(target) || this.control("table")?.contains(target)) return;
|
|
6836
|
+
this.closePicker();
|
|
6837
|
+
}
|
|
6838
|
+
// ── tabs ────────────────────────────────────────────────────────────────────
|
|
6839
|
+
selectTab(name) {
|
|
6840
|
+
for (const tab of Array.from(this.element.querySelectorAll(".dxr-tab"))) {
|
|
6841
|
+
tab.setAttribute("aria-selected", String(tab.dataset.tab === name));
|
|
6842
|
+
}
|
|
6843
|
+
for (const panel of Array.from(this.element.querySelectorAll(".dxr-panel"))) {
|
|
6844
|
+
if (panel.dataset.panel === name) panel.setAttribute("data-active", "");
|
|
6845
|
+
else panel.removeAttribute("data-active");
|
|
6846
|
+
}
|
|
6847
|
+
if (name === "layout") this.syncPageNumbering();
|
|
6848
|
+
}
|
|
6849
|
+
// ── selection-driven state ──────────────────────────────────────────────────
|
|
6850
|
+
selectionElement() {
|
|
6851
|
+
const selection = (this.element.ownerDocument ?? document).getSelection();
|
|
6852
|
+
const node = selection?.anchorNode ?? null;
|
|
6853
|
+
if (!node) return null;
|
|
6854
|
+
const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
6855
|
+
return el && this.surface.contains(el) ? el : null;
|
|
6856
|
+
}
|
|
6857
|
+
syncSelection() {
|
|
6858
|
+
if (!this.live || this.destroyed) return;
|
|
6859
|
+
const el = this.selectionElement();
|
|
6860
|
+
const state = this.live.queryFormatState();
|
|
6861
|
+
for (const button of Array.from(this.element.querySelectorAll("button[data-cmd]"))) {
|
|
6862
|
+
button.classList.toggle("dxr-on", !!state[button.dataset.cmd]);
|
|
6863
|
+
}
|
|
6864
|
+
const fontsize = this.control("fontsize");
|
|
6865
|
+
if (el && fontsize && (this.element.ownerDocument ?? document).activeElement !== fontsize) {
|
|
6866
|
+
const px = parseFloat(getComputedStyle(el).fontSize);
|
|
6867
|
+
if (px) fontsize.value = String(Math.round(px * 0.75 * 2) / 2);
|
|
6868
|
+
}
|
|
6869
|
+
const inTable = !!el?.closest("table");
|
|
6870
|
+
const tableTab = this.element.querySelector('.dxr-tab[data-tab="table"]');
|
|
6871
|
+
if (tableTab) {
|
|
6872
|
+
tableTab.hidden = !inTable;
|
|
6873
|
+
if (!inTable && tableTab.getAttribute("aria-selected") === "true") this.selectTab("home");
|
|
6874
|
+
}
|
|
6875
|
+
this.refreshRailAnchor();
|
|
6876
|
+
}
|
|
6877
|
+
/** Scope is read from where the block lives, which is exactly what the anchor encodes. */
|
|
6878
|
+
scopeOf(el) {
|
|
6879
|
+
const band = el.closest(".docx-hf-band");
|
|
6880
|
+
if (band) return band.getAttribute("data-hf-band") === "header" ? "hdr" : "ftr";
|
|
6881
|
+
if (el.closest(".footnotes")) return "fn";
|
|
6882
|
+
if (el.closest(".endnotes")) return "en";
|
|
6883
|
+
return "body";
|
|
6884
|
+
}
|
|
6885
|
+
kindOf(el) {
|
|
6886
|
+
if (/^H[1-6]$/.test(el.tagName)) return "h";
|
|
6887
|
+
if (el.tagName === "LI" || el.hasAttribute("data-list-marker") || el.querySelector("[data-list-marker]")) {
|
|
6888
|
+
return "li";
|
|
6889
|
+
}
|
|
6890
|
+
return "p";
|
|
6891
|
+
}
|
|
6892
|
+
/**
|
|
6893
|
+
* Block count and session handle — an O(blocks) query, so it runs only when
|
|
6894
|
+
* something could have changed them (a command, or a document opening), never on
|
|
6895
|
+
* the selectionchange path that fires per keystroke.
|
|
6896
|
+
*/
|
|
6897
|
+
refreshRailCounts() {
|
|
6898
|
+
const blocks = this.control("railBlocks");
|
|
6899
|
+
if (!blocks) return;
|
|
6900
|
+
blocks.textContent = this.live ? String(this.surface.querySelectorAll("[data-anchor]").length) : "\u2014";
|
|
6901
|
+
const session = this.control("railSession");
|
|
6902
|
+
if (session) session.textContent = this.live ? `#${this.live.sessionHandle}` : "\u2014";
|
|
6903
|
+
}
|
|
6904
|
+
/** The focused anchor — cheap, and the only part the caret can change. */
|
|
6905
|
+
refreshRailAnchor() {
|
|
6906
|
+
const anchorEl = this.control("railAnchor");
|
|
6907
|
+
if (!anchorEl) return;
|
|
6908
|
+
const block = this.selectionElement()?.closest("[data-anchor]") ?? null;
|
|
6909
|
+
if (!block) {
|
|
6910
|
+
anchorEl.textContent = this.live ? "none" : "\u2014";
|
|
6911
|
+
this.lastAnchorText = "";
|
|
6912
|
+
return;
|
|
6913
|
+
}
|
|
6914
|
+
const unid = block.getAttribute("data-anchor") ?? "";
|
|
6915
|
+
const text = `${this.kindOf(block)}:${this.scopeOf(block)}:${unid.slice(0, 10)}\u2026`;
|
|
6916
|
+
if (text === this.lastAnchorText) return;
|
|
6917
|
+
anchorEl.textContent = text;
|
|
6918
|
+
this.lastAnchorText = text;
|
|
6919
|
+
anchorEl.classList.remove("dxr-flash");
|
|
6920
|
+
void anchorEl.offsetWidth;
|
|
6921
|
+
anchorEl.classList.add("dxr-flash");
|
|
6922
|
+
}
|
|
6923
|
+
// ── loading overlay ─────────────────────────────────────────────────────────
|
|
6924
|
+
createLoaderController() {
|
|
6925
|
+
const overlay = this.control("loader");
|
|
6926
|
+
if (!overlay) {
|
|
6927
|
+
const noop = () => {
|
|
6928
|
+
};
|
|
6929
|
+
return { show: noop, stage: noop, progress: noop, done: noop, fail: noop };
|
|
6930
|
+
}
|
|
6931
|
+
const options = this.loaderOptions ?? {};
|
|
6932
|
+
const eyebrow = this.control("loaderEyebrow");
|
|
6933
|
+
if (eyebrow && options.eyebrow) eyebrow.textContent = options.eyebrow;
|
|
6934
|
+
const meta = this.control("loaderMeta");
|
|
6935
|
+
if (meta && options.meta) meta.textContent = options.meta;
|
|
6936
|
+
this.control("loaderRetry")?.addEventListener("click", () => {
|
|
6937
|
+
if (options.onRetry) options.onRetry();
|
|
6938
|
+
else location.reload();
|
|
6939
|
+
});
|
|
6940
|
+
if (this.features.length === 0) this.control("loaderAd")?.remove();
|
|
6941
|
+
const setProgress = (percent, label) => {
|
|
6942
|
+
const bar = this.control("loaderBar");
|
|
6943
|
+
if (bar) bar.style.width = `${Math.max(0, Math.min(100, percent))}%`;
|
|
6944
|
+
if (label) {
|
|
6945
|
+
const el = this.control("loaderLabel");
|
|
6946
|
+
if (el) el.textContent = label;
|
|
6947
|
+
}
|
|
6948
|
+
};
|
|
6949
|
+
return {
|
|
6950
|
+
show: () => {
|
|
6951
|
+
overlay.hidden = false;
|
|
6952
|
+
overlay.removeAttribute("data-done");
|
|
6953
|
+
overlay.removeAttribute("data-error");
|
|
6954
|
+
this.setState("loading");
|
|
6955
|
+
this.showFeature(0);
|
|
6956
|
+
this.startRotation();
|
|
6957
|
+
this.loaderStage(0);
|
|
6958
|
+
},
|
|
6959
|
+
stage: (step) => this.loaderStage(step),
|
|
6960
|
+
progress: setProgress,
|
|
6961
|
+
done: () => {
|
|
6962
|
+
this.stopRotation();
|
|
6963
|
+
this.setState("ready");
|
|
6964
|
+
setTimeout(() => overlay.setAttribute("data-done", ""), 420);
|
|
6965
|
+
setTimeout(() => {
|
|
6966
|
+
overlay.hidden = true;
|
|
6967
|
+
}, 1050);
|
|
6968
|
+
},
|
|
6969
|
+
fail: (error) => {
|
|
6970
|
+
this.stopRotation();
|
|
6971
|
+
this.setState("error");
|
|
6972
|
+
overlay.hidden = false;
|
|
6973
|
+
overlay.setAttribute("data-error", "");
|
|
6974
|
+
overlay.removeAttribute("data-done");
|
|
6975
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6976
|
+
const title = this.control("loaderTitle");
|
|
6977
|
+
if (title) title.textContent = "The local engine did not start";
|
|
6978
|
+
const copy = this.control("loaderCopy");
|
|
6979
|
+
if (copy) copy.textContent = message.slice(0, 220);
|
|
6980
|
+
setProgress(100, "Load failed");
|
|
6981
|
+
this.setStatus(message.slice(0, 180));
|
|
6982
|
+
}
|
|
6983
|
+
};
|
|
6984
|
+
}
|
|
6985
|
+
loaderStage(step) {
|
|
6986
|
+
const stage = typeof step === "number" ? this.stages[Math.max(0, Math.min(this.stages.length - 1, step))] : step;
|
|
6987
|
+
if (!stage) return;
|
|
6988
|
+
const title = this.control("loaderTitle");
|
|
6989
|
+
if (title && stage.title) title.textContent = stage.title;
|
|
6990
|
+
const copy = this.control("loaderCopy");
|
|
6991
|
+
if (copy && stage.copy) copy.textContent = stage.copy;
|
|
6992
|
+
const bar = this.control("loaderBar");
|
|
6993
|
+
if (bar && stage.progress != null) bar.style.width = `${stage.progress}%`;
|
|
6994
|
+
const label = this.control("loaderLabel");
|
|
6995
|
+
if (label && stage.label) label.textContent = stage.label;
|
|
6996
|
+
}
|
|
6997
|
+
showFeature(index) {
|
|
6998
|
+
const feature = this.features[index];
|
|
6999
|
+
if (!feature) return;
|
|
7000
|
+
const card = this.control("loaderAd");
|
|
7001
|
+
const number = this.control("loaderNumber");
|
|
7002
|
+
const title = this.control("loaderAdTitle");
|
|
7003
|
+
const copy = this.control("loaderAdCopy");
|
|
7004
|
+
if (number) number.textContent = feature.number;
|
|
7005
|
+
if (title) title.textContent = feature.title;
|
|
7006
|
+
if (copy) copy.textContent = feature.copy;
|
|
7007
|
+
if (card) {
|
|
7008
|
+
card.removeAttribute("data-swap");
|
|
7009
|
+
void card.offsetWidth;
|
|
7010
|
+
card.setAttribute("data-swap", "");
|
|
7011
|
+
}
|
|
7012
|
+
}
|
|
7013
|
+
startRotation() {
|
|
7014
|
+
this.stopRotation();
|
|
7015
|
+
if (this.features.length < 2) return;
|
|
7016
|
+
const every = this.loaderOptions?.rotateMs ?? 1750;
|
|
7017
|
+
this.featureTimer = setInterval(() => {
|
|
7018
|
+
this.featureIndex = (this.featureIndex + 1) % this.features.length;
|
|
7019
|
+
this.showFeature(this.featureIndex);
|
|
7020
|
+
}, every);
|
|
7021
|
+
}
|
|
7022
|
+
stopRotation() {
|
|
7023
|
+
if (this.featureTimer == null) return;
|
|
7024
|
+
clearInterval(this.featureTimer);
|
|
7025
|
+
this.featureTimer = null;
|
|
7026
|
+
}
|
|
7027
|
+
};
|
|
7028
|
+
|
|
5139
7029
|
// src/index.ts
|
|
5140
7030
|
var import_meta = {};
|
|
5141
7031
|
function openDocxSession2(bytes, settings) {
|
|
@@ -6387,7 +8277,7 @@ var Docxodus = (() => {
|
|
|
6387
8277
|
await initialize(dir);
|
|
6388
8278
|
}
|
|
6389
8279
|
}
|
|
6390
|
-
function
|
|
8280
|
+
function resolveContainer2(container) {
|
|
6391
8281
|
if (typeof container !== "string") return container;
|
|
6392
8282
|
const el = document.querySelector(container);
|
|
6393
8283
|
if (!el) throw new Error(`Docxodus embed: no element matches "${container}"`);
|
|
@@ -6540,7 +8430,7 @@ ${parsed.documentElement.outerHTML}`;
|
|
|
6540
8430
|
throw new Error("Docxodus embed: unsupported document source");
|
|
6541
8431
|
}
|
|
6542
8432
|
async function createViewer(container, source, options = {}) {
|
|
6543
|
-
const el =
|
|
8433
|
+
const el = resolveContainer2(container);
|
|
6544
8434
|
const { wasmBasePath: wasmBasePath2, ...conversion } = options;
|
|
6545
8435
|
await ensureWasm(wasmBasePath2);
|
|
6546
8436
|
const mount = createScopedMount(el);
|
|
@@ -6577,7 +8467,7 @@ ${parsed.documentElement.outerHTML}`;
|
|
|
6577
8467
|
};
|
|
6578
8468
|
}
|
|
6579
8469
|
async function createEditor(container, source, options = {}) {
|
|
6580
|
-
const el =
|
|
8470
|
+
const el = resolveContainer2(container);
|
|
6581
8471
|
const { wasmBasePath: wasmBasePath2, ...editorOptions } = options;
|
|
6582
8472
|
await ensureWasm(wasmBasePath2);
|
|
6583
8473
|
const mount = createScopedMount(el);
|
|
@@ -6592,5 +8482,48 @@ ${parsed.documentElement.outerHTML}`;
|
|
|
6592
8482
|
throw error;
|
|
6593
8483
|
}
|
|
6594
8484
|
}
|
|
8485
|
+
function nameFromSource(source) {
|
|
8486
|
+
if (typeof source !== "string") return void 0;
|
|
8487
|
+
try {
|
|
8488
|
+
const path = new URL(source, typeof location === "undefined" ? void 0 : location.href).pathname;
|
|
8489
|
+
return decodeURIComponent(path.split("/").pop() ?? "") || void 0;
|
|
8490
|
+
} catch {
|
|
8491
|
+
return source.split("/").pop() || void 0;
|
|
8492
|
+
}
|
|
8493
|
+
}
|
|
8494
|
+
async function createRibbonEditor(container, source, options = {}) {
|
|
8495
|
+
const el = resolveContainer2(container);
|
|
8496
|
+
const { wasmBasePath: wasmBasePath2, ...ribbonOptions } = options;
|
|
8497
|
+
const mount = createScopedMount(el);
|
|
8498
|
+
mount.root.style.height = "100%";
|
|
8499
|
+
mount.root.style.minHeight = "0";
|
|
8500
|
+
const ribbon = mountRibbon(mount.root, {
|
|
8501
|
+
documentName: ribbonOptions.documentName ?? nameFromSource(source),
|
|
8502
|
+
...ribbonOptions,
|
|
8503
|
+
// Exports arrive after the runtime boots; the loader covers that gap.
|
|
8504
|
+
exports: void 0
|
|
8505
|
+
});
|
|
8506
|
+
try {
|
|
8507
|
+
ribbon.loader.stage(0);
|
|
8508
|
+
await ensureWasm(wasmBasePath2);
|
|
8509
|
+
ribbon.setExports(
|
|
8510
|
+
createScopedEditorExports(
|
|
8511
|
+
getWasmExports(),
|
|
8512
|
+
`${mount.selector} [data-dxr-surface]`
|
|
8513
|
+
)
|
|
8514
|
+
);
|
|
8515
|
+
ribbon.loader.stage(1);
|
|
8516
|
+
const bytes = source == null ? null : await toDocumentBytes(source);
|
|
8517
|
+
ribbon.loader.stage(2);
|
|
8518
|
+
if (bytes == null) ribbon.openBlank(ribbonOptions.documentName);
|
|
8519
|
+
else ribbon.open(bytes, ribbonOptions.documentName ?? nameFromSource(source));
|
|
8520
|
+
ribbon.loader.stage(3);
|
|
8521
|
+
ribbon.loader.done();
|
|
8522
|
+
return ribbon;
|
|
8523
|
+
} catch (error) {
|
|
8524
|
+
ribbon.loader.fail(error);
|
|
8525
|
+
throw error;
|
|
8526
|
+
}
|
|
8527
|
+
}
|
|
6595
8528
|
return __toCommonJS(embed_exports);
|
|
6596
8529
|
})();
|