bun-docx 0.5.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.
Files changed (3) hide show
  1. package/README.md +44 -4
  2. package/dist/index.js +950 -107
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13780,7 +13780,17 @@ function buildDoc(view, path) {
13780
13780
  };
13781
13781
  const blocks = readBlocks(view, body, state);
13782
13782
  const comments = readComments(view, state.commentAnchors);
13783
- return { schemaVersion: 1, path, properties, blocks, comments };
13783
+ const footnotes = readNotes(view.footnotesTree, "w:footnotes", "w:footnote", "fn");
13784
+ const endnotes = readNotes(view.endnotesTree, "w:endnotes", "w:endnote", "en");
13785
+ return {
13786
+ schemaVersion: 1,
13787
+ path,
13788
+ properties,
13789
+ blocks,
13790
+ comments,
13791
+ footnotes,
13792
+ endnotes
13793
+ };
13784
13794
  }
13785
13795
  function readBlocks(view, body, state) {
13786
13796
  const blocks = [];
@@ -13868,6 +13878,28 @@ function walkRunContainer(context, container, trackedChange, hyperlink) {
13868
13878
  }
13869
13879
  continue;
13870
13880
  }
13881
+ if (child.tag === "m:oMath") {
13882
+ const text = collectMathText(child);
13883
+ if (text.length > 0) {
13884
+ context.paragraph.runs.push({
13885
+ type: "equation",
13886
+ text,
13887
+ display: false
13888
+ });
13889
+ }
13890
+ continue;
13891
+ }
13892
+ if (child.tag === "m:oMathPara") {
13893
+ const text = collectMathText(child);
13894
+ if (text.length > 0) {
13895
+ context.paragraph.runs.push({
13896
+ type: "equation",
13897
+ text,
13898
+ display: true
13899
+ });
13900
+ }
13901
+ continue;
13902
+ }
13871
13903
  if (child.tag === "w:ins" || child.tag === "w:del") {
13872
13904
  const change = {
13873
13905
  kind: child.tag === "w:ins" ? "ins" : "del",
@@ -13937,9 +13969,9 @@ function readRun(view, node, activeComments, trackedChange, hyperlink, state) {
13937
13969
  const runProperties = node.findChild("w:rPr");
13938
13970
  for (const child of node.children) {
13939
13971
  if (child.tag === "w:drawing") {
13940
- const image = readImageFromDrawing(view, child, state);
13941
- if (image)
13942
- return image;
13972
+ const drawing = readDrawing(view, child, state);
13973
+ if (drawing)
13974
+ return drawing;
13943
13975
  }
13944
13976
  if (child.tag === "w:br") {
13945
13977
  const kind = child.getAttribute("w:type") ?? "line";
@@ -13948,6 +13980,16 @@ function readRun(view, node, activeComments, trackedChange, hyperlink, state) {
13948
13980
  if (child.tag === "w:tab") {
13949
13981
  return { type: "tab" };
13950
13982
  }
13983
+ if (child.tag === "w:footnoteReference") {
13984
+ const id = child.getAttribute("w:id");
13985
+ if (id)
13986
+ return { type: "footnoteRef", kind: "footnote", id: `fn${id}` };
13987
+ }
13988
+ if (child.tag === "w:endnoteReference") {
13989
+ const id = child.getAttribute("w:id");
13990
+ if (id)
13991
+ return { type: "footnoteRef", kind: "endnote", id: `en${id}` };
13992
+ }
13951
13993
  }
13952
13994
  let combinedText = "";
13953
13995
  for (const child of node.children) {
@@ -13968,6 +14010,29 @@ function readRun(view, node, activeComments, trackedChange, hyperlink, state) {
13968
14010
  run.hyperlink = hyperlink;
13969
14011
  return run;
13970
14012
  }
14013
+ function readDrawing(view, drawing, state) {
14014
+ const image = readImageFromDrawing(view, drawing, state);
14015
+ if (image)
14016
+ return image;
14017
+ if (drawing.findDescendant("c:chart"))
14018
+ return { type: "chart", kind: "chart" };
14019
+ if (drawing.findDescendant("dgm:relIds"))
14020
+ return { type: "chart", kind: "smartart" };
14021
+ if (drawing.findDescendant("wps:wsp"))
14022
+ return { type: "chart", kind: "shape" };
14023
+ return { type: "chart", kind: "drawing" };
14024
+ }
14025
+ function collectMathText(node) {
14026
+ let out = "";
14027
+ for (const child of node.children) {
14028
+ if (child.tag === "m:t") {
14029
+ out += child.collectText();
14030
+ continue;
14031
+ }
14032
+ out += collectMathText(child);
14033
+ }
14034
+ return out;
14035
+ }
13971
14036
  function applyRunProperties(run, runProperties) {
13972
14037
  const colorNode = runProperties.findChild("w:color");
13973
14038
  if (colorNode) {
@@ -14222,6 +14287,26 @@ function readCommentsExtended(view) {
14222
14287
  }
14223
14288
  return out;
14224
14289
  }
14290
+ function readNotes(tree, rootTag, itemTag, idPrefix) {
14291
+ if (!tree)
14292
+ return [];
14293
+ const root = XmlNode2.findRoot(tree, rootTag);
14294
+ if (!root)
14295
+ return [];
14296
+ const out = [];
14297
+ for (const child of root.children) {
14298
+ if (child.tag !== itemTag)
14299
+ continue;
14300
+ if (child.getAttribute("w:type"))
14301
+ continue;
14302
+ const numericId = child.getAttribute("w:id");
14303
+ if (numericId == null)
14304
+ continue;
14305
+ const text = child.collectText().replace(/\s+/g, " ").trim();
14306
+ out.push({ id: `${idPrefix}${numericId}`, text });
14307
+ }
14308
+ return out;
14309
+ }
14225
14310
  var RELATIONSHIP_NAMESPACE_IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", RELATIONSHIP_NAMESPACE_HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
14226
14311
  var init_read = __esm(() => {
14227
14312
  init_parser();
@@ -14237,11 +14322,15 @@ async function openDocView(path) {
14237
14322
  const commentsExtTree = pkg.hasPart("word/commentsExtended.xml") ? XmlNode2.parse(await pkg.readText("word/commentsExtended.xml")) : undefined;
14238
14323
  const corePropertiesTree = pkg.hasPart("docProps/core.xml") ? XmlNode2.parse(await pkg.readText("docProps/core.xml")) : undefined;
14239
14324
  const settingsTree = pkg.hasPart("word/settings.xml") ? XmlNode2.parse(await pkg.readText("word/settings.xml")) : undefined;
14325
+ const footnotesTree = pkg.hasPart("word/footnotes.xml") ? XmlNode2.parse(await pkg.readText("word/footnotes.xml")) : undefined;
14326
+ const endnotesTree = pkg.hasPart("word/endnotes.xml") ? XmlNode2.parse(await pkg.readText("word/endnotes.xml")) : undefined;
14240
14327
  const view = {
14241
14328
  pkg,
14242
14329
  documentTree,
14243
14330
  commentsTree,
14244
14331
  commentsExtTree,
14332
+ footnotesTree,
14333
+ endnotesTree,
14245
14334
  relationshipsTree,
14246
14335
  contentTypesTree,
14247
14336
  corePropertiesTree,
@@ -14322,6 +14411,49 @@ var init_doc_view = __esm(() => {
14322
14411
  init_read();
14323
14412
  });
14324
14413
 
14414
+ // src/core/ast/text.ts
14415
+ function paragraphText(paragraph) {
14416
+ let out = "";
14417
+ for (const run of paragraph.runs) {
14418
+ if (run.type === "text")
14419
+ out += run.text;
14420
+ }
14421
+ return out;
14422
+ }
14423
+ function flattenParagraphs(blocks) {
14424
+ const out = [];
14425
+ for (const block of blocks) {
14426
+ if (block.type === "paragraph") {
14427
+ out.push(block);
14428
+ continue;
14429
+ }
14430
+ if (block.type === "table") {
14431
+ for (const row of block.rows) {
14432
+ for (const cell of row.cells) {
14433
+ out.push(...flattenParagraphs(cell.blocks));
14434
+ }
14435
+ }
14436
+ }
14437
+ }
14438
+ return out;
14439
+ }
14440
+ function findBlockById(blocks, blockId) {
14441
+ for (const block of blocks) {
14442
+ if (block.id === blockId)
14443
+ return block;
14444
+ if (block.type === "table") {
14445
+ for (const row of block.rows) {
14446
+ for (const cell of row.cells) {
14447
+ const inner = findBlockById(cell.blocks, blockId);
14448
+ if (inner)
14449
+ return inner;
14450
+ }
14451
+ }
14452
+ }
14453
+ }
14454
+ return null;
14455
+ }
14456
+
14325
14457
  // src/core/ast/index.ts
14326
14458
  var init_ast = __esm(() => {
14327
14459
  init_doc_view();
@@ -14513,8 +14645,6 @@ function resolveAuthor(authorFlag) {
14513
14645
  return authorFlag;
14514
14646
  if (Bun.env.DOCX_AUTHOR)
14515
14647
  return Bun.env.DOCX_AUTHOR;
14516
- if (Bun.env.DOCX_CLI_AUTHOR)
14517
- return Bun.env.DOCX_CLI_AUTHOR;
14518
14648
  return "docx-cli";
14519
14649
  }
14520
14650
  function resolveDate() {
@@ -14935,15 +15065,15 @@ function ensureCommentsExtPart(view) {
14935
15065
  return root;
14936
15066
  }
14937
15067
  function paragraphTextLength(paragraph) {
15068
+ return sumTextLength(paragraph.children);
15069
+ }
15070
+ function sumTextLength(children) {
14938
15071
  let total = 0;
14939
- for (const child of paragraph.children) {
15072
+ for (const child of children) {
14940
15073
  if (child.tag === "w:r")
14941
15074
  total += runTextLength(child);
14942
- else if (child.tag === "w:ins" || child.tag === "w:del") {
14943
- for (const inner of child.children) {
14944
- if (inner.tag === "w:r")
14945
- total += runTextLength(inner);
14946
- }
15075
+ else if (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:hyperlink") {
15076
+ total += sumTextLength(child.children);
14947
15077
  }
14948
15078
  }
14949
15079
  return total;
@@ -14982,6 +15112,97 @@ function addCommentRangeMarkers(startParagraph, startOffset, endParagraph, endOf
14982
15112
  }
14983
15113
  ]);
14984
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
+ }
14985
15206
  function commentRangeStartMarker(commentId) {
14986
15207
  return /* @__PURE__ */ jsxDEV(w.commentRangeStart, {
14987
15208
  "w-id": commentId
@@ -15017,13 +15238,13 @@ function placeMarkersInParagraph(paragraph, markers) {
15017
15238
  }
15018
15239
  const pending = markers.slice();
15019
15240
  const state = { offset: 0, placedCount: 0 };
15020
- paragraph.children = walkAndPlace(paragraph.children, pending, true, state);
15241
+ paragraph.children = walkAndPlace(paragraph.children, pending, state);
15021
15242
  flushAtCurrentOffset(paragraph.children, pending, state);
15022
15243
  if (state.placedCount !== markers.length) {
15023
15244
  throw new SpanOutOfRangeError(`Could not place comment markers (placed ${state.placedCount} of ${markers.length})`);
15024
15245
  }
15025
15246
  }
15026
- function walkAndPlace(children, pending, isParagraphLevel, state) {
15247
+ function walkAndPlace(children, pending, state) {
15027
15248
  const result = [];
15028
15249
  for (const child of children) {
15029
15250
  if (child.tag === "w:r") {
@@ -15065,9 +15286,9 @@ function walkAndPlace(children, pending, isParagraphLevel, state) {
15065
15286
  state.offset = runEnd;
15066
15287
  continue;
15067
15288
  }
15068
- if (isParagraphLevel && (child.tag === "w:ins" || child.tag === "w:del")) {
15289
+ if (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:hyperlink") {
15069
15290
  flushAtCurrentOffset(result, pending, state);
15070
- const innerChildren = walkAndPlace(child.children, pending, false, state);
15291
+ const innerChildren = walkAndPlace(child.children, pending, state);
15071
15292
  const wrapper = new XmlNode2(child.tag, { ...child.attributes });
15072
15293
  wrapper.children = innerChildren;
15073
15294
  result.push(wrapper);
@@ -16042,6 +16263,7 @@ async function run8(args) {
16042
16263
  allowPositionals: true,
16043
16264
  options: {
16044
16265
  at: { type: "string" },
16266
+ author: { type: "string" },
16045
16267
  output: { type: "string", short: "o" },
16046
16268
  "dry-run": { type: "boolean" },
16047
16269
  help: { type: "boolean", short: "h" }
@@ -16087,7 +16309,7 @@ async function run8(args) {
16087
16309
  if (blockRef.node.tag !== "w:p") {
16088
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.");
16089
16311
  }
16090
- applyTrackedDeletion(view, blockRef.node);
16312
+ applyTrackedDeletion(view, blockRef.node, parsed.values.author);
16091
16313
  } else {
16092
16314
  blockRef.parent.splice(targetIndex, 1);
16093
16315
  }
@@ -16100,9 +16322,9 @@ async function run8(args) {
16100
16322
  });
16101
16323
  return EXIT.OK;
16102
16324
  }
16103
- function applyTrackedDeletion(view, paragraph) {
16325
+ function applyTrackedDeletion(view, paragraph, authorFlag) {
16104
16326
  const allocator = createRevisionAllocator(view);
16105
- const baseMeta = { author: resolveAuthor(), date: resolveDate() };
16327
+ const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
16106
16328
  const mintMeta = () => ({
16107
16329
  ...baseMeta,
16108
16330
  revisionId: allocator.next()
@@ -16139,6 +16361,7 @@ Usage:
16139
16361
  Locator (required):
16140
16362
  --at LOCATOR Block to remove (e.g., p3, t0)
16141
16363
 
16364
+ --author NAME Author for tracked changes (default: $DOCX_AUTHOR)
16142
16365
  -o, --output PATH Write to PATH instead of overwriting FILE
16143
16366
  --dry-run Print what would be removed; do not write the file
16144
16367
  -h, --help Show this help
@@ -16185,7 +16408,7 @@ function RunElement({ run: run9 }) {
16185
16408
  children: /* @__PURE__ */ jsxDEV(w.tab, {}, undefined, false, undefined, this)
16186
16409
  }, undefined, false, undefined, this);
16187
16410
  }
16188
- throw new Error(`Cannot emit run of type "${run9.type}" \u2014 image emission lives in the images command.`);
16411
+ return null;
16189
16412
  }
16190
16413
  function TextRunElement({ run: run9 }) {
16191
16414
  return /* @__PURE__ */ jsxDEV(w.r, {
@@ -16291,6 +16514,7 @@ async function run9(args) {
16291
16514
  color: { type: "string" },
16292
16515
  bold: { type: "boolean" },
16293
16516
  italic: { type: "boolean" },
16517
+ author: { type: "string" },
16294
16518
  output: { type: "string", short: "o" },
16295
16519
  "dry-run": { type: "boolean" },
16296
16520
  help: { type: "boolean", short: "h" }
@@ -16378,7 +16602,7 @@ async function run9(args) {
16378
16602
  return EXIT.OK;
16379
16603
  }
16380
16604
  if (isTrackChangesEnabled(view)) {
16381
- applyTrackedEdit(view, blockRef.node, paragraphNode);
16605
+ applyTrackedEdit(view, blockRef.node, paragraphNode, parsed.values.author);
16382
16606
  } else {
16383
16607
  blockRef.parent.splice(targetIndex, 1, paragraphNode);
16384
16608
  }
@@ -16391,9 +16615,9 @@ async function run9(args) {
16391
16615
  });
16392
16616
  return EXIT.OK;
16393
16617
  }
16394
- function applyTrackedEdit(view, existingParagraph, newParagraph) {
16618
+ function applyTrackedEdit(view, existingParagraph, newParagraph, authorFlag) {
16395
16619
  const allocator = createRevisionAllocator(view);
16396
- const baseMeta = { author: resolveAuthor(), date: resolveDate() };
16620
+ const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
16397
16621
  const mintMeta = () => ({
16398
16622
  ...baseMeta,
16399
16623
  revisionId: allocator.next()
@@ -16460,6 +16684,7 @@ Run options (only with --text):
16460
16684
  --bold Bold
16461
16685
  --italic Italic
16462
16686
 
16687
+ --author NAME Author for tracked changes (default: $DOCX_AUTHOR)
16463
16688
  -o, --output PATH Write to PATH instead of overwriting FILE
16464
16689
  --dry-run Print what would change; do not write the file
16465
16690
  -h, --help Show this help
@@ -16487,15 +16712,15 @@ function literalMatcher(query, ignoreCase) {
16487
16712
  throw new Error("query cannot be empty");
16488
16713
  }
16489
16714
  const needle = ignoreCase ? query.toLowerCase() : query;
16490
- return (paragraphText) => {
16491
- const haystack = ignoreCase ? paragraphText.toLowerCase() : paragraphText;
16715
+ return (paragraphText2) => {
16716
+ const haystack = ignoreCase ? paragraphText2.toLowerCase() : paragraphText2;
16492
16717
  const matches = [];
16493
16718
  let cursor = haystack.indexOf(needle);
16494
16719
  while (cursor !== -1) {
16495
16720
  matches.push({
16496
16721
  start: cursor,
16497
16722
  end: cursor + needle.length,
16498
- text: paragraphText.slice(cursor, cursor + needle.length)
16723
+ text: paragraphText2.slice(cursor, cursor + needle.length)
16499
16724
  });
16500
16725
  cursor = haystack.indexOf(needle, cursor + needle.length);
16501
16726
  }
@@ -16505,15 +16730,15 @@ function literalMatcher(query, ignoreCase) {
16505
16730
  function regexMatcher(pattern, ignoreCase) {
16506
16731
  const flags = `g${ignoreCase ? "i" : ""}`;
16507
16732
  const regex = new RegExp(pattern, flags);
16508
- return (paragraphText) => {
16733
+ return (paragraphText2) => {
16509
16734
  const matches = [];
16510
16735
  regex.lastIndex = 0;
16511
- let result = regex.exec(paragraphText);
16736
+ let result = regex.exec(paragraphText2);
16512
16737
  while (result !== null) {
16513
16738
  const matched = result[0];
16514
16739
  if (matched.length === 0) {
16515
16740
  regex.lastIndex += 1;
16516
- result = regex.exec(paragraphText);
16741
+ result = regex.exec(paragraphText2);
16517
16742
  continue;
16518
16743
  }
16519
16744
  matches.push({
@@ -16521,7 +16746,7 @@ function regexMatcher(pattern, ignoreCase) {
16521
16746
  end: result.index + matched.length,
16522
16747
  text: matched
16523
16748
  });
16524
- result = regex.exec(paragraphText);
16749
+ result = regex.exec(paragraphText2);
16525
16750
  }
16526
16751
  return matches;
16527
16752
  };
@@ -16529,8 +16754,8 @@ function regexMatcher(pattern, ignoreCase) {
16529
16754
  function collectMatches(blocks, matcher, out) {
16530
16755
  for (const block of blocks) {
16531
16756
  if (block.type === "paragraph") {
16532
- const paragraphText = block.runs.map((run10) => run10.type === "text" ? run10.text : "").join("");
16533
- for (const span of matcher(paragraphText)) {
16757
+ const paragraphText2 = block.runs.map((run10) => run10.type === "text" ? run10.text : "").join("");
16758
+ for (const span of matcher(paragraphText2)) {
16534
16759
  const match = {
16535
16760
  blockId: block.id,
16536
16761
  start: span.start,
@@ -16801,6 +17026,7 @@ async function run11(args) {
16801
17026
  options: {
16802
17027
  at: { type: "string" },
16803
17028
  url: { type: "string" },
17029
+ author: { type: "string" },
16804
17030
  output: { type: "string", short: "o" },
16805
17031
  "dry-run": { type: "boolean" },
16806
17032
  help: { type: "boolean", short: "h" }
@@ -16869,6 +17095,13 @@ async function run11(args) {
16869
17095
  throw error;
16870
17096
  }
16871
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
+ }
16872
17105
  await saveDocView(view, outputPath);
16873
17106
  await respond({
16874
17107
  ok: true,
@@ -16891,6 +17124,8 @@ Required:
16891
17124
  --url URL Target URL
16892
17125
 
16893
17126
  Optional:
17127
+ --author NAME Author for the audit comment when track-changes is on
17128
+ (default: $DOCX_AUTHOR)
16894
17129
  -o, --output PATH Write to PATH instead of overwriting FILE
16895
17130
  --dry-run Print what would change; do not write the file
16896
17131
  -h, --help Show this help
@@ -16898,12 +17133,16 @@ Optional:
16898
17133
  The span must lie inside a single paragraph and must not overlap an existing
16899
17134
  hyperlink or a tracked-change wrapper.
16900
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
+
16901
17139
  Examples:
16902
17140
  docx hyperlinks add doc.docx --at p3:5-20 --url https://example.com
16903
17141
  `;
16904
17142
  var init_add2 = __esm(() => {
16905
17143
  init_core();
16906
17144
  init_parser();
17145
+ init_helpers();
16907
17146
  init_respond();
16908
17147
  init_wrap();
16909
17148
  });
@@ -16922,6 +17161,7 @@ async function run12(args) {
16922
17161
  allowPositionals: true,
16923
17162
  options: {
16924
17163
  at: { type: "string" },
17164
+ author: { type: "string" },
16925
17165
  output: { type: "string", short: "o" },
16926
17166
  "dry-run": { type: "boolean" },
16927
17167
  help: { type: "boolean", short: "h" }
@@ -16966,6 +17206,9 @@ async function run12(args) {
16966
17206
  if (index === -1) {
16967
17207
  return fail("HYPERLINK_NOT_FOUND", `Hyperlink reference is stale (parent does not contain it): ${targetId}`);
16968
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;
16969
17212
  reference.parent.splice(index, 1, ...reference.node.children);
16970
17213
  view.hyperlinkById.delete(targetId);
16971
17214
  if (reference.relationshipId) {
@@ -16975,6 +17218,13 @@ async function run12(args) {
16975
17218
  view.hyperlinksByRelationshipId.delete(reference.relationshipId);
16976
17219
  }
16977
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
+ }
16978
17228
  await saveDocView(view, outputPath);
16979
17229
  await respond({
16980
17230
  ok: true,
@@ -17017,6 +17267,8 @@ Required:
17017
17267
  --at LINK_ID Existing hyperlink to remove (e.g., link0)
17018
17268
 
17019
17269
  Optional:
17270
+ --author NAME Author for the audit comment when track-changes is on
17271
+ (default: $DOCX_AUTHOR)
17020
17272
  -o, --output PATH Write to PATH instead of overwriting FILE
17021
17273
  --dry-run Print what would change; do not write the file
17022
17274
  -h, --help Show this help
@@ -17025,12 +17277,16 @@ The display text stays in place; only the <w:hyperlink> wrapper is removed.
17025
17277
  If the underlying relationship is no longer referenced, it is pruned from the
17026
17278
  rels file too.
17027
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
+
17028
17283
  Examples:
17029
17284
  docx hyperlinks delete doc.docx --at link0
17030
17285
  `;
17031
17286
  var init_delete3 = __esm(() => {
17032
17287
  init_core();
17033
17288
  init_parser();
17289
+ init_helpers();
17034
17290
  init_respond();
17035
17291
  });
17036
17292
 
@@ -17140,6 +17396,7 @@ async function run14(args) {
17140
17396
  options: {
17141
17397
  at: { type: "string" },
17142
17398
  with: { type: "string" },
17399
+ author: { type: "string" },
17143
17400
  output: { type: "string", short: "o" },
17144
17401
  "dry-run": { type: "boolean" },
17145
17402
  help: { type: "boolean", short: "h" }
@@ -17201,6 +17458,17 @@ async function run14(args) {
17201
17458
  updateRelationshipTarget(relationships, existingId, newUrl);
17202
17459
  view.hyperlinksByRelationshipId.set(existingId, { url: newUrl });
17203
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
+ }
17204
17472
  await saveDocView(view, outputPath);
17205
17473
  await respond({
17206
17474
  ok: true,
@@ -17249,6 +17517,8 @@ Required:
17249
17517
  --with URL New target URL
17250
17518
 
17251
17519
  Optional:
17520
+ --author NAME Author for the audit comment when track-changes is on
17521
+ (default: $DOCX_AUTHOR)
17252
17522
  -o, --output PATH Write to PATH instead of overwriting FILE
17253
17523
  --dry-run Print what would change; do not write the file
17254
17524
  -h, --help Show this help
@@ -17257,12 +17527,17 @@ Replaces only the targeted hyperlink. If multiple hyperlinks shared the same
17257
17527
  underlying relationship, a new relationship is allocated so the others are
17258
17528
  unaffected.
17259
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
+
17260
17534
  Examples:
17261
17535
  docx hyperlinks replace doc.docx --at link0 --with https://example.com
17262
17536
  `;
17263
17537
  var init_replace = __esm(() => {
17264
17538
  init_core();
17265
17539
  init_parser();
17540
+ init_helpers();
17266
17541
  init_respond();
17267
17542
  });
17268
17543
 
@@ -17522,6 +17797,7 @@ async function run18(args) {
17522
17797
  options: {
17523
17798
  at: { type: "string" },
17524
17799
  with: { type: "string" },
17800
+ author: { type: "string" },
17525
17801
  output: { type: "string", short: "o" },
17526
17802
  "dry-run": { type: "boolean" },
17527
17803
  help: { type: "boolean", short: "h" }
@@ -17577,6 +17853,7 @@ async function run18(args) {
17577
17853
  return EXIT.OK;
17578
17854
  }
17579
17855
  const bytes = new Uint8Array(await sourceFile.arrayBuffer());
17856
+ const originalMimeType = reference.contentType;
17580
17857
  if (newPartName === originalPartName) {
17581
17858
  view.pkg.writeBytes(originalPartName, bytes);
17582
17859
  } else {
@@ -17587,6 +17864,18 @@ async function run18(args) {
17587
17864
  reference.partName = newPartName;
17588
17865
  reference.contentType = newMimeType;
17589
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
+ }
17590
17879
  await saveDocView(view, outputPath);
17591
17880
  await respond({
17592
17881
  ok: true,
@@ -17599,6 +17888,33 @@ async function run18(args) {
17599
17888
  });
17600
17889
  return EXIT.OK;
17601
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
+ }
17602
17918
  function renameExtension(partName, newExtension) {
17603
17919
  const dotIndex = partName.lastIndexOf(".");
17604
17920
  const slashIndex = partName.lastIndexOf("/");
@@ -17645,6 +17961,8 @@ Required:
17645
17961
  --with PATH New image file (any image MIME type)
17646
17962
 
17647
17963
  Optional:
17964
+ --author NAME Author for the audit comment when track-changes is on
17965
+ (default: $DOCX_AUTHOR)
17648
17966
  -o, --output PATH Write to PATH instead of overwriting FILE
17649
17967
  --dry-run Print what would change; do not write the file
17650
17968
  -h, --help Show this help
@@ -17653,6 +17971,10 @@ If the replacement uses a different format from the original, the part is
17653
17971
  renamed (extension changes), the relationship Target is rewritten, and
17654
17972
  [Content_Types].xml gets a Default entry for the new extension if needed.
17655
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
+
17656
17978
  Examples:
17657
17979
  docx images replace doc.docx --at img2 --with ./new-photo.png
17658
17980
  docx images replace doc.docx --at img0 --with ./diagram.svg
@@ -17660,6 +17982,7 @@ Examples:
17660
17982
  var init_replace2 = __esm(() => {
17661
17983
  init_core();
17662
17984
  init_parser();
17985
+ init_helpers();
17663
17986
  init_respond();
17664
17987
  EXTENSION_BY_MIME = {
17665
17988
  "image/png": "png",
@@ -17721,6 +18044,8 @@ var types_default = `export type Doc = {
17721
18044
  properties: DocProperties;
17722
18045
  blocks: Block[];
17723
18046
  comments: Comment[];
18047
+ footnotes: Footnote[];
18048
+ endnotes: Footnote[];
17724
18049
  };
17725
18050
 
17726
18051
  export type DocProperties = {
@@ -17760,7 +18085,14 @@ export type SectionBreak = {
17760
18085
  type: "sectionBreak";
17761
18086
  };
17762
18087
 
17763
- export type Run = TextRun | ImageRun | BreakRun | TabRun;
18088
+ export type Run =
18089
+ | TextRun
18090
+ | ImageRun
18091
+ | BreakRun
18092
+ | TabRun
18093
+ | EquationRun
18094
+ | FootnoteRefRun
18095
+ | ChartRun;
17764
18096
 
17765
18097
  export type TextRun = {
17766
18098
  type: "text";
@@ -17804,6 +18136,37 @@ export type TabRun = {
17804
18136
  type: "tab";
17805
18137
  };
17806
18138
 
18139
+ /** A math equation surfaced as concatenated <m:t> plaintext. The structural
18140
+ * OOMath markup (subscripts, fractions, etc.) is collapsed to literal characters,
18141
+ * so the rendering is degraded. \`display\` distinguishes inline equations
18142
+ * (<m:oMath>) from block-level display equations (<m:oMathPara>). */
18143
+ export type EquationRun = {
18144
+ type: "equation";
18145
+ text: string;
18146
+ display: boolean;
18147
+ };
18148
+
18149
+ /** Reference to a footnote or endnote whose body lives in Doc.footnotes or
18150
+ * Doc.endnotes. The id matches the footnote/endnote id (\`fn1\`, \`en1\`). */
18151
+ export type FootnoteRefRun = {
18152
+ type: "footnoteRef";
18153
+ kind: "footnote" | "endnote";
18154
+ id: string;
18155
+ };
18156
+
18157
+ /** Placeholder for a non-picture drawing (chart, shape, SmartArt, etc.). The
18158
+ * full content is preserved in the underlying XML; the run is a marker so
18159
+ * callers can know "something visual lives here." */
18160
+ export type ChartRun = {
18161
+ type: "chart";
18162
+ kind: "chart" | "shape" | "smartart" | "drawing";
18163
+ };
18164
+
18165
+ export type Footnote = {
18166
+ id: string;
18167
+ text: string;
18168
+ };
18169
+
17807
18170
  export type TrackedChange = {
17808
18171
  kind: "ins" | "del";
17809
18172
  author: string;
@@ -17864,10 +18227,10 @@ async function run20(args) {
17864
18227
  await respond(JSON_SCHEMA);
17865
18228
  return EXIT.OK;
17866
18229
  }
17867
- var HELP20 = `docx schema \u2014 print the AST type definitions
18230
+ var HELP20 = `docx info schema \u2014 print the AST type definitions
17868
18231
 
17869
18232
  Usage:
17870
- docx schema [options]
18233
+ docx info schema [options]
17871
18234
 
17872
18235
  Options:
17873
18236
  --json Print as a JSON Schema document (default)
@@ -17875,8 +18238,8 @@ Options:
17875
18238
  -h, --help Show this help
17876
18239
 
17877
18240
  Examples:
17878
- docx schema | jq '.definitions.Run'
17879
- docx schema --ts > ast.d.ts
18241
+ docx info schema | jq '.$defs.Run'
18242
+ docx info schema --ts > ast.d.ts
17880
18243
  `, JSON_SCHEMA;
17881
18244
  var init_schema = __esm(() => {
17882
18245
  init_types();
@@ -17886,7 +18249,15 @@ var init_schema = __esm(() => {
17886
18249
  $id: "https://github.com/kklimuk/docx-cli/schema",
17887
18250
  title: "docx-cli AST",
17888
18251
  type: "object",
17889
- required: ["schemaVersion", "path", "properties", "blocks", "comments"],
18252
+ required: [
18253
+ "schemaVersion",
18254
+ "path",
18255
+ "properties",
18256
+ "blocks",
18257
+ "comments",
18258
+ "footnotes",
18259
+ "endnotes"
18260
+ ],
17890
18261
  properties: {
17891
18262
  schemaVersion: { const: 1 },
17892
18263
  path: { type: "string" },
@@ -17900,7 +18271,9 @@ var init_schema = __esm(() => {
17900
18271
  }
17901
18272
  },
17902
18273
  blocks: { type: "array", items: { $ref: "#/$defs/Block" } },
17903
- comments: { type: "array", items: { $ref: "#/$defs/Comment" } }
18274
+ comments: { type: "array", items: { $ref: "#/$defs/Comment" } },
18275
+ footnotes: { type: "array", items: { $ref: "#/$defs/Footnote" } },
18276
+ endnotes: { type: "array", items: { $ref: "#/$defs/Footnote" } }
17904
18277
  },
17905
18278
  $defs: {
17906
18279
  Block: {
@@ -17934,7 +18307,10 @@ var init_schema = __esm(() => {
17934
18307
  { $ref: "#/$defs/TextRun" },
17935
18308
  { $ref: "#/$defs/ImageRun" },
17936
18309
  { $ref: "#/$defs/BreakRun" },
17937
- { $ref: "#/$defs/TabRun" }
18310
+ { $ref: "#/$defs/TabRun" },
18311
+ { $ref: "#/$defs/EquationRun" },
18312
+ { $ref: "#/$defs/FootnoteRefRun" },
18313
+ { $ref: "#/$defs/ChartRun" }
17938
18314
  ]
17939
18315
  },
17940
18316
  TextRun: {
@@ -18001,6 +18377,40 @@ var init_schema = __esm(() => {
18001
18377
  required: ["type"],
18002
18378
  properties: { type: { const: "tab" } }
18003
18379
  },
18380
+ EquationRun: {
18381
+ type: "object",
18382
+ required: ["type", "text", "display"],
18383
+ properties: {
18384
+ type: { const: "equation" },
18385
+ text: { type: "string" },
18386
+ display: { type: "boolean" }
18387
+ }
18388
+ },
18389
+ FootnoteRefRun: {
18390
+ type: "object",
18391
+ required: ["type", "kind", "id"],
18392
+ properties: {
18393
+ type: { const: "footnoteRef" },
18394
+ kind: { enum: ["footnote", "endnote"] },
18395
+ id: { type: "string" }
18396
+ }
18397
+ },
18398
+ ChartRun: {
18399
+ type: "object",
18400
+ required: ["type", "kind"],
18401
+ properties: {
18402
+ type: { const: "chart" },
18403
+ kind: { enum: ["chart", "shape", "smartart", "drawing"] }
18404
+ }
18405
+ },
18406
+ Footnote: {
18407
+ type: "object",
18408
+ required: ["id", "text"],
18409
+ properties: {
18410
+ id: { type: "string" },
18411
+ text: { type: "string" }
18412
+ }
18413
+ },
18004
18414
  Table: {
18005
18415
  type: "object",
18006
18416
  required: ["id", "type", "rows"],
@@ -18097,14 +18507,18 @@ async function run21(args) {
18097
18507
  await writeStdout(REFERENCE);
18098
18508
  return EXIT.OK;
18099
18509
  }
18100
- var HELP21 = `docx locators \u2014 print the locator grammar reference
18510
+ var HELP21 = `docx info locators \u2014 print the locator grammar reference
18101
18511
 
18102
18512
  Usage:
18103
- docx locators [options]
18513
+ docx info locators [options]
18104
18514
 
18105
18515
  Options:
18106
18516
  --json Print as JSON
18107
18517
  -h, --help Show this help
18518
+
18519
+ Examples:
18520
+ docx info locators
18521
+ docx info locators --json | jq '.entityLocators'
18108
18522
  `, REFERENCE = `LOCATOR GRAMMAR
18109
18523
 
18110
18524
  Block locators:
@@ -18237,6 +18651,7 @@ async function run23(args) {
18237
18651
  bold: { type: "boolean" },
18238
18652
  italic: { type: "boolean" },
18239
18653
  url: { type: "string" },
18654
+ author: { type: "string" },
18240
18655
  output: { type: "string", short: "o" },
18241
18656
  "dry-run": { type: "boolean" },
18242
18657
  help: { type: "boolean", short: "h" }
@@ -18325,7 +18740,7 @@ async function run23(args) {
18325
18740
  }
18326
18741
  const insertIndex = after !== undefined ? targetIndex + 1 : targetIndex;
18327
18742
  if (isTrackChangesEnabled(view)) {
18328
- applyTrackedInsertion(paragraphNode, view);
18743
+ applyTrackedInsertion(paragraphNode, view, parsed.values.author);
18329
18744
  }
18330
18745
  const outputPath = parsed.values.output;
18331
18746
  if (parsed.values["dry-run"]) {
@@ -18374,9 +18789,9 @@ function wrapFirstRunInHyperlink(view, paragraph, url) {
18374
18789
  }
18375
18790
  paragraph.children = newChildren;
18376
18791
  }
18377
- function applyTrackedInsertion(paragraph, view) {
18792
+ function applyTrackedInsertion(paragraph, view, authorFlag) {
18378
18793
  const allocator = createRevisionAllocator(view);
18379
- const baseMeta = { author: resolveAuthor(), date: resolveDate() };
18794
+ const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
18380
18795
  const mintMeta = () => ({
18381
18796
  ...baseMeta,
18382
18797
  revisionId: allocator.next()
@@ -18427,6 +18842,7 @@ Run options (only with --text):
18427
18842
  --italic Italic
18428
18843
  --url URL Wrap the inserted text in a hyperlink to URL
18429
18844
 
18845
+ --author NAME Author for tracked changes (default: $DOCX_AUTHOR)
18430
18846
  -o, --output PATH Write to PATH instead of overwriting FILE
18431
18847
  --dry-run Print what would be inserted; do not write the file
18432
18848
  -h, --help Show this help
@@ -18500,14 +18916,9 @@ function* headingParagraphs(blocks, stylePrefix) {
18500
18916
  yield block;
18501
18917
  }
18502
18918
  }
18503
- function paragraphText(paragraph) {
18504
- let out = "";
18505
- for (const run24 of paragraph.runs) {
18506
- if (run24.type === "text")
18507
- out += run24.text;
18508
- }
18509
- return out;
18510
- }
18919
+ var init_build = __esm(() => {
18920
+ init_core();
18921
+ });
18511
18922
 
18512
18923
  // src/cli/outline/index.ts
18513
18924
  var exports_outline = {};
@@ -18579,6 +18990,425 @@ Examples:
18579
18990
  `;
18580
18991
  var init_outline = __esm(() => {
18581
18992
  init_respond();
18993
+ init_build();
18994
+ });
18995
+
18996
+ // src/cli/read/markdown.ts
18997
+ function renderMarkdown(doc, options = {}) {
18998
+ const blocks = sliceBlocks(doc.blocks, options.from, options.to);
18999
+ const commentIndex = options.showComments ? buildCommentIndex(blocks, options) : emptyCommentIndex();
19000
+ const ctx = {
19001
+ options,
19002
+ commentIndex,
19003
+ referencedFootnoteIds: new Set,
19004
+ referencedEndnoteIds: new Set
19005
+ };
19006
+ const parts = [];
19007
+ for (const block of blocks) {
19008
+ const rendered = renderBlock(block, ctx);
19009
+ if (rendered !== null)
19010
+ parts.push(rendered);
19011
+ }
19012
+ const definitions = [];
19013
+ if (options.showComments) {
19014
+ const commentFootnotes = renderCommentFootnotes(commentIndex, doc.comments);
19015
+ if (commentFootnotes.length > 0)
19016
+ definitions.push(commentFootnotes);
19017
+ }
19018
+ const footnoteDefs = renderNoteDefinitions(doc.footnotes, ctx.referencedFootnoteIds);
19019
+ if (footnoteDefs.length > 0)
19020
+ definitions.push(footnoteDefs);
19021
+ const endnoteDefs = renderNoteDefinitions(doc.endnotes, ctx.referencedEndnoteIds);
19022
+ if (endnoteDefs.length > 0)
19023
+ definitions.push(endnoteDefs);
19024
+ if (definitions.length > 0)
19025
+ parts.push(definitions.join(`
19026
+ `));
19027
+ if (parts.length === 0)
19028
+ return "";
19029
+ return `${parts.join(`
19030
+
19031
+ `)}
19032
+ `;
19033
+ }
19034
+ function emptyCommentIndex() {
19035
+ return {
19036
+ endingsByRun: new Map,
19037
+ spanText: new Map,
19038
+ orderedIds: []
19039
+ };
19040
+ }
19041
+ function buildCommentIndex(blocks, options) {
19042
+ const showChanges = options.showChanges ?? false;
19043
+ const lastSlot = new Map;
19044
+ const spanText = new Map;
19045
+ const orderedIds = [];
19046
+ for (const paragraph of flattenParagraphs(blocks)) {
19047
+ paragraph.runs.forEach((run25, index) => {
19048
+ if (run25.type !== "text")
19049
+ return;
19050
+ if (!showChanges && run25.trackedChange?.kind === "del")
19051
+ return;
19052
+ for (const commentId of run25.comments ?? []) {
19053
+ if (!spanText.has(commentId))
19054
+ orderedIds.push(commentId);
19055
+ spanText.set(commentId, (spanText.get(commentId) ?? "") + run25.text);
19056
+ lastSlot.set(commentId, slotKey(paragraph.id, index));
19057
+ }
19058
+ });
19059
+ }
19060
+ const endingsByRun = new Map;
19061
+ for (const commentId of orderedIds) {
19062
+ const slot = lastSlot.get(commentId);
19063
+ if (!slot)
19064
+ continue;
19065
+ const list = endingsByRun.get(slot) ?? [];
19066
+ list.push(commentId);
19067
+ endingsByRun.set(slot, list);
19068
+ }
19069
+ return { endingsByRun, spanText, orderedIds };
19070
+ }
19071
+ function slotKey(paragraphId, runIndex) {
19072
+ return `${paragraphId}#${runIndex}`;
19073
+ }
19074
+ function renderBlock(block, ctx) {
19075
+ if (block.type === "paragraph")
19076
+ return renderParagraph(block, ctx);
19077
+ if (block.type === "table")
19078
+ return renderTable(block, ctx);
19079
+ if (block.type === "sectionBreak")
19080
+ return `--- <!-- ${block.id} -->`;
19081
+ return null;
19082
+ }
19083
+ function renderParagraph(paragraph, ctx) {
19084
+ const body = renderRuns(paragraph.id, paragraph.runs, ctx);
19085
+ if (body.length === 0)
19086
+ return null;
19087
+ const prefix = paragraphPrefix(paragraph);
19088
+ return `${prefix}${body} <!-- ${paragraph.id} -->`;
19089
+ }
19090
+ function paragraphPrefix(paragraph) {
19091
+ const headingLevel2 = headingLevelFor(paragraph.style);
19092
+ if (headingLevel2 !== null)
19093
+ return `${"#".repeat(headingLevel2)} `;
19094
+ if (paragraph.list) {
19095
+ const indent = " ".repeat(paragraph.list.level);
19096
+ return `${indent}- `;
19097
+ }
19098
+ return "";
19099
+ }
19100
+ function headingLevelFor(style) {
19101
+ if (!style)
19102
+ return null;
19103
+ if (!style.startsWith("Heading"))
19104
+ return null;
19105
+ const remainder = style.slice("Heading".length).trim();
19106
+ if (remainder === "")
19107
+ return 1;
19108
+ const parsed = Number(remainder);
19109
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 6)
19110
+ return null;
19111
+ return parsed;
19112
+ }
19113
+ function renderRuns(paragraphId, runs, ctx) {
19114
+ const showChanges = ctx.options.showChanges ?? false;
19115
+ const visibleEntries = [];
19116
+ runs.forEach((run25, index) => {
19117
+ if (!showChanges && run25.type === "text" && run25.trackedChange?.kind === "del") {
19118
+ return;
19119
+ }
19120
+ visibleEntries.push({ run: run25, originalIndex: index });
19121
+ });
19122
+ let out = "";
19123
+ let cursor = 0;
19124
+ while (cursor < visibleEntries.length) {
19125
+ const entry = visibleEntries[cursor];
19126
+ if (!entry) {
19127
+ cursor++;
19128
+ continue;
19129
+ }
19130
+ const { run: run25 } = entry;
19131
+ if (run25.type === "text") {
19132
+ let lookahead = cursor + 1;
19133
+ while (lookahead < visibleEntries.length) {
19134
+ const next = visibleEntries[lookahead];
19135
+ if (!next || next.run.type !== "text")
19136
+ break;
19137
+ if (!sameDecoration(run25, next.run))
19138
+ break;
19139
+ lookahead++;
19140
+ }
19141
+ const segment = visibleEntries.slice(cursor, lookahead);
19142
+ out += renderTextSegment(segment.map((entry2) => entry2.run), showChanges);
19143
+ out += commentEndingsFor(paragraphId, segment, ctx.commentIndex);
19144
+ cursor = lookahead;
19145
+ continue;
19146
+ }
19147
+ if (run25.type === "image") {
19148
+ const alt = sanitizeAltText(run25.alt ?? run25.id);
19149
+ out += `![${alt}](${run25.id})`;
19150
+ } else if (run25.type === "break") {
19151
+ if (run25.kind === "line")
19152
+ out += "<br>";
19153
+ } else if (run25.type === "tab") {
19154
+ out += "\t";
19155
+ } else if (run25.type === "equation") {
19156
+ const escaped = run25.text.replace(/`/g, "\\`");
19157
+ out += `\`equation: ${escaped}\``;
19158
+ } else if (run25.type === "footnoteRef") {
19159
+ if (run25.kind === "footnote")
19160
+ ctx.referencedFootnoteIds.add(run25.id);
19161
+ else
19162
+ ctx.referencedEndnoteIds.add(run25.id);
19163
+ out += `[^${run25.id}]`;
19164
+ } else if (run25.type === "chart") {
19165
+ out += `\`[${run25.kind}]\``;
19166
+ }
19167
+ cursor++;
19168
+ }
19169
+ return out;
19170
+ }
19171
+ function commentEndingsFor(paragraphId, segment, commentIndex) {
19172
+ if (commentIndex.endingsByRun.size === 0)
19173
+ return "";
19174
+ let out = "";
19175
+ for (const entry of segment) {
19176
+ const ids = commentIndex.endingsByRun.get(slotKey(paragraphId, entry.originalIndex));
19177
+ if (!ids)
19178
+ continue;
19179
+ for (const commentId of ids)
19180
+ out += `[^${commentId}]`;
19181
+ }
19182
+ return out;
19183
+ }
19184
+ function sameDecoration(a2, b) {
19185
+ return (a2.bold ?? false) === (b.bold ?? false) && (a2.italic ?? false) === (b.italic ?? false) && (a2.strike ?? false) === (b.strike ?? false) && (a2.underline ?? "") === (b.underline ?? "") && (a2.color ?? "") === (b.color ?? "") && (a2.highlight ?? "") === (b.highlight ?? "") && a2.hyperlink?.id === b.hyperlink?.id && a2.trackedChange?.kind === b.trackedChange?.kind && sameCommentSet(a2.comments, b.comments);
19186
+ }
19187
+ function sameCommentSet(left, right) {
19188
+ const a2 = left ?? [];
19189
+ const b = right ?? [];
19190
+ if (a2.length !== b.length)
19191
+ return false;
19192
+ for (let i = 0;i < a2.length; i++) {
19193
+ if (a2[i] !== b[i])
19194
+ return false;
19195
+ }
19196
+ return true;
19197
+ }
19198
+ function renderTextSegment(runs, showChanges) {
19199
+ const text = runs.map((run25) => run25.text).join("");
19200
+ if (text.length === 0)
19201
+ return "";
19202
+ const first = runs[0];
19203
+ if (!first)
19204
+ return "";
19205
+ let out = text;
19206
+ if (first.bold)
19207
+ out = `**${out}**`;
19208
+ if (first.italic)
19209
+ out = `*${out}*`;
19210
+ if (first.strike)
19211
+ out = `~~${out}~~`;
19212
+ if (first.underline)
19213
+ out = `<u>${out}</u>`;
19214
+ const color = colorAttrFor(first.color);
19215
+ if (color)
19216
+ out = `<span style="color:${color}">${out}</span>`;
19217
+ const highlight = highlightCssFor(first.highlight);
19218
+ if (highlight)
19219
+ out = `<span style="background-color:${highlight}">${out}</span>`;
19220
+ if (first.hyperlink) {
19221
+ const target = first.hyperlink.url ?? `#${first.hyperlink.anchor ?? ""}`;
19222
+ out = `[${out}](${target})`;
19223
+ }
19224
+ if (showChanges && first.trackedChange) {
19225
+ out = `<${first.trackedChange.kind}>${out}</${first.trackedChange.kind}>`;
19226
+ }
19227
+ return out;
19228
+ }
19229
+ function colorAttrFor(value) {
19230
+ if (!value)
19231
+ return null;
19232
+ const lowered = value.toLowerCase();
19233
+ if (lowered === "auto")
19234
+ return null;
19235
+ if (/^[0-9a-f]{6}$/.test(lowered))
19236
+ return `#${lowered}`;
19237
+ return value;
19238
+ }
19239
+ function highlightCssFor(value) {
19240
+ if (!value)
19241
+ return null;
19242
+ if (value === "none")
19243
+ return null;
19244
+ return value.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
19245
+ }
19246
+ function renderTable(table, ctx) {
19247
+ if (table.rows.length === 0)
19248
+ return null;
19249
+ const colCount = Math.max(...table.rows.map((row) => row.cells.length));
19250
+ if (colCount === 0)
19251
+ return null;
19252
+ const renderedRows = table.rows.map((row) => {
19253
+ const cells = [];
19254
+ for (let columnIndex = 0;columnIndex < colCount; columnIndex++) {
19255
+ const cell = row.cells[columnIndex];
19256
+ cells.push(cell ? renderCell(cell, ctx) : "");
19257
+ }
19258
+ return cells;
19259
+ });
19260
+ const lines = [];
19261
+ const headerRow = renderedRows[0];
19262
+ if (!headerRow)
19263
+ return null;
19264
+ lines.push(rowToLine(headerRow));
19265
+ lines.push(`| ${Array(colCount).fill("---").join(" | ")} |`);
19266
+ for (let rowIndex = 1;rowIndex < renderedRows.length; rowIndex++) {
19267
+ const row = renderedRows[rowIndex];
19268
+ if (row)
19269
+ lines.push(rowToLine(row));
19270
+ }
19271
+ return lines.join(`
19272
+ `);
19273
+ }
19274
+ function rowToLine(cells) {
19275
+ return `| ${cells.join(" | ")} |`;
19276
+ }
19277
+ function renderCell(cell, ctx) {
19278
+ const parts = [];
19279
+ for (const block of cell.blocks) {
19280
+ if (block.type === "paragraph") {
19281
+ const body = renderRuns(block.id, block.runs, ctx);
19282
+ if (body.length === 0)
19283
+ continue;
19284
+ parts.push(`${body} <!-- ${block.id} -->`);
19285
+ continue;
19286
+ }
19287
+ if (block.type === "table") {
19288
+ parts.push(renderNestedTable(block, ctx));
19289
+ }
19290
+ }
19291
+ return escapeCell(parts.join("<br>"));
19292
+ }
19293
+ function renderNestedTable(table, ctx) {
19294
+ const rows = table.rows.map((row) => row.cells.map((cell) => renderCell(cell, ctx)).join(" / "));
19295
+ return rows.join(" // ");
19296
+ }
19297
+ function escapeCell(text) {
19298
+ return text.replace(/\|/g, "\\|");
19299
+ }
19300
+ function sanitizeAltText(text) {
19301
+ return text.replace(/[\r\n]+/g, " ").replace(/\]/g, "\\]");
19302
+ }
19303
+ function renderCommentFootnotes(commentIndex, comments) {
19304
+ if (commentIndex.orderedIds.length === 0)
19305
+ return "";
19306
+ const byId = new Map(comments.map((comment) => [comment.id, comment]));
19307
+ const sorted = [...commentIndex.orderedIds].sort(commentIdCompare);
19308
+ const lines = [];
19309
+ for (const commentId of sorted) {
19310
+ const comment = byId.get(commentId);
19311
+ if (!comment)
19312
+ continue;
19313
+ const span = commentIndex.spanText.get(commentId) ?? "";
19314
+ lines.push(formatFootnote(comment, span));
19315
+ }
19316
+ return lines.join(`
19317
+ `);
19318
+ }
19319
+ function renderNoteDefinitions(notes, referenced) {
19320
+ if (referenced.size === 0)
19321
+ return "";
19322
+ const byId = new Map(notes.map((note) => [note.id, note]));
19323
+ const sorted = [...referenced].sort(noteIdCompare);
19324
+ const lines = [];
19325
+ for (const id of sorted) {
19326
+ const note = byId.get(id);
19327
+ const body = (note?.text ?? "").replace(/\s+/g, " ").trim();
19328
+ lines.push(`[^${id}]: ${body}`);
19329
+ }
19330
+ return lines.join(`
19331
+ `);
19332
+ }
19333
+ function noteIdCompare(left, right) {
19334
+ return numericIdCompare(left, right, /(\d+)$/);
19335
+ }
19336
+ function commentIdCompare(left, right) {
19337
+ return numericIdCompare(left, right, /^c(\d+)$/);
19338
+ }
19339
+ function numericIdCompare(left, right, pattern) {
19340
+ const leftMatch = left.match(pattern);
19341
+ const rightMatch = right.match(pattern);
19342
+ if (leftMatch?.[1] && rightMatch?.[1]) {
19343
+ return Number(leftMatch[1]) - Number(rightMatch[1]);
19344
+ }
19345
+ return left.localeCompare(right);
19346
+ }
19347
+ function formatFootnote(comment, spanText) {
19348
+ const quoted = quoteSpan(spanText);
19349
+ const reply = comment.parentId ? ` \u21B3 ${comment.parentId}` : "";
19350
+ const resolved = comment.resolved ? "\u2713 " : "";
19351
+ const body = comment.text.replace(/\s+/g, " ").trim();
19352
+ return `[^${comment.id}]: ${quoted} \u2014 ${resolved}${comment.author} (${comment.date})${reply}: ${body}`;
19353
+ }
19354
+ function quoteSpan(text) {
19355
+ const collapsed = text.replace(/\s+/g, " ").trim();
19356
+ const escaped = collapsed.replace(/"/g, "\\\"");
19357
+ return `"${escaped}"`;
19358
+ }
19359
+ function sliceBlocks(blocks, from, to) {
19360
+ if (!from && !to)
19361
+ return blocks;
19362
+ const fromId = from ? blockIdForLocator(from, "from") : null;
19363
+ const toId = to ? blockIdForLocator(to, "to") : null;
19364
+ const fromIndex = fromId ? blocks.findIndex((b) => b.id === fromId) : 0;
19365
+ if (from && fromId && fromIndex === -1) {
19366
+ throw new MarkdownLocatorError(from, `--from ${from} not found at document top level`);
19367
+ }
19368
+ const toIndex = toId ? blocks.findIndex((b) => b.id === toId) : blocks.length - 1;
19369
+ if (to && toId && toIndex === -1) {
19370
+ throw new MarkdownLocatorError(to, `--to ${to} not found at document top level`);
19371
+ }
19372
+ if (toIndex < fromIndex)
19373
+ return [];
19374
+ return blocks.slice(fromIndex, toIndex + 1);
19375
+ }
19376
+ function blockIdForLocator(input, position) {
19377
+ let parsed;
19378
+ try {
19379
+ parsed = parseLocator(input);
19380
+ } catch (err) {
19381
+ if (err instanceof LocatorParseError) {
19382
+ throw new MarkdownLocatorError(input, err.message);
19383
+ }
19384
+ throw err;
19385
+ }
19386
+ switch (parsed.kind) {
19387
+ case "block":
19388
+ return parsed.blockId;
19389
+ case "blockSpan":
19390
+ return parsed.blockId;
19391
+ case "range":
19392
+ return position === "from" ? parsed.start.blockId : parsed.end.blockId;
19393
+ case "cell":
19394
+ return parsed.tableId;
19395
+ case "comment":
19396
+ case "image":
19397
+ case "hyperlink":
19398
+ throw new MarkdownLocatorError(input, `--${position} does not accept a ${parsed.kind} locator \u2014 use a paragraph or table locator`);
19399
+ }
19400
+ }
19401
+ var MarkdownLocatorError;
19402
+ var init_markdown = __esm(() => {
19403
+ init_core();
19404
+ MarkdownLocatorError = class MarkdownLocatorError extends Error {
19405
+ input;
19406
+ constructor(input, message) {
19407
+ super(message);
19408
+ this.input = input;
19409
+ this.name = "MarkdownLocatorError";
19410
+ }
19411
+ };
18582
19412
  });
18583
19413
 
18584
19414
  // src/cli/read/index.ts
@@ -18594,7 +19424,11 @@ async function run25(args) {
18594
19424
  args,
18595
19425
  allowPositionals: true,
18596
19426
  options: {
18597
- text: { type: "boolean" },
19427
+ markdown: { type: "boolean" },
19428
+ from: { type: "string" },
19429
+ to: { type: "string" },
19430
+ changes: { type: "boolean" },
19431
+ comments: { type: "boolean" },
18598
19432
  help: { type: "boolean", short: "h" }
18599
19433
  }
18600
19434
  });
@@ -18608,29 +19442,72 @@ async function run25(args) {
18608
19442
  const path = parsed.positionals[0];
18609
19443
  if (!path)
18610
19444
  return fail("USAGE", "Missing FILE argument", HELP25);
19445
+ const markdown = Boolean(parsed.values.markdown);
19446
+ const from = parsed.values.from;
19447
+ const to = parsed.values.to;
19448
+ const showChanges = Boolean(parsed.values.changes);
19449
+ const showComments = Boolean(parsed.values.comments);
19450
+ if (!markdown && (from || to || showChanges || showComments)) {
19451
+ return fail("USAGE", "--from, --to, --changes, and --comments require --markdown", HELP25);
19452
+ }
18611
19453
  const view = await openOrFail(path);
18612
19454
  if (typeof view === "number")
18613
19455
  return view;
19456
+ if (markdown) {
19457
+ try {
19458
+ const rendered = renderMarkdown(view.doc, {
19459
+ from,
19460
+ to,
19461
+ showChanges,
19462
+ showComments
19463
+ });
19464
+ await writeStdout(rendered);
19465
+ return EXIT.OK;
19466
+ } catch (err) {
19467
+ if (err instanceof MarkdownLocatorError) {
19468
+ return fail("INVALID_LOCATOR", err.message);
19469
+ }
19470
+ throw err;
19471
+ }
19472
+ }
18614
19473
  await enrichImageHashes(view);
18615
19474
  await respond(view.doc);
18616
19475
  return EXIT.OK;
18617
19476
  }
18618
- var HELP25 = `docx read \u2014 print AST as JSON
19477
+ var HELP25 = `docx read \u2014 print AST as JSON, or render document body as Markdown
18619
19478
 
18620
19479
  Usage:
18621
19480
  docx read FILE [options]
18622
19481
 
18623
19482
  Options:
18624
- --text Render as human-readable text instead of JSON (NOT YET IMPLEMENTED)
18625
- -h, --help Show this help
19483
+ --markdown Render the body as GitHub-flavored Markdown
19484
+ (instead of JSON). Locators are emitted as
19485
+ <!-- pN --> HTML comments after each paragraph and
19486
+ inside cell content (invisible in rendered view,
19487
+ but parseable from raw markdown).
19488
+ --from LOC Start markdown rendering at top-level block LOC (inclusive)
19489
+ --to LOC End markdown rendering at top-level block LOC (inclusive)
19490
+ Accepts pN, tN, tN:rRcC[:pK[:S-E]], pN:S-E, pN:S-pM:E.
19491
+ Cell/span/range locators collapse to their enclosing
19492
+ top-level block (the table or paragraph).
19493
+ --changes With --markdown, render tracked insertions/deletions as
19494
+ <ins>/<del> instead of producing the accepted view.
19495
+ --comments With --markdown, append [^cN] after each commented span
19496
+ and emit a footnote definition for each comment at the
19497
+ end of the output (author, date, body).
19498
+ -h, --help Show this help
18626
19499
 
18627
19500
  Examples:
18628
19501
  docx read input.docx
19502
+ docx read input.docx --markdown
19503
+ docx read input.docx --markdown --from p3 --to p20
19504
+ docx read input.docx --markdown --changes --comments
18629
19505
  docx read input.docx | jq '.blocks[] | select(.type == "paragraph")'
18630
19506
  `;
18631
19507
  var init_read2 = __esm(() => {
18632
19508
  init_core();
18633
19509
  init_respond();
19510
+ init_markdown();
18634
19511
  });
18635
19512
 
18636
19513
  // src/cli/replace/replace-span.tsx
@@ -19014,6 +19891,7 @@ async function run26(args) {
19014
19891
  "ignore-case": { type: "boolean" },
19015
19892
  all: { type: "boolean" },
19016
19893
  limit: { type: "string" },
19894
+ author: { type: "string" },
19017
19895
  output: { type: "string", short: "o" },
19018
19896
  "dry-run": { type: "boolean" },
19019
19897
  help: { type: "boolean", short: "h" }
@@ -19112,8 +19990,9 @@ async function run26(args) {
19112
19990
  }
19113
19991
  return rightMatch.start - leftMatch.start;
19114
19992
  });
19993
+ const authorFlag = parsed.values.author;
19115
19994
  const tracked = isTrackChangesEnabled(view) ? {
19116
- meta: { author: resolveAuthor(), date: resolveDate() },
19995
+ meta: { author: resolveAuthor(authorFlag), date: resolveDate() },
19117
19996
  allocator: createRevisionAllocator(view)
19118
19997
  } : undefined;
19119
19998
  const regexFlags = ignoreCase ? "i" : "";
@@ -19147,6 +20026,7 @@ Options:
19147
20026
  --ignore-case case-insensitive match
19148
20027
  --all replace every match (default: just the first)
19149
20028
  --limit N replace at most N matches (in document order)
20029
+ --author NAME author for tracked changes (default: $DOCX_AUTHOR)
19150
20030
  -o, --output PATH write to PATH instead of overwriting FILE
19151
20031
  --dry-run report what would change without writing the file
19152
20032
  -h, --help show this help
@@ -19293,19 +20173,11 @@ function countWords(text) {
19293
20173
  const matches = text.match(/\S+/g);
19294
20174
  return matches?.length ?? 0;
19295
20175
  }
19296
- function paragraphText2(paragraph) {
19297
- let out = "";
19298
- for (const run28 of paragraph.runs) {
19299
- if (run28.type === "text")
19300
- out += run28.text;
19301
- }
19302
- return out;
19303
- }
19304
20176
  function countWordsInBlocks(blocks) {
19305
20177
  let total = 0;
19306
20178
  for (const block of blocks) {
19307
20179
  if (block.type === "paragraph") {
19308
- total += countWords(paragraphText2(block));
20180
+ total += countWords(paragraphText(block));
19309
20181
  } else if (block.type === "table") {
19310
20182
  for (const row of block.rows) {
19311
20183
  for (const cell of row.cells) {
@@ -19316,24 +20188,8 @@ function countWordsInBlocks(blocks) {
19316
20188
  }
19317
20189
  return total;
19318
20190
  }
19319
- function findBlockById(blocks, blockId) {
19320
- for (const block of blocks) {
19321
- if (block.id === blockId)
19322
- return block;
19323
- if (block.type === "table") {
19324
- for (const row of block.rows) {
19325
- for (const cell of row.cells) {
19326
- const inner = findBlockById(cell.blocks, blockId);
19327
- if (inner)
19328
- return inner;
19329
- }
19330
- }
19331
- }
19332
- }
19333
- return null;
19334
- }
19335
20191
  function countWordsInParagraphSpan(paragraph, start, end) {
19336
- const text = paragraphText2(paragraph);
20192
+ const text = paragraphText(paragraph);
19337
20193
  const clampedEnd = Math.min(Math.max(end, 0), text.length);
19338
20194
  const clampedStart = Math.min(Math.max(start, 0), clampedEnd);
19339
20195
  return countWords(text.slice(clampedStart, clampedEnd));
@@ -19354,35 +20210,21 @@ function countWordsInRange(paragraphsInOrder, startBlockId, startOffset, endBloc
19354
20210
  let total = 0;
19355
20211
  const first = paragraphsInOrder[startIndex];
19356
20212
  if (first) {
19357
- total += countWordsInParagraphSpan(first, startOffset, paragraphText2(first).length);
20213
+ total += countWordsInParagraphSpan(first, startOffset, paragraphText(first).length);
19358
20214
  }
19359
20215
  for (let index = startIndex + 1;index < endIndex; index++) {
19360
20216
  const middle = paragraphsInOrder[index];
19361
20217
  if (middle)
19362
- total += countWords(paragraphText2(middle));
20218
+ total += countWords(paragraphText(middle));
19363
20219
  }
19364
20220
  const last = paragraphsInOrder[endIndex];
19365
20221
  if (last)
19366
20222
  total += countWordsInParagraphSpan(last, 0, endOffset);
19367
20223
  return total;
19368
20224
  }
19369
- function flattenParagraphs(blocks) {
19370
- const out = [];
19371
- for (const block of blocks) {
19372
- if (block.type === "paragraph") {
19373
- out.push(block);
19374
- continue;
19375
- }
19376
- if (block.type === "table") {
19377
- for (const row of block.rows) {
19378
- for (const cell of row.cells) {
19379
- out.push(...flattenParagraphs(cell.blocks));
19380
- }
19381
- }
19382
- }
19383
- }
19384
- return out;
19385
- }
20225
+ var init_count = __esm(() => {
20226
+ init_core();
20227
+ });
19386
20228
 
19387
20229
  // src/cli/wc/index.ts
19388
20230
  var exports_wc = {};
@@ -19443,7 +20285,7 @@ async function run28(args) {
19443
20285
  if (!block) {
19444
20286
  return fail("BLOCK_NOT_FOUND", `Block not found: ${locator.blockId}`);
19445
20287
  }
19446
- const words = block.type === "paragraph" ? countWords(paragraphText2(block)) : block.type === "table" ? countWordsInBlocks([block]) : 0;
20288
+ const words = block.type === "paragraph" ? countWords(paragraphText(block)) : block.type === "table" ? countWordsInBlocks([block]) : 0;
19447
20289
  await respond({
19448
20290
  ok: true,
19449
20291
  operation: "wc",
@@ -19515,7 +20357,7 @@ async function run28(args) {
19515
20357
  if (!block || block.type !== "paragraph") {
19516
20358
  return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${composedId}`);
19517
20359
  }
19518
- const words = locator.inner.kind === "blockSpan" ? countWordsInParagraphSpan(block, locator.inner.start, locator.inner.end) : countWords(paragraphText2(block));
20360
+ const words = locator.inner.kind === "blockSpan" ? countWordsInParagraphSpan(block, locator.inner.start, locator.inner.end) : countWords(paragraphText(block));
19519
20361
  await respond({
19520
20362
  ok: true,
19521
20363
  operation: "wc",
@@ -19573,11 +20415,12 @@ Examples:
19573
20415
  var init_wc = __esm(() => {
19574
20416
  init_core();
19575
20417
  init_respond();
20418
+ init_count();
19576
20419
  });
19577
20420
  // package.json
19578
20421
  var package_default = {
19579
20422
  name: "bun-docx",
19580
- version: "0.5.0",
20423
+ version: "0.6.1",
19581
20424
  description: "CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity.",
19582
20425
  keywords: [
19583
20426
  "docx",
@@ -19656,7 +20499,7 @@ Commands:
19656
20499
  outline FILE List headings as a hierarchical tree
19657
20500
  comments \u2026 Add, reply, resolve, delete, list comments
19658
20501
  images \u2026 Extract, replace, list images
19659
- hyperlinks \u2026 List, replace hyperlinks
20502
+ hyperlinks \u2026 Add, list, replace, delete hyperlinks
19660
20503
  track-changes FILE on|off Toggle tracked-changes mode
19661
20504
  info \u2026 Reference material (schema, locators)
19662
20505