web-doc 0.7.0 → 0.9.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 +607 -0
- package/dist/edit/ai/types.d.ts +175 -0
- package/dist/edit/ai/types.js +1 -0
- package/dist/edit/docx/elements.js +19 -1
- 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 +24 -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/engine/document.js +48 -3
- package/dist/edit/pdf/engine/elements.d.ts +6 -0
- package/dist/edit/pdf/engine/fonts.js +13 -10
- package/dist/edit/pdf/engine/pages.js +9 -1
- package/dist/edit/pdf/range-map.d.ts +4 -2
- package/dist/edit/pdf/schemas.js +4 -1
- package/dist/edit/pdf/session.d.ts +10 -0
- package/dist/edit/pdf/session.js +39 -0
- package/dist/edit/pdf/types.d.ts +15 -3
- 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/fonts/THIRD_PARTY_NOTICES.md +6 -3
- package/dist/fonts/manifest.json +4 -4
- package/dist/fonts/noto-sans-latin-cyrillic.ttf +0 -0
- 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 +1415 -860
- package/dist/workers/pdf-edit-worker.js +64 -16
- 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
|
|
@@ -3274,13 +3300,27 @@ function paragraphElement(model, record, base) {
|
|
|
3274
3300
|
id: record.elementId,
|
|
3275
3301
|
kind: "paragraph",
|
|
3276
3302
|
text: record.text.text,
|
|
3277
|
-
...record.table ? { parentId: record.table.elementId } : {},
|
|
3303
|
+
...record.table ? { parentId: record.table.elementId, ...cellOf(record.table, record) } : {},
|
|
3278
3304
|
textStyle: resolveTextStyle(model.styles, record.pPr, first?.rPr),
|
|
3279
3305
|
paragraphStyle: resolveParagraphStyle(model.styles, record.pPr),
|
|
3280
3306
|
...record.readOnlyReason ? { readOnlyReason: record.readOnlyReason } : {},
|
|
3281
3307
|
operations: operationsOf("paragraph", record.readOnlyReason !== void 0)
|
|
3282
3308
|
};
|
|
3283
3309
|
}
|
|
3310
|
+
var CELLS = /* @__PURE__ */ new WeakMap();
|
|
3311
|
+
function cellOf(table, record) {
|
|
3312
|
+
let cells = CELLS.get(table);
|
|
3313
|
+
if (!cells) {
|
|
3314
|
+
cells = /* @__PURE__ */ new Map();
|
|
3315
|
+
for (const [row, cellsOfRow] of table.rows.entries())
|
|
3316
|
+
for (const [column, cell2] of cellsOfRow.entries())
|
|
3317
|
+
for (const paragraph of cell2.paragraphs)
|
|
3318
|
+
cells.set(paragraph, { row, column });
|
|
3319
|
+
CELLS.set(table, cells);
|
|
3320
|
+
}
|
|
3321
|
+
const cell = cells.get(record);
|
|
3322
|
+
return cell ? { cell } : {};
|
|
3323
|
+
}
|
|
3284
3324
|
|
|
3285
3325
|
// src/edit/docx/write.ts
|
|
3286
3326
|
var MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
|
|
@@ -3448,8 +3488,13 @@ function mergedProperties(part, source, tag, order, set) {
|
|
|
3448
3488
|
function toggleXml(local, on) {
|
|
3449
3489
|
return on ? `<w:${local}/>` : `<w:${local} w:val="0"/>`;
|
|
3450
3490
|
}
|
|
3451
|
-
function changedRunProperties(part, rPr, change, styles) {
|
|
3491
|
+
function changedRunProperties(part, rPr, change, styles, revision) {
|
|
3452
3492
|
const set = /* @__PURE__ */ new Map();
|
|
3493
|
+
if (revision !== void 0)
|
|
3494
|
+
set.set(
|
|
3495
|
+
"rPrChange",
|
|
3496
|
+
`<w:rPrChange${revision}>${wrapped("w:rPr", innerPropertiesXml(part, rPr, ["rPrChange"]))}</w:rPrChange>`
|
|
3497
|
+
);
|
|
3453
3498
|
if (change.bold !== void 0) {
|
|
3454
3499
|
set.set("b", toggleXml("b", change.bold));
|
|
3455
3500
|
set.set("bCs", toggleXml("bCs", change.bold));
|
|
@@ -3497,8 +3542,19 @@ function fontsXml(part, rPr, family) {
|
|
|
3497
3542
|
const rest = kept.map((a) => ` ${a.name}="${a.rawValue}"`).join("");
|
|
3498
3543
|
return `<w:rFonts w:ascii="${face}" w:hAnsi="${face}"${rest}/>`;
|
|
3499
3544
|
}
|
|
3500
|
-
function
|
|
3545
|
+
function wrapped(tag, inner) {
|
|
3546
|
+
return inner.length === 0 ? `<${tag}/>` : `<${tag}>${inner}</${tag}>`;
|
|
3547
|
+
}
|
|
3548
|
+
function innerPropertiesXml(part, properties, without) {
|
|
3549
|
+
return (properties?.children ?? []).filter((child) => !without.includes(child.local)).map((child) => sliceOf(part, child)).join("");
|
|
3550
|
+
}
|
|
3551
|
+
function changedParagraphProperties(part, pPr, change, revision) {
|
|
3501
3552
|
const set = /* @__PURE__ */ new Map();
|
|
3553
|
+
if (revision !== void 0)
|
|
3554
|
+
set.set(
|
|
3555
|
+
"pPrChange",
|
|
3556
|
+
`<w:pPrChange${revision}>${wrapped("w:pPr", innerPropertiesXml(part, pPr, ["rPr", "sectPr", "pPrChange"]))}</w:pPrChange>`
|
|
3557
|
+
);
|
|
3502
3558
|
if (change.align !== void 0)
|
|
3503
3559
|
set.set("jc", `<w:jc w:val="${alignValue(change.align)}"/>`);
|
|
3504
3560
|
if (change.spacing !== void 0) {
|
|
@@ -3544,11 +3600,11 @@ function alignValue(align) {
|
|
|
3544
3600
|
return "left";
|
|
3545
3601
|
}
|
|
3546
3602
|
}
|
|
3547
|
-
function paragraphMarkProperties(part, pPr, change, styles) {
|
|
3603
|
+
function paragraphMarkProperties(part, pPr, change, styles, revision) {
|
|
3548
3604
|
const rPr = pPr?.children.find(
|
|
3549
3605
|
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
3550
3606
|
);
|
|
3551
|
-
const changed = changedRunProperties(part, rPr, change, styles);
|
|
3607
|
+
const changed = changedRunProperties(part, rPr, change, styles, revision);
|
|
3552
3608
|
return mergedProperties(
|
|
3553
3609
|
part,
|
|
3554
3610
|
pPr,
|
|
@@ -3599,538 +3655,653 @@ function namespacePatches(part) {
|
|
|
3599
3655
|
return items;
|
|
3600
3656
|
}
|
|
3601
3657
|
|
|
3602
|
-
// src/edit/docx/
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
var EMU_PER_PT = 12700;
|
|
3609
|
-
function blockNode(record) {
|
|
3610
|
-
return record.node;
|
|
3658
|
+
// src/edit/docx/text-ops.ts
|
|
3659
|
+
function topOf(node, container) {
|
|
3660
|
+
let current = node;
|
|
3661
|
+
while (current.parent && current.parent !== container)
|
|
3662
|
+
current = current.parent;
|
|
3663
|
+
return current;
|
|
3611
3664
|
}
|
|
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;
|
|
3665
|
+
function unitsOf(container, items) {
|
|
3666
|
+
const byTop = /* @__PURE__ */ new Map();
|
|
3667
|
+
for (const item of items) {
|
|
3668
|
+
const top = topOf(item.run, container);
|
|
3669
|
+
const list = byTop.get(top) ?? [];
|
|
3670
|
+
list.push(item);
|
|
3671
|
+
byTop.set(top, list);
|
|
3634
3672
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3673
|
+
const groupEnd = /* @__PURE__ */ new Map();
|
|
3674
|
+
for (const item of items)
|
|
3675
|
+
if (item.fieldRuns && item.fieldRuns.length > 1) {
|
|
3676
|
+
const first = topOf(item.fieldRuns[0], container);
|
|
3677
|
+
const last = topOf(item.fieldRuns.at(-1), container);
|
|
3678
|
+
if (first !== last) groupEnd.set(first, last);
|
|
3679
|
+
}
|
|
3680
|
+
const units = [];
|
|
3681
|
+
let position = 0;
|
|
3682
|
+
const children = container.children.filter(
|
|
3683
|
+
(child) => !(child.local === "pPr" && child.namespace === W_NS)
|
|
3644
3684
|
);
|
|
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;
|
|
3685
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
3686
|
+
const child = children[index];
|
|
3687
|
+
const end = groupEnd.get(child);
|
|
3688
|
+
const nodes = [child];
|
|
3689
|
+
if (end) {
|
|
3690
|
+
let cursor = index + 1;
|
|
3691
|
+
while (cursor < children.length && children[cursor - 1] !== end)
|
|
3692
|
+
nodes.push(children[cursor++]);
|
|
3693
|
+
index = cursor - 1;
|
|
3684
3694
|
}
|
|
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);
|
|
3695
|
+
const own = nodes.flatMap((node) => byTop.get(node) ?? []);
|
|
3696
|
+
const kind = own.length === 0 ? "zero" : end || child.local === "fldSimple" || own.some((item) => item.fieldRuns) ? "field" : child.local === "r" ? "run" : "wrapper";
|
|
3697
|
+
const start = own.length > 0 ? own[0].start : position;
|
|
3698
|
+
const stop = own.length > 0 ? own.at(-1).end : position;
|
|
3699
|
+
units.push({ nodes, kind, start, end: stop, items: own });
|
|
3700
|
+
position = stop;
|
|
3693
3701
|
}
|
|
3694
|
-
return
|
|
3702
|
+
return units;
|
|
3695
3703
|
}
|
|
3696
|
-
function
|
|
3697
|
-
|
|
3698
|
-
return table && table.rows[0]?.[0]?.paragraphs[0] === record ? table : void 0;
|
|
3704
|
+
function rPrOf(part, item) {
|
|
3705
|
+
return item ? sliceOf(part, item.rPr) : "";
|
|
3699
3706
|
}
|
|
3700
|
-
function
|
|
3701
|
-
return
|
|
3707
|
+
function startTagOf(part, node) {
|
|
3708
|
+
return part.text.slice(node.start, node.contentStart);
|
|
3702
3709
|
}
|
|
3703
|
-
function
|
|
3704
|
-
|
|
3705
|
-
transaction.patch(part, [...namespacePatches(part), ...items]);
|
|
3706
|
-
return transaction.commit().then((change) => ({ ...result, warnings: change.warnings }));
|
|
3710
|
+
function endTagOf(part, node) {
|
|
3711
|
+
return part.text.slice(node.contentEnd, node.end);
|
|
3707
3712
|
}
|
|
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}`);
|
|
3713
|
+
function contentOf(part, items, from, to, includeStart) {
|
|
3714
|
+
let out = "";
|
|
3715
|
+
for (const item of items) {
|
|
3716
|
+
if (item.start === item.end) {
|
|
3717
|
+
const after = includeStart ? item.start >= from : item.start > from;
|
|
3718
|
+
if (after && item.start <= to) out += sliceOf(part, item.child);
|
|
3719
|
+
continue;
|
|
3719
3720
|
}
|
|
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))
|
|
3721
|
+
const begin = Math.max(item.start, from);
|
|
3722
|
+
const stop = Math.min(item.end, to);
|
|
3723
|
+
if (stop <= begin) continue;
|
|
3724
|
+
if (item.kind === "text" && item.child.local === "t")
|
|
3725
|
+
out += runContentXml(
|
|
3726
|
+
item.text.slice(begin - item.start, stop - item.start)
|
|
3745
3727
|
);
|
|
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
|
-
});
|
|
3728
|
+
else if (begin === item.start && stop === item.end)
|
|
3729
|
+
out += sliceOf(part, item.child);
|
|
3755
3730
|
}
|
|
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 };
|
|
3731
|
+
return out;
|
|
3766
3732
|
}
|
|
3767
|
-
function
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3733
|
+
function droppedInlines(model, record, items, from, to) {
|
|
3734
|
+
const removedIds = [];
|
|
3735
|
+
const removedParagraphIds = [];
|
|
3736
|
+
for (const item of items) {
|
|
3737
|
+
if (item.kind !== "picture" && item.kind !== "object" && item.kind !== "anchor")
|
|
3738
|
+
continue;
|
|
3739
|
+
const inside = item.start === item.end ? from < item.start && item.start < to : item.start >= from && item.end <= to;
|
|
3740
|
+
if (!inside) continue;
|
|
3741
|
+
const inline = record.inlines.find(
|
|
3742
|
+
(candidate) => candidate.node === item.child
|
|
3743
|
+
);
|
|
3744
|
+
if (inline) removedIds.push(inline.elementId);
|
|
3745
|
+
for (const paragraph of model.document.findAll("p", item.child)) {
|
|
3746
|
+
const id = model.paragraphIds.get(paragraph);
|
|
3747
|
+
if (id && paragraph.namespace === W_NS) removedParagraphIds.push(id);
|
|
3748
|
+
}
|
|
3774
3749
|
}
|
|
3775
|
-
return
|
|
3776
|
-
(block) => block.kind === "paragraph" && block !== record
|
|
3777
|
-
);
|
|
3750
|
+
return { removedIds, removedParagraphIds };
|
|
3778
3751
|
}
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3752
|
+
function splitAt(part, container, items, start, end, insert, fallbackRPr) {
|
|
3753
|
+
let before = "";
|
|
3754
|
+
let after = "";
|
|
3755
|
+
let inserted = false;
|
|
3756
|
+
let nested = false;
|
|
3757
|
+
let rPr = fallbackRPr;
|
|
3758
|
+
let lastRPr;
|
|
3759
|
+
const raw = (unit) => unit.nodes.map((node) => sliceOf(part, node)).join("");
|
|
3760
|
+
const place = (style) => {
|
|
3761
|
+
rPr = style ?? lastRPr ?? fallbackRPr;
|
|
3762
|
+
before += insert(rPr);
|
|
3763
|
+
inserted = true;
|
|
3764
|
+
};
|
|
3765
|
+
for (const unit of unitsOf(container, items)) {
|
|
3766
|
+
if (unit.kind === "zero") {
|
|
3767
|
+
if (inserted) after += raw(unit);
|
|
3768
|
+
else before += raw(unit);
|
|
3769
|
+
continue;
|
|
3770
|
+
}
|
|
3771
|
+
if (unit.end <= start) {
|
|
3772
|
+
before += raw(unit);
|
|
3773
|
+
lastRPr = rPrOf(part, unit.items.at(-1));
|
|
3774
|
+
if (!inserted && start === end && unit.end === start) place(lastRPr);
|
|
3775
|
+
continue;
|
|
3776
|
+
}
|
|
3777
|
+
if (unit.start >= end) {
|
|
3778
|
+
if (!inserted) place(lastRPr ?? rPrOf(part, unit.items[0]));
|
|
3779
|
+
after += raw(unit);
|
|
3780
|
+
continue;
|
|
3781
|
+
}
|
|
3782
|
+
if (unit.start >= start && unit.end <= end) {
|
|
3783
|
+
if (!inserted) place(rPrOf(part, unit.items[0]));
|
|
3784
|
+
continue;
|
|
3785
|
+
}
|
|
3786
|
+
if (unit.kind === "run") {
|
|
3787
|
+
const style = rPrOf(part, unit.items[0]);
|
|
3788
|
+
const head = runXml(
|
|
3789
|
+
style,
|
|
3790
|
+
contentOf(part, unit.items, unit.start, start, true)
|
|
3787
3791
|
);
|
|
3788
|
-
|
|
3792
|
+
const tail = runXml(
|
|
3793
|
+
style,
|
|
3794
|
+
contentOf(part, unit.items, end, unit.end, start < end)
|
|
3795
|
+
);
|
|
3796
|
+
if (!inserted) {
|
|
3797
|
+
before += head;
|
|
3798
|
+
place(style);
|
|
3799
|
+
after += tail;
|
|
3800
|
+
} else after += tail;
|
|
3801
|
+
continue;
|
|
3789
3802
|
}
|
|
3790
|
-
if (
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3803
|
+
if (unit.kind === "wrapper" && unit.start < start && end < unit.end) {
|
|
3804
|
+
const wrapper = unit.nodes[0];
|
|
3805
|
+
const inner = splitAt(
|
|
3806
|
+
part,
|
|
3807
|
+
wrapper,
|
|
3808
|
+
unit.items,
|
|
3809
|
+
start,
|
|
3810
|
+
end,
|
|
3811
|
+
insert,
|
|
3812
|
+
fallbackRPr
|
|
3813
|
+
);
|
|
3814
|
+
before += startTagOf(part, wrapper) + inner.before + inner.after + endTagOf(part, wrapper);
|
|
3815
|
+
rPr = inner.rPr;
|
|
3816
|
+
inserted = true;
|
|
3817
|
+
nested = true;
|
|
3818
|
+
continue;
|
|
3819
|
+
}
|
|
3820
|
+
throw new Error("A range cannot cut a field or a wrapper");
|
|
3821
|
+
}
|
|
3822
|
+
if (!inserted) place(lastRPr);
|
|
3823
|
+
return { before, after, rPr, nested };
|
|
3824
|
+
}
|
|
3825
|
+
async function textTarget(id, context, issue) {
|
|
3826
|
+
const record = context.model.byId.get(id);
|
|
3827
|
+
if (!record || record.kind !== "paragraph") {
|
|
3828
|
+
issue(
|
|
3829
|
+
"/target",
|
|
3830
|
+
record ? "invalid-target" : "unknown-target",
|
|
3831
|
+
record ? `Element ${id} is not a paragraph` : `No element ${id}`
|
|
3832
|
+
);
|
|
3833
|
+
return void 0;
|
|
3834
|
+
}
|
|
3835
|
+
if (record.readOnlyReason) {
|
|
3836
|
+
issue(
|
|
3837
|
+
"/target",
|
|
3838
|
+
"invalid-target",
|
|
3839
|
+
`Paragraph ${id} is read-only (${record.readOnlyReason})`
|
|
3840
|
+
);
|
|
3841
|
+
return void 0;
|
|
3842
|
+
}
|
|
3843
|
+
return { record, part: context.model.document };
|
|
3844
|
+
}
|
|
3845
|
+
function checkRange(target, id, range, issue, structural) {
|
|
3846
|
+
const text = target.record.text.text;
|
|
3847
|
+
const length = text.length;
|
|
3848
|
+
if (!range) return { start: 0, end: length };
|
|
3849
|
+
if (range.start.elementId !== id || range.end.elementId !== id) {
|
|
3850
|
+
issue("/range", "invalid-range", "Both ends must lie on the target");
|
|
3851
|
+
return void 0;
|
|
3852
|
+
}
|
|
3853
|
+
const { start, end } = { start: range.start.offset, end: range.end.offset };
|
|
3854
|
+
if (start > length || end > length || start > end) {
|
|
3855
|
+
issue(
|
|
3856
|
+
"/range",
|
|
3857
|
+
"invalid-range",
|
|
3858
|
+
`Offsets must satisfy 0 \u2264 start \u2264 end \u2264 ${length}`
|
|
3859
|
+
);
|
|
3860
|
+
return void 0;
|
|
3861
|
+
}
|
|
3862
|
+
for (const offset of [start, end])
|
|
3863
|
+
if (offset > 0 && offset < length && (text.charCodeAt(offset) & 64512) === 56320 && (text.charCodeAt(offset - 1) & 64512) === 55296) {
|
|
3864
|
+
issue("/range", "invalid-range", "A range cannot split a surrogate pair");
|
|
3865
|
+
return void 0;
|
|
3866
|
+
}
|
|
3867
|
+
if (!structural) return { start, end };
|
|
3868
|
+
const inside = (unit, offset) => unit.start < offset && offset < unit.end;
|
|
3869
|
+
const check = (container, items) => {
|
|
3870
|
+
for (const unit of unitsOf(container, items)) {
|
|
3871
|
+
if (unit.kind === "field" && (inside(unit, start) || inside(unit, end))) {
|
|
3798
3872
|
issue(
|
|
3799
|
-
"/
|
|
3800
|
-
"invalid-
|
|
3801
|
-
|
|
3873
|
+
"/range",
|
|
3874
|
+
"invalid-range",
|
|
3875
|
+
"A range cannot start or end inside a field"
|
|
3802
3876
|
);
|
|
3803
|
-
|
|
3877
|
+
return false;
|
|
3878
|
+
}
|
|
3879
|
+
if (unit.kind !== "wrapper") continue;
|
|
3880
|
+
const startIn = inside(unit, start);
|
|
3881
|
+
const endIn = inside(unit, end);
|
|
3882
|
+
if (startIn !== endIn) {
|
|
3804
3883
|
issue(
|
|
3805
|
-
"/
|
|
3806
|
-
"
|
|
3807
|
-
"
|
|
3884
|
+
"/range",
|
|
3885
|
+
"invalid-range",
|
|
3886
|
+
"A range cannot cut the edge of a hyperlink or content control"
|
|
3808
3887
|
);
|
|
3888
|
+
return false;
|
|
3889
|
+
}
|
|
3890
|
+
if (startIn && !check(unit.nodes[0], unit.items)) return false;
|
|
3809
3891
|
}
|
|
3892
|
+
return true;
|
|
3893
|
+
};
|
|
3894
|
+
return check(target.record.node, target.record.text.items) ? { start, end } : void 0;
|
|
3895
|
+
}
|
|
3896
|
+
function commitParagraph(context, target, items, extra = {}) {
|
|
3897
|
+
const { part, record } = target;
|
|
3898
|
+
const transaction = context.pkg.transaction();
|
|
3899
|
+
transaction.patch(part, [...namespacePatches(part), ...items]);
|
|
3900
|
+
return transaction.commit().then((change) => ({
|
|
3901
|
+
createdIds: [],
|
|
3902
|
+
warnings: change.warnings,
|
|
3903
|
+
...context.model.unauthoredSet.has(record.id) ? { stamped: [record.id] } : {},
|
|
3904
|
+
reflowFrom: record.id,
|
|
3905
|
+
...extra
|
|
3906
|
+
}));
|
|
3907
|
+
}
|
|
3908
|
+
var replaceTextHandler = {
|
|
3909
|
+
async validate(operation3, context, issue) {
|
|
3910
|
+
const text = normalizeText(operation3.text);
|
|
3911
|
+
const problem = textProblem(text);
|
|
3912
|
+
if (problem) issue("/text", "invalid-text", `The text holds ${problem}`);
|
|
3913
|
+
const target = await textTarget(operation3.target, context, issue);
|
|
3914
|
+
if (!target) return;
|
|
3915
|
+
const range = checkRange(
|
|
3916
|
+
target,
|
|
3917
|
+
operation3.target,
|
|
3918
|
+
operation3.range,
|
|
3919
|
+
issue,
|
|
3920
|
+
true
|
|
3921
|
+
);
|
|
3922
|
+
if (range && context.tracked && trackedRangeProblem(
|
|
3923
|
+
target.record,
|
|
3924
|
+
range.start,
|
|
3925
|
+
range.end,
|
|
3926
|
+
issue,
|
|
3927
|
+
"/range"
|
|
3928
|
+
))
|
|
3929
|
+
return;
|
|
3930
|
+
if (!range || !text.includes("\n")) return;
|
|
3931
|
+
const probe = splitAt(
|
|
3932
|
+
target.part,
|
|
3933
|
+
target.record.node,
|
|
3934
|
+
target.record.text.items,
|
|
3935
|
+
range.start,
|
|
3936
|
+
range.end,
|
|
3937
|
+
() => "",
|
|
3938
|
+
""
|
|
3939
|
+
);
|
|
3940
|
+
if (probe.nested)
|
|
3941
|
+
issue(
|
|
3942
|
+
"/text",
|
|
3943
|
+
"invalid-range",
|
|
3944
|
+
"A paragraph break cannot be inserted inside a hyperlink or content control"
|
|
3945
|
+
);
|
|
3810
3946
|
},
|
|
3811
3947
|
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, {
|
|
3948
|
+
const target = await textTarget(operation3.target, context, () => {
|
|
3949
|
+
});
|
|
3950
|
+
const { start, end } = checkRange(
|
|
3951
|
+
target,
|
|
3952
|
+
operation3.target,
|
|
3953
|
+
operation3.range,
|
|
3954
|
+
() => {
|
|
3955
|
+
},
|
|
3956
|
+
true
|
|
3957
|
+
);
|
|
3958
|
+
const { items, createdIds, removedIds, removedParagraphIds } = replacedParagraph(
|
|
3959
|
+
context,
|
|
3960
|
+
target.record,
|
|
3961
|
+
start,
|
|
3962
|
+
end,
|
|
3963
|
+
normalizeText(operation3.text)
|
|
3964
|
+
);
|
|
3965
|
+
return commitParagraph(context, target, items, {
|
|
3836
3966
|
createdIds,
|
|
3837
|
-
removedIds:
|
|
3838
|
-
removedParagraphIds
|
|
3839
|
-
reflowFrom: record.id,
|
|
3840
|
-
...remappedIds ? { remappedIds } : {}
|
|
3967
|
+
...removedIds.length > 0 ? { removedIds } : {},
|
|
3968
|
+
...removedParagraphIds.length > 0 ? { removedParagraphIds } : {}
|
|
3841
3969
|
});
|
|
3842
3970
|
}
|
|
3843
3971
|
};
|
|
3844
|
-
|
|
3845
|
-
const
|
|
3846
|
-
const
|
|
3847
|
-
const
|
|
3848
|
-
const
|
|
3849
|
-
const
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3972
|
+
function replacedParagraph(context, record, start, end, text) {
|
|
3973
|
+
const part = context.model.document;
|
|
3974
|
+
const segments = text.split("\n");
|
|
3975
|
+
const single = segments.length === 1;
|
|
3976
|
+
const items = [];
|
|
3977
|
+
const createdIds = [];
|
|
3978
|
+
const copiedPPr = paragraphPropertiesWithoutSection(part, record.pPr);
|
|
3979
|
+
if (context.tracked) {
|
|
3980
|
+
const split = trackedSplit(
|
|
3981
|
+
context,
|
|
3982
|
+
record,
|
|
3983
|
+
start,
|
|
3984
|
+
end,
|
|
3985
|
+
paragraphMarkRPr(part, record)
|
|
3853
3986
|
);
|
|
3854
|
-
const
|
|
3855
|
-
|
|
3987
|
+
const inserted = (segment) => insertedRunXml(context, split.rPr, segment);
|
|
3988
|
+
const originalPPr = sliceOf(part, record.pPr);
|
|
3989
|
+
const firstPPr = single ? originalPPr : markedParagraphProperties(context, record.pPr, "ins");
|
|
3990
|
+
items.push(
|
|
3991
|
+
patches.replaceElement(
|
|
3992
|
+
part,
|
|
3993
|
+
record.node,
|
|
3994
|
+
paragraphXml(
|
|
3995
|
+
record.node,
|
|
3996
|
+
record.id,
|
|
3997
|
+
firstPPr,
|
|
3998
|
+
split.before + split.deleted + inserted(segments[0]) + (single ? split.after : "")
|
|
3999
|
+
)
|
|
4000
|
+
)
|
|
3856
4001
|
);
|
|
3857
|
-
|
|
3858
|
-
|
|
4002
|
+
segments.slice(1).forEach((segment, index) => {
|
|
4003
|
+
const id = context.freshParagraphId();
|
|
4004
|
+
createdIds.push(`p:${id}`);
|
|
4005
|
+
const last = index === segments.length - 2;
|
|
4006
|
+
items.push(
|
|
4007
|
+
patches.insertAfter(
|
|
4008
|
+
part,
|
|
4009
|
+
record.node,
|
|
4010
|
+
paragraphXml(
|
|
4011
|
+
record.node,
|
|
4012
|
+
id,
|
|
4013
|
+
last ? originalPPr : markedParagraphProperties(context, record.pPr, "ins"),
|
|
4014
|
+
inserted(segment) + (last ? split.after : "")
|
|
4015
|
+
)
|
|
4016
|
+
)
|
|
4017
|
+
);
|
|
4018
|
+
});
|
|
4019
|
+
} else {
|
|
4020
|
+
const split = splitAt(
|
|
4021
|
+
part,
|
|
4022
|
+
record.node,
|
|
4023
|
+
record.text.items,
|
|
4024
|
+
start,
|
|
4025
|
+
end,
|
|
4026
|
+
(rPr) => runXml(rPr, runContentXml(segments[0])),
|
|
4027
|
+
paragraphMarkRPr(part, record)
|
|
4028
|
+
);
|
|
4029
|
+
const pPr = sliceOf(part, record.pPr);
|
|
4030
|
+
items.push(
|
|
4031
|
+
patches.replaceElement(
|
|
4032
|
+
part,
|
|
4033
|
+
record.node,
|
|
4034
|
+
paragraphXml(
|
|
4035
|
+
record.node,
|
|
4036
|
+
record.id,
|
|
4037
|
+
pPr,
|
|
4038
|
+
split.before + (single ? split.after : "")
|
|
4039
|
+
)
|
|
4040
|
+
)
|
|
4041
|
+
);
|
|
4042
|
+
segments.slice(1).forEach((segment, index) => {
|
|
4043
|
+
const id = context.freshParagraphId();
|
|
4044
|
+
createdIds.push(`p:${id}`);
|
|
4045
|
+
const last = index === segments.length - 2;
|
|
4046
|
+
items.push(
|
|
4047
|
+
patches.insertAfter(
|
|
4048
|
+
part,
|
|
4049
|
+
record.node,
|
|
4050
|
+
paragraphXml(
|
|
4051
|
+
record.node,
|
|
4052
|
+
id,
|
|
4053
|
+
copiedPPr,
|
|
4054
|
+
runXml(split.rPr, runContentXml(segment)) + (last ? split.after : "")
|
|
4055
|
+
)
|
|
4056
|
+
)
|
|
4057
|
+
);
|
|
4058
|
+
});
|
|
3859
4059
|
}
|
|
3860
|
-
const
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
4060
|
+
const dropped = droppedInlines(
|
|
4061
|
+
context.model,
|
|
4062
|
+
record,
|
|
4063
|
+
record.text.items,
|
|
4064
|
+
start,
|
|
4065
|
+
end
|
|
4066
|
+
);
|
|
4067
|
+
const removedIds = dropped.removedIds;
|
|
4068
|
+
if (!single)
|
|
4069
|
+
for (const inline of record.inlines) {
|
|
4070
|
+
const item = record.text.items.find(
|
|
4071
|
+
(candidate) => candidate.child === inline.node
|
|
4072
|
+
);
|
|
4073
|
+
if (item && item.start >= end && !removedIds.includes(inline.elementId))
|
|
4074
|
+
removedIds.push(inline.elementId);
|
|
4075
|
+
}
|
|
4076
|
+
return {
|
|
4077
|
+
items,
|
|
4078
|
+
createdIds,
|
|
4079
|
+
removedIds,
|
|
4080
|
+
removedParagraphIds: dropped.removedParagraphIds
|
|
4081
|
+
};
|
|
4082
|
+
}
|
|
4083
|
+
function paragraphMarkRPr(part, record) {
|
|
4084
|
+
const rPr = record.pPr?.children.find(
|
|
4085
|
+
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
3871
4086
|
);
|
|
4087
|
+
return sliceOf(part, rPr);
|
|
3872
4088
|
}
|
|
3873
|
-
function
|
|
3874
|
-
|
|
4089
|
+
function restyledRun(part, run2, rPr) {
|
|
4090
|
+
const content = run2.children.filter((child) => !(child.local === "rPr" && child.namespace === W_NS)).map((child) => sliceOf(part, child)).join("");
|
|
4091
|
+
return `<w:r>${rPr}${content}</w:r>`;
|
|
3875
4092
|
}
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
4093
|
+
function restyled(part, container, items, start, end, change, context) {
|
|
4094
|
+
let out = "";
|
|
4095
|
+
const changed = (rPr) => changedRunProperties(
|
|
4096
|
+
part,
|
|
4097
|
+
rPr,
|
|
4098
|
+
change,
|
|
4099
|
+
context.model.styles,
|
|
4100
|
+
context.tracked ? revisionAttributes(context) : void 0
|
|
4101
|
+
);
|
|
4102
|
+
for (const unit of unitsOf(container, items)) {
|
|
4103
|
+
const raw = unit.nodes.map((node) => sliceOf(part, node)).join("");
|
|
4104
|
+
const overlaps = unit.start < end && start < unit.end ? true : unit.start === unit.end && start < unit.start && unit.start < end;
|
|
4105
|
+
if (unit.kind === "zero" || !overlaps) {
|
|
4106
|
+
out += raw;
|
|
4107
|
+
continue;
|
|
3886
4108
|
}
|
|
3887
|
-
if (
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
4109
|
+
if (unit.kind === "run") {
|
|
4110
|
+
const run2 = unit.nodes[0];
|
|
4111
|
+
const own = rPrOf(part, unit.items[0]);
|
|
4112
|
+
const rPr = unit.items[0]?.rPr;
|
|
4113
|
+
out += runXml(
|
|
4114
|
+
own,
|
|
4115
|
+
contentOf(
|
|
4116
|
+
part,
|
|
4117
|
+
unit.items,
|
|
4118
|
+
unit.start,
|
|
4119
|
+
Math.min(start, unit.end),
|
|
4120
|
+
true
|
|
4121
|
+
)
|
|
3892
4122
|
);
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
4123
|
+
out += runXml(
|
|
4124
|
+
changed(rPr),
|
|
4125
|
+
contentOf(
|
|
4126
|
+
part,
|
|
4127
|
+
unit.items,
|
|
4128
|
+
Math.max(unit.start, start),
|
|
4129
|
+
Math.min(unit.end, end),
|
|
4130
|
+
false
|
|
4131
|
+
)
|
|
3900
4132
|
);
|
|
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"
|
|
4133
|
+
out += runXml(
|
|
4134
|
+
own,
|
|
4135
|
+
contentOf(part, unit.items, Math.max(end, unit.start), unit.end, false)
|
|
3910
4136
|
);
|
|
3911
|
-
|
|
4137
|
+
continue;
|
|
3912
4138
|
}
|
|
3913
|
-
if (
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
4139
|
+
if (unit.kind === "field") {
|
|
4140
|
+
for (const node of unit.nodes)
|
|
4141
|
+
if (node.local === "r" && node.namespace === W_NS)
|
|
4142
|
+
out += restyledRun(
|
|
4143
|
+
part,
|
|
4144
|
+
node,
|
|
4145
|
+
changed(
|
|
4146
|
+
node.children.find(
|
|
4147
|
+
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
4148
|
+
)
|
|
4149
|
+
)
|
|
4150
|
+
);
|
|
4151
|
+
else if (node.local === "fldSimple")
|
|
4152
|
+
out += startTagOf(part, node) + restyled(part, node, unit.items, start, end, change, context) + endTagOf(part, node);
|
|
4153
|
+
else out += sliceOf(part, node);
|
|
4154
|
+
continue;
|
|
4155
|
+
}
|
|
4156
|
+
const wrapper = unit.nodes[0];
|
|
4157
|
+
out += startTagOf(part, wrapper) + restyled(part, wrapper, unit.items, start, end, change, context) + endTagOf(part, wrapper);
|
|
4158
|
+
}
|
|
4159
|
+
return out;
|
|
4160
|
+
}
|
|
4161
|
+
var setTextStyleHandler = {
|
|
4162
|
+
async validate(operation3, context, issue) {
|
|
4163
|
+
const { style } = operation3;
|
|
4164
|
+
if (style.color !== void 0) {
|
|
4165
|
+
const problem = colorProblem(style.color);
|
|
4166
|
+
if (problem)
|
|
4167
|
+
issue("/style/color", "invalid-value", `Colour: ${problem}`);
|
|
4168
|
+
}
|
|
4169
|
+
if (style.fontFamily !== void 0) {
|
|
4170
|
+
const problem = attributeProblem(style.fontFamily);
|
|
4171
|
+
if (problem)
|
|
4172
|
+
issue("/style/fontFamily", "invalid-value", `Font: ${problem}`);
|
|
4173
|
+
}
|
|
4174
|
+
const target = await textTarget(operation3.target, context, issue);
|
|
4175
|
+
if (target)
|
|
4176
|
+
checkRange(target, operation3.target, operation3.range, issue, false);
|
|
3919
4177
|
},
|
|
3920
4178
|
async apply(operation3, context) {
|
|
3921
|
-
const
|
|
3922
|
-
|
|
3923
|
-
const {
|
|
3924
|
-
|
|
3925
|
-
|
|
4179
|
+
const target = await textTarget(operation3.target, context, () => {
|
|
4180
|
+
});
|
|
4181
|
+
const { part, record } = target;
|
|
4182
|
+
const { start, end } = checkRange(
|
|
4183
|
+
target,
|
|
4184
|
+
operation3.target,
|
|
4185
|
+
operation3.range,
|
|
3926
4186
|
() => {
|
|
3927
|
-
}
|
|
4187
|
+
},
|
|
4188
|
+
false
|
|
3928
4189
|
);
|
|
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);
|
|
4190
|
+
if (Object.keys(operation3.style).length === 0)
|
|
4191
|
+
return { createdIds: [], warnings: [] };
|
|
4192
|
+
const content = restyled(
|
|
4193
|
+
part,
|
|
4194
|
+
record.node,
|
|
4195
|
+
record.text.items,
|
|
4196
|
+
start,
|
|
4197
|
+
end,
|
|
4198
|
+
operation3.style,
|
|
4199
|
+
context
|
|
4200
|
+
);
|
|
4201
|
+
const pPr = end >= record.text.text.length ? paragraphMarkProperties(
|
|
4202
|
+
part,
|
|
4203
|
+
record.pPr,
|
|
4204
|
+
operation3.style,
|
|
4205
|
+
context.model.styles,
|
|
4206
|
+
context.tracked ? revisionAttributes(context) : void 0
|
|
4207
|
+
) : sliceOf(part, record.pPr);
|
|
4208
|
+
return commitParagraph(context, target, [
|
|
4209
|
+
patches.replaceElement(
|
|
4210
|
+
part,
|
|
4211
|
+
record.node,
|
|
4212
|
+
paragraphXml(record.node, record.id, pPr, content)
|
|
4213
|
+
)
|
|
4214
|
+
]);
|
|
4001
4215
|
}
|
|
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 = {
|
|
4216
|
+
};
|
|
4217
|
+
var setParagraphStyleHandler = {
|
|
4015
4218
|
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
|
-
}
|
|
4219
|
+
await textTarget(operation3.target, context, issue);
|
|
4034
4220
|
},
|
|
4035
4221
|
async apply(operation3, context) {
|
|
4036
|
-
const
|
|
4037
|
-
const { record, side } = placement2(operation3, context, () => {
|
|
4222
|
+
const target = await textTarget(operation3.target, context, () => {
|
|
4038
4223
|
});
|
|
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
|
|
4224
|
+
const { part, record } = target;
|
|
4225
|
+
const { style } = operation3;
|
|
4226
|
+
if (style.align === void 0 && (style.spacing === void 0 || Object.keys(style.spacing).length === 0))
|
|
4227
|
+
return { createdIds: [], warnings: [] };
|
|
4228
|
+
const pPr = changedParagraphProperties(
|
|
4229
|
+
part,
|
|
4230
|
+
record.pPr,
|
|
4231
|
+
style,
|
|
4232
|
+
context.tracked ? revisionAttributes(context) : void 0
|
|
4233
|
+
);
|
|
4234
|
+
const node = record.node;
|
|
4235
|
+
const content = node.selfClosing ? "" : part.text.slice(
|
|
4236
|
+
record.pPr ? record.pPr.end : node.contentStart,
|
|
4237
|
+
node.contentEnd
|
|
4069
4238
|
);
|
|
4239
|
+
return commitParagraph(context, target, [
|
|
4240
|
+
patches.replaceElement(
|
|
4241
|
+
part,
|
|
4242
|
+
node,
|
|
4243
|
+
paragraphXml(node, record.id, pPr, content)
|
|
4244
|
+
)
|
|
4245
|
+
]);
|
|
4070
4246
|
}
|
|
4071
4247
|
};
|
|
4072
4248
|
|
|
4073
|
-
// src/edit/docx/
|
|
4074
|
-
function
|
|
4249
|
+
// src/edit/docx/tracked.ts
|
|
4250
|
+
function revisionAttributes(context) {
|
|
4251
|
+
const tracked = context.tracked;
|
|
4252
|
+
return ` w:id="${context.nextRevisionId()}" w:author="${escapeAttributeValue(tracked.author)}"${tracked.date === void 0 ? "" : ` w:date="${escapeAttributeValue(tracked.date)}"`}`;
|
|
4253
|
+
}
|
|
4254
|
+
function unsupportedTracked(issue, path, what) {
|
|
4255
|
+
issue(
|
|
4256
|
+
path,
|
|
4257
|
+
"unsupported-change-mode",
|
|
4258
|
+
`${what} has no tracked form; apply it directly and review with checkpoints`
|
|
4259
|
+
);
|
|
4260
|
+
}
|
|
4261
|
+
function topOf2(node, container) {
|
|
4075
4262
|
let current = node;
|
|
4076
4263
|
while (current.parent && current.parent !== container)
|
|
4077
4264
|
current = current.parent;
|
|
4078
4265
|
return current;
|
|
4079
4266
|
}
|
|
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;
|
|
4267
|
+
function trackedRangeProblem(record, start, end, issue, path) {
|
|
4268
|
+
for (const item of record.text.items) {
|
|
4269
|
+
const overlaps = item.start === item.end ? start < item.start && item.start < end : item.start < end && start < item.end;
|
|
4270
|
+
if (!overlaps) continue;
|
|
4271
|
+
const top = topOf2(item.run, record.node);
|
|
4272
|
+
const plainRun = item.run.local === "r" && item.run.namespace === W_NS;
|
|
4273
|
+
if (!plainRun || item.fieldRuns || item.kind === "field" || top !== item.run || item.wrapper !== void 0) {
|
|
4274
|
+
unsupportedTracked(
|
|
4275
|
+
issue,
|
|
4276
|
+
path,
|
|
4277
|
+
"A change touching a hyperlink, content control, field or equation"
|
|
4278
|
+
);
|
|
4279
|
+
return true;
|
|
4109
4280
|
}
|
|
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
4281
|
}
|
|
4117
|
-
return
|
|
4282
|
+
return false;
|
|
4118
4283
|
}
|
|
4119
|
-
function
|
|
4120
|
-
return
|
|
4284
|
+
function deletedContent(content) {
|
|
4285
|
+
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
4286
|
}
|
|
4122
|
-
function
|
|
4123
|
-
return
|
|
4287
|
+
function deletedRunXml(rPr, content) {
|
|
4288
|
+
return content.length === 0 ? "" : `<w:r>${rPr}${deletedContent(content)}</w:r>`;
|
|
4124
4289
|
}
|
|
4125
|
-
function
|
|
4126
|
-
return
|
|
4290
|
+
function runContentOf(part, run2) {
|
|
4291
|
+
return run2.children.filter((child) => !(child.local === "rPr" && child.namespace === W_NS)).map((child) => sliceOf(part, child)).join("");
|
|
4127
4292
|
}
|
|
4128
|
-
function
|
|
4293
|
+
function runPropertiesOf(part, run2) {
|
|
4294
|
+
const rPr = run2.children.find(
|
|
4295
|
+
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
4296
|
+
);
|
|
4297
|
+
return sliceOf(part, rPr);
|
|
4298
|
+
}
|
|
4299
|
+
function deletedContentOf(part, items, from, to) {
|
|
4129
4300
|
let out = "";
|
|
4130
4301
|
for (const item of items) {
|
|
4131
4302
|
if (item.start === item.end) {
|
|
4132
|
-
|
|
4133
|
-
|
|
4303
|
+
if (from < item.start && item.start < to)
|
|
4304
|
+
out += sliceOf(part, item.child);
|
|
4134
4305
|
continue;
|
|
4135
4306
|
}
|
|
4136
4307
|
const begin = Math.max(item.start, from);
|
|
@@ -4145,451 +4316,732 @@ function contentOf(part, items, from, to, includeStart) {
|
|
|
4145
4316
|
}
|
|
4146
4317
|
return out;
|
|
4147
4318
|
}
|
|
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) {
|
|
4319
|
+
function trackedSplit(context, record, start, end, fallbackRPr) {
|
|
4320
|
+
const part = context.model.document;
|
|
4168
4321
|
let before = "";
|
|
4169
4322
|
let after = "";
|
|
4170
|
-
|
|
4171
|
-
let
|
|
4172
|
-
let rPr = fallbackRPr;
|
|
4323
|
+
const deletedRuns = [];
|
|
4324
|
+
let rPr;
|
|
4173
4325
|
let lastRPr;
|
|
4326
|
+
let passed = false;
|
|
4174
4327
|
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)) {
|
|
4328
|
+
for (const unit of unitsOf(record.node, record.text.items)) {
|
|
4181
4329
|
if (unit.kind === "zero") {
|
|
4182
|
-
if (
|
|
4330
|
+
if (passed) after += raw(unit);
|
|
4183
4331
|
else before += raw(unit);
|
|
4184
4332
|
continue;
|
|
4185
4333
|
}
|
|
4186
4334
|
if (unit.end <= start) {
|
|
4187
4335
|
before += raw(unit);
|
|
4188
4336
|
lastRPr = rPrOf(part, unit.items.at(-1));
|
|
4189
|
-
if (
|
|
4337
|
+
if (start === end && unit.end === start) {
|
|
4338
|
+
rPr ??= lastRPr;
|
|
4339
|
+
passed = true;
|
|
4340
|
+
}
|
|
4190
4341
|
continue;
|
|
4191
4342
|
}
|
|
4192
4343
|
if (unit.start >= end) {
|
|
4193
|
-
|
|
4344
|
+
rPr ??= lastRPr ?? rPrOf(part, unit.items[0]);
|
|
4345
|
+
passed = true;
|
|
4194
4346
|
after += raw(unit);
|
|
4195
4347
|
continue;
|
|
4196
4348
|
}
|
|
4349
|
+
if (unit.kind !== "run")
|
|
4350
|
+
throw new Error("A tracked change cannot cut a field or a wrapper");
|
|
4351
|
+
const run2 = unit.nodes[0];
|
|
4352
|
+
const style = runPropertiesOf(part, run2);
|
|
4197
4353
|
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(
|
|
4354
|
+
deletedRuns.push(deletedRunXml(style, runContentOf(part, run2)));
|
|
4355
|
+
} else {
|
|
4356
|
+
const head = contentOf(part, unit.items, unit.start, start, true);
|
|
4357
|
+
const middle = deletedContentOf(
|
|
4221
4358
|
part,
|
|
4222
|
-
wrapper,
|
|
4223
4359
|
unit.items,
|
|
4224
|
-
start,
|
|
4225
|
-
end,
|
|
4226
|
-
insert,
|
|
4227
|
-
fallbackRPr
|
|
4360
|
+
Math.max(unit.start, start),
|
|
4361
|
+
Math.min(unit.end, end)
|
|
4228
4362
|
);
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4363
|
+
const tail = contentOf(part, unit.items, end, unit.end, start < end);
|
|
4364
|
+
if (head) before += runXml(style, head);
|
|
4365
|
+
if (middle) deletedRuns.push(deletedRunXml(style, middle));
|
|
4366
|
+
if (tail) after += runXml(style, tail);
|
|
4367
|
+
}
|
|
4368
|
+
rPr ??= style;
|
|
4369
|
+
if (unit.end >= end) passed = true;
|
|
4370
|
+
}
|
|
4371
|
+
const deleted = deletedRuns.length > 0 ? `<w:del${revisionAttributes(context)}>${deletedRuns.join("")}</w:del>` : "";
|
|
4372
|
+
return { before, deleted, rPr: rPr ?? lastRPr ?? fallbackRPr, after };
|
|
4373
|
+
}
|
|
4374
|
+
function insertedRunXml(context, rPr, text) {
|
|
4375
|
+
const content = runContentXml(text);
|
|
4376
|
+
return content.length === 0 ? "" : `<w:ins${revisionAttributes(context)}>${runXml(rPr, content)}</w:ins>`;
|
|
4377
|
+
}
|
|
4378
|
+
function markedParagraphProperties(context, pPr, mark) {
|
|
4379
|
+
const part = context.model.document;
|
|
4380
|
+
const rPr = pPr?.children.find(
|
|
4381
|
+
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
4382
|
+
);
|
|
4383
|
+
const inner = innerPropertiesXml(part, rPr, ["ins", "del"]);
|
|
4384
|
+
const marked = `<w:rPr><w:${mark}${revisionAttributes(context)}/>${inner}</w:rPr>`;
|
|
4385
|
+
return mergedProperties(
|
|
4386
|
+
part,
|
|
4387
|
+
pPr,
|
|
4388
|
+
"w:pPr",
|
|
4389
|
+
PPR_ORDER,
|
|
4390
|
+
/* @__PURE__ */ new Map([
|
|
4391
|
+
["rPr", marked],
|
|
4392
|
+
["sectPr", null]
|
|
4393
|
+
])
|
|
4394
|
+
);
|
|
4395
|
+
}
|
|
4396
|
+
function deletedParagraphXml(context, record) {
|
|
4397
|
+
const part = context.model.document;
|
|
4398
|
+
const pPr = markedParagraphProperties(context, record.pPr, "del");
|
|
4399
|
+
let content = "";
|
|
4400
|
+
let open = [];
|
|
4401
|
+
const flush = () => {
|
|
4402
|
+
if (open.length > 0)
|
|
4403
|
+
content += `<w:del${revisionAttributes(context)}>${open.join("")}</w:del>`;
|
|
4404
|
+
open = [];
|
|
4405
|
+
};
|
|
4406
|
+
for (const child of record.node.children) {
|
|
4407
|
+
if (child.local === "pPr" && child.namespace === W_NS) continue;
|
|
4408
|
+
if (child.local === "r" && child.namespace === W_NS) {
|
|
4409
|
+
const run2 = deletedRunXml(
|
|
4410
|
+
runPropertiesOf(part, child),
|
|
4411
|
+
runContentOf(part, child)
|
|
4412
|
+
);
|
|
4413
|
+
if (run2) open.push(run2);
|
|
4233
4414
|
continue;
|
|
4234
4415
|
}
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4416
|
+
flush();
|
|
4417
|
+
content += sliceOf(part, child);
|
|
4418
|
+
}
|
|
4419
|
+
flush();
|
|
4420
|
+
return paragraphXml(record.node, record.id, pPr, content);
|
|
4421
|
+
}
|
|
4422
|
+
var RUN_REVISIONS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
4423
|
+
function revisionsOf(part, paragraph) {
|
|
4424
|
+
const out = [];
|
|
4425
|
+
const attributes = (node) => {
|
|
4426
|
+
const author = part.attribute(node, "w:author");
|
|
4427
|
+
const date = part.attribute(node, "w:date");
|
|
4428
|
+
const raw = part.attribute(node, "w:id");
|
|
4429
|
+
return {
|
|
4430
|
+
id: raw !== void 0 && /^\d+$/.test(raw) ? Number(raw) : -1,
|
|
4431
|
+
...author === void 0 ? {} : { author },
|
|
4432
|
+
...date === void 0 ? {} : { date }
|
|
4433
|
+
};
|
|
4434
|
+
};
|
|
4435
|
+
const visit = (node, inRPr, inPPr) => {
|
|
4436
|
+
for (const child of node.children) {
|
|
4437
|
+
if (child.namespace !== W_NS) {
|
|
4438
|
+
visit(child, inRPr, inPPr);
|
|
4439
|
+
continue;
|
|
4440
|
+
}
|
|
4441
|
+
if (child.local === "pPr") {
|
|
4442
|
+
visit(child, false, true);
|
|
4443
|
+
continue;
|
|
4444
|
+
}
|
|
4445
|
+
if (child.local === "rPr") {
|
|
4446
|
+
visit(child, true, inPPr);
|
|
4447
|
+
continue;
|
|
4448
|
+
}
|
|
4449
|
+
if (inRPr && RUN_REVISIONS.has(child.local)) {
|
|
4450
|
+
out.push({
|
|
4451
|
+
start: child.start,
|
|
4452
|
+
revision: {
|
|
4453
|
+
kind: child.local,
|
|
4454
|
+
scope: inPPr ? "mark" : "runs",
|
|
4455
|
+
...attributes(child),
|
|
4456
|
+
...inPPr ? { text: "\n" } : {}
|
|
4457
|
+
}
|
|
4458
|
+
});
|
|
4459
|
+
continue;
|
|
4460
|
+
}
|
|
4461
|
+
if (inRPr && child.local === "rPrChange") {
|
|
4462
|
+
out.push({
|
|
4463
|
+
start: child.start,
|
|
4464
|
+
revision: {
|
|
4465
|
+
kind: "rPrChange",
|
|
4466
|
+
scope: inPPr ? "mark" : "runs",
|
|
4467
|
+
...attributes(child),
|
|
4468
|
+
...inPPr || !node.parent ? {} : { text: textOf(part, node.parent) }
|
|
4469
|
+
}
|
|
4470
|
+
});
|
|
4471
|
+
continue;
|
|
4472
|
+
}
|
|
4473
|
+
if (inPPr && child.local === "pPrChange") {
|
|
4474
|
+
out.push({
|
|
4475
|
+
start: child.start,
|
|
4476
|
+
revision: {
|
|
4477
|
+
kind: "pPrChange",
|
|
4478
|
+
scope: "paragraph",
|
|
4479
|
+
...attributes(child)
|
|
4480
|
+
}
|
|
4481
|
+
});
|
|
4482
|
+
continue;
|
|
4483
|
+
}
|
|
4484
|
+
if (!inRPr && !inPPr && RUN_REVISIONS.has(child.local)) {
|
|
4485
|
+
out.push({
|
|
4486
|
+
start: child.start,
|
|
4487
|
+
revision: {
|
|
4488
|
+
kind: child.local,
|
|
4489
|
+
scope: "runs",
|
|
4490
|
+
...attributes(child),
|
|
4491
|
+
text: textOf(part, child)
|
|
4492
|
+
}
|
|
4493
|
+
});
|
|
4494
|
+
visit(child, false, false);
|
|
4495
|
+
continue;
|
|
4496
|
+
}
|
|
4497
|
+
visit(child, inRPr, inPPr);
|
|
4498
|
+
}
|
|
4499
|
+
};
|
|
4500
|
+
visit(paragraph, false, false);
|
|
4501
|
+
return out.sort((a, b) => a.start - b.start).map((entry) => Object.freeze(entry.revision));
|
|
4502
|
+
}
|
|
4503
|
+
function textOf(part, node) {
|
|
4504
|
+
let text = "";
|
|
4505
|
+
const visit = (current) => {
|
|
4506
|
+
for (const child of current.children) {
|
|
4507
|
+
if (child.namespace === W_NS) {
|
|
4508
|
+
if (child.local === "t" || child.local === "delText") {
|
|
4509
|
+
text += part.textOf(child);
|
|
4510
|
+
continue;
|
|
4511
|
+
}
|
|
4512
|
+
if (child.local === "tab") {
|
|
4513
|
+
text += TAB;
|
|
4514
|
+
continue;
|
|
4515
|
+
}
|
|
4516
|
+
if (child.local === "br" || child.local === "cr") {
|
|
4517
|
+
text += LINE_BREAK;
|
|
4518
|
+
continue;
|
|
4519
|
+
}
|
|
4520
|
+
if (child.local === "rPr" || child.local === "pPr") continue;
|
|
4521
|
+
}
|
|
4522
|
+
visit(child);
|
|
4523
|
+
}
|
|
4524
|
+
};
|
|
4525
|
+
visit(node);
|
|
4526
|
+
return text;
|
|
4239
4527
|
}
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4528
|
+
|
|
4529
|
+
// src/edit/docx/structure-ops.ts
|
|
4530
|
+
var MEDIA_FOLDER = "/word/media";
|
|
4531
|
+
var WP_NS = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
|
|
4532
|
+
var A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
4533
|
+
var PIC_NS = "http://schemas.openxmlformats.org/drawingml/2006/picture";
|
|
4534
|
+
var R_NS = OFFICE_RELATIONSHIPS.slice(0, -1);
|
|
4535
|
+
var EMU_PER_PT = 12700;
|
|
4536
|
+
function blockNode(record) {
|
|
4537
|
+
return record.node;
|
|
4538
|
+
}
|
|
4539
|
+
function placement2(operation3, context, issue) {
|
|
4540
|
+
const named = [operation3.before, operation3.after].filter(
|
|
4541
|
+
(value) => value !== void 0
|
|
4542
|
+
);
|
|
4543
|
+
if (named.length !== 1) {
|
|
4243
4544
|
issue(
|
|
4244
|
-
"/
|
|
4245
|
-
|
|
4246
|
-
|
|
4545
|
+
"/before",
|
|
4546
|
+
"invalid-value",
|
|
4547
|
+
"Exactly one of before and after names the reference element"
|
|
4247
4548
|
);
|
|
4248
4549
|
return void 0;
|
|
4249
4550
|
}
|
|
4250
|
-
|
|
4551
|
+
const side = operation3.before !== void 0 ? "before" : "after";
|
|
4552
|
+
const id = operation3[side];
|
|
4553
|
+
const record = context.model.byId.get(id);
|
|
4554
|
+
if (!record || record.kind !== "paragraph" && record.kind !== "table") {
|
|
4251
4555
|
issue(
|
|
4252
|
-
|
|
4253
|
-
"invalid-target",
|
|
4254
|
-
`
|
|
4556
|
+
`/${side}`,
|
|
4557
|
+
record ? "invalid-target" : "unknown-target",
|
|
4558
|
+
record ? `Element ${id} is not a paragraph or a table` : `No element ${id}`
|
|
4255
4559
|
);
|
|
4256
4560
|
return void 0;
|
|
4257
4561
|
}
|
|
4258
|
-
return { record,
|
|
4562
|
+
return { record, side };
|
|
4259
4563
|
}
|
|
4260
|
-
function
|
|
4261
|
-
const
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
if (
|
|
4265
|
-
|
|
4266
|
-
|
|
4564
|
+
function referenceRunProperties(part, record) {
|
|
4565
|
+
const paragraph = record.kind === "paragraph" ? record : record.rows[0]?.[0]?.paragraphs[0];
|
|
4566
|
+
if (!paragraph) return void 0;
|
|
4567
|
+
const first = firstTextItem(paragraph.text);
|
|
4568
|
+
if (first?.rPr) return first.rPr;
|
|
4569
|
+
return paragraph.pPr?.children.find(
|
|
4570
|
+
(child) => child.local === "rPr" && child.namespace === W_NS
|
|
4571
|
+
);
|
|
4572
|
+
}
|
|
4573
|
+
function paragraphIdsUnder(model, node) {
|
|
4574
|
+
const out = [];
|
|
4575
|
+
if (node.local === "p" && node.namespace === W_NS) {
|
|
4576
|
+
const own = model.paragraphIds.get(node);
|
|
4577
|
+
if (own) out.push(own);
|
|
4267
4578
|
}
|
|
4268
|
-
const
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
"invalid-range",
|
|
4273
|
-
`Offsets must satisfy 0 \u2264 start \u2264 end \u2264 ${length}`
|
|
4274
|
-
);
|
|
4275
|
-
return void 0;
|
|
4579
|
+
for (const paragraph of model.document.findAll("p", node)) {
|
|
4580
|
+
if (paragraph === node || paragraph.namespace !== W_NS) continue;
|
|
4581
|
+
const id = model.paragraphIds.get(paragraph);
|
|
4582
|
+
if (id) out.push(id);
|
|
4276
4583
|
}
|
|
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;
|
|
4584
|
+
return out;
|
|
4585
|
+
}
|
|
4586
|
+
function elementIdsUnder(model, node) {
|
|
4587
|
+
const out = [];
|
|
4588
|
+
for (const record of model.records) {
|
|
4589
|
+
let current = record.node;
|
|
4590
|
+
while (current && current !== node) current = current.parent;
|
|
4591
|
+
if (current === node) out.push(record.elementId);
|
|
4592
|
+
}
|
|
4593
|
+
return out;
|
|
4594
|
+
}
|
|
4595
|
+
function stampedSlice(model, node) {
|
|
4596
|
+
const part = model.document;
|
|
4597
|
+
const targets = [
|
|
4598
|
+
...node.local === "p" && node.namespace === W_NS ? [node] : [],
|
|
4599
|
+
...part.findAll("p", node).filter((p) => p !== node && p.namespace === W_NS)
|
|
4600
|
+
].filter((paragraph) => {
|
|
4601
|
+
const id = model.paragraphIds.get(paragraph);
|
|
4602
|
+
return id !== void 0 && model.unauthoredSet.has(id);
|
|
4603
|
+
}).sort((a, b) => b.start - a.start);
|
|
4604
|
+
let xml = sliceOf(part, node);
|
|
4605
|
+
const stamped = [];
|
|
4606
|
+
for (const paragraph of targets) {
|
|
4607
|
+
const id = model.paragraphIds.get(paragraph);
|
|
4608
|
+
if (part.attribute(paragraph, "w14:paraId")) {
|
|
4609
|
+
stamped.push(id);
|
|
4610
|
+
continue;
|
|
4306
4611
|
}
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4612
|
+
const last = paragraph.attributes.at(-1);
|
|
4613
|
+
const from = last ? last.end : paragraph.start + 1 + paragraph.name.length;
|
|
4614
|
+
const close = part.text.indexOf(">", from);
|
|
4615
|
+
let at = close;
|
|
4616
|
+
if (part.text[close - 1] === "/") at = close - 1;
|
|
4617
|
+
const offset = at - node.start;
|
|
4618
|
+
xml = `${xml.slice(0, offset)} w14:paraId="${id}"${xml.slice(offset)}`;
|
|
4619
|
+
stamped.push(id);
|
|
4620
|
+
}
|
|
4621
|
+
return { xml, stamped: stamped.reverse() };
|
|
4310
4622
|
}
|
|
4311
|
-
function
|
|
4312
|
-
const
|
|
4313
|
-
|
|
4623
|
+
function firstCellParagraph(record) {
|
|
4624
|
+
const table = record.table;
|
|
4625
|
+
return table && table.rows[0]?.[0]?.paragraphs[0] === record ? table : void 0;
|
|
4626
|
+
}
|
|
4627
|
+
function tableRemap(table, newFirstParagraphId) {
|
|
4628
|
+
return table.id === newFirstParagraphId ? void 0 : { [table.elementId]: `tbl:${newFirstParagraphId}` };
|
|
4629
|
+
}
|
|
4630
|
+
function commit(context, items, result, transaction = context.pkg.transaction()) {
|
|
4631
|
+
const part = context.model.document;
|
|
4314
4632
|
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
|
-
}));
|
|
4633
|
+
return transaction.commit().then((change) => ({ ...result, warnings: change.warnings }));
|
|
4322
4634
|
}
|
|
4323
|
-
var
|
|
4635
|
+
var insertParagraphHandler = {
|
|
4324
4636
|
async validate(operation3, context, issue) {
|
|
4325
|
-
const
|
|
4326
|
-
const problem = textProblem(text);
|
|
4637
|
+
const problem = textProblem(normalizeText(operation3.text));
|
|
4327
4638
|
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
|
-
|
|
4639
|
+
if (operation3.style?.color !== void 0) {
|
|
4640
|
+
const colour = colorProblem(operation3.style.color);
|
|
4641
|
+
if (colour) issue("/style/color", "invalid-value", `Colour: ${colour}`);
|
|
4642
|
+
}
|
|
4643
|
+
if (operation3.style?.fontFamily !== void 0) {
|
|
4644
|
+
const font = attributeProblem(operation3.style.fontFamily);
|
|
4645
|
+
if (font) issue("/style/fontFamily", "invalid-value", `Font: ${font}`);
|
|
4646
|
+
}
|
|
4647
|
+
placement2(operation3, context, issue);
|
|
4648
|
+
},
|
|
4649
|
+
async apply(operation3, context) {
|
|
4650
|
+
const { record, side } = placement2(operation3, context, () => {
|
|
4651
|
+
});
|
|
4652
|
+
const part = context.model.document;
|
|
4653
|
+
const node = blockNode(record);
|
|
4654
|
+
const sourceRPr = referenceRunProperties(part, record);
|
|
4655
|
+
const rPr = operation3.style ? changedRunProperties(
|
|
4656
|
+
part,
|
|
4657
|
+
sourceRPr,
|
|
4658
|
+
operation3.style,
|
|
4659
|
+
context.model.styles
|
|
4660
|
+
) : sliceOf(part, sourceRPr);
|
|
4661
|
+
const sourcePPr = record.kind === "paragraph" ? record.pPr : void 0;
|
|
4662
|
+
const pPr = paragraphPropertiesWithoutSection(part, sourcePPr);
|
|
4663
|
+
const segments = normalizeText(operation3.text).split("\n");
|
|
4664
|
+
const createdIds = [];
|
|
4665
|
+
const xml = segments.map((segment) => {
|
|
4666
|
+
const id = context.freshParagraphId();
|
|
4667
|
+
createdIds.push(`p:${id}`);
|
|
4668
|
+
return context.tracked ? paragraphXml(
|
|
4669
|
+
void 0,
|
|
4670
|
+
id,
|
|
4671
|
+
markedParagraphProperties(context, sourcePPr, "ins"),
|
|
4672
|
+
insertedRunXml(context, rPr, segment)
|
|
4673
|
+
) : paragraphXml(
|
|
4674
|
+
void 0,
|
|
4675
|
+
id,
|
|
4676
|
+
pPr,
|
|
4677
|
+
runXml(rPr, runContentXml(segment))
|
|
4678
|
+
);
|
|
4679
|
+
});
|
|
4680
|
+
const items = side === "before" ? xml.map((paragraph) => patches.insertBefore(part, node, paragraph)) : xml.map((paragraph) => patches.insertAfter(part, node, paragraph));
|
|
4681
|
+
const table = side === "before" && record.kind === "paragraph" ? firstCellParagraph(record) : void 0;
|
|
4682
|
+
const remappedIds = table ? tableRemap(table, createdIds[0].slice(2)) : void 0;
|
|
4683
|
+
return commit(context, items, {
|
|
4684
|
+
createdIds,
|
|
4685
|
+
reflowFrom: record.id,
|
|
4686
|
+
...remappedIds ? { remappedIds } : {}
|
|
4687
|
+
});
|
|
4688
|
+
}
|
|
4689
|
+
};
|
|
4690
|
+
function bodyTailPatch(context, remaining) {
|
|
4691
|
+
const { model } = context;
|
|
4692
|
+
const last = remaining.at(-1);
|
|
4693
|
+
if (last && last.kind === "paragraph") return void 0;
|
|
4694
|
+
const id = context.freshParagraphId();
|
|
4695
|
+
const xml = paragraphXml(void 0, id, "", "");
|
|
4696
|
+
const part = model.document;
|
|
4697
|
+
const patch = last ? patches.insertAfter(part, last.node, xml) : model.bodySectPr ? patches.insertBefore(part, model.bodySectPr, xml) : patches.appendChild(part, model.body, xml);
|
|
4698
|
+
return { patch, id };
|
|
4699
|
+
}
|
|
4700
|
+
function otherParagraphsIn(model, record) {
|
|
4701
|
+
if (record.table) {
|
|
4702
|
+
for (const row of record.table.rows)
|
|
4703
|
+
for (const cell of row)
|
|
4704
|
+
if (cell.paragraphs.includes(record))
|
|
4705
|
+
return cell.paragraphs.some((candidate) => candidate !== record);
|
|
4706
|
+
return false;
|
|
4707
|
+
}
|
|
4708
|
+
return model.blocks.some(
|
|
4709
|
+
(block) => block.kind === "paragraph" && block !== record
|
|
4710
|
+
);
|
|
4711
|
+
}
|
|
4712
|
+
var deleteElementHandler = {
|
|
4713
|
+
async validate(operation3, context, issue) {
|
|
4714
|
+
const record = context.model.byId.get(operation3.target);
|
|
4715
|
+
if (!record || record.kind === "other") {
|
|
4348
4716
|
issue(
|
|
4349
|
-
"/
|
|
4350
|
-
"invalid-
|
|
4351
|
-
|
|
4717
|
+
"/target",
|
|
4718
|
+
record ? "invalid-target" : "unknown-target",
|
|
4719
|
+
record ? `Element ${operation3.target} cannot be deleted` : `No element ${operation3.target}`
|
|
4352
4720
|
);
|
|
4721
|
+
return;
|
|
4722
|
+
}
|
|
4723
|
+
if (context.tracked && record.kind !== "paragraph") {
|
|
4724
|
+
unsupportedTracked(issue, "/target", "Deleting a table or a picture");
|
|
4725
|
+
return;
|
|
4726
|
+
}
|
|
4727
|
+
if (record.kind === "paragraph") {
|
|
4728
|
+
if (context.tracked && !record.readOnlyReason && trackedRangeProblem(
|
|
4729
|
+
record,
|
|
4730
|
+
0,
|
|
4731
|
+
record.text.text.length,
|
|
4732
|
+
issue,
|
|
4733
|
+
"/target"
|
|
4734
|
+
))
|
|
4735
|
+
return;
|
|
4736
|
+
if (record.sectPr)
|
|
4737
|
+
issue(
|
|
4738
|
+
"/target",
|
|
4739
|
+
"section-break",
|
|
4740
|
+
"A paragraph that ends a section cannot be deleted"
|
|
4741
|
+
);
|
|
4742
|
+
else if (record.readOnlyReason)
|
|
4743
|
+
issue(
|
|
4744
|
+
"/target",
|
|
4745
|
+
"invalid-target",
|
|
4746
|
+
`Paragraph ${operation3.target} is read-only (${record.readOnlyReason})`
|
|
4747
|
+
);
|
|
4748
|
+
else if (!otherParagraphsIn(context.model, record))
|
|
4749
|
+
issue(
|
|
4750
|
+
"/target",
|
|
4751
|
+
"last-paragraph",
|
|
4752
|
+
"The last paragraph of a body or a cell cannot be deleted"
|
|
4753
|
+
);
|
|
4754
|
+
}
|
|
4353
4755
|
},
|
|
4354
4756
|
async apply(operation3, context) {
|
|
4355
|
-
const
|
|
4356
|
-
|
|
4357
|
-
const
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4757
|
+
const { model } = context;
|
|
4758
|
+
const found = model.byId.get(operation3.target);
|
|
4759
|
+
const part = model.document;
|
|
4760
|
+
if (found.kind !== "paragraph" && found.kind !== "table")
|
|
4761
|
+
return deleteInline(context, found);
|
|
4762
|
+
const record = found;
|
|
4763
|
+
const node = blockNode(record);
|
|
4764
|
+
if (context.tracked && record.kind === "paragraph") {
|
|
4765
|
+
const nested = paragraphIdsUnder(model, node).filter(
|
|
4766
|
+
(id) => id !== record.id
|
|
4767
|
+
);
|
|
4768
|
+
return commit(
|
|
4769
|
+
context,
|
|
4770
|
+
[
|
|
4771
|
+
patches.replaceElement(
|
|
4772
|
+
part,
|
|
4773
|
+
node,
|
|
4774
|
+
deletedParagraphXml(context, record)
|
|
4775
|
+
)
|
|
4776
|
+
],
|
|
4777
|
+
{
|
|
4778
|
+
createdIds: [],
|
|
4779
|
+
removedIds: record.inlines.map((inline) => inline.elementId),
|
|
4780
|
+
...nested.length > 0 ? { removedParagraphIds: nested } : {},
|
|
4781
|
+
...model.unauthoredSet.has(record.id) ? { stamped: [record.id] } : {},
|
|
4782
|
+
reflowFrom: record.id
|
|
4783
|
+
}
|
|
4784
|
+
);
|
|
4785
|
+
}
|
|
4786
|
+
const items = [patches.removeElement(part, node)];
|
|
4787
|
+
const createdIds = [];
|
|
4788
|
+
if (model.blocks.includes(record)) {
|
|
4789
|
+
const tail = bodyTailPatch(
|
|
4790
|
+
context,
|
|
4791
|
+
model.blocks.filter((block) => block !== record)
|
|
4792
|
+
);
|
|
4793
|
+
if (tail) {
|
|
4794
|
+
createdIds.push(`p:${tail.id}`);
|
|
4795
|
+
items.push(tail.patch);
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
const removedParagraphIds = paragraphIdsUnder(model, node);
|
|
4799
|
+
const table = record.kind === "paragraph" ? firstCellParagraph(record) : void 0;
|
|
4800
|
+
const successor = table?.rows[0]?.[0]?.paragraphs[1];
|
|
4801
|
+
const remappedIds = table && successor ? tableRemap(table, successor.id) : void 0;
|
|
4802
|
+
return commit(context, items, {
|
|
4373
4803
|
createdIds,
|
|
4374
|
-
|
|
4375
|
-
|
|
4804
|
+
removedIds: elementIdsUnder(model, node),
|
|
4805
|
+
removedParagraphIds,
|
|
4806
|
+
reflowFrom: record.id,
|
|
4807
|
+
...remappedIds ? { remappedIds } : {}
|
|
4376
4808
|
});
|
|
4377
4809
|
}
|
|
4378
4810
|
};
|
|
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
|
-
)
|
|
4811
|
+
async function deleteInline(context, record) {
|
|
4812
|
+
const { model } = context;
|
|
4813
|
+
const part = model.document;
|
|
4814
|
+
const transaction = context.pkg.transaction();
|
|
4815
|
+
const blip = part.find("blip", record.node);
|
|
4816
|
+
const rId = blip ? part.attribute(blip, "r:embed") ?? part.attribute(blip, "r:link") : void 0;
|
|
4817
|
+
if (rId) {
|
|
4818
|
+
const uses = part.findAll("blip").filter(
|
|
4819
|
+
(candidate) => candidate !== blip && (part.attribute(candidate, "r:embed") === rId || part.attribute(candidate, "r:link") === rId)
|
|
4417
4820
|
);
|
|
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
|
|
4821
|
+
const elsewhere = new RegExp(
|
|
4822
|
+
`r:(?:id|embed|link|pict)="${rId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`
|
|
4823
|
+
);
|
|
4824
|
+
const others = uses.length > 0 || elsewhere.test(stripNode(part, record.node));
|
|
4825
|
+
if (!others) transaction.removeRelationship(model.mainPart, rId);
|
|
4826
|
+
}
|
|
4827
|
+
const removedParagraphIds = paragraphIdsUnder(model, record.node);
|
|
4828
|
+
return commit(
|
|
4829
|
+
context,
|
|
4830
|
+
[patches.removeElement(part, record.node)],
|
|
4831
|
+
{
|
|
4832
|
+
createdIds: [],
|
|
4833
|
+
removedIds: [record.elementId],
|
|
4834
|
+
...removedParagraphIds.length > 0 ? { removedParagraphIds } : {},
|
|
4835
|
+
reflowFrom: record.paragraph.id
|
|
4836
|
+
},
|
|
4837
|
+
transaction
|
|
4445
4838
|
);
|
|
4446
|
-
return sliceOf(part, rPr);
|
|
4447
4839
|
}
|
|
4448
|
-
function
|
|
4449
|
-
|
|
4450
|
-
return `<w:r>${rPr}${content}</w:r>`;
|
|
4840
|
+
function stripNode(part, node) {
|
|
4841
|
+
return part.text.slice(0, node.start) + part.text.slice(node.end);
|
|
4451
4842
|
}
|
|
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;
|
|
4843
|
+
var moveElementHandler = {
|
|
4844
|
+
async validate(operation3, context, issue) {
|
|
4845
|
+
if (context.tracked) {
|
|
4846
|
+
unsupportedTracked(issue, "", "Moving an element");
|
|
4847
|
+
return;
|
|
4461
4848
|
}
|
|
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)
|
|
4849
|
+
const record = context.model.byId.get(operation3.target);
|
|
4850
|
+
if (!record || record.kind !== "paragraph" && record.kind !== "table") {
|
|
4851
|
+
issue(
|
|
4852
|
+
"/target",
|
|
4853
|
+
record ? "invalid-target" : "unknown-target",
|
|
4854
|
+
record ? `Element ${operation3.target} is not a paragraph or a table` : `No element ${operation3.target}`
|
|
4489
4855
|
);
|
|
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;
|
|
4856
|
+
return;
|
|
4508
4857
|
}
|
|
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}`);
|
|
4858
|
+
if (record.kind === "paragraph" && record.sectPr) {
|
|
4859
|
+
issue(
|
|
4860
|
+
"/target",
|
|
4861
|
+
"section-break",
|
|
4862
|
+
"A paragraph that ends a section cannot move"
|
|
4863
|
+
);
|
|
4864
|
+
return;
|
|
4521
4865
|
}
|
|
4522
|
-
if (
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4866
|
+
if (record.kind === "paragraph" && record.readOnlyReason) {
|
|
4867
|
+
issue(
|
|
4868
|
+
"/target",
|
|
4869
|
+
"invalid-target",
|
|
4870
|
+
`Paragraph ${operation3.target} is read-only (${record.readOnlyReason})`
|
|
4871
|
+
);
|
|
4872
|
+
return;
|
|
4526
4873
|
}
|
|
4527
|
-
const
|
|
4528
|
-
if (
|
|
4529
|
-
|
|
4874
|
+
const reference = placement2(operation3, context, issue);
|
|
4875
|
+
if (!reference) return;
|
|
4876
|
+
if (reference.record === record) {
|
|
4877
|
+
issue(
|
|
4878
|
+
`/${reference.side}`,
|
|
4879
|
+
"invalid-target",
|
|
4880
|
+
"An element cannot move next to itself"
|
|
4881
|
+
);
|
|
4882
|
+
return;
|
|
4883
|
+
}
|
|
4884
|
+
if (blockNode(reference.record).parent !== blockNode(record).parent)
|
|
4885
|
+
issue(
|
|
4886
|
+
`/${reference.side}`,
|
|
4887
|
+
"invalid-target",
|
|
4888
|
+
"The reference must be in the same body or cell as the element"
|
|
4889
|
+
);
|
|
4530
4890
|
},
|
|
4531
4891
|
async apply(operation3, context) {
|
|
4532
|
-
const
|
|
4533
|
-
|
|
4534
|
-
const {
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
operation3.target,
|
|
4538
|
-
operation3.range,
|
|
4892
|
+
const { model } = context;
|
|
4893
|
+
const record = model.byId.get(operation3.target);
|
|
4894
|
+
const { record: reference, side } = placement2(
|
|
4895
|
+
operation3,
|
|
4896
|
+
context,
|
|
4539
4897
|
() => {
|
|
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
|
|
4898
|
+
}
|
|
4553
4899
|
);
|
|
4554
|
-
const
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
)
|
|
4566
|
-
|
|
4900
|
+
const part = model.document;
|
|
4901
|
+
const node = blockNode(record);
|
|
4902
|
+
const target = blockNode(reference);
|
|
4903
|
+
const { xml, stamped } = stampedSlice(model, node);
|
|
4904
|
+
const items = [
|
|
4905
|
+
patches.removeElement(part, node),
|
|
4906
|
+
side === "before" ? patches.insertBefore(part, target, xml) : patches.insertAfter(part, target, xml)
|
|
4907
|
+
];
|
|
4908
|
+
const createdIds = [];
|
|
4909
|
+
if (model.blocks.includes(record) && model.blocks.includes(reference)) {
|
|
4910
|
+
const without = model.blocks.filter((block) => block !== record);
|
|
4911
|
+
const at = without.indexOf(reference);
|
|
4912
|
+
without.splice(side === "before" ? at : at + 1, 0, record);
|
|
4913
|
+
const tail = bodyTailPatch(context, without);
|
|
4914
|
+
if (tail) {
|
|
4915
|
+
createdIds.push(`p:${tail.id}`);
|
|
4916
|
+
items.push(tail.patch);
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4919
|
+
const order = (candidate) => model.records.indexOf(candidate);
|
|
4920
|
+
const first = order(record) < order(reference) ? record : reference;
|
|
4921
|
+
let remappedIds;
|
|
4922
|
+
if (record.kind === "paragraph" && record.table) {
|
|
4923
|
+
const cell = record.table.rows[0]?.[0];
|
|
4924
|
+
if (cell && cell.paragraphs.includes(record)) {
|
|
4925
|
+
const without = cell.paragraphs.filter((p) => p !== record);
|
|
4926
|
+
const at = without.indexOf(reference);
|
|
4927
|
+
const reordered = [...without];
|
|
4928
|
+
if (at >= 0)
|
|
4929
|
+
reordered.splice(side === "before" ? at : at + 1, 0, record);
|
|
4930
|
+
const head = reordered[0];
|
|
4931
|
+
if (head) remappedIds = tableRemap(record.table, head.id);
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
return commit(context, items, {
|
|
4935
|
+
createdIds,
|
|
4936
|
+
stamped,
|
|
4937
|
+
reflowFrom: first.id,
|
|
4938
|
+
...remappedIds ? { remappedIds } : {}
|
|
4939
|
+
});
|
|
4567
4940
|
}
|
|
4568
4941
|
};
|
|
4569
|
-
|
|
4942
|
+
async function existingMedia(context, bytes, mimeType) {
|
|
4943
|
+
const extension = `.${extensionForMime(mimeType)}`;
|
|
4944
|
+
for (const name of context.pkg.currentPartNames) {
|
|
4945
|
+
if (!name.startsWith(`${MEDIA_FOLDER}/`) || !name.endsWith(extension))
|
|
4946
|
+
continue;
|
|
4947
|
+
const entry = context.pkg.entry(name);
|
|
4948
|
+
if (entry && entry.uncompressedSize !== bytes.length) continue;
|
|
4949
|
+
const candidate = await context.pkg.part(name);
|
|
4950
|
+
if (candidate.length !== bytes.length) continue;
|
|
4951
|
+
let same = true;
|
|
4952
|
+
for (let index = 0; index < bytes.length; index += 1)
|
|
4953
|
+
if (candidate[index] !== bytes[index]) {
|
|
4954
|
+
same = false;
|
|
4955
|
+
break;
|
|
4956
|
+
}
|
|
4957
|
+
if (same) return name;
|
|
4958
|
+
}
|
|
4959
|
+
return void 0;
|
|
4960
|
+
}
|
|
4961
|
+
function matchesMime(bytes, mimeType) {
|
|
4962
|
+
if (mimeType === "image/png")
|
|
4963
|
+
return bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
|
|
4964
|
+
return bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
|
|
4965
|
+
}
|
|
4966
|
+
function nextDrawingId(part) {
|
|
4967
|
+
let max = 0;
|
|
4968
|
+
for (const node of part.findAll("docPr")) {
|
|
4969
|
+
if (node.namespace !== WP_NS) continue;
|
|
4970
|
+
const id = Number(part.attribute(node, "id") ?? NaN);
|
|
4971
|
+
if (Number.isInteger(id)) max = Math.max(max, id);
|
|
4972
|
+
}
|
|
4973
|
+
return max + 1;
|
|
4974
|
+
}
|
|
4975
|
+
function drawingNamespacePatches(part) {
|
|
4976
|
+
const root = part.root;
|
|
4977
|
+
const has = (name) => root.attributes.some((attribute) => attribute.name === name);
|
|
4978
|
+
const items = [];
|
|
4979
|
+
if (!has("xmlns:wp"))
|
|
4980
|
+
items.push(patches.setAttribute(part, root, "xmlns:wp", WP_NS));
|
|
4981
|
+
if (!has("xmlns:r"))
|
|
4982
|
+
items.push(patches.setAttribute(part, root, "xmlns:r", R_NS));
|
|
4983
|
+
return items;
|
|
4984
|
+
}
|
|
4985
|
+
var insertImageHandler = {
|
|
4570
4986
|
async validate(operation3, context, issue) {
|
|
4571
|
-
|
|
4987
|
+
if (context.tracked) {
|
|
4988
|
+
unsupportedTracked(issue, "", "Inserting a picture");
|
|
4989
|
+
return;
|
|
4990
|
+
}
|
|
4991
|
+
placement2(operation3, context, issue);
|
|
4992
|
+
try {
|
|
4993
|
+
const bytes = resolveBinary(operation3.data, context.assets);
|
|
4994
|
+
if (bytes.length === 0)
|
|
4995
|
+
issue("/data", "invalid-value", "The image has no bytes");
|
|
4996
|
+
else if (!matchesMime(bytes, operation3.mimeType))
|
|
4997
|
+
issue(
|
|
4998
|
+
"/data",
|
|
4999
|
+
"invalid-value",
|
|
5000
|
+
`The bytes are not a ${operation3.mimeType} image`
|
|
5001
|
+
);
|
|
5002
|
+
} catch {
|
|
5003
|
+
issue(
|
|
5004
|
+
"/data",
|
|
5005
|
+
"unknown-asset",
|
|
5006
|
+
"The asset is not known to the session"
|
|
5007
|
+
);
|
|
5008
|
+
}
|
|
4572
5009
|
},
|
|
4573
5010
|
async apply(operation3, context) {
|
|
4574
|
-
const
|
|
5011
|
+
const { model } = context;
|
|
5012
|
+
const { record, side } = placement2(operation3, context, () => {
|
|
4575
5013
|
});
|
|
4576
|
-
const
|
|
4577
|
-
const
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
const
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
5014
|
+
const part = model.document;
|
|
5015
|
+
const bytes = resolveBinary(operation3.data, context.assets);
|
|
5016
|
+
const transaction = context.pkg.transaction();
|
|
5017
|
+
const existing = await existingMedia(context, bytes, operation3.mimeType);
|
|
5018
|
+
const rId = existing ? await transaction.addRelationship(
|
|
5019
|
+
model.mainPart,
|
|
5020
|
+
IMAGE_RELATIONSHIP_TYPE,
|
|
5021
|
+
relativeTarget(model.mainPart, existing)
|
|
5022
|
+
) : (await transaction.addMedia(
|
|
5023
|
+
model.mainPart,
|
|
5024
|
+
MEDIA_FOLDER,
|
|
5025
|
+
bytes,
|
|
5026
|
+
operation3.mimeType
|
|
5027
|
+
)).rId;
|
|
5028
|
+
const cx = Math.round(operation3.size.width * EMU_PER_PT);
|
|
5029
|
+
const cy = Math.round(operation3.size.height * EMU_PER_PT);
|
|
5030
|
+
const drawingId = nextDrawingId(part);
|
|
5031
|
+
const id = context.freshParagraphId();
|
|
5032
|
+
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>`;
|
|
5033
|
+
const xml = paragraphXml(void 0, id, "", drawing);
|
|
5034
|
+
const node = blockNode(record);
|
|
5035
|
+
const items = [
|
|
5036
|
+
...drawingNamespacePatches(part),
|
|
5037
|
+
side === "before" ? patches.insertBefore(part, node, xml) : patches.insertAfter(part, node, xml)
|
|
5038
|
+
];
|
|
5039
|
+
return commit(
|
|
5040
|
+
context,
|
|
5041
|
+
items,
|
|
5042
|
+
{ createdIds: [`p:${id}`, `img:${id}.0`], reflowFrom: record.id },
|
|
5043
|
+
transaction
|
|
4585
5044
|
);
|
|
4586
|
-
return commitParagraph(context, target, [
|
|
4587
|
-
patches.replaceElement(
|
|
4588
|
-
part,
|
|
4589
|
-
node,
|
|
4590
|
-
paragraphXml(node, record.id, pPr, content)
|
|
4591
|
-
)
|
|
4592
|
-
]);
|
|
4593
5045
|
}
|
|
4594
5046
|
};
|
|
4595
5047
|
|
|
@@ -4685,6 +5137,10 @@ function commit2(context, items, result) {
|
|
|
4685
5137
|
}
|
|
4686
5138
|
var insertTableHandler = {
|
|
4687
5139
|
async validate(operation3, context, issue) {
|
|
5140
|
+
if (context.tracked) {
|
|
5141
|
+
unsupportedTracked(issue, "", "Inserting a table");
|
|
5142
|
+
return;
|
|
5143
|
+
}
|
|
4688
5144
|
placement3(operation3, context, issue);
|
|
4689
5145
|
const columns = operation3.rows[0]?.length ?? 0;
|
|
4690
5146
|
if (operation3.rows.some((row) => row.length !== columns))
|
|
@@ -4789,6 +5245,17 @@ var setTableCellHandler = {
|
|
|
4789
5245
|
"invalid-target",
|
|
4790
5246
|
`The cell's paragraph is read-only (${first.readOnlyReason})`
|
|
4791
5247
|
);
|
|
5248
|
+
else if (context.tracked) {
|
|
5249
|
+
for (const paragraph of cell.paragraphs)
|
|
5250
|
+
if (paragraph.readOnlyReason || trackedRangeProblem(
|
|
5251
|
+
paragraph,
|
|
5252
|
+
0,
|
|
5253
|
+
paragraph.text.text.length,
|
|
5254
|
+
issue,
|
|
5255
|
+
"/target"
|
|
5256
|
+
))
|
|
5257
|
+
return;
|
|
5258
|
+
}
|
|
4792
5259
|
},
|
|
4793
5260
|
async apply(operation3, context) {
|
|
4794
5261
|
const { model } = context;
|
|
@@ -4807,13 +5274,28 @@ var setTableCellHandler = {
|
|
|
4807
5274
|
const items = [...replaced.items];
|
|
4808
5275
|
const removedIds = [...replaced.removedIds];
|
|
4809
5276
|
const removedParagraphIds = [...replaced.removedParagraphIds];
|
|
5277
|
+
const stamped = model.unauthoredSet.has(first.id) ? [first.id] : [];
|
|
4810
5278
|
for (const paragraph of rest) {
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
5279
|
+
if (context.tracked) {
|
|
5280
|
+
if (model.unauthoredSet.has(paragraph.id)) stamped.push(paragraph.id);
|
|
5281
|
+
items.push(
|
|
5282
|
+
patches.replaceElement(
|
|
5283
|
+
part,
|
|
5284
|
+
paragraph.node,
|
|
5285
|
+
deletedParagraphXml(context, paragraph)
|
|
5286
|
+
)
|
|
5287
|
+
);
|
|
5288
|
+
removedIds.push(
|
|
5289
|
+
...paragraph.inlines.map((inline) => inline.elementId)
|
|
5290
|
+
);
|
|
5291
|
+
} else {
|
|
5292
|
+
items.push(patches.removeElement(part, paragraph.node));
|
|
5293
|
+
removedIds.push(
|
|
5294
|
+
paragraph.elementId,
|
|
5295
|
+
...paragraph.inlines.map((inline) => inline.elementId)
|
|
5296
|
+
);
|
|
5297
|
+
removedParagraphIds.push(paragraph.id);
|
|
5298
|
+
}
|
|
4817
5299
|
for (const nested of part.findAll("p", paragraph.node)) {
|
|
4818
5300
|
const id = model.paragraphIds.get(nested);
|
|
4819
5301
|
if (id && nested !== paragraph.node) removedParagraphIds.push(id);
|
|
@@ -4823,7 +5305,7 @@ var setTableCellHandler = {
|
|
|
4823
5305
|
createdIds: replaced.createdIds,
|
|
4824
5306
|
...removedIds.length > 0 ? { removedIds } : {},
|
|
4825
5307
|
...removedParagraphIds.length > 0 ? { removedParagraphIds } : {},
|
|
4826
|
-
...
|
|
5308
|
+
...stamped.length > 0 ? { stamped } : {},
|
|
4827
5309
|
reflowFrom: record.id
|
|
4828
5310
|
});
|
|
4829
5311
|
}
|
|
@@ -4860,6 +5342,44 @@ function unauthoredIdsOf(bytes) {
|
|
|
4860
5342
|
const text = new TextDecoder().decode(bytes);
|
|
4861
5343
|
return [...text.matchAll(/<p id="([0-9A-F]{8})"\/>/g)].map((m) => m[1]);
|
|
4862
5344
|
}
|
|
5345
|
+
function trackedOf(mode) {
|
|
5346
|
+
if (mode.changeMode !== "tracked") return void 0;
|
|
5347
|
+
return {
|
|
5348
|
+
author: mode.author ?? "",
|
|
5349
|
+
...mode.timestamp === void 0 ? {} : { date: mode.timestamp }
|
|
5350
|
+
};
|
|
5351
|
+
}
|
|
5352
|
+
var DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/;
|
|
5353
|
+
function trackedModeIssues(tracked) {
|
|
5354
|
+
if (!tracked) return [];
|
|
5355
|
+
const issues = [];
|
|
5356
|
+
const author = attributeProblem(tracked.author);
|
|
5357
|
+
if (tracked.author.trim().length === 0)
|
|
5358
|
+
issues.push({
|
|
5359
|
+
operationIndex: -1,
|
|
5360
|
+
path: "/author",
|
|
5361
|
+
code: "required",
|
|
5362
|
+
message: "Tracked changes name their author; pass ApplyOptions.author"
|
|
5363
|
+
});
|
|
5364
|
+
else if (author)
|
|
5365
|
+
issues.push({
|
|
5366
|
+
operationIndex: -1,
|
|
5367
|
+
path: "/author",
|
|
5368
|
+
code: "invalid-value",
|
|
5369
|
+
message: `The author holds ${author}`
|
|
5370
|
+
});
|
|
5371
|
+
if (tracked.date !== void 0) {
|
|
5372
|
+
const date = attributeProblem(tracked.date);
|
|
5373
|
+
if (date || !DATE_TIME.test(tracked.date) || Number.isNaN(Date.parse(tracked.date)))
|
|
5374
|
+
issues.push({
|
|
5375
|
+
operationIndex: -1,
|
|
5376
|
+
path: "/timestamp",
|
|
5377
|
+
code: "invalid-value",
|
|
5378
|
+
message: date ? `The timestamp holds ${date}` : "The timestamp must be an ISO 8601 date-time"
|
|
5379
|
+
});
|
|
5380
|
+
}
|
|
5381
|
+
return issues;
|
|
5382
|
+
}
|
|
4863
5383
|
var DocxEditEngine = class _DocxEditEngine {
|
|
4864
5384
|
schemas = docxOperationSchemas;
|
|
4865
5385
|
#original;
|
|
@@ -4922,9 +5442,12 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
4922
5442
|
});
|
|
4923
5443
|
return pending;
|
|
4924
5444
|
}
|
|
4925
|
-
async validate(operations, signal) {
|
|
5445
|
+
async validate(operations, signal, mode = {}) {
|
|
4926
5446
|
const issues = [];
|
|
4927
|
-
const
|
|
5447
|
+
const tracked = trackedOf(mode);
|
|
5448
|
+
const modeIssues = trackedModeIssues(tracked);
|
|
5449
|
+
if (modeIssues.length > 0) return modeIssues;
|
|
5450
|
+
const base = await this.#context(0, signal, 0, /* @__PURE__ */ new Set(), tracked);
|
|
4928
5451
|
for (const [index, operation3] of operations.entries()) {
|
|
4929
5452
|
const context = { ...base, operationIndex: index };
|
|
4930
5453
|
const handler = docxHandlers.get(operation3.op);
|
|
@@ -4967,6 +5490,9 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
4967
5490
|
async apply(input, signal) {
|
|
4968
5491
|
const batch = Array.isArray(input) ? { stateId: this.#nextStateId(), operations: input } : input;
|
|
4969
5492
|
this.#stateId = Math.max(this.#stateId, batch.stateId);
|
|
5493
|
+
const tracked = trackedOf(batch);
|
|
5494
|
+
const modeIssues = trackedModeIssues(tracked);
|
|
5495
|
+
if (modeIssues.length > 0) throw invalidOperationError(modeIssues);
|
|
4970
5496
|
const snapshot = this.#pkg.snapshot();
|
|
4971
5497
|
const unauthored = this.#unauthored ? [...this.#unauthored] : void 0;
|
|
4972
5498
|
const createdIds = [];
|
|
@@ -4990,7 +5516,8 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
4990
5516
|
batch.stateId,
|
|
4991
5517
|
signal,
|
|
4992
5518
|
index,
|
|
4993
|
-
issued
|
|
5519
|
+
issued,
|
|
5520
|
+
tracked
|
|
4994
5521
|
);
|
|
4995
5522
|
const issues = [];
|
|
4996
5523
|
await handler.validate(
|
|
@@ -5042,10 +5569,11 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
5042
5569
|
this.#stateId += 1;
|
|
5043
5570
|
return this.#stateId;
|
|
5044
5571
|
}
|
|
5045
|
-
async #context(stateId, signal, operationIndex = 0, issued = /* @__PURE__ */ new Set()) {
|
|
5572
|
+
async #context(stateId, signal, operationIndex = 0, issued = /* @__PURE__ */ new Set(), tracked) {
|
|
5046
5573
|
const model = await this.model(signal);
|
|
5047
5574
|
const taken = /* @__PURE__ */ new Set([...model.takenIds, ...issued]);
|
|
5048
5575
|
let count = 0;
|
|
5576
|
+
let revisions = 0;
|
|
5049
5577
|
return {
|
|
5050
5578
|
pkg: this.#pkg,
|
|
5051
5579
|
model,
|
|
@@ -5059,9 +5587,21 @@ var DocxEditEngine = class _DocxEditEngine {
|
|
|
5059
5587
|
issued.add(id);
|
|
5060
5588
|
taken.add(id);
|
|
5061
5589
|
return id;
|
|
5590
|
+
},
|
|
5591
|
+
...tracked ? { tracked } : {},
|
|
5592
|
+
nextRevisionId: () => {
|
|
5593
|
+
revisions += 1;
|
|
5594
|
+
return model.maxRevisionId + revisions;
|
|
5062
5595
|
}
|
|
5063
5596
|
};
|
|
5064
5597
|
}
|
|
5598
|
+
/** The revisions of a paragraph; none for another element or an unknown id. */
|
|
5599
|
+
async revisions(id, signal) {
|
|
5600
|
+
const model = await this.model(signal);
|
|
5601
|
+
const record = model.byId.get(id);
|
|
5602
|
+
if (!record || record.kind !== "paragraph") return [];
|
|
5603
|
+
return revisionsOf(model.document, record.node);
|
|
5604
|
+
}
|
|
5065
5605
|
async materialize(purposeOrSignal = "show", options = {}, signal = new AbortController().signal) {
|
|
5066
5606
|
return (await this.materializeDocument(purposeOrSignal, options, signal)).bytes;
|
|
5067
5607
|
}
|
|
@@ -8497,6 +9037,16 @@ function createOoxmlEditHandler() {
|
|
|
8497
9037
|
);
|
|
8498
9038
|
return state;
|
|
8499
9039
|
};
|
|
9040
|
+
const docx = () => {
|
|
9041
|
+
const current = engine();
|
|
9042
|
+
if (!(current instanceof DocxEditEngine))
|
|
9043
|
+
throw new ViewerError(
|
|
9044
|
+
"edit-unsupported",
|
|
9045
|
+
"The open document is not a Word document",
|
|
9046
|
+
{ details: { format: "pptx", reason: "no-reads" } }
|
|
9047
|
+
);
|
|
9048
|
+
return current;
|
|
9049
|
+
};
|
|
8500
9050
|
const pptx = () => {
|
|
8501
9051
|
const current = engine();
|
|
8502
9052
|
if (!(current instanceof PptxEditEngine))
|
|
@@ -8529,8 +9079,8 @@ function createOoxmlEditHandler() {
|
|
|
8529
9079
|
return result;
|
|
8530
9080
|
}
|
|
8531
9081
|
case "edit-validate": {
|
|
8532
|
-
const { operations } = payload;
|
|
8533
|
-
return engine().validate(operations, signal);
|
|
9082
|
+
const { operations, mode } = payload;
|
|
9083
|
+
return engine().validate(operations, signal, mode);
|
|
8534
9084
|
}
|
|
8535
9085
|
case "edit-apply": {
|
|
8536
9086
|
const { batch } = payload;
|
|
@@ -8581,6 +9131,11 @@ function createOoxmlEditHandler() {
|
|
|
8581
9131
|
return pptx().slides(signal);
|
|
8582
9132
|
case "edit-pptx-layouts":
|
|
8583
9133
|
return pptx().layouts(signal);
|
|
9134
|
+
case "edit-docx-revisions":
|
|
9135
|
+
return docx().revisions(
|
|
9136
|
+
payload.id,
|
|
9137
|
+
signal
|
|
9138
|
+
);
|
|
8584
9139
|
case "edit-dispose":
|
|
8585
9140
|
await state?.dispose();
|
|
8586
9141
|
state = void 0;
|