bun-docx 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/index.js +230 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,9 @@ docx delete FILE --at p3
|
|
|
40
40
|
docx find FILE QUERY [--regex] [--ignore-case] [--all] [--nth N]
|
|
41
41
|
docx replace FILE PATTERN REPLACEMENT [--regex] [--ignore-case] [--all] [--limit N] [--dry-run]
|
|
42
42
|
|
|
43
|
+
docx wc FILE [LOCATOR]
|
|
44
|
+
docx outline FILE
|
|
45
|
+
|
|
43
46
|
docx comments add FILE --range p3:5-20 --text "..." [--author NAME]
|
|
44
47
|
docx comments reply FILE --to c0 --text "..."
|
|
45
48
|
docx comments resolve FILE --id c0 [--unset]
|
|
@@ -62,7 +65,9 @@ docx info locators [--json]
|
|
|
62
65
|
|
|
63
66
|
Every command has `--help`. Mutating commands accept `--dry-run` and `-o/--output PATH` (write to a parallel file instead of overwriting `FILE`). JSON output by default for `read` and `*.list`; structured `{ok, code, error, hint}` on failure.
|
|
64
67
|
|
|
65
|
-
When `<w:trackChanges/>` is set in the doc (toggle via `docx track-changes FILE on`), `insert`/`edit`/`delete`/`replace` automatically emit `<w:ins>`/`<w:del>` markers
|
|
68
|
+
When `<w:trackChanges/>` is set in the doc (toggle via `docx track-changes FILE on`), `insert`/`edit`/`delete`/`replace` automatically emit `<w:ins>`/`<w:del>` markers. Author resolution: per-call `--author NAME` overrides `$DOCX_AUTHOR`, which falls back to `docx-cli`. To make a one-off untracked edit, flip the flag off, edit, then flip it back on. `find` results inside tracked-change wrappers carry a `trackedChanges` array so agents can decide what to do with hits in pending insertions/deletions.
|
|
69
|
+
|
|
70
|
+
OOXML has no native tracked-change form for hyperlink edits or image swaps, so when track-changes is on, `hyperlinks add/replace/delete` and `images replace` auto-emit a `[docx-cli] …` comment anchored to the affected span/run instead. The comment carries the same `--author` attribution as the other tracked operations. Word itself silently bypasses tracking for these — we trade silence for an explicit audit trail.
|
|
66
71
|
|
|
67
72
|
### Markdown rendering
|
|
68
73
|
|
|
@@ -139,6 +144,10 @@ src/
|
|
|
139
144
|
insert/ # insert FILE (uses ./emit Paragraph component)
|
|
140
145
|
edit/ # edit FILE
|
|
141
146
|
delete/ # delete FILE
|
|
147
|
+
find/ # find FILE QUERY
|
|
148
|
+
replace/ # replace FILE PATTERN REPLACEMENT
|
|
149
|
+
wc/ # wc FILE [LOCATOR]
|
|
150
|
+
outline/ # outline FILE
|
|
142
151
|
comments/ # add | reply | resolve | delete | list
|
|
143
152
|
images/ # list | extract | replace
|
|
144
153
|
hyperlinks/ # add | list | replace | delete
|
package/dist/index.js
CHANGED
|
@@ -14645,8 +14645,6 @@ function resolveAuthor(authorFlag) {
|
|
|
14645
14645
|
return authorFlag;
|
|
14646
14646
|
if (Bun.env.DOCX_AUTHOR)
|
|
14647
14647
|
return Bun.env.DOCX_AUTHOR;
|
|
14648
|
-
if (Bun.env.DOCX_CLI_AUTHOR)
|
|
14649
|
-
return Bun.env.DOCX_CLI_AUTHOR;
|
|
14650
14648
|
return "docx-cli";
|
|
14651
14649
|
}
|
|
14652
14650
|
function resolveDate() {
|
|
@@ -15067,15 +15065,15 @@ function ensureCommentsExtPart(view) {
|
|
|
15067
15065
|
return root;
|
|
15068
15066
|
}
|
|
15069
15067
|
function paragraphTextLength(paragraph) {
|
|
15068
|
+
return sumTextLength(paragraph.children);
|
|
15069
|
+
}
|
|
15070
|
+
function sumTextLength(children) {
|
|
15070
15071
|
let total = 0;
|
|
15071
|
-
for (const child of
|
|
15072
|
+
for (const child of children) {
|
|
15072
15073
|
if (child.tag === "w:r")
|
|
15073
15074
|
total += runTextLength(child);
|
|
15074
|
-
else if (child.tag === "w:ins" || child.tag === "w:del") {
|
|
15075
|
-
|
|
15076
|
-
if (inner.tag === "w:r")
|
|
15077
|
-
total += runTextLength(inner);
|
|
15078
|
-
}
|
|
15075
|
+
else if (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:hyperlink") {
|
|
15076
|
+
total += sumTextLength(child.children);
|
|
15079
15077
|
}
|
|
15080
15078
|
}
|
|
15081
15079
|
return total;
|
|
@@ -15114,6 +15112,97 @@ function addCommentRangeMarkers(startParagraph, startOffset, endParagraph, endOf
|
|
|
15114
15112
|
}
|
|
15115
15113
|
]);
|
|
15116
15114
|
}
|
|
15115
|
+
function addCommentMarkersAroundRun(paragraph, target, commentId) {
|
|
15116
|
+
function walk(parent) {
|
|
15117
|
+
const children = parent.children;
|
|
15118
|
+
for (let index = 0;index < children.length; index++) {
|
|
15119
|
+
const child = children[index];
|
|
15120
|
+
if (child === target) {
|
|
15121
|
+
children.splice(index, 1, commentRangeStartMarker(commentId), target, commentRangeEndMarker(commentId), commentReferenceRun(commentId));
|
|
15122
|
+
return true;
|
|
15123
|
+
}
|
|
15124
|
+
if (child && (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:hyperlink")) {
|
|
15125
|
+
if (walk(child))
|
|
15126
|
+
return true;
|
|
15127
|
+
}
|
|
15128
|
+
}
|
|
15129
|
+
return false;
|
|
15130
|
+
}
|
|
15131
|
+
return walk(paragraph);
|
|
15132
|
+
}
|
|
15133
|
+
function findElementOffsetsInParagraph(paragraph, target) {
|
|
15134
|
+
let cursor = 0;
|
|
15135
|
+
let result = null;
|
|
15136
|
+
function walk(children) {
|
|
15137
|
+
for (const child of children) {
|
|
15138
|
+
if (child === target) {
|
|
15139
|
+
const start = cursor;
|
|
15140
|
+
cursor += sumTextLength([child]);
|
|
15141
|
+
result = { start, end: cursor };
|
|
15142
|
+
return true;
|
|
15143
|
+
}
|
|
15144
|
+
if (child.tag === "w:r") {
|
|
15145
|
+
cursor += runTextLength(child);
|
|
15146
|
+
continue;
|
|
15147
|
+
}
|
|
15148
|
+
if (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:hyperlink") {
|
|
15149
|
+
if (walk(child.children))
|
|
15150
|
+
return true;
|
|
15151
|
+
}
|
|
15152
|
+
}
|
|
15153
|
+
return false;
|
|
15154
|
+
}
|
|
15155
|
+
walk(paragraph.children);
|
|
15156
|
+
return result;
|
|
15157
|
+
}
|
|
15158
|
+
function findContainingParagraph(documentTree, target) {
|
|
15159
|
+
function walk(node) {
|
|
15160
|
+
if (node.tag === "w:p" && containsNode(node, target))
|
|
15161
|
+
return node;
|
|
15162
|
+
for (const child of node.children) {
|
|
15163
|
+
const found = walk(child);
|
|
15164
|
+
if (found)
|
|
15165
|
+
return found;
|
|
15166
|
+
}
|
|
15167
|
+
return null;
|
|
15168
|
+
}
|
|
15169
|
+
for (const root of documentTree) {
|
|
15170
|
+
const found = walk(root);
|
|
15171
|
+
if (found)
|
|
15172
|
+
return found;
|
|
15173
|
+
}
|
|
15174
|
+
return null;
|
|
15175
|
+
}
|
|
15176
|
+
function containsNode(haystack, needle) {
|
|
15177
|
+
if (haystack === needle)
|
|
15178
|
+
return true;
|
|
15179
|
+
for (const child of haystack.children) {
|
|
15180
|
+
if (containsNode(child, needle))
|
|
15181
|
+
return true;
|
|
15182
|
+
}
|
|
15183
|
+
return false;
|
|
15184
|
+
}
|
|
15185
|
+
function emitAuditComment(view, anchor, options) {
|
|
15186
|
+
const numericId = nextCommentId(view);
|
|
15187
|
+
const paraId = generateParaId();
|
|
15188
|
+
if (anchor.kind === "span") {
|
|
15189
|
+
addCommentMarkersToParagraph(anchor.paragraph, numericId, anchor.span);
|
|
15190
|
+
} else {
|
|
15191
|
+
addCommentMarkersAroundRun(anchor.paragraph, anchor.run, numericId);
|
|
15192
|
+
}
|
|
15193
|
+
const commentsRoot = ensureCommentsPart(view);
|
|
15194
|
+
commentsRoot.children.push(/* @__PURE__ */ jsxDEV(CommentBody, {
|
|
15195
|
+
options: {
|
|
15196
|
+
id: numericId,
|
|
15197
|
+
author: options.author,
|
|
15198
|
+
date: options.date,
|
|
15199
|
+
initials: authorInitials(options.author),
|
|
15200
|
+
paraId,
|
|
15201
|
+
text: options.body
|
|
15202
|
+
}
|
|
15203
|
+
}, undefined, false, undefined, this));
|
|
15204
|
+
return numericId;
|
|
15205
|
+
}
|
|
15117
15206
|
function commentRangeStartMarker(commentId) {
|
|
15118
15207
|
return /* @__PURE__ */ jsxDEV(w.commentRangeStart, {
|
|
15119
15208
|
"w-id": commentId
|
|
@@ -15149,13 +15238,13 @@ function placeMarkersInParagraph(paragraph, markers) {
|
|
|
15149
15238
|
}
|
|
15150
15239
|
const pending = markers.slice();
|
|
15151
15240
|
const state = { offset: 0, placedCount: 0 };
|
|
15152
|
-
paragraph.children = walkAndPlace(paragraph.children, pending,
|
|
15241
|
+
paragraph.children = walkAndPlace(paragraph.children, pending, state);
|
|
15153
15242
|
flushAtCurrentOffset(paragraph.children, pending, state);
|
|
15154
15243
|
if (state.placedCount !== markers.length) {
|
|
15155
15244
|
throw new SpanOutOfRangeError(`Could not place comment markers (placed ${state.placedCount} of ${markers.length})`);
|
|
15156
15245
|
}
|
|
15157
15246
|
}
|
|
15158
|
-
function walkAndPlace(children, pending,
|
|
15247
|
+
function walkAndPlace(children, pending, state) {
|
|
15159
15248
|
const result = [];
|
|
15160
15249
|
for (const child of children) {
|
|
15161
15250
|
if (child.tag === "w:r") {
|
|
@@ -15197,9 +15286,9 @@ function walkAndPlace(children, pending, isParagraphLevel, state) {
|
|
|
15197
15286
|
state.offset = runEnd;
|
|
15198
15287
|
continue;
|
|
15199
15288
|
}
|
|
15200
|
-
if (
|
|
15289
|
+
if (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:hyperlink") {
|
|
15201
15290
|
flushAtCurrentOffset(result, pending, state);
|
|
15202
|
-
const innerChildren = walkAndPlace(child.children, pending,
|
|
15291
|
+
const innerChildren = walkAndPlace(child.children, pending, state);
|
|
15203
15292
|
const wrapper = new XmlNode2(child.tag, { ...child.attributes });
|
|
15204
15293
|
wrapper.children = innerChildren;
|
|
15205
15294
|
result.push(wrapper);
|
|
@@ -16174,6 +16263,7 @@ async function run8(args) {
|
|
|
16174
16263
|
allowPositionals: true,
|
|
16175
16264
|
options: {
|
|
16176
16265
|
at: { type: "string" },
|
|
16266
|
+
author: { type: "string" },
|
|
16177
16267
|
output: { type: "string", short: "o" },
|
|
16178
16268
|
"dry-run": { type: "boolean" },
|
|
16179
16269
|
help: { type: "boolean", short: "h" }
|
|
@@ -16219,7 +16309,7 @@ async function run8(args) {
|
|
|
16219
16309
|
if (blockRef.node.tag !== "w:p") {
|
|
16220
16310
|
return fail("TRACKED_CHANGE_CONFLICT", "Tracked deletion of non-paragraph blocks (e.g., tables) is not supported", "Use `docx track-changes off` first, or delete table contents row-by-row.");
|
|
16221
16311
|
}
|
|
16222
|
-
applyTrackedDeletion(view, blockRef.node);
|
|
16312
|
+
applyTrackedDeletion(view, blockRef.node, parsed.values.author);
|
|
16223
16313
|
} else {
|
|
16224
16314
|
blockRef.parent.splice(targetIndex, 1);
|
|
16225
16315
|
}
|
|
@@ -16232,9 +16322,9 @@ async function run8(args) {
|
|
|
16232
16322
|
});
|
|
16233
16323
|
return EXIT.OK;
|
|
16234
16324
|
}
|
|
16235
|
-
function applyTrackedDeletion(view, paragraph) {
|
|
16325
|
+
function applyTrackedDeletion(view, paragraph, authorFlag) {
|
|
16236
16326
|
const allocator = createRevisionAllocator(view);
|
|
16237
|
-
const baseMeta = { author: resolveAuthor(), date: resolveDate() };
|
|
16327
|
+
const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
|
|
16238
16328
|
const mintMeta = () => ({
|
|
16239
16329
|
...baseMeta,
|
|
16240
16330
|
revisionId: allocator.next()
|
|
@@ -16271,6 +16361,7 @@ Usage:
|
|
|
16271
16361
|
Locator (required):
|
|
16272
16362
|
--at LOCATOR Block to remove (e.g., p3, t0)
|
|
16273
16363
|
|
|
16364
|
+
--author NAME Author for tracked changes (default: $DOCX_AUTHOR)
|
|
16274
16365
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
16275
16366
|
--dry-run Print what would be removed; do not write the file
|
|
16276
16367
|
-h, --help Show this help
|
|
@@ -16423,6 +16514,7 @@ async function run9(args) {
|
|
|
16423
16514
|
color: { type: "string" },
|
|
16424
16515
|
bold: { type: "boolean" },
|
|
16425
16516
|
italic: { type: "boolean" },
|
|
16517
|
+
author: { type: "string" },
|
|
16426
16518
|
output: { type: "string", short: "o" },
|
|
16427
16519
|
"dry-run": { type: "boolean" },
|
|
16428
16520
|
help: { type: "boolean", short: "h" }
|
|
@@ -16510,7 +16602,7 @@ async function run9(args) {
|
|
|
16510
16602
|
return EXIT.OK;
|
|
16511
16603
|
}
|
|
16512
16604
|
if (isTrackChangesEnabled(view)) {
|
|
16513
|
-
applyTrackedEdit(view, blockRef.node, paragraphNode);
|
|
16605
|
+
applyTrackedEdit(view, blockRef.node, paragraphNode, parsed.values.author);
|
|
16514
16606
|
} else {
|
|
16515
16607
|
blockRef.parent.splice(targetIndex, 1, paragraphNode);
|
|
16516
16608
|
}
|
|
@@ -16523,9 +16615,9 @@ async function run9(args) {
|
|
|
16523
16615
|
});
|
|
16524
16616
|
return EXIT.OK;
|
|
16525
16617
|
}
|
|
16526
|
-
function applyTrackedEdit(view, existingParagraph, newParagraph) {
|
|
16618
|
+
function applyTrackedEdit(view, existingParagraph, newParagraph, authorFlag) {
|
|
16527
16619
|
const allocator = createRevisionAllocator(view);
|
|
16528
|
-
const baseMeta = { author: resolveAuthor(), date: resolveDate() };
|
|
16620
|
+
const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
|
|
16529
16621
|
const mintMeta = () => ({
|
|
16530
16622
|
...baseMeta,
|
|
16531
16623
|
revisionId: allocator.next()
|
|
@@ -16592,6 +16684,7 @@ Run options (only with --text):
|
|
|
16592
16684
|
--bold Bold
|
|
16593
16685
|
--italic Italic
|
|
16594
16686
|
|
|
16687
|
+
--author NAME Author for tracked changes (default: $DOCX_AUTHOR)
|
|
16595
16688
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
16596
16689
|
--dry-run Print what would change; do not write the file
|
|
16597
16690
|
-h, --help Show this help
|
|
@@ -16933,6 +17026,7 @@ async function run11(args) {
|
|
|
16933
17026
|
options: {
|
|
16934
17027
|
at: { type: "string" },
|
|
16935
17028
|
url: { type: "string" },
|
|
17029
|
+
author: { type: "string" },
|
|
16936
17030
|
output: { type: "string", short: "o" },
|
|
16937
17031
|
"dry-run": { type: "boolean" },
|
|
16938
17032
|
help: { type: "boolean", short: "h" }
|
|
@@ -17001,6 +17095,13 @@ async function run11(args) {
|
|
|
17001
17095
|
throw error;
|
|
17002
17096
|
}
|
|
17003
17097
|
view.hyperlinksByRelationshipId.set(relationshipId, { url });
|
|
17098
|
+
if (isTrackChangesEnabled(view)) {
|
|
17099
|
+
emitAuditComment(view, { kind: "span", paragraph: paragraphRef.node, span: target.span }, {
|
|
17100
|
+
body: `[docx-cli] hyperlink added \u2192 ${url}`,
|
|
17101
|
+
author: resolveAuthor(parsed.values.author),
|
|
17102
|
+
date: resolveDate()
|
|
17103
|
+
});
|
|
17104
|
+
}
|
|
17004
17105
|
await saveDocView(view, outputPath);
|
|
17005
17106
|
await respond({
|
|
17006
17107
|
ok: true,
|
|
@@ -17023,6 +17124,8 @@ Required:
|
|
|
17023
17124
|
--url URL Target URL
|
|
17024
17125
|
|
|
17025
17126
|
Optional:
|
|
17127
|
+
--author NAME Author for the audit comment when track-changes is on
|
|
17128
|
+
(default: $DOCX_AUTHOR)
|
|
17026
17129
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17027
17130
|
--dry-run Print what would change; do not write the file
|
|
17028
17131
|
-h, --help Show this help
|
|
@@ -17030,12 +17133,16 @@ Optional:
|
|
|
17030
17133
|
The span must lie inside a single paragraph and must not overlap an existing
|
|
17031
17134
|
hyperlink or a tracked-change wrapper.
|
|
17032
17135
|
|
|
17136
|
+
When track-changes is on, an audit comment is anchored to the wrapped span
|
|
17137
|
+
since OOXML has no native tracked-change form for hyperlink edits.
|
|
17138
|
+
|
|
17033
17139
|
Examples:
|
|
17034
17140
|
docx hyperlinks add doc.docx --at p3:5-20 --url https://example.com
|
|
17035
17141
|
`;
|
|
17036
17142
|
var init_add2 = __esm(() => {
|
|
17037
17143
|
init_core();
|
|
17038
17144
|
init_parser();
|
|
17145
|
+
init_helpers();
|
|
17039
17146
|
init_respond();
|
|
17040
17147
|
init_wrap();
|
|
17041
17148
|
});
|
|
@@ -17054,6 +17161,7 @@ async function run12(args) {
|
|
|
17054
17161
|
allowPositionals: true,
|
|
17055
17162
|
options: {
|
|
17056
17163
|
at: { type: "string" },
|
|
17164
|
+
author: { type: "string" },
|
|
17057
17165
|
output: { type: "string", short: "o" },
|
|
17058
17166
|
"dry-run": { type: "boolean" },
|
|
17059
17167
|
help: { type: "boolean", short: "h" }
|
|
@@ -17098,6 +17206,9 @@ async function run12(args) {
|
|
|
17098
17206
|
if (index === -1) {
|
|
17099
17207
|
return fail("HYPERLINK_NOT_FOUND", `Hyperlink reference is stale (parent does not contain it): ${targetId}`);
|
|
17100
17208
|
}
|
|
17209
|
+
const trackingOn = isTrackChangesEnabled(view);
|
|
17210
|
+
const paragraph = trackingOn ? findContainingParagraph(view.documentTree, reference.node) : null;
|
|
17211
|
+
const offsets = trackingOn && paragraph ? findElementOffsetsInParagraph(paragraph, reference.node) : null;
|
|
17101
17212
|
reference.parent.splice(index, 1, ...reference.node.children);
|
|
17102
17213
|
view.hyperlinkById.delete(targetId);
|
|
17103
17214
|
if (reference.relationshipId) {
|
|
@@ -17107,6 +17218,13 @@ async function run12(args) {
|
|
|
17107
17218
|
view.hyperlinksByRelationshipId.delete(reference.relationshipId);
|
|
17108
17219
|
}
|
|
17109
17220
|
}
|
|
17221
|
+
if (trackingOn && paragraph && offsets) {
|
|
17222
|
+
emitAuditComment(view, { kind: "span", paragraph, span: offsets }, {
|
|
17223
|
+
body: `[docx-cli] hyperlink removed (was: ${oldUrl ?? "(none)"})`,
|
|
17224
|
+
author: resolveAuthor(parsed.values.author),
|
|
17225
|
+
date: resolveDate()
|
|
17226
|
+
});
|
|
17227
|
+
}
|
|
17110
17228
|
await saveDocView(view, outputPath);
|
|
17111
17229
|
await respond({
|
|
17112
17230
|
ok: true,
|
|
@@ -17149,6 +17267,8 @@ Required:
|
|
|
17149
17267
|
--at LINK_ID Existing hyperlink to remove (e.g., link0)
|
|
17150
17268
|
|
|
17151
17269
|
Optional:
|
|
17270
|
+
--author NAME Author for the audit comment when track-changes is on
|
|
17271
|
+
(default: $DOCX_AUTHOR)
|
|
17152
17272
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17153
17273
|
--dry-run Print what would change; do not write the file
|
|
17154
17274
|
-h, --help Show this help
|
|
@@ -17157,12 +17277,16 @@ The display text stays in place; only the <w:hyperlink> wrapper is removed.
|
|
|
17157
17277
|
If the underlying relationship is no longer referenced, it is pruned from the
|
|
17158
17278
|
rels file too.
|
|
17159
17279
|
|
|
17280
|
+
When track-changes is on, an audit comment is anchored to the surviving text
|
|
17281
|
+
since OOXML has no native tracked-change form for hyperlink removal.
|
|
17282
|
+
|
|
17160
17283
|
Examples:
|
|
17161
17284
|
docx hyperlinks delete doc.docx --at link0
|
|
17162
17285
|
`;
|
|
17163
17286
|
var init_delete3 = __esm(() => {
|
|
17164
17287
|
init_core();
|
|
17165
17288
|
init_parser();
|
|
17289
|
+
init_helpers();
|
|
17166
17290
|
init_respond();
|
|
17167
17291
|
});
|
|
17168
17292
|
|
|
@@ -17272,6 +17396,7 @@ async function run14(args) {
|
|
|
17272
17396
|
options: {
|
|
17273
17397
|
at: { type: "string" },
|
|
17274
17398
|
with: { type: "string" },
|
|
17399
|
+
author: { type: "string" },
|
|
17275
17400
|
output: { type: "string", short: "o" },
|
|
17276
17401
|
"dry-run": { type: "boolean" },
|
|
17277
17402
|
help: { type: "boolean", short: "h" }
|
|
@@ -17333,6 +17458,17 @@ async function run14(args) {
|
|
|
17333
17458
|
updateRelationshipTarget(relationships, existingId, newUrl);
|
|
17334
17459
|
view.hyperlinksByRelationshipId.set(existingId, { url: newUrl });
|
|
17335
17460
|
}
|
|
17461
|
+
if (isTrackChangesEnabled(view)) {
|
|
17462
|
+
const paragraph = findContainingParagraph(view.documentTree, reference.node);
|
|
17463
|
+
const offsets = paragraph ? findElementOffsetsInParagraph(paragraph, reference.node) : null;
|
|
17464
|
+
if (paragraph && offsets) {
|
|
17465
|
+
emitAuditComment(view, { kind: "span", paragraph, span: offsets }, {
|
|
17466
|
+
body: `[docx-cli] hyperlink target changed: ${oldUrl ?? "(none)"} \u2192 ${newUrl}`,
|
|
17467
|
+
author: resolveAuthor(parsed.values.author),
|
|
17468
|
+
date: resolveDate()
|
|
17469
|
+
});
|
|
17470
|
+
}
|
|
17471
|
+
}
|
|
17336
17472
|
await saveDocView(view, outputPath);
|
|
17337
17473
|
await respond({
|
|
17338
17474
|
ok: true,
|
|
@@ -17381,6 +17517,8 @@ Required:
|
|
|
17381
17517
|
--with URL New target URL
|
|
17382
17518
|
|
|
17383
17519
|
Optional:
|
|
17520
|
+
--author NAME Author for the audit comment when track-changes is on
|
|
17521
|
+
(default: $DOCX_AUTHOR)
|
|
17384
17522
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17385
17523
|
--dry-run Print what would change; do not write the file
|
|
17386
17524
|
-h, --help Show this help
|
|
@@ -17389,12 +17527,17 @@ Replaces only the targeted hyperlink. If multiple hyperlinks shared the same
|
|
|
17389
17527
|
underlying relationship, a new relationship is allocated so the others are
|
|
17390
17528
|
unaffected.
|
|
17391
17529
|
|
|
17530
|
+
When track-changes is on, an audit comment is anchored to the affected
|
|
17531
|
+
hyperlink span since OOXML has no native tracked-change form for hyperlink
|
|
17532
|
+
target edits.
|
|
17533
|
+
|
|
17392
17534
|
Examples:
|
|
17393
17535
|
docx hyperlinks replace doc.docx --at link0 --with https://example.com
|
|
17394
17536
|
`;
|
|
17395
17537
|
var init_replace = __esm(() => {
|
|
17396
17538
|
init_core();
|
|
17397
17539
|
init_parser();
|
|
17540
|
+
init_helpers();
|
|
17398
17541
|
init_respond();
|
|
17399
17542
|
});
|
|
17400
17543
|
|
|
@@ -17654,6 +17797,7 @@ async function run18(args) {
|
|
|
17654
17797
|
options: {
|
|
17655
17798
|
at: { type: "string" },
|
|
17656
17799
|
with: { type: "string" },
|
|
17800
|
+
author: { type: "string" },
|
|
17657
17801
|
output: { type: "string", short: "o" },
|
|
17658
17802
|
"dry-run": { type: "boolean" },
|
|
17659
17803
|
help: { type: "boolean", short: "h" }
|
|
@@ -17709,6 +17853,7 @@ async function run18(args) {
|
|
|
17709
17853
|
return EXIT.OK;
|
|
17710
17854
|
}
|
|
17711
17855
|
const bytes = new Uint8Array(await sourceFile.arrayBuffer());
|
|
17856
|
+
const originalMimeType = reference.contentType;
|
|
17712
17857
|
if (newPartName === originalPartName) {
|
|
17713
17858
|
view.pkg.writeBytes(originalPartName, bytes);
|
|
17714
17859
|
} else {
|
|
@@ -17719,6 +17864,18 @@ async function run18(args) {
|
|
|
17719
17864
|
reference.partName = newPartName;
|
|
17720
17865
|
reference.contentType = newMimeType;
|
|
17721
17866
|
}
|
|
17867
|
+
if (isTrackChangesEnabled(view)) {
|
|
17868
|
+
const author = resolveAuthor(parsed.values.author);
|
|
17869
|
+
const date = resolveDate();
|
|
17870
|
+
const body = `[docx-cli] image replaced: ${originalPartName} (${originalMimeType}) \u2192 ${newPartName} (${newMimeType}, ${bytes.length} bytes)`;
|
|
17871
|
+
const drawingRuns = findDrawingRunsForRelationship(view.documentTree, reference.relationshipId);
|
|
17872
|
+
for (const drawingRun of drawingRuns) {
|
|
17873
|
+
const paragraph = findContainingParagraph(view.documentTree, drawingRun);
|
|
17874
|
+
if (!paragraph)
|
|
17875
|
+
continue;
|
|
17876
|
+
emitAuditComment(view, { kind: "run", paragraph, run: drawingRun }, { body, author, date });
|
|
17877
|
+
}
|
|
17878
|
+
}
|
|
17722
17879
|
await saveDocView(view, outputPath);
|
|
17723
17880
|
await respond({
|
|
17724
17881
|
ok: true,
|
|
@@ -17731,6 +17888,33 @@ async function run18(args) {
|
|
|
17731
17888
|
});
|
|
17732
17889
|
return EXIT.OK;
|
|
17733
17890
|
}
|
|
17891
|
+
function findDrawingRunsForRelationship(documentTree, relationshipId) {
|
|
17892
|
+
const matches = [];
|
|
17893
|
+
function walk(node) {
|
|
17894
|
+
if (node.tag === "w:r" && runReferencesImage(node, relationshipId)) {
|
|
17895
|
+
matches.push(node);
|
|
17896
|
+
return;
|
|
17897
|
+
}
|
|
17898
|
+
for (const child of node.children)
|
|
17899
|
+
walk(child);
|
|
17900
|
+
}
|
|
17901
|
+
for (const root of documentTree)
|
|
17902
|
+
walk(root);
|
|
17903
|
+
return matches;
|
|
17904
|
+
}
|
|
17905
|
+
function runReferencesImage(run19, relationshipId) {
|
|
17906
|
+
for (const child of run19.children) {
|
|
17907
|
+
if (child.tag !== "w:drawing")
|
|
17908
|
+
continue;
|
|
17909
|
+
const blip = child.findDescendant("a:blip");
|
|
17910
|
+
if (!blip)
|
|
17911
|
+
continue;
|
|
17912
|
+
const embed = blip.getAttribute("r:embed") ?? blip.getAttribute("r:link");
|
|
17913
|
+
if (embed === relationshipId)
|
|
17914
|
+
return true;
|
|
17915
|
+
}
|
|
17916
|
+
return false;
|
|
17917
|
+
}
|
|
17734
17918
|
function renameExtension(partName, newExtension) {
|
|
17735
17919
|
const dotIndex = partName.lastIndexOf(".");
|
|
17736
17920
|
const slashIndex = partName.lastIndexOf("/");
|
|
@@ -17777,6 +17961,8 @@ Required:
|
|
|
17777
17961
|
--with PATH New image file (any image MIME type)
|
|
17778
17962
|
|
|
17779
17963
|
Optional:
|
|
17964
|
+
--author NAME Author for the audit comment when track-changes is on
|
|
17965
|
+
(default: $DOCX_AUTHOR)
|
|
17780
17966
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17781
17967
|
--dry-run Print what would change; do not write the file
|
|
17782
17968
|
-h, --help Show this help
|
|
@@ -17785,6 +17971,10 @@ If the replacement uses a different format from the original, the part is
|
|
|
17785
17971
|
renamed (extension changes), the relationship Target is rewritten, and
|
|
17786
17972
|
[Content_Types].xml gets a Default entry for the new extension if needed.
|
|
17787
17973
|
|
|
17974
|
+
When track-changes is on, an audit comment is anchored to each drawing that
|
|
17975
|
+
referenced the swapped image since OOXML has no native tracked-change form
|
|
17976
|
+
for image replacement.
|
|
17977
|
+
|
|
17788
17978
|
Examples:
|
|
17789
17979
|
docx images replace doc.docx --at img2 --with ./new-photo.png
|
|
17790
17980
|
docx images replace doc.docx --at img0 --with ./diagram.svg
|
|
@@ -17792,6 +17982,7 @@ Examples:
|
|
|
17792
17982
|
var init_replace2 = __esm(() => {
|
|
17793
17983
|
init_core();
|
|
17794
17984
|
init_parser();
|
|
17985
|
+
init_helpers();
|
|
17795
17986
|
init_respond();
|
|
17796
17987
|
EXTENSION_BY_MIME = {
|
|
17797
17988
|
"image/png": "png",
|
|
@@ -18036,10 +18227,10 @@ async function run20(args) {
|
|
|
18036
18227
|
await respond(JSON_SCHEMA);
|
|
18037
18228
|
return EXIT.OK;
|
|
18038
18229
|
}
|
|
18039
|
-
var HELP20 = `docx schema \u2014 print the AST type definitions
|
|
18230
|
+
var HELP20 = `docx info schema \u2014 print the AST type definitions
|
|
18040
18231
|
|
|
18041
18232
|
Usage:
|
|
18042
|
-
docx schema [options]
|
|
18233
|
+
docx info schema [options]
|
|
18043
18234
|
|
|
18044
18235
|
Options:
|
|
18045
18236
|
--json Print as a JSON Schema document (default)
|
|
@@ -18047,8 +18238,8 @@ Options:
|
|
|
18047
18238
|
-h, --help Show this help
|
|
18048
18239
|
|
|
18049
18240
|
Examples:
|
|
18050
|
-
docx schema | jq '.
|
|
18051
|
-
docx schema --ts > ast.d.ts
|
|
18241
|
+
docx info schema | jq '.$defs.Run'
|
|
18242
|
+
docx info schema --ts > ast.d.ts
|
|
18052
18243
|
`, JSON_SCHEMA;
|
|
18053
18244
|
var init_schema = __esm(() => {
|
|
18054
18245
|
init_types();
|
|
@@ -18316,14 +18507,18 @@ async function run21(args) {
|
|
|
18316
18507
|
await writeStdout(REFERENCE);
|
|
18317
18508
|
return EXIT.OK;
|
|
18318
18509
|
}
|
|
18319
|
-
var HELP21 = `docx locators \u2014 print the locator grammar reference
|
|
18510
|
+
var HELP21 = `docx info locators \u2014 print the locator grammar reference
|
|
18320
18511
|
|
|
18321
18512
|
Usage:
|
|
18322
|
-
docx locators [options]
|
|
18513
|
+
docx info locators [options]
|
|
18323
18514
|
|
|
18324
18515
|
Options:
|
|
18325
18516
|
--json Print as JSON
|
|
18326
18517
|
-h, --help Show this help
|
|
18518
|
+
|
|
18519
|
+
Examples:
|
|
18520
|
+
docx info locators
|
|
18521
|
+
docx info locators --json | jq '.entityLocators'
|
|
18327
18522
|
`, REFERENCE = `LOCATOR GRAMMAR
|
|
18328
18523
|
|
|
18329
18524
|
Block locators:
|
|
@@ -18456,6 +18651,7 @@ async function run23(args) {
|
|
|
18456
18651
|
bold: { type: "boolean" },
|
|
18457
18652
|
italic: { type: "boolean" },
|
|
18458
18653
|
url: { type: "string" },
|
|
18654
|
+
author: { type: "string" },
|
|
18459
18655
|
output: { type: "string", short: "o" },
|
|
18460
18656
|
"dry-run": { type: "boolean" },
|
|
18461
18657
|
help: { type: "boolean", short: "h" }
|
|
@@ -18544,7 +18740,7 @@ async function run23(args) {
|
|
|
18544
18740
|
}
|
|
18545
18741
|
const insertIndex = after !== undefined ? targetIndex + 1 : targetIndex;
|
|
18546
18742
|
if (isTrackChangesEnabled(view)) {
|
|
18547
|
-
applyTrackedInsertion(paragraphNode, view);
|
|
18743
|
+
applyTrackedInsertion(paragraphNode, view, parsed.values.author);
|
|
18548
18744
|
}
|
|
18549
18745
|
const outputPath = parsed.values.output;
|
|
18550
18746
|
if (parsed.values["dry-run"]) {
|
|
@@ -18593,9 +18789,9 @@ function wrapFirstRunInHyperlink(view, paragraph, url) {
|
|
|
18593
18789
|
}
|
|
18594
18790
|
paragraph.children = newChildren;
|
|
18595
18791
|
}
|
|
18596
|
-
function applyTrackedInsertion(paragraph, view) {
|
|
18792
|
+
function applyTrackedInsertion(paragraph, view, authorFlag) {
|
|
18597
18793
|
const allocator = createRevisionAllocator(view);
|
|
18598
|
-
const baseMeta = { author: resolveAuthor(), date: resolveDate() };
|
|
18794
|
+
const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
|
|
18599
18795
|
const mintMeta = () => ({
|
|
18600
18796
|
...baseMeta,
|
|
18601
18797
|
revisionId: allocator.next()
|
|
@@ -18646,6 +18842,7 @@ Run options (only with --text):
|
|
|
18646
18842
|
--italic Italic
|
|
18647
18843
|
--url URL Wrap the inserted text in a hyperlink to URL
|
|
18648
18844
|
|
|
18845
|
+
--author NAME Author for tracked changes (default: $DOCX_AUTHOR)
|
|
18649
18846
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
18650
18847
|
--dry-run Print what would be inserted; do not write the file
|
|
18651
18848
|
-h, --help Show this help
|
|
@@ -19694,6 +19891,7 @@ async function run26(args) {
|
|
|
19694
19891
|
"ignore-case": { type: "boolean" },
|
|
19695
19892
|
all: { type: "boolean" },
|
|
19696
19893
|
limit: { type: "string" },
|
|
19894
|
+
author: { type: "string" },
|
|
19697
19895
|
output: { type: "string", short: "o" },
|
|
19698
19896
|
"dry-run": { type: "boolean" },
|
|
19699
19897
|
help: { type: "boolean", short: "h" }
|
|
@@ -19792,8 +19990,9 @@ async function run26(args) {
|
|
|
19792
19990
|
}
|
|
19793
19991
|
return rightMatch.start - leftMatch.start;
|
|
19794
19992
|
});
|
|
19993
|
+
const authorFlag = parsed.values.author;
|
|
19795
19994
|
const tracked = isTrackChangesEnabled(view) ? {
|
|
19796
|
-
meta: { author: resolveAuthor(), date: resolveDate() },
|
|
19995
|
+
meta: { author: resolveAuthor(authorFlag), date: resolveDate() },
|
|
19797
19996
|
allocator: createRevisionAllocator(view)
|
|
19798
19997
|
} : undefined;
|
|
19799
19998
|
const regexFlags = ignoreCase ? "i" : "";
|
|
@@ -19827,6 +20026,7 @@ Options:
|
|
|
19827
20026
|
--ignore-case case-insensitive match
|
|
19828
20027
|
--all replace every match (default: just the first)
|
|
19829
20028
|
--limit N replace at most N matches (in document order)
|
|
20029
|
+
--author NAME author for tracked changes (default: $DOCX_AUTHOR)
|
|
19830
20030
|
-o, --output PATH write to PATH instead of overwriting FILE
|
|
19831
20031
|
--dry-run report what would change without writing the file
|
|
19832
20032
|
-h, --help show this help
|
|
@@ -20220,7 +20420,7 @@ var init_wc = __esm(() => {
|
|
|
20220
20420
|
// package.json
|
|
20221
20421
|
var package_default = {
|
|
20222
20422
|
name: "bun-docx",
|
|
20223
|
-
version: "0.6.
|
|
20423
|
+
version: "0.6.1",
|
|
20224
20424
|
description: "CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity.",
|
|
20225
20425
|
keywords: [
|
|
20226
20426
|
"docx",
|
|
@@ -20299,7 +20499,7 @@ Commands:
|
|
|
20299
20499
|
outline FILE List headings as a hierarchical tree
|
|
20300
20500
|
comments \u2026 Add, reply, resolve, delete, list comments
|
|
20301
20501
|
images \u2026 Extract, replace, list images
|
|
20302
|
-
hyperlinks \u2026
|
|
20502
|
+
hyperlinks \u2026 Add, list, replace, delete hyperlinks
|
|
20303
20503
|
track-changes FILE on|off Toggle tracked-changes mode
|
|
20304
20504
|
info \u2026 Reference material (schema, locators)
|
|
20305
20505
|
|