web-doc 0.7.0 → 0.8.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/THIRD_PARTY_NOTICES.md +10 -3
- package/dist/contracts.d.ts +6 -0
- package/dist/edit/ai/outline.d.ts +47 -0
- package/dist/edit/ai/outline.js +338 -0
- package/dist/edit/ai/targets.d.ts +16 -0
- package/dist/edit/ai/targets.js +309 -0
- package/dist/edit/ai/tools.d.ts +28 -0
- package/dist/edit/ai/tools.js +605 -0
- package/dist/edit/ai/types.d.ts +175 -0
- package/dist/edit/ai/types.js +1 -0
- package/dist/edit/docx/engine.d.ts +10 -4
- package/dist/edit/docx/engine.js +80 -6
- package/dist/edit/docx/model.d.ts +2 -0
- package/dist/edit/docx/model.js +11 -0
- package/dist/edit/docx/operations.d.ts +10 -0
- package/dist/edit/docx/provider.d.ts +4 -1
- package/dist/edit/docx/provider.js +3 -0
- package/dist/edit/docx/session.d.ts +12 -1
- package/dist/edit/docx/session.js +41 -0
- package/dist/edit/docx/structure-ops.js +39 -4
- package/dist/edit/docx/table-ops.js +26 -4
- package/dist/edit/docx/text-ops.d.ts +22 -0
- package/dist/edit/docx/text-ops.js +49 -16
- package/dist/edit/docx/text.js +21 -0
- package/dist/edit/docx/tracked.d.ts +52 -0
- package/dist/edit/docx/tracked.js +347 -0
- package/dist/edit/docx/types.d.ts +16 -1
- package/dist/edit/docx/write.d.ts +19 -5
- package/dist/edit/docx/write.js +31 -8
- package/dist/edit/engine.d.ts +14 -3
- package/dist/edit/history.d.ts +27 -3
- package/dist/edit/history.js +29 -7
- package/dist/edit/pdf/range-map.d.ts +4 -2
- package/dist/edit/pdf/session.d.ts +10 -0
- package/dist/edit/pdf/session.js +39 -0
- package/dist/edit/pptx/handler.js +10 -2
- package/dist/edit/pptx/session.d.ts +10 -0
- package/dist/edit/pptx/session.js +31 -0
- package/dist/edit/session.d.ts +11 -0
- package/dist/edit/session.js +269 -27
- package/dist/edit/types.d.ts +17 -3
- package/dist/edit/worker-engine.d.ts +2 -2
- package/dist/edit/worker-engine.js +2 -2
- package/dist/fuzzy-alignment.d.ts +11 -4
- package/dist/fuzzy-alignment.js +3 -9
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +4 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +11 -2
- package/dist/limits.js +3 -0
- package/dist/worker-protocol.d.ts +1 -1
- package/dist/workers/fuzzy-search-worker.js +1 -1
- package/dist/workers/ooxml-edit-worker.js +1400 -859
- package/dist/workers/pdf-edit-worker.js +4 -1
- package/package.json +1 -1
|
@@ -2452,6 +2452,10 @@ var Reader = class _Reader {
|
|
|
2452
2452
|
paragraph(paragraph) {
|
|
2453
2453
|
for (const child of paragraph.children) this.#child(child, void 0);
|
|
2454
2454
|
if (this.#field) this.#closeField();
|
|
2455
|
+
const pPr = paragraph.children.find(
|
|
2456
|
+
(child) => child.local === "pPr" && child.namespace === W_NS
|
|
2457
|
+
);
|
|
2458
|
+
if (pPr && paragraphMarkTracked(pPr)) this.#tracked();
|
|
2455
2459
|
}
|
|
2456
2460
|
#child(node, wrapper) {
|
|
2457
2461
|
if (node.namespace !== W_NS) {
|
|
@@ -2653,6 +2657,17 @@ var Reader = class _Reader {
|
|
|
2653
2657
|
this.text += item.text;
|
|
2654
2658
|
}
|
|
2655
2659
|
};
|
|
2660
|
+
var MARK_REVISIONS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
2661
|
+
function paragraphMarkTracked(pPr) {
|
|
2662
|
+
for (const child of pPr.children) {
|
|
2663
|
+
if (child.namespace !== W_NS) continue;
|
|
2664
|
+
if (child.local === "rPr" && child.children.some(
|
|
2665
|
+
(mark) => mark.namespace === W_NS && MARK_REVISIONS.has(mark.local)
|
|
2666
|
+
))
|
|
2667
|
+
return true;
|
|
2668
|
+
}
|
|
2669
|
+
return false;
|
|
2670
|
+
}
|
|
2656
2671
|
function readParagraphText(part, paragraph) {
|
|
2657
2672
|
const reader = new Reader(part);
|
|
2658
2673
|
reader.paragraph(paragraph);
|
|
@@ -2692,6 +2707,17 @@ var DocxModel = class _DocxModel {
|
|
|
2692
2707
|
unauthoredIds;
|
|
2693
2708
|
takenIds;
|
|
2694
2709
|
styles;
|
|
2710
|
+
#maxRevisionId;
|
|
2711
|
+
/** The largest `w:id` of the main part, over revisions and range markup alike; 0 without any. */
|
|
2712
|
+
get maxRevisionId() {
|
|
2713
|
+
if (this.#maxRevisionId === void 0) {
|
|
2714
|
+
let max = 0;
|
|
2715
|
+
for (const match of this.document.text.matchAll(/ w:id="(\d{1,9})"/g))
|
|
2716
|
+
max = Math.max(max, Number(match[1]));
|
|
2717
|
+
this.#maxRevisionId = max;
|
|
2718
|
+
}
|
|
2719
|
+
return this.#maxRevisionId;
|
|
2720
|
+
}
|
|
2695
2721
|
/**
|
|
2696
2722
|
* Builds the index. `unauthored` carries the ids of the paragraphs that
|
|
2697
2723
|
* have no `w14:paraId`, in document order, as the engine tracks them
|
|
@@ -3448,8 +3474,13 @@ function mergedProperties(part, source, tag, order, set) {
|
|
|
3448
3474
|
function toggleXml(local, on) {
|
|
3449
3475
|
return on ? `<w:${local}/>` : `<w:${local} w:val="0"/>`;
|
|
3450
3476
|
}
|
|
3451
|
-
function changedRunProperties(part, rPr, change, styles) {
|
|
3477
|
+
function changedRunProperties(part, rPr, change, styles, revision) {
|
|
3452
3478
|
const set = /* @__PURE__ */ new Map();
|
|
3479
|
+
if (revision !== void 0)
|
|
3480
|
+
set.set(
|
|
3481
|
+
"rPrChange",
|
|
3482
|
+
`<w:rPrChange${revision}>${wrapped("w:rPr", innerPropertiesXml(part, rPr, ["rPrChange"]))}</w:rPrChange>`
|
|
3483
|
+
);
|
|
3453
3484
|
if (change.bold !== void 0) {
|
|
3454
3485
|
set.set("b", toggleXml("b", change.bold));
|
|
3455
3486
|
set.set("bCs", toggleXml("bCs", change.bold));
|
|
@@ -3497,8 +3528,19 @@ function fontsXml(part, rPr, family) {
|
|
|
3497
3528
|
const rest = kept.map((a) => ` ${a.name}="${a.rawValue}"`).join("");
|
|
3498
3529
|
return `<w:rFonts w:ascii="${face}" w:hAnsi="${face}"${rest}/>`;
|
|
3499
3530
|
}
|
|
3500
|
-
function
|
|
3531
|
+
function wrapped(tag, inner) {
|
|
3532
|
+
return inner.length === 0 ? `<${tag}/>` : `<${tag}>${inner}</${tag}>`;
|
|
3533
|
+
}
|
|
3534
|
+
function innerPropertiesXml(part, properties, without) {
|
|
3535
|
+
return (properties?.children ?? []).filter((child) => !without.includes(child.local)).map((child) => sliceOf(part, child)).join("");
|
|
3536
|
+
}
|
|
3537
|
+
function changedParagraphProperties(part, pPr, change, revision) {
|
|
3501
3538
|
const set = /* @__PURE__ */ new Map();
|
|
3539
|
+
if (revision !== void 0)
|
|
3540
|
+
set.set(
|
|
3541
|
+
"pPrChange",
|
|
3542
|
+
`<w:pPrChange${revision}>${wrapped("w:pPr", innerPropertiesXml(part, pPr, ["rPr", "sectPr", "pPrChange"]))}</w:pPrChange>`
|
|
3543
|
+
);
|
|
3502
3544
|
if (change.align !== void 0)
|
|
3503
3545
|
set.set("jc", `<w:jc w:val="${alignValue(change.align)}"/>`);
|
|
3504
3546
|
if (change.spacing !== void 0) {
|
|
@@ -3544,11 +3586,11 @@ function alignValue(align) {
|
|
|
3544
3586
|
return "left";
|
|
3545
3587
|
}
|
|
3546
3588
|
}
|
|
3547
|
-
function paragraphMarkProperties(part, pPr, change, styles) {
|
|
3589
|
+
function paragraphMarkProperties(part, pPr, change, styles, revision) {
|
|
3548
3590
|
const rPr = pPr?.children.find(
|
|
3549
3591
|
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
3550
3592
|
);
|
|
3551
|
-
const changed = changedRunProperties(part, rPr, change, styles);
|
|
3593
|
+
const changed = changedRunProperties(part, rPr, change, styles, revision);
|
|
3552
3594
|
return mergedProperties(
|
|
3553
3595
|
part,
|
|
3554
3596
|
pPr,
|
|
@@ -3599,538 +3641,653 @@ function namespacePatches(part) {
|
|
|
3599
3641
|
return items;
|
|
3600
3642
|
}
|
|
3601
3643
|
|
|
3602
|
-
// src/edit/docx/
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
var EMU_PER_PT = 12700;
|
|
3609
|
-
function blockNode(record) {
|
|
3610
|
-
return record.node;
|
|
3644
|
+
// src/edit/docx/text-ops.ts
|
|
3645
|
+
function topOf(node, container) {
|
|
3646
|
+
let current = node;
|
|
3647
|
+
while (current.parent && current.parent !== container)
|
|
3648
|
+
current = current.parent;
|
|
3649
|
+
return current;
|
|
3611
3650
|
}
|
|
3612
|
-
function
|
|
3613
|
-
const
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
"invalid-value",
|
|
3620
|
-
"Exactly one of before and after names the reference element"
|
|
3621
|
-
);
|
|
3622
|
-
return void 0;
|
|
3623
|
-
}
|
|
3624
|
-
const side = operation3.before !== void 0 ? "before" : "after";
|
|
3625
|
-
const id = operation3[side];
|
|
3626
|
-
const record = context.model.byId.get(id);
|
|
3627
|
-
if (!record || record.kind !== "paragraph" && record.kind !== "table") {
|
|
3628
|
-
issue(
|
|
3629
|
-
`/${side}`,
|
|
3630
|
-
record ? "invalid-target" : "unknown-target",
|
|
3631
|
-
record ? `Element ${id} is not a paragraph or a table` : `No element ${id}`
|
|
3632
|
-
);
|
|
3633
|
-
return void 0;
|
|
3651
|
+
function unitsOf(container, items) {
|
|
3652
|
+
const byTop = /* @__PURE__ */ new Map();
|
|
3653
|
+
for (const item of items) {
|
|
3654
|
+
const top = topOf(item.run, container);
|
|
3655
|
+
const list = byTop.get(top) ?? [];
|
|
3656
|
+
list.push(item);
|
|
3657
|
+
byTop.set(top, list);
|
|
3634
3658
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3659
|
+
const groupEnd = /* @__PURE__ */ new Map();
|
|
3660
|
+
for (const item of items)
|
|
3661
|
+
if (item.fieldRuns && item.fieldRuns.length > 1) {
|
|
3662
|
+
const first = topOf(item.fieldRuns[0], container);
|
|
3663
|
+
const last = topOf(item.fieldRuns.at(-1), container);
|
|
3664
|
+
if (first !== last) groupEnd.set(first, last);
|
|
3665
|
+
}
|
|
3666
|
+
const units = [];
|
|
3667
|
+
let position = 0;
|
|
3668
|
+
const children = container.children.filter(
|
|
3669
|
+
(child) => !(child.local === "pPr" && child.namespace === W_NS)
|
|
3644
3670
|
);
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
const id = model.paragraphIds.get(paragraph);
|
|
3655
|
-
if (id) out.push(id);
|
|
3656
|
-
}
|
|
3657
|
-
return out;
|
|
3658
|
-
}
|
|
3659
|
-
function elementIdsUnder(model, node) {
|
|
3660
|
-
const out = [];
|
|
3661
|
-
for (const record of model.records) {
|
|
3662
|
-
let current = record.node;
|
|
3663
|
-
while (current && current !== node) current = current.parent;
|
|
3664
|
-
if (current === node) out.push(record.elementId);
|
|
3665
|
-
}
|
|
3666
|
-
return out;
|
|
3667
|
-
}
|
|
3668
|
-
function stampedSlice(model, node) {
|
|
3669
|
-
const part = model.document;
|
|
3670
|
-
const targets = [
|
|
3671
|
-
...node.local === "p" && node.namespace === W_NS ? [node] : [],
|
|
3672
|
-
...part.findAll("p", node).filter((p) => p !== node && p.namespace === W_NS)
|
|
3673
|
-
].filter((paragraph) => {
|
|
3674
|
-
const id = model.paragraphIds.get(paragraph);
|
|
3675
|
-
return id !== void 0 && model.unauthoredSet.has(id);
|
|
3676
|
-
}).sort((a, b) => b.start - a.start);
|
|
3677
|
-
let xml = sliceOf(part, node);
|
|
3678
|
-
const stamped = [];
|
|
3679
|
-
for (const paragraph of targets) {
|
|
3680
|
-
const id = model.paragraphIds.get(paragraph);
|
|
3681
|
-
if (part.attribute(paragraph, "w14:paraId")) {
|
|
3682
|
-
stamped.push(id);
|
|
3683
|
-
continue;
|
|
3671
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
3672
|
+
const child = children[index];
|
|
3673
|
+
const end = groupEnd.get(child);
|
|
3674
|
+
const nodes = [child];
|
|
3675
|
+
if (end) {
|
|
3676
|
+
let cursor = index + 1;
|
|
3677
|
+
while (cursor < children.length && children[cursor - 1] !== end)
|
|
3678
|
+
nodes.push(children[cursor++]);
|
|
3679
|
+
index = cursor - 1;
|
|
3684
3680
|
}
|
|
3685
|
-
const
|
|
3686
|
-
const
|
|
3687
|
-
const
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
xml = `${xml.slice(0, offset)} w14:paraId="${id}"${xml.slice(offset)}`;
|
|
3692
|
-
stamped.push(id);
|
|
3681
|
+
const own = nodes.flatMap((node) => byTop.get(node) ?? []);
|
|
3682
|
+
const kind = own.length === 0 ? "zero" : end || child.local === "fldSimple" || own.some((item) => item.fieldRuns) ? "field" : child.local === "r" ? "run" : "wrapper";
|
|
3683
|
+
const start = own.length > 0 ? own[0].start : position;
|
|
3684
|
+
const stop = own.length > 0 ? own.at(-1).end : position;
|
|
3685
|
+
units.push({ nodes, kind, start, end: stop, items: own });
|
|
3686
|
+
position = stop;
|
|
3693
3687
|
}
|
|
3694
|
-
return
|
|
3688
|
+
return units;
|
|
3695
3689
|
}
|
|
3696
|
-
function
|
|
3697
|
-
|
|
3698
|
-
return table && table.rows[0]?.[0]?.paragraphs[0] === record ? table : void 0;
|
|
3690
|
+
function rPrOf(part, item) {
|
|
3691
|
+
return item ? sliceOf(part, item.rPr) : "";
|
|
3699
3692
|
}
|
|
3700
|
-
function
|
|
3701
|
-
return
|
|
3693
|
+
function startTagOf(part, node) {
|
|
3694
|
+
return part.text.slice(node.start, node.contentStart);
|
|
3702
3695
|
}
|
|
3703
|
-
function
|
|
3704
|
-
|
|
3705
|
-
transaction.patch(part, [...namespacePatches(part), ...items]);
|
|
3706
|
-
return transaction.commit().then((change) => ({ ...result, warnings: change.warnings }));
|
|
3696
|
+
function endTagOf(part, node) {
|
|
3697
|
+
return part.text.slice(node.contentEnd, node.end);
|
|
3707
3698
|
}
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
if (
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
}
|
|
3716
|
-
if (operation3.style?.fontFamily !== void 0) {
|
|
3717
|
-
const font = attributeProblem(operation3.style.fontFamily);
|
|
3718
|
-
if (font) issue("/style/fontFamily", "invalid-value", `Font: ${font}`);
|
|
3699
|
+
function contentOf(part, items, from, to, includeStart) {
|
|
3700
|
+
let out = "";
|
|
3701
|
+
for (const item of items) {
|
|
3702
|
+
if (item.start === item.end) {
|
|
3703
|
+
const after = includeStart ? item.start >= from : item.start > from;
|
|
3704
|
+
if (after && item.start <= to) out += sliceOf(part, item.child);
|
|
3705
|
+
continue;
|
|
3719
3706
|
}
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
const node = blockNode(record);
|
|
3727
|
-
const sourceRPr = referenceRunProperties(part, record);
|
|
3728
|
-
const rPr = operation3.style ? changedRunProperties(
|
|
3729
|
-
part,
|
|
3730
|
-
sourceRPr,
|
|
3731
|
-
operation3.style,
|
|
3732
|
-
context.model.styles
|
|
3733
|
-
) : sliceOf(part, sourceRPr);
|
|
3734
|
-
const pPr = record.kind === "paragraph" ? paragraphPropertiesWithoutSection(part, record.pPr) : "";
|
|
3735
|
-
const segments = normalizeText(operation3.text).split("\n");
|
|
3736
|
-
const createdIds = [];
|
|
3737
|
-
const xml = segments.map((segment) => {
|
|
3738
|
-
const id = context.freshParagraphId();
|
|
3739
|
-
createdIds.push(`p:${id}`);
|
|
3740
|
-
return paragraphXml(
|
|
3741
|
-
void 0,
|
|
3742
|
-
id,
|
|
3743
|
-
pPr,
|
|
3744
|
-
runXml(rPr, runContentXml(segment))
|
|
3707
|
+
const begin = Math.max(item.start, from);
|
|
3708
|
+
const stop = Math.min(item.end, to);
|
|
3709
|
+
if (stop <= begin) continue;
|
|
3710
|
+
if (item.kind === "text" && item.child.local === "t")
|
|
3711
|
+
out += runContentXml(
|
|
3712
|
+
item.text.slice(begin - item.start, stop - item.start)
|
|
3745
3713
|
);
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
const table = side === "before" && record.kind === "paragraph" ? firstCellParagraph(record) : void 0;
|
|
3749
|
-
const remappedIds = table ? tableRemap(table, createdIds[0].slice(2)) : void 0;
|
|
3750
|
-
return commit(context, items, {
|
|
3751
|
-
createdIds,
|
|
3752
|
-
reflowFrom: record.id,
|
|
3753
|
-
...remappedIds ? { remappedIds } : {}
|
|
3754
|
-
});
|
|
3714
|
+
else if (begin === item.start && stop === item.end)
|
|
3715
|
+
out += sliceOf(part, item.child);
|
|
3755
3716
|
}
|
|
3756
|
-
|
|
3757
|
-
function bodyTailPatch(context, remaining) {
|
|
3758
|
-
const { model } = context;
|
|
3759
|
-
const last = remaining.at(-1);
|
|
3760
|
-
if (last && last.kind === "paragraph") return void 0;
|
|
3761
|
-
const id = context.freshParagraphId();
|
|
3762
|
-
const xml = paragraphXml(void 0, id, "", "");
|
|
3763
|
-
const part = model.document;
|
|
3764
|
-
const patch = last ? patches.insertAfter(part, last.node, xml) : model.bodySectPr ? patches.insertBefore(part, model.bodySectPr, xml) : patches.appendChild(part, model.body, xml);
|
|
3765
|
-
return { patch, id };
|
|
3717
|
+
return out;
|
|
3766
3718
|
}
|
|
3767
|
-
function
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3719
|
+
function droppedInlines(model, record, items, from, to) {
|
|
3720
|
+
const removedIds = [];
|
|
3721
|
+
const removedParagraphIds = [];
|
|
3722
|
+
for (const item of items) {
|
|
3723
|
+
if (item.kind !== "picture" && item.kind !== "object" && item.kind !== "anchor")
|
|
3724
|
+
continue;
|
|
3725
|
+
const inside = item.start === item.end ? from < item.start && item.start < to : item.start >= from && item.end <= to;
|
|
3726
|
+
if (!inside) continue;
|
|
3727
|
+
const inline = record.inlines.find(
|
|
3728
|
+
(candidate) => candidate.node === item.child
|
|
3729
|
+
);
|
|
3730
|
+
if (inline) removedIds.push(inline.elementId);
|
|
3731
|
+
for (const paragraph of model.document.findAll("p", item.child)) {
|
|
3732
|
+
const id = model.paragraphIds.get(paragraph);
|
|
3733
|
+
if (id && paragraph.namespace === W_NS) removedParagraphIds.push(id);
|
|
3734
|
+
}
|
|
3774
3735
|
}
|
|
3775
|
-
return
|
|
3776
|
-
(block) => block.kind === "paragraph" && block !== record
|
|
3777
|
-
);
|
|
3736
|
+
return { removedIds, removedParagraphIds };
|
|
3778
3737
|
}
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3738
|
+
function splitAt(part, container, items, start, end, insert, fallbackRPr) {
|
|
3739
|
+
let before = "";
|
|
3740
|
+
let after = "";
|
|
3741
|
+
let inserted = false;
|
|
3742
|
+
let nested = false;
|
|
3743
|
+
let rPr = fallbackRPr;
|
|
3744
|
+
let lastRPr;
|
|
3745
|
+
const raw = (unit) => unit.nodes.map((node) => sliceOf(part, node)).join("");
|
|
3746
|
+
const place = (style) => {
|
|
3747
|
+
rPr = style ?? lastRPr ?? fallbackRPr;
|
|
3748
|
+
before += insert(rPr);
|
|
3749
|
+
inserted = true;
|
|
3750
|
+
};
|
|
3751
|
+
for (const unit of unitsOf(container, items)) {
|
|
3752
|
+
if (unit.kind === "zero") {
|
|
3753
|
+
if (inserted) after += raw(unit);
|
|
3754
|
+
else before += raw(unit);
|
|
3755
|
+
continue;
|
|
3756
|
+
}
|
|
3757
|
+
if (unit.end <= start) {
|
|
3758
|
+
before += raw(unit);
|
|
3759
|
+
lastRPr = rPrOf(part, unit.items.at(-1));
|
|
3760
|
+
if (!inserted && start === end && unit.end === start) place(lastRPr);
|
|
3761
|
+
continue;
|
|
3762
|
+
}
|
|
3763
|
+
if (unit.start >= end) {
|
|
3764
|
+
if (!inserted) place(lastRPr ?? rPrOf(part, unit.items[0]));
|
|
3765
|
+
after += raw(unit);
|
|
3766
|
+
continue;
|
|
3767
|
+
}
|
|
3768
|
+
if (unit.start >= start && unit.end <= end) {
|
|
3769
|
+
if (!inserted) place(rPrOf(part, unit.items[0]));
|
|
3770
|
+
continue;
|
|
3771
|
+
}
|
|
3772
|
+
if (unit.kind === "run") {
|
|
3773
|
+
const style = rPrOf(part, unit.items[0]);
|
|
3774
|
+
const head = runXml(
|
|
3775
|
+
style,
|
|
3776
|
+
contentOf(part, unit.items, unit.start, start, true)
|
|
3787
3777
|
);
|
|
3788
|
-
|
|
3778
|
+
const tail = runXml(
|
|
3779
|
+
style,
|
|
3780
|
+
contentOf(part, unit.items, end, unit.end, start < end)
|
|
3781
|
+
);
|
|
3782
|
+
if (!inserted) {
|
|
3783
|
+
before += head;
|
|
3784
|
+
place(style);
|
|
3785
|
+
after += tail;
|
|
3786
|
+
} else after += tail;
|
|
3787
|
+
continue;
|
|
3789
3788
|
}
|
|
3790
|
-
if (
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3789
|
+
if (unit.kind === "wrapper" && unit.start < start && end < unit.end) {
|
|
3790
|
+
const wrapper = unit.nodes[0];
|
|
3791
|
+
const inner = splitAt(
|
|
3792
|
+
part,
|
|
3793
|
+
wrapper,
|
|
3794
|
+
unit.items,
|
|
3795
|
+
start,
|
|
3796
|
+
end,
|
|
3797
|
+
insert,
|
|
3798
|
+
fallbackRPr
|
|
3799
|
+
);
|
|
3800
|
+
before += startTagOf(part, wrapper) + inner.before + inner.after + endTagOf(part, wrapper);
|
|
3801
|
+
rPr = inner.rPr;
|
|
3802
|
+
inserted = true;
|
|
3803
|
+
nested = true;
|
|
3804
|
+
continue;
|
|
3805
|
+
}
|
|
3806
|
+
throw new Error("A range cannot cut a field or a wrapper");
|
|
3807
|
+
}
|
|
3808
|
+
if (!inserted) place(lastRPr);
|
|
3809
|
+
return { before, after, rPr, nested };
|
|
3810
|
+
}
|
|
3811
|
+
async function textTarget(id, context, issue) {
|
|
3812
|
+
const record = context.model.byId.get(id);
|
|
3813
|
+
if (!record || record.kind !== "paragraph") {
|
|
3814
|
+
issue(
|
|
3815
|
+
"/target",
|
|
3816
|
+
record ? "invalid-target" : "unknown-target",
|
|
3817
|
+
record ? `Element ${id} is not a paragraph` : `No element ${id}`
|
|
3818
|
+
);
|
|
3819
|
+
return void 0;
|
|
3820
|
+
}
|
|
3821
|
+
if (record.readOnlyReason) {
|
|
3822
|
+
issue(
|
|
3823
|
+
"/target",
|
|
3824
|
+
"invalid-target",
|
|
3825
|
+
`Paragraph ${id} is read-only (${record.readOnlyReason})`
|
|
3826
|
+
);
|
|
3827
|
+
return void 0;
|
|
3828
|
+
}
|
|
3829
|
+
return { record, part: context.model.document };
|
|
3830
|
+
}
|
|
3831
|
+
function checkRange(target, id, range, issue, structural) {
|
|
3832
|
+
const text = target.record.text.text;
|
|
3833
|
+
const length = text.length;
|
|
3834
|
+
if (!range) return { start: 0, end: length };
|
|
3835
|
+
if (range.start.elementId !== id || range.end.elementId !== id) {
|
|
3836
|
+
issue("/range", "invalid-range", "Both ends must lie on the target");
|
|
3837
|
+
return void 0;
|
|
3838
|
+
}
|
|
3839
|
+
const { start, end } = { start: range.start.offset, end: range.end.offset };
|
|
3840
|
+
if (start > length || end > length || start > end) {
|
|
3841
|
+
issue(
|
|
3842
|
+
"/range",
|
|
3843
|
+
"invalid-range",
|
|
3844
|
+
`Offsets must satisfy 0 \u2264 start \u2264 end \u2264 ${length}`
|
|
3845
|
+
);
|
|
3846
|
+
return void 0;
|
|
3847
|
+
}
|
|
3848
|
+
for (const offset of [start, end])
|
|
3849
|
+
if (offset > 0 && offset < length && (text.charCodeAt(offset) & 64512) === 56320 && (text.charCodeAt(offset - 1) & 64512) === 55296) {
|
|
3850
|
+
issue("/range", "invalid-range", "A range cannot split a surrogate pair");
|
|
3851
|
+
return void 0;
|
|
3852
|
+
}
|
|
3853
|
+
if (!structural) return { start, end };
|
|
3854
|
+
const inside = (unit, offset) => unit.start < offset && offset < unit.end;
|
|
3855
|
+
const check = (container, items) => {
|
|
3856
|
+
for (const unit of unitsOf(container, items)) {
|
|
3857
|
+
if (unit.kind === "field" && (inside(unit, start) || inside(unit, end))) {
|
|
3798
3858
|
issue(
|
|
3799
|
-
"/
|
|
3800
|
-
"invalid-
|
|
3801
|
-
|
|
3859
|
+
"/range",
|
|
3860
|
+
"invalid-range",
|
|
3861
|
+
"A range cannot start or end inside a field"
|
|
3802
3862
|
);
|
|
3803
|
-
|
|
3863
|
+
return false;
|
|
3864
|
+
}
|
|
3865
|
+
if (unit.kind !== "wrapper") continue;
|
|
3866
|
+
const startIn = inside(unit, start);
|
|
3867
|
+
const endIn = inside(unit, end);
|
|
3868
|
+
if (startIn !== endIn) {
|
|
3804
3869
|
issue(
|
|
3805
|
-
"/
|
|
3806
|
-
"
|
|
3807
|
-
"
|
|
3870
|
+
"/range",
|
|
3871
|
+
"invalid-range",
|
|
3872
|
+
"A range cannot cut the edge of a hyperlink or content control"
|
|
3808
3873
|
);
|
|
3874
|
+
return false;
|
|
3875
|
+
}
|
|
3876
|
+
if (startIn && !check(unit.nodes[0], unit.items)) return false;
|
|
3809
3877
|
}
|
|
3878
|
+
return true;
|
|
3879
|
+
};
|
|
3880
|
+
return check(target.record.node, target.record.text.items) ? { start, end } : void 0;
|
|
3881
|
+
}
|
|
3882
|
+
function commitParagraph(context, target, items, extra = {}) {
|
|
3883
|
+
const { part, record } = target;
|
|
3884
|
+
const transaction = context.pkg.transaction();
|
|
3885
|
+
transaction.patch(part, [...namespacePatches(part), ...items]);
|
|
3886
|
+
return transaction.commit().then((change) => ({
|
|
3887
|
+
createdIds: [],
|
|
3888
|
+
warnings: change.warnings,
|
|
3889
|
+
...context.model.unauthoredSet.has(record.id) ? { stamped: [record.id] } : {},
|
|
3890
|
+
reflowFrom: record.id,
|
|
3891
|
+
...extra
|
|
3892
|
+
}));
|
|
3893
|
+
}
|
|
3894
|
+
var replaceTextHandler = {
|
|
3895
|
+
async validate(operation3, context, issue) {
|
|
3896
|
+
const text = normalizeText(operation3.text);
|
|
3897
|
+
const problem = textProblem(text);
|
|
3898
|
+
if (problem) issue("/text", "invalid-text", `The text holds ${problem}`);
|
|
3899
|
+
const target = await textTarget(operation3.target, context, issue);
|
|
3900
|
+
if (!target) return;
|
|
3901
|
+
const range = checkRange(
|
|
3902
|
+
target,
|
|
3903
|
+
operation3.target,
|
|
3904
|
+
operation3.range,
|
|
3905
|
+
issue,
|
|
3906
|
+
true
|
|
3907
|
+
);
|
|
3908
|
+
if (range && context.tracked && trackedRangeProblem(
|
|
3909
|
+
target.record,
|
|
3910
|
+
range.start,
|
|
3911
|
+
range.end,
|
|
3912
|
+
issue,
|
|
3913
|
+
"/range"
|
|
3914
|
+
))
|
|
3915
|
+
return;
|
|
3916
|
+
if (!range || !text.includes("\n")) return;
|
|
3917
|
+
const probe = splitAt(
|
|
3918
|
+
target.part,
|
|
3919
|
+
target.record.node,
|
|
3920
|
+
target.record.text.items,
|
|
3921
|
+
range.start,
|
|
3922
|
+
range.end,
|
|
3923
|
+
() => "",
|
|
3924
|
+
""
|
|
3925
|
+
);
|
|
3926
|
+
if (probe.nested)
|
|
3927
|
+
issue(
|
|
3928
|
+
"/text",
|
|
3929
|
+
"invalid-range",
|
|
3930
|
+
"A paragraph break cannot be inserted inside a hyperlink or content control"
|
|
3931
|
+
);
|
|
3810
3932
|
},
|
|
3811
3933
|
async apply(operation3, context) {
|
|
3812
|
-
const
|
|
3813
|
-
|
|
3814
|
-
const
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
}
|
|
3831
|
-
const removedParagraphIds = paragraphIdsUnder(model, node);
|
|
3832
|
-
const table = record.kind === "paragraph" ? firstCellParagraph(record) : void 0;
|
|
3833
|
-
const successor = table?.rows[0]?.[0]?.paragraphs[1];
|
|
3834
|
-
const remappedIds = table && successor ? tableRemap(table, successor.id) : void 0;
|
|
3835
|
-
return commit(context, items, {
|
|
3934
|
+
const target = await textTarget(operation3.target, context, () => {
|
|
3935
|
+
});
|
|
3936
|
+
const { start, end } = checkRange(
|
|
3937
|
+
target,
|
|
3938
|
+
operation3.target,
|
|
3939
|
+
operation3.range,
|
|
3940
|
+
() => {
|
|
3941
|
+
},
|
|
3942
|
+
true
|
|
3943
|
+
);
|
|
3944
|
+
const { items, createdIds, removedIds, removedParagraphIds } = replacedParagraph(
|
|
3945
|
+
context,
|
|
3946
|
+
target.record,
|
|
3947
|
+
start,
|
|
3948
|
+
end,
|
|
3949
|
+
normalizeText(operation3.text)
|
|
3950
|
+
);
|
|
3951
|
+
return commitParagraph(context, target, items, {
|
|
3836
3952
|
createdIds,
|
|
3837
|
-
removedIds:
|
|
3838
|
-
removedParagraphIds
|
|
3839
|
-
reflowFrom: record.id,
|
|
3840
|
-
...remappedIds ? { remappedIds } : {}
|
|
3953
|
+
...removedIds.length > 0 ? { removedIds } : {},
|
|
3954
|
+
...removedParagraphIds.length > 0 ? { removedParagraphIds } : {}
|
|
3841
3955
|
});
|
|
3842
3956
|
}
|
|
3843
3957
|
};
|
|
3844
|
-
|
|
3845
|
-
const
|
|
3846
|
-
const
|
|
3847
|
-
const
|
|
3848
|
-
const
|
|
3849
|
-
const
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3958
|
+
function replacedParagraph(context, record, start, end, text) {
|
|
3959
|
+
const part = context.model.document;
|
|
3960
|
+
const segments = text.split("\n");
|
|
3961
|
+
const single = segments.length === 1;
|
|
3962
|
+
const items = [];
|
|
3963
|
+
const createdIds = [];
|
|
3964
|
+
const copiedPPr = paragraphPropertiesWithoutSection(part, record.pPr);
|
|
3965
|
+
if (context.tracked) {
|
|
3966
|
+
const split = trackedSplit(
|
|
3967
|
+
context,
|
|
3968
|
+
record,
|
|
3969
|
+
start,
|
|
3970
|
+
end,
|
|
3971
|
+
paragraphMarkRPr(part, record)
|
|
3853
3972
|
);
|
|
3854
|
-
const
|
|
3855
|
-
|
|
3973
|
+
const inserted = (segment) => insertedRunXml(context, split.rPr, segment);
|
|
3974
|
+
const originalPPr = sliceOf(part, record.pPr);
|
|
3975
|
+
const firstPPr = single ? originalPPr : markedParagraphProperties(context, record.pPr, "ins");
|
|
3976
|
+
items.push(
|
|
3977
|
+
patches.replaceElement(
|
|
3978
|
+
part,
|
|
3979
|
+
record.node,
|
|
3980
|
+
paragraphXml(
|
|
3981
|
+
record.node,
|
|
3982
|
+
record.id,
|
|
3983
|
+
firstPPr,
|
|
3984
|
+
split.before + split.deleted + inserted(segments[0]) + (single ? split.after : "")
|
|
3985
|
+
)
|
|
3986
|
+
)
|
|
3856
3987
|
);
|
|
3857
|
-
|
|
3858
|
-
|
|
3988
|
+
segments.slice(1).forEach((segment, index) => {
|
|
3989
|
+
const id = context.freshParagraphId();
|
|
3990
|
+
createdIds.push(`p:${id}`);
|
|
3991
|
+
const last = index === segments.length - 2;
|
|
3992
|
+
items.push(
|
|
3993
|
+
patches.insertAfter(
|
|
3994
|
+
part,
|
|
3995
|
+
record.node,
|
|
3996
|
+
paragraphXml(
|
|
3997
|
+
record.node,
|
|
3998
|
+
id,
|
|
3999
|
+
last ? originalPPr : markedParagraphProperties(context, record.pPr, "ins"),
|
|
4000
|
+
inserted(segment) + (last ? split.after : "")
|
|
4001
|
+
)
|
|
4002
|
+
)
|
|
4003
|
+
);
|
|
4004
|
+
});
|
|
4005
|
+
} else {
|
|
4006
|
+
const split = splitAt(
|
|
4007
|
+
part,
|
|
4008
|
+
record.node,
|
|
4009
|
+
record.text.items,
|
|
4010
|
+
start,
|
|
4011
|
+
end,
|
|
4012
|
+
(rPr) => runXml(rPr, runContentXml(segments[0])),
|
|
4013
|
+
paragraphMarkRPr(part, record)
|
|
4014
|
+
);
|
|
4015
|
+
const pPr = sliceOf(part, record.pPr);
|
|
4016
|
+
items.push(
|
|
4017
|
+
patches.replaceElement(
|
|
4018
|
+
part,
|
|
4019
|
+
record.node,
|
|
4020
|
+
paragraphXml(
|
|
4021
|
+
record.node,
|
|
4022
|
+
record.id,
|
|
4023
|
+
pPr,
|
|
4024
|
+
split.before + (single ? split.after : "")
|
|
4025
|
+
)
|
|
4026
|
+
)
|
|
4027
|
+
);
|
|
4028
|
+
segments.slice(1).forEach((segment, index) => {
|
|
4029
|
+
const id = context.freshParagraphId();
|
|
4030
|
+
createdIds.push(`p:${id}`);
|
|
4031
|
+
const last = index === segments.length - 2;
|
|
4032
|
+
items.push(
|
|
4033
|
+
patches.insertAfter(
|
|
4034
|
+
part,
|
|
4035
|
+
record.node,
|
|
4036
|
+
paragraphXml(
|
|
4037
|
+
record.node,
|
|
4038
|
+
id,
|
|
4039
|
+
copiedPPr,
|
|
4040
|
+
runXml(split.rPr, runContentXml(segment)) + (last ? split.after : "")
|
|
4041
|
+
)
|
|
4042
|
+
)
|
|
4043
|
+
);
|
|
4044
|
+
});
|
|
3859
4045
|
}
|
|
3860
|
-
const
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
4046
|
+
const dropped = droppedInlines(
|
|
4047
|
+
context.model,
|
|
4048
|
+
record,
|
|
4049
|
+
record.text.items,
|
|
4050
|
+
start,
|
|
4051
|
+
end
|
|
4052
|
+
);
|
|
4053
|
+
const removedIds = dropped.removedIds;
|
|
4054
|
+
if (!single)
|
|
4055
|
+
for (const inline of record.inlines) {
|
|
4056
|
+
const item = record.text.items.find(
|
|
4057
|
+
(candidate) => candidate.child === inline.node
|
|
4058
|
+
);
|
|
4059
|
+
if (item && item.start >= end && !removedIds.includes(inline.elementId))
|
|
4060
|
+
removedIds.push(inline.elementId);
|
|
4061
|
+
}
|
|
4062
|
+
return {
|
|
4063
|
+
items,
|
|
4064
|
+
createdIds,
|
|
4065
|
+
removedIds,
|
|
4066
|
+
removedParagraphIds: dropped.removedParagraphIds
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
4069
|
+
function paragraphMarkRPr(part, record) {
|
|
4070
|
+
const rPr = record.pPr?.children.find(
|
|
4071
|
+
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
3871
4072
|
);
|
|
4073
|
+
return sliceOf(part, rPr);
|
|
3872
4074
|
}
|
|
3873
|
-
function
|
|
3874
|
-
|
|
4075
|
+
function restyledRun(part, run2, rPr) {
|
|
4076
|
+
const content = run2.children.filter((child) => !(child.local === "rPr" && child.namespace === W_NS)).map((child) => sliceOf(part, child)).join("");
|
|
4077
|
+
return `<w:r>${rPr}${content}</w:r>`;
|
|
3875
4078
|
}
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
4079
|
+
function restyled(part, container, items, start, end, change, context) {
|
|
4080
|
+
let out = "";
|
|
4081
|
+
const changed = (rPr) => changedRunProperties(
|
|
4082
|
+
part,
|
|
4083
|
+
rPr,
|
|
4084
|
+
change,
|
|
4085
|
+
context.model.styles,
|
|
4086
|
+
context.tracked ? revisionAttributes(context) : void 0
|
|
4087
|
+
);
|
|
4088
|
+
for (const unit of unitsOf(container, items)) {
|
|
4089
|
+
const raw = unit.nodes.map((node) => sliceOf(part, node)).join("");
|
|
4090
|
+
const overlaps = unit.start < end && start < unit.end ? true : unit.start === unit.end && start < unit.start && unit.start < end;
|
|
4091
|
+
if (unit.kind === "zero" || !overlaps) {
|
|
4092
|
+
out += raw;
|
|
4093
|
+
continue;
|
|
3886
4094
|
}
|
|
3887
|
-
if (
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
4095
|
+
if (unit.kind === "run") {
|
|
4096
|
+
const run2 = unit.nodes[0];
|
|
4097
|
+
const own = rPrOf(part, unit.items[0]);
|
|
4098
|
+
const rPr = unit.items[0]?.rPr;
|
|
4099
|
+
out += runXml(
|
|
4100
|
+
own,
|
|
4101
|
+
contentOf(
|
|
4102
|
+
part,
|
|
4103
|
+
unit.items,
|
|
4104
|
+
unit.start,
|
|
4105
|
+
Math.min(start, unit.end),
|
|
4106
|
+
true
|
|
4107
|
+
)
|
|
3892
4108
|
);
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
4109
|
+
out += runXml(
|
|
4110
|
+
changed(rPr),
|
|
4111
|
+
contentOf(
|
|
4112
|
+
part,
|
|
4113
|
+
unit.items,
|
|
4114
|
+
Math.max(unit.start, start),
|
|
4115
|
+
Math.min(unit.end, end),
|
|
4116
|
+
false
|
|
4117
|
+
)
|
|
3900
4118
|
);
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
if (!reference) return;
|
|
3905
|
-
if (reference.record === record) {
|
|
3906
|
-
issue(
|
|
3907
|
-
`/${reference.side}`,
|
|
3908
|
-
"invalid-target",
|
|
3909
|
-
"An element cannot move next to itself"
|
|
4119
|
+
out += runXml(
|
|
4120
|
+
own,
|
|
4121
|
+
contentOf(part, unit.items, Math.max(end, unit.start), unit.end, false)
|
|
3910
4122
|
);
|
|
3911
|
-
|
|
4123
|
+
continue;
|
|
3912
4124
|
}
|
|
3913
|
-
if (
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
4125
|
+
if (unit.kind === "field") {
|
|
4126
|
+
for (const node of unit.nodes)
|
|
4127
|
+
if (node.local === "r" && node.namespace === W_NS)
|
|
4128
|
+
out += restyledRun(
|
|
4129
|
+
part,
|
|
4130
|
+
node,
|
|
4131
|
+
changed(
|
|
4132
|
+
node.children.find(
|
|
4133
|
+
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
4134
|
+
)
|
|
4135
|
+
)
|
|
4136
|
+
);
|
|
4137
|
+
else if (node.local === "fldSimple")
|
|
4138
|
+
out += startTagOf(part, node) + restyled(part, node, unit.items, start, end, change, context) + endTagOf(part, node);
|
|
4139
|
+
else out += sliceOf(part, node);
|
|
4140
|
+
continue;
|
|
4141
|
+
}
|
|
4142
|
+
const wrapper = unit.nodes[0];
|
|
4143
|
+
out += startTagOf(part, wrapper) + restyled(part, wrapper, unit.items, start, end, change, context) + endTagOf(part, wrapper);
|
|
4144
|
+
}
|
|
4145
|
+
return out;
|
|
4146
|
+
}
|
|
4147
|
+
var setTextStyleHandler = {
|
|
4148
|
+
async validate(operation3, context, issue) {
|
|
4149
|
+
const { style } = operation3;
|
|
4150
|
+
if (style.color !== void 0) {
|
|
4151
|
+
const problem = colorProblem(style.color);
|
|
4152
|
+
if (problem)
|
|
4153
|
+
issue("/style/color", "invalid-value", `Colour: ${problem}`);
|
|
4154
|
+
}
|
|
4155
|
+
if (style.fontFamily !== void 0) {
|
|
4156
|
+
const problem = attributeProblem(style.fontFamily);
|
|
4157
|
+
if (problem)
|
|
4158
|
+
issue("/style/fontFamily", "invalid-value", `Font: ${problem}`);
|
|
4159
|
+
}
|
|
4160
|
+
const target = await textTarget(operation3.target, context, issue);
|
|
4161
|
+
if (target)
|
|
4162
|
+
checkRange(target, operation3.target, operation3.range, issue, false);
|
|
3919
4163
|
},
|
|
3920
4164
|
async apply(operation3, context) {
|
|
3921
|
-
const
|
|
3922
|
-
|
|
3923
|
-
const {
|
|
3924
|
-
|
|
3925
|
-
|
|
4165
|
+
const target = await textTarget(operation3.target, context, () => {
|
|
4166
|
+
});
|
|
4167
|
+
const { part, record } = target;
|
|
4168
|
+
const { start, end } = checkRange(
|
|
4169
|
+
target,
|
|
4170
|
+
operation3.target,
|
|
4171
|
+
operation3.range,
|
|
3926
4172
|
() => {
|
|
3927
|
-
}
|
|
4173
|
+
},
|
|
4174
|
+
false
|
|
3928
4175
|
);
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
const
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
const without = cell.paragraphs.filter((p) => p !== record);
|
|
3955
|
-
const at = without.indexOf(reference);
|
|
3956
|
-
const reordered = [...without];
|
|
3957
|
-
if (at >= 0)
|
|
3958
|
-
reordered.splice(side === "before" ? at : at + 1, 0, record);
|
|
3959
|
-
const head = reordered[0];
|
|
3960
|
-
if (head) remappedIds = tableRemap(record.table, head.id);
|
|
3961
|
-
}
|
|
3962
|
-
}
|
|
3963
|
-
return commit(context, items, {
|
|
3964
|
-
createdIds,
|
|
3965
|
-
stamped,
|
|
3966
|
-
reflowFrom: first.id,
|
|
3967
|
-
...remappedIds ? { remappedIds } : {}
|
|
3968
|
-
});
|
|
3969
|
-
}
|
|
3970
|
-
};
|
|
3971
|
-
async function existingMedia(context, bytes, mimeType) {
|
|
3972
|
-
const extension = `.${extensionForMime(mimeType)}`;
|
|
3973
|
-
for (const name of context.pkg.currentPartNames) {
|
|
3974
|
-
if (!name.startsWith(`${MEDIA_FOLDER}/`) || !name.endsWith(extension))
|
|
3975
|
-
continue;
|
|
3976
|
-
const entry = context.pkg.entry(name);
|
|
3977
|
-
if (entry && entry.uncompressedSize !== bytes.length) continue;
|
|
3978
|
-
const candidate = await context.pkg.part(name);
|
|
3979
|
-
if (candidate.length !== bytes.length) continue;
|
|
3980
|
-
let same = true;
|
|
3981
|
-
for (let index = 0; index < bytes.length; index += 1)
|
|
3982
|
-
if (candidate[index] !== bytes[index]) {
|
|
3983
|
-
same = false;
|
|
3984
|
-
break;
|
|
3985
|
-
}
|
|
3986
|
-
if (same) return name;
|
|
3987
|
-
}
|
|
3988
|
-
return void 0;
|
|
3989
|
-
}
|
|
3990
|
-
function matchesMime(bytes, mimeType) {
|
|
3991
|
-
if (mimeType === "image/png")
|
|
3992
|
-
return bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
|
|
3993
|
-
return bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
|
|
3994
|
-
}
|
|
3995
|
-
function nextDrawingId(part) {
|
|
3996
|
-
let max = 0;
|
|
3997
|
-
for (const node of part.findAll("docPr")) {
|
|
3998
|
-
if (node.namespace !== WP_NS) continue;
|
|
3999
|
-
const id = Number(part.attribute(node, "id") ?? NaN);
|
|
4000
|
-
if (Number.isInteger(id)) max = Math.max(max, id);
|
|
4176
|
+
if (Object.keys(operation3.style).length === 0)
|
|
4177
|
+
return { createdIds: [], warnings: [] };
|
|
4178
|
+
const content = restyled(
|
|
4179
|
+
part,
|
|
4180
|
+
record.node,
|
|
4181
|
+
record.text.items,
|
|
4182
|
+
start,
|
|
4183
|
+
end,
|
|
4184
|
+
operation3.style,
|
|
4185
|
+
context
|
|
4186
|
+
);
|
|
4187
|
+
const pPr = end >= record.text.text.length ? paragraphMarkProperties(
|
|
4188
|
+
part,
|
|
4189
|
+
record.pPr,
|
|
4190
|
+
operation3.style,
|
|
4191
|
+
context.model.styles,
|
|
4192
|
+
context.tracked ? revisionAttributes(context) : void 0
|
|
4193
|
+
) : sliceOf(part, record.pPr);
|
|
4194
|
+
return commitParagraph(context, target, [
|
|
4195
|
+
patches.replaceElement(
|
|
4196
|
+
part,
|
|
4197
|
+
record.node,
|
|
4198
|
+
paragraphXml(record.node, record.id, pPr, content)
|
|
4199
|
+
)
|
|
4200
|
+
]);
|
|
4001
4201
|
}
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
function drawingNamespacePatches(part) {
|
|
4005
|
-
const root = part.root;
|
|
4006
|
-
const has = (name) => root.attributes.some((attribute) => attribute.name === name);
|
|
4007
|
-
const items = [];
|
|
4008
|
-
if (!has("xmlns:wp"))
|
|
4009
|
-
items.push(patches.setAttribute(part, root, "xmlns:wp", WP_NS));
|
|
4010
|
-
if (!has("xmlns:r"))
|
|
4011
|
-
items.push(patches.setAttribute(part, root, "xmlns:r", R_NS));
|
|
4012
|
-
return items;
|
|
4013
|
-
}
|
|
4014
|
-
var insertImageHandler = {
|
|
4202
|
+
};
|
|
4203
|
+
var setParagraphStyleHandler = {
|
|
4015
4204
|
async validate(operation3, context, issue) {
|
|
4016
|
-
|
|
4017
|
-
try {
|
|
4018
|
-
const bytes = resolveBinary(operation3.data, context.assets);
|
|
4019
|
-
if (bytes.length === 0)
|
|
4020
|
-
issue("/data", "invalid-value", "The image has no bytes");
|
|
4021
|
-
else if (!matchesMime(bytes, operation3.mimeType))
|
|
4022
|
-
issue(
|
|
4023
|
-
"/data",
|
|
4024
|
-
"invalid-value",
|
|
4025
|
-
`The bytes are not a ${operation3.mimeType} image`
|
|
4026
|
-
);
|
|
4027
|
-
} catch {
|
|
4028
|
-
issue(
|
|
4029
|
-
"/data",
|
|
4030
|
-
"unknown-asset",
|
|
4031
|
-
"The asset is not known to the session"
|
|
4032
|
-
);
|
|
4033
|
-
}
|
|
4205
|
+
await textTarget(operation3.target, context, issue);
|
|
4034
4206
|
},
|
|
4035
4207
|
async apply(operation3, context) {
|
|
4036
|
-
const
|
|
4037
|
-
const { record, side } = placement2(operation3, context, () => {
|
|
4208
|
+
const target = await textTarget(operation3.target, context, () => {
|
|
4038
4209
|
});
|
|
4039
|
-
const part =
|
|
4040
|
-
const
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
const
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
const cx = Math.round(operation3.size.width * EMU_PER_PT);
|
|
4054
|
-
const cy = Math.round(operation3.size.height * EMU_PER_PT);
|
|
4055
|
-
const drawingId = nextDrawingId(part);
|
|
4056
|
-
const id = context.freshParagraphId();
|
|
4057
|
-
const drawing = `<w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:effectExtent l="0" t="0" r="0" b="0"/><wp:docPr id="${drawingId}" name="Picture ${drawingId}"/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="${A_NS}" noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic xmlns:a="${A_NS}"><a:graphicData uri="${PIC_NS}"><pic:pic xmlns:pic="${PIC_NS}"><pic:nvPicPr><pic:cNvPr id="0" name="Picture ${drawingId}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${rId}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>`;
|
|
4058
|
-
const xml = paragraphXml(void 0, id, "", drawing);
|
|
4059
|
-
const node = blockNode(record);
|
|
4060
|
-
const items = [
|
|
4061
|
-
...drawingNamespacePatches(part),
|
|
4062
|
-
side === "before" ? patches.insertBefore(part, node, xml) : patches.insertAfter(part, node, xml)
|
|
4063
|
-
];
|
|
4064
|
-
return commit(
|
|
4065
|
-
context,
|
|
4066
|
-
items,
|
|
4067
|
-
{ createdIds: [`p:${id}`, `img:${id}.0`], reflowFrom: record.id },
|
|
4068
|
-
transaction
|
|
4210
|
+
const { part, record } = target;
|
|
4211
|
+
const { style } = operation3;
|
|
4212
|
+
if (style.align === void 0 && (style.spacing === void 0 || Object.keys(style.spacing).length === 0))
|
|
4213
|
+
return { createdIds: [], warnings: [] };
|
|
4214
|
+
const pPr = changedParagraphProperties(
|
|
4215
|
+
part,
|
|
4216
|
+
record.pPr,
|
|
4217
|
+
style,
|
|
4218
|
+
context.tracked ? revisionAttributes(context) : void 0
|
|
4219
|
+
);
|
|
4220
|
+
const node = record.node;
|
|
4221
|
+
const content = node.selfClosing ? "" : part.text.slice(
|
|
4222
|
+
record.pPr ? record.pPr.end : node.contentStart,
|
|
4223
|
+
node.contentEnd
|
|
4069
4224
|
);
|
|
4225
|
+
return commitParagraph(context, target, [
|
|
4226
|
+
patches.replaceElement(
|
|
4227
|
+
part,
|
|
4228
|
+
node,
|
|
4229
|
+
paragraphXml(node, record.id, pPr, content)
|
|
4230
|
+
)
|
|
4231
|
+
]);
|
|
4070
4232
|
}
|
|
4071
4233
|
};
|
|
4072
4234
|
|
|
4073
|
-
// src/edit/docx/
|
|
4074
|
-
function
|
|
4235
|
+
// src/edit/docx/tracked.ts
|
|
4236
|
+
function revisionAttributes(context) {
|
|
4237
|
+
const tracked = context.tracked;
|
|
4238
|
+
return ` w:id="${context.nextRevisionId()}" w:author="${escapeAttributeValue(tracked.author)}"${tracked.date === void 0 ? "" : ` w:date="${escapeAttributeValue(tracked.date)}"`}`;
|
|
4239
|
+
}
|
|
4240
|
+
function unsupportedTracked(issue, path, what) {
|
|
4241
|
+
issue(
|
|
4242
|
+
path,
|
|
4243
|
+
"unsupported-change-mode",
|
|
4244
|
+
`${what} has no tracked form; apply it directly and review with checkpoints`
|
|
4245
|
+
);
|
|
4246
|
+
}
|
|
4247
|
+
function topOf2(node, container) {
|
|
4075
4248
|
let current = node;
|
|
4076
4249
|
while (current.parent && current.parent !== container)
|
|
4077
4250
|
current = current.parent;
|
|
4078
4251
|
return current;
|
|
4079
4252
|
}
|
|
4080
|
-
function
|
|
4081
|
-
const
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
const
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
if (first !== last) groupEnd.set(first, last);
|
|
4094
|
-
}
|
|
4095
|
-
const units = [];
|
|
4096
|
-
let position = 0;
|
|
4097
|
-
const children = container.children.filter(
|
|
4098
|
-
(child) => !(child.local === "pPr" && child.namespace === W_NS)
|
|
4099
|
-
);
|
|
4100
|
-
for (let index = 0; index < children.length; index += 1) {
|
|
4101
|
-
const child = children[index];
|
|
4102
|
-
const end = groupEnd.get(child);
|
|
4103
|
-
const nodes = [child];
|
|
4104
|
-
if (end) {
|
|
4105
|
-
let cursor = index + 1;
|
|
4106
|
-
while (cursor < children.length && children[cursor - 1] !== end)
|
|
4107
|
-
nodes.push(children[cursor++]);
|
|
4108
|
-
index = cursor - 1;
|
|
4253
|
+
function trackedRangeProblem(record, start, end, issue, path) {
|
|
4254
|
+
for (const item of record.text.items) {
|
|
4255
|
+
const overlaps = item.start === item.end ? start < item.start && item.start < end : item.start < end && start < item.end;
|
|
4256
|
+
if (!overlaps) continue;
|
|
4257
|
+
const top = topOf2(item.run, record.node);
|
|
4258
|
+
const plainRun = item.run.local === "r" && item.run.namespace === W_NS;
|
|
4259
|
+
if (!plainRun || item.fieldRuns || item.kind === "field" || top !== item.run || item.wrapper !== void 0) {
|
|
4260
|
+
unsupportedTracked(
|
|
4261
|
+
issue,
|
|
4262
|
+
path,
|
|
4263
|
+
"A change touching a hyperlink, content control, field or equation"
|
|
4264
|
+
);
|
|
4265
|
+
return true;
|
|
4109
4266
|
}
|
|
4110
|
-
const own = nodes.flatMap((node) => byTop.get(node) ?? []);
|
|
4111
|
-
const kind = own.length === 0 ? "zero" : end || child.local === "fldSimple" || own.some((item) => item.fieldRuns) ? "field" : child.local === "r" ? "run" : "wrapper";
|
|
4112
|
-
const start = own.length > 0 ? own[0].start : position;
|
|
4113
|
-
const stop = own.length > 0 ? own.at(-1).end : position;
|
|
4114
|
-
units.push({ nodes, kind, start, end: stop, items: own });
|
|
4115
|
-
position = stop;
|
|
4116
4267
|
}
|
|
4117
|
-
return
|
|
4268
|
+
return false;
|
|
4118
4269
|
}
|
|
4119
|
-
function
|
|
4120
|
-
return
|
|
4270
|
+
function deletedContent(content) {
|
|
4271
|
+
return content.replaceAll("<w:t>", "<w:delText>").replaceAll("<w:t ", "<w:delText ").replaceAll("</w:t>", "</w:delText>").replaceAll("<w:instrText", "<w:delInstrText").replaceAll("</w:instrText>", "</w:delInstrText>");
|
|
4121
4272
|
}
|
|
4122
|
-
function
|
|
4123
|
-
return
|
|
4273
|
+
function deletedRunXml(rPr, content) {
|
|
4274
|
+
return content.length === 0 ? "" : `<w:r>${rPr}${deletedContent(content)}</w:r>`;
|
|
4124
4275
|
}
|
|
4125
|
-
function
|
|
4126
|
-
return
|
|
4276
|
+
function runContentOf(part, run2) {
|
|
4277
|
+
return run2.children.filter((child) => !(child.local === "rPr" && child.namespace === W_NS)).map((child) => sliceOf(part, child)).join("");
|
|
4127
4278
|
}
|
|
4128
|
-
function
|
|
4279
|
+
function runPropertiesOf(part, run2) {
|
|
4280
|
+
const rPr = run2.children.find(
|
|
4281
|
+
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
4282
|
+
);
|
|
4283
|
+
return sliceOf(part, rPr);
|
|
4284
|
+
}
|
|
4285
|
+
function deletedContentOf(part, items, from, to) {
|
|
4129
4286
|
let out = "";
|
|
4130
4287
|
for (const item of items) {
|
|
4131
4288
|
if (item.start === item.end) {
|
|
4132
|
-
|
|
4133
|
-
|
|
4289
|
+
if (from < item.start && item.start < to)
|
|
4290
|
+
out += sliceOf(part, item.child);
|
|
4134
4291
|
continue;
|
|
4135
4292
|
}
|
|
4136
4293
|
const begin = Math.max(item.start, from);
|
|
@@ -4145,451 +4302,732 @@ function contentOf(part, items, from, to, includeStart) {
|
|
|
4145
4302
|
}
|
|
4146
4303
|
return out;
|
|
4147
4304
|
}
|
|
4148
|
-
function
|
|
4149
|
-
const
|
|
4150
|
-
const removedParagraphIds = [];
|
|
4151
|
-
for (const item of items) {
|
|
4152
|
-
if (item.kind !== "picture" && item.kind !== "object" && item.kind !== "anchor")
|
|
4153
|
-
continue;
|
|
4154
|
-
const inside = item.start === item.end ? from < item.start && item.start < to : item.start >= from && item.end <= to;
|
|
4155
|
-
if (!inside) continue;
|
|
4156
|
-
const inline = record.inlines.find(
|
|
4157
|
-
(candidate) => candidate.node === item.child
|
|
4158
|
-
);
|
|
4159
|
-
if (inline) removedIds.push(inline.elementId);
|
|
4160
|
-
for (const paragraph of model.document.findAll("p", item.child)) {
|
|
4161
|
-
const id = model.paragraphIds.get(paragraph);
|
|
4162
|
-
if (id && paragraph.namespace === W_NS) removedParagraphIds.push(id);
|
|
4163
|
-
}
|
|
4164
|
-
}
|
|
4165
|
-
return { removedIds, removedParagraphIds };
|
|
4166
|
-
}
|
|
4167
|
-
function splitAt(part, container, items, start, end, insert, fallbackRPr) {
|
|
4305
|
+
function trackedSplit(context, record, start, end, fallbackRPr) {
|
|
4306
|
+
const part = context.model.document;
|
|
4168
4307
|
let before = "";
|
|
4169
4308
|
let after = "";
|
|
4170
|
-
|
|
4171
|
-
let
|
|
4172
|
-
let rPr = fallbackRPr;
|
|
4309
|
+
const deletedRuns = [];
|
|
4310
|
+
let rPr;
|
|
4173
4311
|
let lastRPr;
|
|
4312
|
+
let passed = false;
|
|
4174
4313
|
const raw = (unit) => unit.nodes.map((node) => sliceOf(part, node)).join("");
|
|
4175
|
-
const
|
|
4176
|
-
rPr = style ?? lastRPr ?? fallbackRPr;
|
|
4177
|
-
before += insert(rPr);
|
|
4178
|
-
inserted = true;
|
|
4179
|
-
};
|
|
4180
|
-
for (const unit of unitsOf(container, items)) {
|
|
4314
|
+
for (const unit of unitsOf(record.node, record.text.items)) {
|
|
4181
4315
|
if (unit.kind === "zero") {
|
|
4182
|
-
if (
|
|
4316
|
+
if (passed) after += raw(unit);
|
|
4183
4317
|
else before += raw(unit);
|
|
4184
4318
|
continue;
|
|
4185
4319
|
}
|
|
4186
4320
|
if (unit.end <= start) {
|
|
4187
4321
|
before += raw(unit);
|
|
4188
4322
|
lastRPr = rPrOf(part, unit.items.at(-1));
|
|
4189
|
-
if (
|
|
4323
|
+
if (start === end && unit.end === start) {
|
|
4324
|
+
rPr ??= lastRPr;
|
|
4325
|
+
passed = true;
|
|
4326
|
+
}
|
|
4190
4327
|
continue;
|
|
4191
4328
|
}
|
|
4192
4329
|
if (unit.start >= end) {
|
|
4193
|
-
|
|
4330
|
+
rPr ??= lastRPr ?? rPrOf(part, unit.items[0]);
|
|
4331
|
+
passed = true;
|
|
4194
4332
|
after += raw(unit);
|
|
4195
4333
|
continue;
|
|
4196
4334
|
}
|
|
4335
|
+
if (unit.kind !== "run")
|
|
4336
|
+
throw new Error("A tracked change cannot cut a field or a wrapper");
|
|
4337
|
+
const run2 = unit.nodes[0];
|
|
4338
|
+
const style = runPropertiesOf(part, run2);
|
|
4197
4339
|
if (unit.start >= start && unit.end <= end) {
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
const style = rPrOf(part, unit.items[0]);
|
|
4203
|
-
const head = runXml(
|
|
4204
|
-
style,
|
|
4205
|
-
contentOf(part, unit.items, unit.start, start, true)
|
|
4206
|
-
);
|
|
4207
|
-
const tail = runXml(
|
|
4208
|
-
style,
|
|
4209
|
-
contentOf(part, unit.items, end, unit.end, start < end)
|
|
4210
|
-
);
|
|
4211
|
-
if (!inserted) {
|
|
4212
|
-
before += head;
|
|
4213
|
-
place(style);
|
|
4214
|
-
after += tail;
|
|
4215
|
-
} else after += tail;
|
|
4216
|
-
continue;
|
|
4217
|
-
}
|
|
4218
|
-
if (unit.kind === "wrapper" && unit.start < start && end < unit.end) {
|
|
4219
|
-
const wrapper = unit.nodes[0];
|
|
4220
|
-
const inner = splitAt(
|
|
4340
|
+
deletedRuns.push(deletedRunXml(style, runContentOf(part, run2)));
|
|
4341
|
+
} else {
|
|
4342
|
+
const head = contentOf(part, unit.items, unit.start, start, true);
|
|
4343
|
+
const middle = deletedContentOf(
|
|
4221
4344
|
part,
|
|
4222
|
-
wrapper,
|
|
4223
4345
|
unit.items,
|
|
4224
|
-
start,
|
|
4225
|
-
end,
|
|
4226
|
-
insert,
|
|
4227
|
-
fallbackRPr
|
|
4346
|
+
Math.max(unit.start, start),
|
|
4347
|
+
Math.min(unit.end, end)
|
|
4228
4348
|
);
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4349
|
+
const tail = contentOf(part, unit.items, end, unit.end, start < end);
|
|
4350
|
+
if (head) before += runXml(style, head);
|
|
4351
|
+
if (middle) deletedRuns.push(deletedRunXml(style, middle));
|
|
4352
|
+
if (tail) after += runXml(style, tail);
|
|
4353
|
+
}
|
|
4354
|
+
rPr ??= style;
|
|
4355
|
+
if (unit.end >= end) passed = true;
|
|
4356
|
+
}
|
|
4357
|
+
const deleted = deletedRuns.length > 0 ? `<w:del${revisionAttributes(context)}>${deletedRuns.join("")}</w:del>` : "";
|
|
4358
|
+
return { before, deleted, rPr: rPr ?? lastRPr ?? fallbackRPr, after };
|
|
4359
|
+
}
|
|
4360
|
+
function insertedRunXml(context, rPr, text) {
|
|
4361
|
+
const content = runContentXml(text);
|
|
4362
|
+
return content.length === 0 ? "" : `<w:ins${revisionAttributes(context)}>${runXml(rPr, content)}</w:ins>`;
|
|
4363
|
+
}
|
|
4364
|
+
function markedParagraphProperties(context, pPr, mark) {
|
|
4365
|
+
const part = context.model.document;
|
|
4366
|
+
const rPr = pPr?.children.find(
|
|
4367
|
+
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
4368
|
+
);
|
|
4369
|
+
const inner = innerPropertiesXml(part, rPr, ["ins", "del"]);
|
|
4370
|
+
const marked = `<w:rPr><w:${mark}${revisionAttributes(context)}/>${inner}</w:rPr>`;
|
|
4371
|
+
return mergedProperties(
|
|
4372
|
+
part,
|
|
4373
|
+
pPr,
|
|
4374
|
+
"w:pPr",
|
|
4375
|
+
PPR_ORDER,
|
|
4376
|
+
/* @__PURE__ */ new Map([
|
|
4377
|
+
["rPr", marked],
|
|
4378
|
+
["sectPr", null]
|
|
4379
|
+
])
|
|
4380
|
+
);
|
|
4381
|
+
}
|
|
4382
|
+
function deletedParagraphXml(context, record) {
|
|
4383
|
+
const part = context.model.document;
|
|
4384
|
+
const pPr = markedParagraphProperties(context, record.pPr, "del");
|
|
4385
|
+
let content = "";
|
|
4386
|
+
let open = [];
|
|
4387
|
+
const flush = () => {
|
|
4388
|
+
if (open.length > 0)
|
|
4389
|
+
content += `<w:del${revisionAttributes(context)}>${open.join("")}</w:del>`;
|
|
4390
|
+
open = [];
|
|
4391
|
+
};
|
|
4392
|
+
for (const child of record.node.children) {
|
|
4393
|
+
if (child.local === "pPr" && child.namespace === W_NS) continue;
|
|
4394
|
+
if (child.local === "r" && child.namespace === W_NS) {
|
|
4395
|
+
const run2 = deletedRunXml(
|
|
4396
|
+
runPropertiesOf(part, child),
|
|
4397
|
+
runContentOf(part, child)
|
|
4398
|
+
);
|
|
4399
|
+
if (run2) open.push(run2);
|
|
4233
4400
|
continue;
|
|
4234
4401
|
}
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4402
|
+
flush();
|
|
4403
|
+
content += sliceOf(part, child);
|
|
4404
|
+
}
|
|
4405
|
+
flush();
|
|
4406
|
+
return paragraphXml(record.node, record.id, pPr, content);
|
|
4407
|
+
}
|
|
4408
|
+
var RUN_REVISIONS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
4409
|
+
function revisionsOf(part, paragraph) {
|
|
4410
|
+
const out = [];
|
|
4411
|
+
const attributes = (node) => {
|
|
4412
|
+
const author = part.attribute(node, "w:author");
|
|
4413
|
+
const date = part.attribute(node, "w:date");
|
|
4414
|
+
const raw = part.attribute(node, "w:id");
|
|
4415
|
+
return {
|
|
4416
|
+
id: raw !== void 0 && /^\d+$/.test(raw) ? Number(raw) : -1,
|
|
4417
|
+
...author === void 0 ? {} : { author },
|
|
4418
|
+
...date === void 0 ? {} : { date }
|
|
4419
|
+
};
|
|
4420
|
+
};
|
|
4421
|
+
const visit = (node, inRPr, inPPr) => {
|
|
4422
|
+
for (const child of node.children) {
|
|
4423
|
+
if (child.namespace !== W_NS) {
|
|
4424
|
+
visit(child, inRPr, inPPr);
|
|
4425
|
+
continue;
|
|
4426
|
+
}
|
|
4427
|
+
if (child.local === "pPr") {
|
|
4428
|
+
visit(child, false, true);
|
|
4429
|
+
continue;
|
|
4430
|
+
}
|
|
4431
|
+
if (child.local === "rPr") {
|
|
4432
|
+
visit(child, true, inPPr);
|
|
4433
|
+
continue;
|
|
4434
|
+
}
|
|
4435
|
+
if (inRPr && RUN_REVISIONS.has(child.local)) {
|
|
4436
|
+
out.push({
|
|
4437
|
+
start: child.start,
|
|
4438
|
+
revision: {
|
|
4439
|
+
kind: child.local,
|
|
4440
|
+
scope: inPPr ? "mark" : "runs",
|
|
4441
|
+
...attributes(child),
|
|
4442
|
+
...inPPr ? { text: "\n" } : {}
|
|
4443
|
+
}
|
|
4444
|
+
});
|
|
4445
|
+
continue;
|
|
4446
|
+
}
|
|
4447
|
+
if (inRPr && child.local === "rPrChange") {
|
|
4448
|
+
out.push({
|
|
4449
|
+
start: child.start,
|
|
4450
|
+
revision: {
|
|
4451
|
+
kind: "rPrChange",
|
|
4452
|
+
scope: inPPr ? "mark" : "runs",
|
|
4453
|
+
...attributes(child),
|
|
4454
|
+
...inPPr || !node.parent ? {} : { text: textOf(part, node.parent) }
|
|
4455
|
+
}
|
|
4456
|
+
});
|
|
4457
|
+
continue;
|
|
4458
|
+
}
|
|
4459
|
+
if (inPPr && child.local === "pPrChange") {
|
|
4460
|
+
out.push({
|
|
4461
|
+
start: child.start,
|
|
4462
|
+
revision: {
|
|
4463
|
+
kind: "pPrChange",
|
|
4464
|
+
scope: "paragraph",
|
|
4465
|
+
...attributes(child)
|
|
4466
|
+
}
|
|
4467
|
+
});
|
|
4468
|
+
continue;
|
|
4469
|
+
}
|
|
4470
|
+
if (!inRPr && !inPPr && RUN_REVISIONS.has(child.local)) {
|
|
4471
|
+
out.push({
|
|
4472
|
+
start: child.start,
|
|
4473
|
+
revision: {
|
|
4474
|
+
kind: child.local,
|
|
4475
|
+
scope: "runs",
|
|
4476
|
+
...attributes(child),
|
|
4477
|
+
text: textOf(part, child)
|
|
4478
|
+
}
|
|
4479
|
+
});
|
|
4480
|
+
visit(child, false, false);
|
|
4481
|
+
continue;
|
|
4482
|
+
}
|
|
4483
|
+
visit(child, inRPr, inPPr);
|
|
4484
|
+
}
|
|
4485
|
+
};
|
|
4486
|
+
visit(paragraph, false, false);
|
|
4487
|
+
return out.sort((a, b) => a.start - b.start).map((entry) => Object.freeze(entry.revision));
|
|
4488
|
+
}
|
|
4489
|
+
function textOf(part, node) {
|
|
4490
|
+
let text = "";
|
|
4491
|
+
const visit = (current) => {
|
|
4492
|
+
for (const child of current.children) {
|
|
4493
|
+
if (child.namespace === W_NS) {
|
|
4494
|
+
if (child.local === "t" || child.local === "delText") {
|
|
4495
|
+
text += part.textOf(child);
|
|
4496
|
+
continue;
|
|
4497
|
+
}
|
|
4498
|
+
if (child.local === "tab") {
|
|
4499
|
+
text += TAB;
|
|
4500
|
+
continue;
|
|
4501
|
+
}
|
|
4502
|
+
if (child.local === "br" || child.local === "cr") {
|
|
4503
|
+
text += LINE_BREAK;
|
|
4504
|
+
continue;
|
|
4505
|
+
}
|
|
4506
|
+
if (child.local === "rPr" || child.local === "pPr") continue;
|
|
4507
|
+
}
|
|
4508
|
+
visit(child);
|
|
4509
|
+
}
|
|
4510
|
+
};
|
|
4511
|
+
visit(node);
|
|
4512
|
+
return text;
|
|
4239
4513
|
}
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4514
|
+
|
|
4515
|
+
// src/edit/docx/structure-ops.ts
|
|
4516
|
+
var MEDIA_FOLDER = "/word/media";
|
|
4517
|
+
var WP_NS = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
|
|
4518
|
+
var A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
4519
|
+
var PIC_NS = "http://schemas.openxmlformats.org/drawingml/2006/picture";
|
|
4520
|
+
var R_NS = OFFICE_RELATIONSHIPS.slice(0, -1);
|
|
4521
|
+
var EMU_PER_PT = 12700;
|
|
4522
|
+
function blockNode(record) {
|
|
4523
|
+
return record.node;
|
|
4524
|
+
}
|
|
4525
|
+
function placement2(operation3, context, issue) {
|
|
4526
|
+
const named = [operation3.before, operation3.after].filter(
|
|
4527
|
+
(value) => value !== void 0
|
|
4528
|
+
);
|
|
4529
|
+
if (named.length !== 1) {
|
|
4243
4530
|
issue(
|
|
4244
|
-
"/
|
|
4245
|
-
|
|
4246
|
-
|
|
4531
|
+
"/before",
|
|
4532
|
+
"invalid-value",
|
|
4533
|
+
"Exactly one of before and after names the reference element"
|
|
4247
4534
|
);
|
|
4248
4535
|
return void 0;
|
|
4249
4536
|
}
|
|
4250
|
-
|
|
4537
|
+
const side = operation3.before !== void 0 ? "before" : "after";
|
|
4538
|
+
const id = operation3[side];
|
|
4539
|
+
const record = context.model.byId.get(id);
|
|
4540
|
+
if (!record || record.kind !== "paragraph" && record.kind !== "table") {
|
|
4251
4541
|
issue(
|
|
4252
|
-
|
|
4253
|
-
"invalid-target",
|
|
4254
|
-
`
|
|
4542
|
+
`/${side}`,
|
|
4543
|
+
record ? "invalid-target" : "unknown-target",
|
|
4544
|
+
record ? `Element ${id} is not a paragraph or a table` : `No element ${id}`
|
|
4255
4545
|
);
|
|
4256
4546
|
return void 0;
|
|
4257
4547
|
}
|
|
4258
|
-
return { record,
|
|
4548
|
+
return { record, side };
|
|
4259
4549
|
}
|
|
4260
|
-
function
|
|
4261
|
-
const
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
if (
|
|
4265
|
-
|
|
4266
|
-
|
|
4550
|
+
function referenceRunProperties(part, record) {
|
|
4551
|
+
const paragraph = record.kind === "paragraph" ? record : record.rows[0]?.[0]?.paragraphs[0];
|
|
4552
|
+
if (!paragraph) return void 0;
|
|
4553
|
+
const first = firstTextItem(paragraph.text);
|
|
4554
|
+
if (first?.rPr) return first.rPr;
|
|
4555
|
+
return paragraph.pPr?.children.find(
|
|
4556
|
+
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
4557
|
+
);
|
|
4558
|
+
}
|
|
4559
|
+
function paragraphIdsUnder(model, node) {
|
|
4560
|
+
const out = [];
|
|
4561
|
+
if (node.local === "p" && node.namespace === W_NS) {
|
|
4562
|
+
const own = model.paragraphIds.get(node);
|
|
4563
|
+
if (own) out.push(own);
|
|
4267
4564
|
}
|
|
4268
|
-
const
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
"invalid-range",
|
|
4273
|
-
`Offsets must satisfy 0 \u2264 start \u2264 end \u2264 ${length}`
|
|
4274
|
-
);
|
|
4275
|
-
return void 0;
|
|
4565
|
+
for (const paragraph of model.document.findAll("p", node)) {
|
|
4566
|
+
if (paragraph === node || paragraph.namespace !== W_NS) continue;
|
|
4567
|
+
const id = model.paragraphIds.get(paragraph);
|
|
4568
|
+
if (id) out.push(id);
|
|
4276
4569
|
}
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
}
|
|
4305
|
-
if (startIn && !check(unit.nodes[0], unit.items)) return false;
|
|
4570
|
+
return out;
|
|
4571
|
+
}
|
|
4572
|
+
function elementIdsUnder(model, node) {
|
|
4573
|
+
const out = [];
|
|
4574
|
+
for (const record of model.records) {
|
|
4575
|
+
let current = record.node;
|
|
4576
|
+
while (current && current !== node) current = current.parent;
|
|
4577
|
+
if (current === node) out.push(record.elementId);
|
|
4578
|
+
}
|
|
4579
|
+
return out;
|
|
4580
|
+
}
|
|
4581
|
+
function stampedSlice(model, node) {
|
|
4582
|
+
const part = model.document;
|
|
4583
|
+
const targets = [
|
|
4584
|
+
...node.local === "p" && node.namespace === W_NS ? [node] : [],
|
|
4585
|
+
...part.findAll("p", node).filter((p) => p !== node && p.namespace === W_NS)
|
|
4586
|
+
].filter((paragraph) => {
|
|
4587
|
+
const id = model.paragraphIds.get(paragraph);
|
|
4588
|
+
return id !== void 0 && model.unauthoredSet.has(id);
|
|
4589
|
+
}).sort((a, b) => b.start - a.start);
|
|
4590
|
+
let xml = sliceOf(part, node);
|
|
4591
|
+
const stamped = [];
|
|
4592
|
+
for (const paragraph of targets) {
|
|
4593
|
+
const id = model.paragraphIds.get(paragraph);
|
|
4594
|
+
if (part.attribute(paragraph, "w14:paraId")) {
|
|
4595
|
+
stamped.push(id);
|
|
4596
|
+
continue;
|
|
4306
4597
|
}
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4598
|
+
const last = paragraph.attributes.at(-1);
|
|
4599
|
+
const from = last ? last.end : paragraph.start + 1 + paragraph.name.length;
|
|
4600
|
+
const close = part.text.indexOf(">", from);
|
|
4601
|
+
let at = close;
|
|
4602
|
+
if (part.text[close - 1] === "/") at = close - 1;
|
|
4603
|
+
const offset = at - node.start;
|
|
4604
|
+
xml = `${xml.slice(0, offset)} w14:paraId="${id}"${xml.slice(offset)}`;
|
|
4605
|
+
stamped.push(id);
|
|
4606
|
+
}
|
|
4607
|
+
return { xml, stamped: stamped.reverse() };
|
|
4310
4608
|
}
|
|
4311
|
-
function
|
|
4312
|
-
const
|
|
4313
|
-
|
|
4609
|
+
function firstCellParagraph(record) {
|
|
4610
|
+
const table = record.table;
|
|
4611
|
+
return table && table.rows[0]?.[0]?.paragraphs[0] === record ? table : void 0;
|
|
4612
|
+
}
|
|
4613
|
+
function tableRemap(table, newFirstParagraphId) {
|
|
4614
|
+
return table.id === newFirstParagraphId ? void 0 : { [table.elementId]: `tbl:${newFirstParagraphId}` };
|
|
4615
|
+
}
|
|
4616
|
+
function commit(context, items, result, transaction = context.pkg.transaction()) {
|
|
4617
|
+
const part = context.model.document;
|
|
4314
4618
|
transaction.patch(part, [...namespacePatches(part), ...items]);
|
|
4315
|
-
return transaction.commit().then((change) => ({
|
|
4316
|
-
createdIds: [],
|
|
4317
|
-
warnings: change.warnings,
|
|
4318
|
-
...context.model.unauthoredSet.has(record.id) ? { stamped: [record.id] } : {},
|
|
4319
|
-
reflowFrom: record.id,
|
|
4320
|
-
...extra
|
|
4321
|
-
}));
|
|
4619
|
+
return transaction.commit().then((change) => ({ ...result, warnings: change.warnings }));
|
|
4322
4620
|
}
|
|
4323
|
-
var
|
|
4621
|
+
var insertParagraphHandler = {
|
|
4324
4622
|
async validate(operation3, context, issue) {
|
|
4325
|
-
const
|
|
4326
|
-
const problem = textProblem(text);
|
|
4623
|
+
const problem = textProblem(normalizeText(operation3.text));
|
|
4327
4624
|
if (problem) issue("/text", "invalid-text", `The text holds ${problem}`);
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
operation3.
|
|
4334
|
-
issue,
|
|
4335
|
-
|
|
4336
|
-
);
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4625
|
+
if (operation3.style?.color !== void 0) {
|
|
4626
|
+
const colour = colorProblem(operation3.style.color);
|
|
4627
|
+
if (colour) issue("/style/color", "invalid-value", `Colour: ${colour}`);
|
|
4628
|
+
}
|
|
4629
|
+
if (operation3.style?.fontFamily !== void 0) {
|
|
4630
|
+
const font = attributeProblem(operation3.style.fontFamily);
|
|
4631
|
+
if (font) issue("/style/fontFamily", "invalid-value", `Font: ${font}`);
|
|
4632
|
+
}
|
|
4633
|
+
placement2(operation3, context, issue);
|
|
4634
|
+
},
|
|
4635
|
+
async apply(operation3, context) {
|
|
4636
|
+
const { record, side } = placement2(operation3, context, () => {
|
|
4637
|
+
});
|
|
4638
|
+
const part = context.model.document;
|
|
4639
|
+
const node = blockNode(record);
|
|
4640
|
+
const sourceRPr = referenceRunProperties(part, record);
|
|
4641
|
+
const rPr = operation3.style ? changedRunProperties(
|
|
4642
|
+
part,
|
|
4643
|
+
sourceRPr,
|
|
4644
|
+
operation3.style,
|
|
4645
|
+
context.model.styles
|
|
4646
|
+
) : sliceOf(part, sourceRPr);
|
|
4647
|
+
const sourcePPr = record.kind === "paragraph" ? record.pPr : void 0;
|
|
4648
|
+
const pPr = paragraphPropertiesWithoutSection(part, sourcePPr);
|
|
4649
|
+
const segments = normalizeText(operation3.text).split("\n");
|
|
4650
|
+
const createdIds = [];
|
|
4651
|
+
const xml = segments.map((segment) => {
|
|
4652
|
+
const id = context.freshParagraphId();
|
|
4653
|
+
createdIds.push(`p:${id}`);
|
|
4654
|
+
return context.tracked ? paragraphXml(
|
|
4655
|
+
void 0,
|
|
4656
|
+
id,
|
|
4657
|
+
markedParagraphProperties(context, sourcePPr, "ins"),
|
|
4658
|
+
insertedRunXml(context, rPr, segment)
|
|
4659
|
+
) : paragraphXml(
|
|
4660
|
+
void 0,
|
|
4661
|
+
id,
|
|
4662
|
+
pPr,
|
|
4663
|
+
runXml(rPr, runContentXml(segment))
|
|
4664
|
+
);
|
|
4665
|
+
});
|
|
4666
|
+
const items = side === "before" ? xml.map((paragraph) => patches.insertBefore(part, node, paragraph)) : xml.map((paragraph) => patches.insertAfter(part, node, paragraph));
|
|
4667
|
+
const table = side === "before" && record.kind === "paragraph" ? firstCellParagraph(record) : void 0;
|
|
4668
|
+
const remappedIds = table ? tableRemap(table, createdIds[0].slice(2)) : void 0;
|
|
4669
|
+
return commit(context, items, {
|
|
4670
|
+
createdIds,
|
|
4671
|
+
reflowFrom: record.id,
|
|
4672
|
+
...remappedIds ? { remappedIds } : {}
|
|
4673
|
+
});
|
|
4674
|
+
}
|
|
4675
|
+
};
|
|
4676
|
+
function bodyTailPatch(context, remaining) {
|
|
4677
|
+
const { model } = context;
|
|
4678
|
+
const last = remaining.at(-1);
|
|
4679
|
+
if (last && last.kind === "paragraph") return void 0;
|
|
4680
|
+
const id = context.freshParagraphId();
|
|
4681
|
+
const xml = paragraphXml(void 0, id, "", "");
|
|
4682
|
+
const part = model.document;
|
|
4683
|
+
const patch = last ? patches.insertAfter(part, last.node, xml) : model.bodySectPr ? patches.insertBefore(part, model.bodySectPr, xml) : patches.appendChild(part, model.body, xml);
|
|
4684
|
+
return { patch, id };
|
|
4685
|
+
}
|
|
4686
|
+
function otherParagraphsIn(model, record) {
|
|
4687
|
+
if (record.table) {
|
|
4688
|
+
for (const row of record.table.rows)
|
|
4689
|
+
for (const cell of row)
|
|
4690
|
+
if (cell.paragraphs.includes(record))
|
|
4691
|
+
return cell.paragraphs.some((candidate) => candidate !== record);
|
|
4692
|
+
return false;
|
|
4693
|
+
}
|
|
4694
|
+
return model.blocks.some(
|
|
4695
|
+
(block) => block.kind === "paragraph" && block !== record
|
|
4696
|
+
);
|
|
4697
|
+
}
|
|
4698
|
+
var deleteElementHandler = {
|
|
4699
|
+
async validate(operation3, context, issue) {
|
|
4700
|
+
const record = context.model.byId.get(operation3.target);
|
|
4701
|
+
if (!record || record.kind === "other") {
|
|
4348
4702
|
issue(
|
|
4349
|
-
"/
|
|
4350
|
-
"invalid-
|
|
4351
|
-
|
|
4703
|
+
"/target",
|
|
4704
|
+
record ? "invalid-target" : "unknown-target",
|
|
4705
|
+
record ? `Element ${operation3.target} cannot be deleted` : `No element ${operation3.target}`
|
|
4352
4706
|
);
|
|
4707
|
+
return;
|
|
4708
|
+
}
|
|
4709
|
+
if (context.tracked && record.kind !== "paragraph") {
|
|
4710
|
+
unsupportedTracked(issue, "/target", "Deleting a table or a picture");
|
|
4711
|
+
return;
|
|
4712
|
+
}
|
|
4713
|
+
if (record.kind === "paragraph") {
|
|
4714
|
+
if (context.tracked && !record.readOnlyReason && trackedRangeProblem(
|
|
4715
|
+
record,
|
|
4716
|
+
0,
|
|
4717
|
+
record.text.text.length,
|
|
4718
|
+
issue,
|
|
4719
|
+
"/target"
|
|
4720
|
+
))
|
|
4721
|
+
return;
|
|
4722
|
+
if (record.sectPr)
|
|
4723
|
+
issue(
|
|
4724
|
+
"/target",
|
|
4725
|
+
"section-break",
|
|
4726
|
+
"A paragraph that ends a section cannot be deleted"
|
|
4727
|
+
);
|
|
4728
|
+
else if (record.readOnlyReason)
|
|
4729
|
+
issue(
|
|
4730
|
+
"/target",
|
|
4731
|
+
"invalid-target",
|
|
4732
|
+
`Paragraph ${operation3.target} is read-only (${record.readOnlyReason})`
|
|
4733
|
+
);
|
|
4734
|
+
else if (!otherParagraphsIn(context.model, record))
|
|
4735
|
+
issue(
|
|
4736
|
+
"/target",
|
|
4737
|
+
"last-paragraph",
|
|
4738
|
+
"The last paragraph of a body or a cell cannot be deleted"
|
|
4739
|
+
);
|
|
4740
|
+
}
|
|
4353
4741
|
},
|
|
4354
4742
|
async apply(operation3, context) {
|
|
4355
|
-
const
|
|
4356
|
-
|
|
4357
|
-
const
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4743
|
+
const { model } = context;
|
|
4744
|
+
const found = model.byId.get(operation3.target);
|
|
4745
|
+
const part = model.document;
|
|
4746
|
+
if (found.kind !== "paragraph" && found.kind !== "table")
|
|
4747
|
+
return deleteInline(context, found);
|
|
4748
|
+
const record = found;
|
|
4749
|
+
const node = blockNode(record);
|
|
4750
|
+
if (context.tracked && record.kind === "paragraph") {
|
|
4751
|
+
const nested = paragraphIdsUnder(model, node).filter(
|
|
4752
|
+
(id) => id !== record.id
|
|
4753
|
+
);
|
|
4754
|
+
return commit(
|
|
4755
|
+
context,
|
|
4756
|
+
[
|
|
4757
|
+
patches.replaceElement(
|
|
4758
|
+
part,
|
|
4759
|
+
node,
|
|
4760
|
+
deletedParagraphXml(context, record)
|
|
4761
|
+
)
|
|
4762
|
+
],
|
|
4763
|
+
{
|
|
4764
|
+
createdIds: [],
|
|
4765
|
+
removedIds: record.inlines.map((inline) => inline.elementId),
|
|
4766
|
+
...nested.length > 0 ? { removedParagraphIds: nested } : {},
|
|
4767
|
+
...model.unauthoredSet.has(record.id) ? { stamped: [record.id] } : {},
|
|
4768
|
+
reflowFrom: record.id
|
|
4769
|
+
}
|
|
4770
|
+
);
|
|
4771
|
+
}
|
|
4772
|
+
const items = [patches.removeElement(part, node)];
|
|
4773
|
+
const createdIds = [];
|
|
4774
|
+
if (model.blocks.includes(record)) {
|
|
4775
|
+
const tail = bodyTailPatch(
|
|
4776
|
+
context,
|
|
4777
|
+
model.blocks.filter((block) => block !== record)
|
|
4778
|
+
);
|
|
4779
|
+
if (tail) {
|
|
4780
|
+
createdIds.push(`p:${tail.id}`);
|
|
4781
|
+
items.push(tail.patch);
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
const removedParagraphIds = paragraphIdsUnder(model, node);
|
|
4785
|
+
const table = record.kind === "paragraph" ? firstCellParagraph(record) : void 0;
|
|
4786
|
+
const successor = table?.rows[0]?.[0]?.paragraphs[1];
|
|
4787
|
+
const remappedIds = table && successor ? tableRemap(table, successor.id) : void 0;
|
|
4788
|
+
return commit(context, items, {
|
|
4373
4789
|
createdIds,
|
|
4374
|
-
|
|
4375
|
-
|
|
4790
|
+
removedIds: elementIdsUnder(model, node),
|
|
4791
|
+
removedParagraphIds,
|
|
4792
|
+
reflowFrom: record.id,
|
|
4793
|
+
...remappedIds ? { remappedIds } : {}
|
|
4376
4794
|
});
|
|
4377
4795
|
}
|
|
4378
4796
|
};
|
|
4379
|
-
function
|
|
4380
|
-
const
|
|
4381
|
-
const
|
|
4382
|
-
const
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
(rPr) => runXml(rPr, runContentXml(segments[0])),
|
|
4389
|
-
paragraphMarkRPr(part, record)
|
|
4390
|
-
);
|
|
4391
|
-
const pPr = sliceOf(part, record.pPr);
|
|
4392
|
-
const single = segments.length === 1;
|
|
4393
|
-
const first = paragraphXml(
|
|
4394
|
-
record.node,
|
|
4395
|
-
record.id,
|
|
4396
|
-
pPr,
|
|
4397
|
-
split.before + (single ? split.after : "")
|
|
4398
|
-
);
|
|
4399
|
-
const items = [patches.replaceElement(part, record.node, first)];
|
|
4400
|
-
const createdIds = [];
|
|
4401
|
-
const copiedPPr = paragraphPropertiesWithoutSection(part, record.pPr);
|
|
4402
|
-
segments.slice(1).forEach((segment, index) => {
|
|
4403
|
-
const id = context.freshParagraphId();
|
|
4404
|
-
createdIds.push(`p:${id}`);
|
|
4405
|
-
const last = index === segments.length - 2;
|
|
4406
|
-
items.push(
|
|
4407
|
-
patches.insertAfter(
|
|
4408
|
-
part,
|
|
4409
|
-
record.node,
|
|
4410
|
-
paragraphXml(
|
|
4411
|
-
record.node,
|
|
4412
|
-
id,
|
|
4413
|
-
copiedPPr,
|
|
4414
|
-
runXml(split.rPr, runContentXml(segment)) + (last ? split.after : "")
|
|
4415
|
-
)
|
|
4416
|
-
)
|
|
4797
|
+
async function deleteInline(context, record) {
|
|
4798
|
+
const { model } = context;
|
|
4799
|
+
const part = model.document;
|
|
4800
|
+
const transaction = context.pkg.transaction();
|
|
4801
|
+
const blip = part.find("blip", record.node);
|
|
4802
|
+
const rId = blip ? part.attribute(blip, "r:embed") ?? part.attribute(blip, "r:link") : void 0;
|
|
4803
|
+
if (rId) {
|
|
4804
|
+
const uses = part.findAll("blip").filter(
|
|
4805
|
+
(candidate) => candidate !== blip && (part.attribute(candidate, "r:embed") === rId || part.attribute(candidate, "r:link") === rId)
|
|
4417
4806
|
);
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
record
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
return {
|
|
4436
|
-
items,
|
|
4437
|
-
createdIds,
|
|
4438
|
-
removedIds,
|
|
4439
|
-
removedParagraphIds: dropped.removedParagraphIds
|
|
4440
|
-
};
|
|
4441
|
-
}
|
|
4442
|
-
function paragraphMarkRPr(part, record) {
|
|
4443
|
-
const rPr = record.pPr?.children.find(
|
|
4444
|
-
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
4807
|
+
const elsewhere = new RegExp(
|
|
4808
|
+
`r:(?:id|embed|link|pict)="${rId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`
|
|
4809
|
+
);
|
|
4810
|
+
const others = uses.length > 0 || elsewhere.test(stripNode(part, record.node));
|
|
4811
|
+
if (!others) transaction.removeRelationship(model.mainPart, rId);
|
|
4812
|
+
}
|
|
4813
|
+
const removedParagraphIds = paragraphIdsUnder(model, record.node);
|
|
4814
|
+
return commit(
|
|
4815
|
+
context,
|
|
4816
|
+
[patches.removeElement(part, record.node)],
|
|
4817
|
+
{
|
|
4818
|
+
createdIds: [],
|
|
4819
|
+
removedIds: [record.elementId],
|
|
4820
|
+
...removedParagraphIds.length > 0 ? { removedParagraphIds } : {},
|
|
4821
|
+
reflowFrom: record.paragraph.id
|
|
4822
|
+
},
|
|
4823
|
+
transaction
|
|
4445
4824
|
);
|
|
4446
|
-
return sliceOf(part, rPr);
|
|
4447
4825
|
}
|
|
4448
|
-
function
|
|
4449
|
-
|
|
4450
|
-
return `<w:r>${rPr}${content}</w:r>`;
|
|
4826
|
+
function stripNode(part, node) {
|
|
4827
|
+
return part.text.slice(0, node.start) + part.text.slice(node.end);
|
|
4451
4828
|
}
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
const overlaps = unit.start < end && start < unit.end ? true : unit.start === unit.end && start < unit.start && unit.start < end;
|
|
4458
|
-
if (unit.kind === "zero" || !overlaps) {
|
|
4459
|
-
out += raw;
|
|
4460
|
-
continue;
|
|
4829
|
+
var moveElementHandler = {
|
|
4830
|
+
async validate(operation3, context, issue) {
|
|
4831
|
+
if (context.tracked) {
|
|
4832
|
+
unsupportedTracked(issue, "", "Moving an element");
|
|
4833
|
+
return;
|
|
4461
4834
|
}
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
contentOf(
|
|
4469
|
-
part,
|
|
4470
|
-
unit.items,
|
|
4471
|
-
unit.start,
|
|
4472
|
-
Math.min(start, unit.end),
|
|
4473
|
-
true
|
|
4474
|
-
)
|
|
4475
|
-
);
|
|
4476
|
-
out += runXml(
|
|
4477
|
-
changed(rPr),
|
|
4478
|
-
contentOf(
|
|
4479
|
-
part,
|
|
4480
|
-
unit.items,
|
|
4481
|
-
Math.max(unit.start, start),
|
|
4482
|
-
Math.min(unit.end, end),
|
|
4483
|
-
false
|
|
4484
|
-
)
|
|
4485
|
-
);
|
|
4486
|
-
out += runXml(
|
|
4487
|
-
own,
|
|
4488
|
-
contentOf(part, unit.items, Math.max(end, unit.start), unit.end, false)
|
|
4835
|
+
const record = context.model.byId.get(operation3.target);
|
|
4836
|
+
if (!record || record.kind !== "paragraph" && record.kind !== "table") {
|
|
4837
|
+
issue(
|
|
4838
|
+
"/target",
|
|
4839
|
+
record ? "invalid-target" : "unknown-target",
|
|
4840
|
+
record ? `Element ${operation3.target} is not a paragraph or a table` : `No element ${operation3.target}`
|
|
4489
4841
|
);
|
|
4490
|
-
|
|
4491
|
-
}
|
|
4492
|
-
if (unit.kind === "field") {
|
|
4493
|
-
for (const node of unit.nodes)
|
|
4494
|
-
if (node.local === "r" && node.namespace === W_NS)
|
|
4495
|
-
out += restyledRun(
|
|
4496
|
-
part,
|
|
4497
|
-
node,
|
|
4498
|
-
changed(
|
|
4499
|
-
node.children.find(
|
|
4500
|
-
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
4501
|
-
)
|
|
4502
|
-
)
|
|
4503
|
-
);
|
|
4504
|
-
else if (node.local === "fldSimple")
|
|
4505
|
-
out += startTagOf(part, node) + restyled(part, node, unit.items, start, end, change, context) + endTagOf(part, node);
|
|
4506
|
-
else out += sliceOf(part, node);
|
|
4507
|
-
continue;
|
|
4842
|
+
return;
|
|
4508
4843
|
}
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
const { style } = operation3;
|
|
4517
|
-
if (style.color !== void 0) {
|
|
4518
|
-
const problem = colorProblem(style.color);
|
|
4519
|
-
if (problem)
|
|
4520
|
-
issue("/style/color", "invalid-value", `Colour: ${problem}`);
|
|
4844
|
+
if (record.kind === "paragraph" && record.sectPr) {
|
|
4845
|
+
issue(
|
|
4846
|
+
"/target",
|
|
4847
|
+
"section-break",
|
|
4848
|
+
"A paragraph that ends a section cannot move"
|
|
4849
|
+
);
|
|
4850
|
+
return;
|
|
4521
4851
|
}
|
|
4522
|
-
if (
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4852
|
+
if (record.kind === "paragraph" && record.readOnlyReason) {
|
|
4853
|
+
issue(
|
|
4854
|
+
"/target",
|
|
4855
|
+
"invalid-target",
|
|
4856
|
+
`Paragraph ${operation3.target} is read-only (${record.readOnlyReason})`
|
|
4857
|
+
);
|
|
4858
|
+
return;
|
|
4526
4859
|
}
|
|
4527
|
-
const
|
|
4528
|
-
if (
|
|
4529
|
-
|
|
4860
|
+
const reference = placement2(operation3, context, issue);
|
|
4861
|
+
if (!reference) return;
|
|
4862
|
+
if (reference.record === record) {
|
|
4863
|
+
issue(
|
|
4864
|
+
`/${reference.side}`,
|
|
4865
|
+
"invalid-target",
|
|
4866
|
+
"An element cannot move next to itself"
|
|
4867
|
+
);
|
|
4868
|
+
return;
|
|
4869
|
+
}
|
|
4870
|
+
if (blockNode(reference.record).parent !== blockNode(record).parent)
|
|
4871
|
+
issue(
|
|
4872
|
+
`/${reference.side}`,
|
|
4873
|
+
"invalid-target",
|
|
4874
|
+
"The reference must be in the same body or cell as the element"
|
|
4875
|
+
);
|
|
4530
4876
|
},
|
|
4531
4877
|
async apply(operation3, context) {
|
|
4532
|
-
const
|
|
4533
|
-
|
|
4534
|
-
const {
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
operation3.target,
|
|
4538
|
-
operation3.range,
|
|
4878
|
+
const { model } = context;
|
|
4879
|
+
const record = model.byId.get(operation3.target);
|
|
4880
|
+
const { record: reference, side } = placement2(
|
|
4881
|
+
operation3,
|
|
4882
|
+
context,
|
|
4539
4883
|
() => {
|
|
4540
|
-
}
|
|
4541
|
-
false
|
|
4542
|
-
);
|
|
4543
|
-
if (Object.keys(operation3.style).length === 0)
|
|
4544
|
-
return { createdIds: [], warnings: [] };
|
|
4545
|
-
const content = restyled(
|
|
4546
|
-
part,
|
|
4547
|
-
record.node,
|
|
4548
|
-
record.text.items,
|
|
4549
|
-
start,
|
|
4550
|
-
end,
|
|
4551
|
-
operation3.style,
|
|
4552
|
-
context
|
|
4884
|
+
}
|
|
4553
4885
|
);
|
|
4554
|
-
const
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
)
|
|
4566
|
-
|
|
4886
|
+
const part = model.document;
|
|
4887
|
+
const node = blockNode(record);
|
|
4888
|
+
const target = blockNode(reference);
|
|
4889
|
+
const { xml, stamped } = stampedSlice(model, node);
|
|
4890
|
+
const items = [
|
|
4891
|
+
patches.removeElement(part, node),
|
|
4892
|
+
side === "before" ? patches.insertBefore(part, target, xml) : patches.insertAfter(part, target, xml)
|
|
4893
|
+
];
|
|
4894
|
+
const createdIds = [];
|
|
4895
|
+
if (model.blocks.includes(record) && model.blocks.includes(reference)) {
|
|
4896
|
+
const without = model.blocks.filter((block) => block !== record);
|
|
4897
|
+
const at = without.indexOf(reference);
|
|
4898
|
+
without.splice(side === "before" ? at : at + 1, 0, record);
|
|
4899
|
+
const tail = bodyTailPatch(context, without);
|
|
4900
|
+
if (tail) {
|
|
4901
|
+
createdIds.push(`p:${tail.id}`);
|
|
4902
|
+
items.push(tail.patch);
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
const order = (candidate) => model.records.indexOf(candidate);
|
|
4906
|
+
const first = order(record) < order(reference) ? record : reference;
|
|
4907
|
+
let remappedIds;
|
|
4908
|
+
if (record.kind === "paragraph" && record.table) {
|
|
4909
|
+
const cell = record.table.rows[0]?.[0];
|
|
4910
|
+
if (cell && cell.paragraphs.includes(record)) {
|
|
4911
|
+
const without = cell.paragraphs.filter((p) => p !== record);
|
|
4912
|
+
const at = without.indexOf(reference);
|
|
4913
|
+
const reordered = [...without];
|
|
4914
|
+
if (at >= 0)
|
|
4915
|
+
reordered.splice(side === "before" ? at : at + 1, 0, record);
|
|
4916
|
+
const head = reordered[0];
|
|
4917
|
+
if (head) remappedIds = tableRemap(record.table, head.id);
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
return commit(context, items, {
|
|
4921
|
+
createdIds,
|
|
4922
|
+
stamped,
|
|
4923
|
+
reflowFrom: first.id,
|
|
4924
|
+
...remappedIds ? { remappedIds } : {}
|
|
4925
|
+
});
|
|
4567
4926
|
}
|
|
4568
4927
|
};
|
|
4569
|
-
|
|
4928
|
+
async function existingMedia(context, bytes, mimeType) {
|
|
4929
|
+
const extension = `.${extensionForMime(mimeType)}`;
|
|
4930
|
+
for (const name of context.pkg.currentPartNames) {
|
|
4931
|
+
if (!name.startsWith(`${MEDIA_FOLDER}/`) || !name.endsWith(extension))
|
|
4932
|
+
continue;
|
|
4933
|
+
const entry = context.pkg.entry(name);
|
|
4934
|
+
if (entry && entry.uncompressedSize !== bytes.length) continue;
|
|
4935
|
+
const candidate = await context.pkg.part(name);
|
|
4936
|
+
if (candidate.length !== bytes.length) continue;
|
|
4937
|
+
let same = true;
|
|
4938
|
+
for (let index = 0; index < bytes.length; index += 1)
|
|
4939
|
+
if (candidate[index] !== bytes[index]) {
|
|
4940
|
+
same = false;
|
|
4941
|
+
break;
|
|
4942
|
+
}
|
|
4943
|
+
if (same) return name;
|
|
4944
|
+
}
|
|
4945
|
+
return void 0;
|
|
4946
|
+
}
|
|
4947
|
+
function matchesMime(bytes, mimeType) {
|
|
4948
|
+
if (mimeType === "image/png")
|
|
4949
|
+
return bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
|
|
4950
|
+
return bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
|
|
4951
|
+
}
|
|
4952
|
+
function nextDrawingId(part) {
|
|
4953
|
+
let max = 0;
|
|
4954
|
+
for (const node of part.findAll("docPr")) {
|
|
4955
|
+
if (node.namespace !== WP_NS) continue;
|
|
4956
|
+
const id = Number(part.attribute(node, "id") ?? NaN);
|
|
4957
|
+
if (Number.isInteger(id)) max = Math.max(max, id);
|
|
4958
|
+
}
|
|
4959
|
+
return max + 1;
|
|
4960
|
+
}
|
|
4961
|
+
function drawingNamespacePatches(part) {
|
|
4962
|
+
const root = part.root;
|
|
4963
|
+
const has = (name) => root.attributes.some((attribute) => attribute.name === name);
|
|
4964
|
+
const items = [];
|
|
4965
|
+
if (!has("xmlns:wp"))
|
|
4966
|
+
items.push(patches.setAttribute(part, root, "xmlns:wp", WP_NS));
|
|
4967
|
+
if (!has("xmlns:r"))
|
|
4968
|
+
items.push(patches.setAttribute(part, root, "xmlns:r", R_NS));
|
|
4969
|
+
return items;
|
|
4970
|
+
}
|
|
4971
|
+
var insertImageHandler = {
|
|
4570
4972
|
async validate(operation3, context, issue) {
|
|
4571
|
-
|
|
4973
|
+
if (context.tracked) {
|
|
4974
|
+
unsupportedTracked(issue, "", "Inserting a picture");
|
|
4975
|
+
return;
|
|
4976
|
+
}
|
|
4977
|
+
placement2(operation3, context, issue);
|
|
4978
|
+
try {
|
|
4979
|
+
const bytes = resolveBinary(operation3.data, context.assets);
|
|
4980
|
+
if (bytes.length === 0)
|
|
4981
|
+
issue("/data", "invalid-value", "The image has no bytes");
|
|
4982
|
+
else if (!matchesMime(bytes, operation3.mimeType))
|
|
4983
|
+
issue(
|
|
4984
|
+
"/data",
|
|
4985
|
+
"invalid-value",
|
|
4986
|
+
`The bytes are not a ${operation3.mimeType} image`
|
|
4987
|
+
);
|
|
4988
|
+
} catch {
|
|
4989
|
+
issue(
|
|
4990
|
+
"/data",
|
|
4991
|
+
"unknown-asset",
|
|
4992
|
+
"The asset is not known to the session"
|
|
4993
|
+
);
|
|
4994
|
+
}
|
|
4572
4995
|
},
|
|
4573
4996
|
async apply(operation3, context) {
|
|
4574
|
-
const
|
|
4997
|
+
const { model } = context;
|
|
4998
|
+
const { record, side } = placement2(operation3, context, () => {
|
|
4575
4999
|
});
|
|
4576
|
-
const
|
|
4577
|
-
const
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
const
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
5000
|
+
const part = model.document;
|
|
5001
|
+
const bytes = resolveBinary(operation3.data, context.assets);
|
|
5002
|
+
const transaction = context.pkg.transaction();
|
|
5003
|
+
const existing = await existingMedia(context, bytes, operation3.mimeType);
|
|
5004
|
+
const rId = existing ? await transaction.addRelationship(
|
|
5005
|
+
model.mainPart,
|
|
5006
|
+
IMAGE_RELATIONSHIP_TYPE,
|
|
5007
|
+
relativeTarget(model.mainPart, existing)
|
|
5008
|
+
) : (await transaction.addMedia(
|
|
5009
|
+
model.mainPart,
|
|
5010
|
+
MEDIA_FOLDER,
|
|
5011
|
+
bytes,
|
|
5012
|
+
operation3.mimeType
|
|
5013
|
+
)).rId;
|
|
5014
|
+
const cx = Math.round(operation3.size.width * EMU_PER_PT);
|
|
5015
|
+
const cy = Math.round(operation3.size.height * EMU_PER_PT);
|
|
5016
|
+
const drawingId = nextDrawingId(part);
|
|
5017
|
+
const id = context.freshParagraphId();
|
|
5018
|
+
const drawing = `<w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:effectExtent l="0" t="0" r="0" b="0"/><wp:docPr id="${drawingId}" name="Picture ${drawingId}"/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="${A_NS}" noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic xmlns:a="${A_NS}"><a:graphicData uri="${PIC_NS}"><pic:pic xmlns:pic="${PIC_NS}"><pic:nvPicPr><pic:cNvPr id="0" name="Picture ${drawingId}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${rId}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>`;
|
|
5019
|
+
const xml = paragraphXml(void 0, id, "", drawing);
|
|
5020
|
+
const node = blockNode(record);
|
|
5021
|
+
const items = [
|
|
5022
|
+
...drawingNamespacePatches(part),
|
|
5023
|
+
side === "before" ? patches.insertBefore(part, node, xml) : patches.insertAfter(part, node, xml)
|
|
5024
|
+
];
|
|
5025
|
+
return commit(
|
|
5026
|
+
context,
|
|
5027
|
+
items,
|
|
5028
|
+
{ createdIds: [`p:${id}`, `img:${id}.0`], reflowFrom: record.id },
|
|
5029
|
+
transaction
|
|
4585
5030
|
);
|
|
4586
|
-
return commitParagraph(context, target, [
|
|
4587
|
-
patches.replaceElement(
|
|
4588
|
-
part,
|
|
4589
|
-
node,
|
|
4590
|
-
paragraphXml(node, record.id, pPr, content)
|
|
4591
|
-
)
|
|
4592
|
-
]);
|
|
4593
5031
|
}
|
|
4594
5032
|
};
|
|
4595
5033
|
|
|
@@ -4685,6 +5123,10 @@ function commit2(context, items, result) {
|
|
|
4685
5123
|
}
|
|
4686
5124
|
var insertTableHandler = {
|
|
4687
5125
|
async validate(operation3, context, issue) {
|
|
5126
|
+
if (context.tracked) {
|
|
5127
|
+
unsupportedTracked(issue, "", "Inserting a table");
|
|
5128
|
+
return;
|
|
5129
|
+
}
|
|
4688
5130
|
placement3(operation3, context, issue);
|
|
4689
5131
|
const columns = operation3.rows[0]?.length ?? 0;
|
|
4690
5132
|
if (operation3.rows.some((row) => row.length !== columns))
|
|
@@ -4789,6 +5231,17 @@ var setTableCellHandler = {
|
|
|
4789
5231
|
"invalid-target",
|
|
4790
5232
|
`The cell's paragraph is read-only (${first.readOnlyReason})`
|
|
4791
5233
|
);
|
|
5234
|
+
else if (context.tracked) {
|
|
5235
|
+
for (const paragraph of cell.paragraphs)
|
|
5236
|
+
if (paragraph.readOnlyReason || trackedRangeProblem(
|
|
5237
|
+
paragraph,
|
|
5238
|
+
0,
|
|
5239
|
+
paragraph.text.text.length,
|
|
5240
|
+
issue,
|
|
5241
|
+
"/target"
|
|
5242
|
+
))
|
|
5243
|
+
return;
|
|
5244
|
+
}
|
|
4792
5245
|
},
|
|
4793
5246
|
async apply(operation3, context) {
|
|
4794
5247
|
const { model } = context;
|
|
@@ -4807,13 +5260,28 @@ var setTableCellHandler = {
|
|
|
4807
5260
|
const items = [...replaced.items];
|
|
4808
5261
|
const removedIds = [...replaced.removedIds];
|
|
4809
5262
|
const removedParagraphIds = [...replaced.removedParagraphIds];
|
|
5263
|
+
const stamped = model.unauthoredSet.has(first.id) ? [first.id] : [];
|
|
4810
5264
|
for (const paragraph of rest) {
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
5265
|
+
if (context.tracked) {
|
|
5266
|
+
if (model.unauthoredSet.has(paragraph.id)) stamped.push(paragraph.id);
|
|
5267
|
+
items.push(
|
|
5268
|
+
patches.replaceElement(
|
|
5269
|
+
part,
|
|
5270
|
+
paragraph.node,
|
|
5271
|
+
deletedParagraphXml(context, paragraph)
|
|
5272
|
+
)
|
|
5273
|
+
);
|
|
5274
|
+
removedIds.push(
|
|
5275
|
+
...paragraph.inlines.map((inline) => inline.elementId)
|
|
5276
|
+
);
|
|
5277
|
+
} else {
|
|
5278
|
+
items.push(patches.removeElement(part, paragraph.node));
|
|
5279
|
+
removedIds.push(
|
|
5280
|
+
paragraph.elementId,
|
|
5281
|
+
...paragraph.inlines.map((inline) => inline.elementId)
|
|
5282
|
+
);
|
|
5283
|
+
removedParagraphIds.push(paragraph.id);
|
|
5284
|
+
}
|
|
4817
5285
|
for (const nested of part.findAll("p", paragraph.node)) {
|
|
4818
5286
|
const id = model.paragraphIds.get(nested);
|
|
4819
5287
|
if (id && nested !== paragraph.node) removedParagraphIds.push(id);
|
|
@@ -4823,7 +5291,7 @@ var setTableCellHandler = {
|
|
|
4823
5291
|
createdIds: replaced.createdIds,
|
|
4824
5292
|
...removedIds.length > 0 ? { removedIds } : {},
|
|
4825
5293
|
...removedParagraphIds.length > 0 ? { removedParagraphIds } : {},
|
|
4826
|
-
...
|
|
5294
|
+
...stamped.length > 0 ? { stamped } : {},
|
|
4827
5295
|
reflowFrom: record.id
|
|
4828
5296
|
});
|
|
4829
5297
|
}
|
|
@@ -4860,6 +5328,44 @@ function unauthoredIdsOf(bytes) {
|
|
|
4860
5328
|
const text = new TextDecoder().decode(bytes);
|
|
4861
5329
|
return [...text.matchAll(/<p id="([0-9A-F]{8})"\/>/g)].map((m) => m[1]);
|
|
4862
5330
|
}
|
|
5331
|
+
function trackedOf(mode) {
|
|
5332
|
+
if (mode.changeMode !== "tracked") return void 0;
|
|
5333
|
+
return {
|
|
5334
|
+
author: mode.author ?? "",
|
|
5335
|
+
...mode.timestamp === void 0 ? {} : { date: mode.timestamp }
|
|
5336
|
+
};
|
|
5337
|
+
}
|
|
5338
|
+
var DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/;
|
|
5339
|
+
function trackedModeIssues(tracked) {
|
|
5340
|
+
if (!tracked) return [];
|
|
5341
|
+
const issues = [];
|
|
5342
|
+
const author = attributeProblem(tracked.author);
|
|
5343
|
+
if (tracked.author.trim().length === 0)
|
|
5344
|
+
issues.push({
|
|
5345
|
+
operationIndex: -1,
|
|
5346
|
+
path: "/author",
|
|
5347
|
+
code: "required",
|
|
5348
|
+
message: "Tracked changes name their author; pass ApplyOptions.author"
|
|
5349
|
+
});
|
|
5350
|
+
else if (author)
|
|
5351
|
+
issues.push({
|
|
5352
|
+
operationIndex: -1,
|
|
5353
|
+
path: "/author",
|
|
5354
|
+
code: "invalid-value",
|
|
5355
|
+
message: `The author holds ${author}`
|
|
5356
|
+
});
|
|
5357
|
+
if (tracked.date !== void 0) {
|
|
5358
|
+
const date = attributeProblem(tracked.date);
|
|
5359
|
+
if (date || !DATE_TIME.test(tracked.date) || Number.isNaN(Date.parse(tracked.date)))
|
|
5360
|
+
issues.push({
|
|
5361
|
+
operationIndex: -1,
|
|
5362
|
+
path: "/timestamp",
|
|
5363
|
+
code: "invalid-value",
|
|
5364
|
+
message: date ? `The timestamp holds ${date}` : "The timestamp must be an ISO 8601 date-time"
|
|
5365
|
+
});
|
|
5366
|
+
}
|
|
5367
|
+
return issues;
|
|
5368
|
+
}
|
|
4863
5369
|
var DocxEditEngine = class _DocxEditEngine {
|
|
4864
5370
|
schemas = docxOperationSchemas;
|
|
4865
5371
|
#original;
|
|
@@ -4922,9 +5428,12 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
4922
5428
|
});
|
|
4923
5429
|
return pending;
|
|
4924
5430
|
}
|
|
4925
|
-
async validate(operations, signal) {
|
|
5431
|
+
async validate(operations, signal, mode = {}) {
|
|
4926
5432
|
const issues = [];
|
|
4927
|
-
const
|
|
5433
|
+
const tracked = trackedOf(mode);
|
|
5434
|
+
const modeIssues = trackedModeIssues(tracked);
|
|
5435
|
+
if (modeIssues.length > 0) return modeIssues;
|
|
5436
|
+
const base = await this.#context(0, signal, 0, /* @__PURE__ */ new Set(), tracked);
|
|
4928
5437
|
for (const [index, operation3] of operations.entries()) {
|
|
4929
5438
|
const context = { ...base, operationIndex: index };
|
|
4930
5439
|
const handler = docxHandlers.get(operation3.op);
|
|
@@ -4967,6 +5476,9 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
4967
5476
|
async apply(input, signal) {
|
|
4968
5477
|
const batch = Array.isArray(input) ? { stateId: this.#nextStateId(), operations: input } : input;
|
|
4969
5478
|
this.#stateId = Math.max(this.#stateId, batch.stateId);
|
|
5479
|
+
const tracked = trackedOf(batch);
|
|
5480
|
+
const modeIssues = trackedModeIssues(tracked);
|
|
5481
|
+
if (modeIssues.length > 0) throw invalidOperationError(modeIssues);
|
|
4970
5482
|
const snapshot = this.#pkg.snapshot();
|
|
4971
5483
|
const unauthored = this.#unauthored ? [...this.#unauthored] : void 0;
|
|
4972
5484
|
const createdIds = [];
|
|
@@ -4990,7 +5502,8 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
4990
5502
|
batch.stateId,
|
|
4991
5503
|
signal,
|
|
4992
5504
|
index,
|
|
4993
|
-
issued
|
|
5505
|
+
issued,
|
|
5506
|
+
tracked
|
|
4994
5507
|
);
|
|
4995
5508
|
const issues = [];
|
|
4996
5509
|
await handler.validate(
|
|
@@ -5042,10 +5555,11 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
5042
5555
|
this.#stateId += 1;
|
|
5043
5556
|
return this.#stateId;
|
|
5044
5557
|
}
|
|
5045
|
-
async #context(stateId, signal, operationIndex = 0, issued = /* @__PURE__ */ new Set()) {
|
|
5558
|
+
async #context(stateId, signal, operationIndex = 0, issued = /* @__PURE__ */ new Set(), tracked) {
|
|
5046
5559
|
const model = await this.model(signal);
|
|
5047
5560
|
const taken = /* @__PURE__ */ new Set([...model.takenIds, ...issued]);
|
|
5048
5561
|
let count = 0;
|
|
5562
|
+
let revisions = 0;
|
|
5049
5563
|
return {
|
|
5050
5564
|
pkg: this.#pkg,
|
|
5051
5565
|
model,
|
|
@@ -5059,9 +5573,21 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
5059
5573
|
issued.add(id);
|
|
5060
5574
|
taken.add(id);
|
|
5061
5575
|
return id;
|
|
5576
|
+
},
|
|
5577
|
+
...tracked ? { tracked } : {},
|
|
5578
|
+
nextRevisionId: () => {
|
|
5579
|
+
revisions += 1;
|
|
5580
|
+
return model.maxRevisionId + revisions;
|
|
5062
5581
|
}
|
|
5063
5582
|
};
|
|
5064
5583
|
}
|
|
5584
|
+
/** The revisions of a paragraph; none for another element or an unknown id. */
|
|
5585
|
+
async revisions(id, signal) {
|
|
5586
|
+
const model = await this.model(signal);
|
|
5587
|
+
const record = model.byId.get(id);
|
|
5588
|
+
if (!record || record.kind !== "paragraph") return [];
|
|
5589
|
+
return revisionsOf(model.document, record.node);
|
|
5590
|
+
}
|
|
5065
5591
|
async materialize(purposeOrSignal = "show", options = {}, signal = new AbortController().signal) {
|
|
5066
5592
|
return (await this.materializeDocument(purposeOrSignal, options, signal)).bytes;
|
|
5067
5593
|
}
|
|
@@ -8497,6 +9023,16 @@ function createOoxmlEditHandler() {
|
|
|
8497
9023
|
);
|
|
8498
9024
|
return state;
|
|
8499
9025
|
};
|
|
9026
|
+
const docx = () => {
|
|
9027
|
+
const current = engine();
|
|
9028
|
+
if (!(current instanceof DocxEditEngine))
|
|
9029
|
+
throw new ViewerError(
|
|
9030
|
+
"edit-unsupported",
|
|
9031
|
+
"The open document is not a Word document",
|
|
9032
|
+
{ details: { format: "pptx", reason: "no-reads" } }
|
|
9033
|
+
);
|
|
9034
|
+
return current;
|
|
9035
|
+
};
|
|
8500
9036
|
const pptx = () => {
|
|
8501
9037
|
const current = engine();
|
|
8502
9038
|
if (!(current instanceof PptxEditEngine))
|
|
@@ -8529,8 +9065,8 @@ function createOoxmlEditHandler() {
|
|
|
8529
9065
|
return result;
|
|
8530
9066
|
}
|
|
8531
9067
|
case "edit-validate": {
|
|
8532
|
-
const { operations } = payload;
|
|
8533
|
-
return engine().validate(operations, signal);
|
|
9068
|
+
const { operations, mode } = payload;
|
|
9069
|
+
return engine().validate(operations, signal, mode);
|
|
8534
9070
|
}
|
|
8535
9071
|
case "edit-apply": {
|
|
8536
9072
|
const { batch } = payload;
|
|
@@ -8581,6 +9117,11 @@ function createOoxmlEditHandler() {
|
|
|
8581
9117
|
return pptx().slides(signal);
|
|
8582
9118
|
case "edit-pptx-layouts":
|
|
8583
9119
|
return pptx().layouts(signal);
|
|
9120
|
+
case "edit-docx-revisions":
|
|
9121
|
+
return docx().revisions(
|
|
9122
|
+
payload.id,
|
|
9123
|
+
signal
|
|
9124
|
+
);
|
|
8584
9125
|
case "edit-dispose":
|
|
8585
9126
|
await state?.dispose();
|
|
8586
9127
|
state = void 0;
|