@drghaliasri/butex 5.6.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/document2.js CHANGED
@@ -28,6 +28,7 @@ __export(document2_exports, {
28
28
  DEFAULT_DOCUMENT2_HISTORY_MAX_DEPTH: () => DEFAULT_DOCUMENT2_HISTORY_MAX_DEPTH,
29
29
  DEFAULT_HIJRI_DATE: () => DEFAULT_HIJRI_DATE,
30
30
  DEFAULT_REFERENCE_FIELD_SEPARATOR: () => DEFAULT_REFERENCE_FIELD_SEPARATOR,
31
+ Document2CommandError: () => Document2CommandError,
31
32
  HIJRI_MONTH_IDS: () => HIJRI_MONTH_IDS,
32
33
  HIJRI_YEAR_MAX: () => HIJRI_YEAR_MAX,
33
34
  HIJRI_YEAR_MIN: () => HIJRI_YEAR_MIN,
@@ -38,6 +39,7 @@ __export(document2_exports, {
38
39
  addDocument2Reference: () => addDocument2Reference,
39
40
  addDocument2TableBlock: () => addDocument2TableBlock,
40
41
  addDocument2TextBlock: () => addDocument2TextBlock,
42
+ applyDocument2Command: () => applyDocument2Command,
41
43
  applyFloatMetaPatch: () => applyFloatMetaPatch,
42
44
  arabicXeLatexPreambleCmd: () => arabicXeLatexPreambleCmd,
43
45
  arabicXeLatexPreambleFont: () => arabicXeLatexPreambleFont,
@@ -53,6 +55,7 @@ __export(document2_exports, {
53
55
  butexMaghribiDisplayLatex: () => butexMaghribiDisplayLatex,
54
56
  butexMaghribiDisplayTex: () => butexMaghribiDisplayTex,
55
57
  citeTokenLatex: () => citeTokenLatex,
58
+ clearDocument2ImageAsset: () => clearDocument2ImageAsset,
56
59
  cloneDocument2Meta: () => cloneDocument2Meta,
57
60
  cloneDocument2Node: () => cloneDocument2Node,
58
61
  collectDocument2Labels: () => collectDocument2Labels,
@@ -72,6 +75,7 @@ __export(document2_exports, {
72
75
  document2HistoryCanUndo: () => document2HistoryCanUndo,
73
76
  document2KeyError: () => document2KeyError,
74
77
  document2Latex: () => document2Latex,
78
+ document2Outline: () => document2Outline,
75
79
  document2Preview: () => document2Preview,
76
80
  emptyBlockSelection: () => emptyBlockSelection,
77
81
  emptyDocument2Meta: () => emptyDocument2Meta,
@@ -138,6 +142,7 @@ __export(document2_exports, {
138
142
  toggleBlockInSelection: () => toggleBlockInSelection,
139
143
  toggleTextTokenStyle: () => toggleTextTokenStyle,
140
144
  updateCiteTokenKeys: () => updateCiteTokenKeys,
145
+ updateDocument2ImageAsset: () => updateDocument2ImageAsset,
141
146
  updateDocument2ImageMeta: () => updateDocument2ImageMeta,
142
147
  updateDocument2ImageValue: () => updateDocument2ImageValue,
143
148
  updateDocument2Meta: () => updateDocument2Meta,
@@ -152,8 +157,9 @@ module.exports = __toCommonJS(document2_exports);
152
157
 
153
158
  // src/document2/ids.ts
154
159
  var nextId = 1;
160
+ var instanceId = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
155
161
  function document2Id(prefix) {
156
- const id = `${prefix}_${String(nextId)}`;
162
+ const id = `${prefix}_${instanceId}_${String(nextId)}`;
157
163
  nextId += 1;
158
164
  return id;
159
165
  }
@@ -1329,6 +1335,19 @@ function asBlockJson(value) {
1329
1335
  }
1330
1336
  return value;
1331
1337
  }
1338
+ function blockIdFromJson(json, state) {
1339
+ const imported = typeof json.id === "string" ? json.id.trim() : "";
1340
+ if (imported.length > 0 && !state.used.has(imported)) {
1341
+ state.used.add(imported);
1342
+ return imported;
1343
+ }
1344
+ let generated = document2Id("block");
1345
+ while (state.used.has(generated)) {
1346
+ generated = document2Id("block");
1347
+ }
1348
+ state.used.add(generated);
1349
+ return generated;
1350
+ }
1332
1351
  function pushDiagnostic(diagnostics, options, path, message) {
1333
1352
  if (options.strict) {
1334
1353
  throw new Error(message);
@@ -1510,40 +1529,44 @@ function pushFormattedTextTokens(tokens, text, sourceStart, formats) {
1510
1529
  function closingForList(command) {
1511
1530
  return command === "\\begin{itemize}" ? "\\end{itemize}" : "\\end{enumerate}";
1512
1531
  }
1513
- function parseTextBlock(json, options, path, diagnostics) {
1532
+ function parseTextBlock(json, options, path, diagnostics, blockIds) {
1514
1533
  const value = requireString(json.value, `${json.command} requires string value`);
1515
1534
  return {
1516
- id: document2Id("block"),
1535
+ id: blockIdFromJson(json, blockIds),
1517
1536
  kind: "textBlock",
1518
1537
  command: json.command,
1519
1538
  field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1520
1539
  ...json.centered === true ? { centered: true } : {}
1521
1540
  };
1522
1541
  }
1523
- function parseListItem(json, options, path, diagnostics) {
1542
+ function parseListItem(json, options, path, diagnostics, blockIds) {
1524
1543
  const value = requireString(json.value, "Document list item requires string value");
1525
1544
  const blocksJson = Array.isArray(json.blocks) ? json.blocks : [];
1526
1545
  return {
1527
1546
  id: document2Id("item"),
1528
1547
  field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1529
- blocks: blocksJson.map((block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics))
1548
+ blocks: blocksJson.map(
1549
+ (block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics, blockIds)
1550
+ )
1530
1551
  };
1531
1552
  }
1532
- function parseListBlock(json, options, path, diagnostics) {
1553
+ function parseListBlock(json, options, path, diagnostics, blockIds) {
1533
1554
  if (!Array.isArray(json.items)) {
1534
1555
  throw new Error(`${json.command} requires items array`);
1535
1556
  }
1536
1557
  const command = json.command;
1537
1558
  const closing = closingForList(command);
1538
1559
  return {
1539
- id: document2Id("block"),
1560
+ id: blockIdFromJson(json, blockIds),
1540
1561
  kind: "list",
1541
1562
  command,
1542
1563
  closing,
1543
- items: json.items.map((item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics))
1564
+ items: json.items.map(
1565
+ (item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics, blockIds)
1566
+ )
1544
1567
  };
1545
1568
  }
1546
- function parseTableBlock(json, options, path, diagnostics) {
1569
+ function parseTableBlock(json, options, path, diagnostics, blockIds) {
1547
1570
  if (!Array.isArray(json.rows)) {
1548
1571
  throw new Error("\\begin{tabular} requires rows array");
1549
1572
  }
@@ -1566,7 +1589,7 @@ function parseTableBlock(json, options, path, diagnostics) {
1566
1589
  pushDiagnostic(diagnostics, options, path, `math_objects count mismatch: detected ${String(mathObjectIndex)}, got ${String(mathObjects.length)}`);
1567
1590
  }
1568
1591
  return {
1569
- id: document2Id("block"),
1592
+ id: blockIdFromJson(json, blockIds),
1570
1593
  kind: "table",
1571
1594
  command: "\\begin{tabular}",
1572
1595
  closing: "\\end{tabular}",
@@ -1575,11 +1598,11 @@ function parseTableBlock(json, options, path, diagnostics) {
1575
1598
  ...parseFloatMetaFromJson(json)
1576
1599
  };
1577
1600
  }
1578
- function parseImageBlock(json) {
1601
+ function parseImageBlock(json, blockIds) {
1579
1602
  const assetId = typeof json.asset_id === "string" && json.asset_id.length > 0 ? json.asset_id : void 0;
1580
1603
  const value = assetId !== void 0 ? typeof json.value === "string" ? json.value : "" : requireString(json.value, "\\includegraphics requires string value");
1581
1604
  return {
1582
- id: document2Id("block"),
1605
+ id: blockIdFromJson(json, blockIds),
1583
1606
  kind: "image",
1584
1607
  command: "\\includegraphics",
1585
1608
  value,
@@ -1588,43 +1611,43 @@ function parseImageBlock(json) {
1588
1611
  ...parseFloatMetaFromJson(json)
1589
1612
  };
1590
1613
  }
1591
- function parseRawBlock(json) {
1614
+ function parseRawBlock(json, blockIds) {
1592
1615
  return {
1593
- id: document2Id("block"),
1616
+ id: blockIdFromJson(json, blockIds),
1594
1617
  kind: "raw",
1595
1618
  command: "\\raw",
1596
1619
  value: typeof json.value === "string" ? json.value : ""
1597
1620
  };
1598
1621
  }
1599
- function parseBibliographyBlock() {
1622
+ function parseBibliographyBlock(json, blockIds) {
1600
1623
  return {
1601
- id: document2Id("block"),
1624
+ id: blockIdFromJson(json, blockIds),
1602
1625
  kind: "bibliography",
1603
1626
  command: "\\begin{thebibliography}",
1604
1627
  closing: "\\end{thebibliography}"
1605
1628
  };
1606
1629
  }
1607
- function parseBlock(json, options, path, diagnostics) {
1630
+ function parseBlock(json, options, path, diagnostics, blockIds) {
1608
1631
  if (TEXT_COMMANDS.has(json.command)) {
1609
- return parseTextBlock(json, options, path, diagnostics);
1632
+ return parseTextBlock(json, options, path, diagnostics, blockIds);
1610
1633
  }
1611
1634
  if (LIST_COMMANDS.has(json.command)) {
1612
- return parseListBlock(json, options, path, diagnostics);
1635
+ return parseListBlock(json, options, path, diagnostics, blockIds);
1613
1636
  }
1614
1637
  if (json.command === "\\begin{tabular}") {
1615
- return parseTableBlock(json, options, path, diagnostics);
1638
+ return parseTableBlock(json, options, path, diagnostics, blockIds);
1616
1639
  }
1617
1640
  if (json.command === "\\includegraphics") {
1618
- return parseImageBlock(json);
1641
+ return parseImageBlock(json, blockIds);
1619
1642
  }
1620
1643
  if (json.command === "\\begin{thebibliography}" || json.command === "\\bibliography") {
1621
- return parseBibliographyBlock();
1644
+ return parseBibliographyBlock(json, blockIds);
1622
1645
  }
1623
1646
  if (json.command === "\\raw") {
1624
- return parseRawBlock(json);
1647
+ return parseRawBlock(json, blockIds);
1625
1648
  }
1626
1649
  return {
1627
- id: document2Id("block"),
1650
+ id: blockIdFromJson(json, blockIds),
1628
1651
  kind: "raw",
1629
1652
  command: "\\raw",
1630
1653
  value: typeof json.value === "string" ? json.value : json.command
@@ -1638,11 +1661,14 @@ function fromDocumentJson2(json, options = {}) {
1638
1661
  throw new Error("DocumentObject requires blocks array");
1639
1662
  }
1640
1663
  const diagnostics = [];
1664
+ const blockIds = { used: /* @__PURE__ */ new Set() };
1641
1665
  return {
1642
1666
  nodeType: "DocumentObject",
1643
1667
  meta: normalizeDocument2Meta(json.meta),
1644
1668
  references: parseReferences(json.references),
1645
- blocks: json.blocks.map((block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics)),
1669
+ blocks: json.blocks.map(
1670
+ (block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics, blockIds)
1671
+ ),
1646
1672
  diagnostics
1647
1673
  };
1648
1674
  }
@@ -4726,21 +4752,21 @@ function splitTextForInsertion(token) {
4726
4752
  function cloneDocument2Node(document2) {
4727
4753
  return cloneDocument(document2);
4728
4754
  }
4729
- function insertBlockAfter(blocks, afterBlockId, block) {
4730
- if (!afterBlockId) {
4755
+ function insertBlockAfter(blocks, afterBlockId2, block) {
4756
+ if (!afterBlockId2) {
4731
4757
  blocks.push(block);
4732
4758
  return;
4733
4759
  }
4734
- const index = blocks.findIndex((entry) => entry.id === afterBlockId);
4760
+ const index = blocks.findIndex((entry) => entry.id === afterBlockId2);
4735
4761
  if (index < 0) {
4736
4762
  blocks.push(block);
4737
4763
  return;
4738
4764
  }
4739
4765
  blocks.splice(index + 1, 0, block);
4740
4766
  }
4741
- function insertDocument2BlockAfter(document2, afterBlockId, block) {
4767
+ function insertDocument2BlockAfter(document2, afterBlockId2, block) {
4742
4768
  const next = cloneDocument(document2);
4743
- insertBlockAfter(next.blocks, afterBlockId, block);
4769
+ insertBlockAfter(next.blocks, afterBlockId2, block);
4744
4770
  return next;
4745
4771
  }
4746
4772
  function moveDocument2BlockById(document2, blockId, direction) {
@@ -4960,7 +4986,7 @@ function replaceMathTokenFromSession(document2, tokenId, session, opening, closi
4960
4986
  });
4961
4987
  return next;
4962
4988
  }
4963
- function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId) {
4989
+ function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId2) {
4964
4990
  const block = {
4965
4991
  id: document2Id("block"),
4966
4992
  kind: "textBlock",
@@ -4968,7 +4994,7 @@ function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId)
4968
4994
  field: createInlineField2(""),
4969
4995
  ...command === "\\paragraph" ? { centered: false } : {}
4970
4996
  };
4971
- return insertDocument2BlockAfter(document2, afterBlockId, block);
4997
+ return insertDocument2BlockAfter(document2, afterBlockId2, block);
4972
4998
  }
4973
4999
  function updateDocument2TextBlockCentered(document2, blockId, centered) {
4974
5000
  const next = cloneDocument(document2);
@@ -5007,10 +5033,10 @@ function newListBlock(ordered) {
5007
5033
  items: [{ id: document2Id("item"), field: createInlineField2(""), blocks: [] }]
5008
5034
  };
5009
5035
  }
5010
- function addDocument2ListBlock(document2, ordered, afterBlockId) {
5011
- return insertDocument2BlockAfter(document2, afterBlockId, newListBlock(ordered));
5036
+ function addDocument2ListBlock(document2, ordered, afterBlockId2) {
5037
+ return insertDocument2BlockAfter(document2, afterBlockId2, newListBlock(ordered));
5012
5038
  }
5013
- function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCount = 3, afterBlockId) {
5039
+ function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCount = 3, afterBlockId2) {
5014
5040
  const rows = Math.max(1, rowCount);
5015
5041
  const cols = Math.max(1, colCount);
5016
5042
  const tableRows = [];
@@ -5030,18 +5056,29 @@ function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCou
5030
5056
  rows: tableRows,
5031
5057
  ...defaultFloatMeta()
5032
5058
  };
5033
- return insertDocument2BlockAfter(document2, afterBlockId, block);
5059
+ return insertDocument2BlockAfter(document2, afterBlockId2, block);
5060
+ }
5061
+ function normalizeImageInput(srcOrAsset) {
5062
+ if (typeof srcOrAsset === "string") {
5063
+ return srcOrAsset.length > 0 ? { value: srcOrAsset, assetId: srcOrAsset } : { value: "" };
5064
+ }
5065
+ if (srcOrAsset && srcOrAsset.assetId.length > 0) {
5066
+ return { value: srcOrAsset.value ?? srcOrAsset.assetId, assetId: srcOrAsset.assetId };
5067
+ }
5068
+ return { value: "" };
5034
5069
  }
5035
- function addDocument2ImageBlock(document2, src = "", afterBlockId) {
5070
+ function addDocument2ImageBlock(document2, srcOrAsset, afterBlockId2) {
5071
+ const image = normalizeImageInput(srcOrAsset);
5036
5072
  const block = {
5037
5073
  id: document2Id("block"),
5038
5074
  kind: "image",
5039
5075
  command: "\\includegraphics",
5040
- value: src,
5076
+ value: image.value,
5077
+ ...image.assetId !== void 0 ? { assetId: image.assetId } : {},
5041
5078
  options: { width: "0.8\\columnwidth" },
5042
5079
  ...defaultFloatMeta()
5043
5080
  };
5044
- return insertDocument2BlockAfter(document2, afterBlockId, block);
5081
+ return insertDocument2BlockAfter(document2, afterBlockId2, block);
5045
5082
  }
5046
5083
  function updateDocument2ImageValue(document2, blockId, value) {
5047
5084
  const next = cloneDocument(document2);
@@ -5064,6 +5101,55 @@ function updateDocument2ImageValue(document2, blockId, value) {
5064
5101
  visit(next.blocks);
5065
5102
  return next;
5066
5103
  }
5104
+ function updateDocument2ImageAsset(document2, blockId, asset) {
5105
+ const next = cloneDocument(document2);
5106
+ const image = normalizeImageInput(asset);
5107
+ function visit(blocks) {
5108
+ for (const block of blocks) {
5109
+ if (block.id === blockId && block.kind === "image") {
5110
+ block.value = image.value;
5111
+ if (image.assetId !== void 0) {
5112
+ block.assetId = image.assetId;
5113
+ } else {
5114
+ delete block.assetId;
5115
+ }
5116
+ return true;
5117
+ }
5118
+ if (block.kind === "list") {
5119
+ for (const item of block.items) {
5120
+ if (visit(item.blocks)) {
5121
+ return true;
5122
+ }
5123
+ }
5124
+ }
5125
+ }
5126
+ return false;
5127
+ }
5128
+ visit(next.blocks);
5129
+ return next;
5130
+ }
5131
+ function clearDocument2ImageAsset(document2, blockId) {
5132
+ const next = cloneDocument(document2);
5133
+ function visit(blocks) {
5134
+ for (const block of blocks) {
5135
+ if (block.id === blockId && block.kind === "image") {
5136
+ block.value = "";
5137
+ delete block.assetId;
5138
+ return true;
5139
+ }
5140
+ if (block.kind === "list") {
5141
+ for (const item of block.items) {
5142
+ if (visit(item.blocks)) {
5143
+ return true;
5144
+ }
5145
+ }
5146
+ }
5147
+ }
5148
+ return false;
5149
+ }
5150
+ visit(next.blocks);
5151
+ return next;
5152
+ }
5067
5153
  function updateDocument2ImageMeta(document2, blockId, patch) {
5068
5154
  const next = cloneDocument(document2);
5069
5155
  function visit(blocks) {
@@ -5282,7 +5368,7 @@ function removeCiteTokenById(document2, tokenId) {
5282
5368
  });
5283
5369
  return next;
5284
5370
  }
5285
- function ensureDocument2BibliographyBlock(document2, afterBlockId) {
5371
+ function ensureDocument2BibliographyBlock(document2, afterBlockId2) {
5286
5372
  if (document2.blocks.some((block2) => block2.kind === "bibliography")) {
5287
5373
  return document2;
5288
5374
  }
@@ -5292,7 +5378,7 @@ function ensureDocument2BibliographyBlock(document2, afterBlockId) {
5292
5378
  command: "\\begin{thebibliography}",
5293
5379
  closing: "\\end{thebibliography}"
5294
5380
  };
5295
- return insertDocument2BlockAfter(document2, afterBlockId ?? null, block);
5381
+ return insertDocument2BlockAfter(document2, afterBlockId2 ?? null, block);
5296
5382
  }
5297
5383
  function addDocument2Reference(document2, partial = {}) {
5298
5384
  const next = cloneDocument(document2);
@@ -5364,6 +5450,440 @@ function updateDocument2Meta(document2, patch) {
5364
5450
  return next;
5365
5451
  }
5366
5452
 
5453
+ // src/document2/exportJson.ts
5454
+ function serializeField(field) {
5455
+ let value = "";
5456
+ let hasPersistedMath = false;
5457
+ const formats = [];
5458
+ const mathObjects = [];
5459
+ for (const token of field.tokens) {
5460
+ if (token.kind === "text") {
5461
+ const start = value.length;
5462
+ value += token.text;
5463
+ if (token.text.length > 0 && (token.style?.bold || token.style?.italic || token.style?.underline)) {
5464
+ formats.push({
5465
+ start,
5466
+ end: value.length,
5467
+ ...token.style.bold ? { bold: true } : {},
5468
+ ...token.style.italic ? { italic: true } : {},
5469
+ ...token.style.underline ? { underline: true } : {}
5470
+ });
5471
+ }
5472
+ continue;
5473
+ }
5474
+ if (token.kind === "cite") {
5475
+ value += citeTokenLatex(token.keys);
5476
+ continue;
5477
+ }
5478
+ if (token.kind === "ref") {
5479
+ value += refTokenLatex(token.keys, token.refCommand);
5480
+ continue;
5481
+ }
5482
+ value += token.source;
5483
+ if (!token.math || token.sourceOwner === "raw") {
5484
+ if (token.labelEnabled !== void 0 || token.label !== void 0) {
5485
+ hasPersistedMath = true;
5486
+ mathObjects.push({
5487
+ node_type: "RawMathObject",
5488
+ ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
5489
+ ...token.label !== void 0 ? { label: token.label } : {}
5490
+ });
5491
+ } else {
5492
+ mathObjects.push(null);
5493
+ }
5494
+ continue;
5495
+ }
5496
+ hasPersistedMath = true;
5497
+ mathObjects.push({
5498
+ ...toMathObjectJson(token.math),
5499
+ ...token.sourceSide ? { source_side: token.sourceSide } : {},
5500
+ source_owner: token.sourceOwner,
5501
+ ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
5502
+ ...token.label !== void 0 ? { label: token.label } : {}
5503
+ });
5504
+ }
5505
+ return { value, formats, mathObjects, hasPersistedMath };
5506
+ }
5507
+ function fieldJson(field) {
5508
+ const serialized = serializeField(field);
5509
+ return {
5510
+ value: serialized.value,
5511
+ ...serialized.formats.length > 0 ? { formats: serialized.formats } : {},
5512
+ ...serialized.hasPersistedMath ? { math_objects: serialized.mathObjects } : {}
5513
+ };
5514
+ }
5515
+ function listItemJson(item) {
5516
+ const field = fieldJson(item.field);
5517
+ return {
5518
+ value: field.value ?? "",
5519
+ ...field.formats ? { formats: field.formats } : {},
5520
+ ...field.math_objects ? { math_objects: field.math_objects } : {},
5521
+ ...item.blocks.length > 0 ? { blocks: item.blocks.map(blockJson) } : {}
5522
+ };
5523
+ }
5524
+ function blockJson(block) {
5525
+ if (block.kind === "textBlock") {
5526
+ return {
5527
+ id: block.id,
5528
+ command: block.command,
5529
+ ...fieldJson(block.field),
5530
+ ...block.command === "\\paragraph" ? { centered: block.centered === true } : {}
5531
+ };
5532
+ }
5533
+ if (block.kind === "list") {
5534
+ return {
5535
+ id: block.id,
5536
+ command: block.command,
5537
+ closing: block.closing,
5538
+ items: block.items.map(listItemJson)
5539
+ };
5540
+ }
5541
+ if (block.kind === "table") {
5542
+ const fields = block.rows.map((row) => row.map(serializeField));
5543
+ const hasPersistedMath = fields.some((row) => row.some((field) => field.hasPersistedMath));
5544
+ return {
5545
+ id: block.id,
5546
+ command: block.command,
5547
+ closing: block.closing,
5548
+ columns: block.columns,
5549
+ rows: fields.map((row) => row.map((field) => field.value)),
5550
+ ...fields.some((row) => row.some((field) => field.formats.length > 0)) ? { cell_formats: fields.map((row) => row.map((field) => field.formats)) } : {},
5551
+ ...hasPersistedMath ? { math_objects: fields.flatMap((row) => row.flatMap((field) => field.mathObjects)) } : {},
5552
+ centered: block.centered,
5553
+ caption_enabled: block.captionEnabled,
5554
+ caption: block.caption,
5555
+ label_enabled: block.labelEnabled,
5556
+ label: block.label
5557
+ };
5558
+ }
5559
+ if (block.kind === "image") {
5560
+ return {
5561
+ id: block.id,
5562
+ command: block.command,
5563
+ value: block.value,
5564
+ ...block.assetId !== void 0 ? { asset_id: block.assetId } : {},
5565
+ options: { ...block.options },
5566
+ centered: block.centered,
5567
+ caption_enabled: block.captionEnabled,
5568
+ caption: block.caption,
5569
+ label_enabled: block.labelEnabled,
5570
+ label: block.label
5571
+ };
5572
+ }
5573
+ if (block.kind === "bibliography") {
5574
+ return { id: block.id, command: block.command, closing: block.closing };
5575
+ }
5576
+ return { id: block.id, command: block.command, value: block.value };
5577
+ }
5578
+ function referenceJson(reference) {
5579
+ return {
5580
+ key: reference.key,
5581
+ authors: reference.authors,
5582
+ title: reference.title,
5583
+ year: reference.year,
5584
+ url: reference.url,
5585
+ venue: reference.venue,
5586
+ field_separator: reference.fieldSeparator
5587
+ };
5588
+ }
5589
+ function toDocumentJson2(document2) {
5590
+ if (document2.nodeType !== "DocumentObject" || !Array.isArray(document2.blocks)) {
5591
+ throw new Error("toDocumentJson2 requires a live Document2Node");
5592
+ }
5593
+ return {
5594
+ node_type: "DocumentObject",
5595
+ meta: {
5596
+ title: document2.meta.title,
5597
+ authors: document2.meta.authors,
5598
+ date: { ...document2.meta.date },
5599
+ abstract: document2.meta.abstract
5600
+ },
5601
+ references: document2.references.map(referenceJson),
5602
+ blocks: document2.blocks.map(blockJson)
5603
+ };
5604
+ }
5605
+
5606
+ // src/document2/jsonCommands.ts
5607
+ var Document2CommandError = class extends Error {
5608
+ code;
5609
+ constructor(code, message) {
5610
+ super(message);
5611
+ this.name = "Document2CommandError";
5612
+ this.code = code;
5613
+ }
5614
+ };
5615
+ function isObject3(value) {
5616
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5617
+ }
5618
+ function requireString2(value, field, allowEmpty = true) {
5619
+ if (typeof value !== "string" || !allowEmpty && value.trim().length === 0) {
5620
+ throw new Document2CommandError("invalid_command", `${field} must be a${allowEmpty ? "" : " non-empty"} string`);
5621
+ }
5622
+ return value;
5623
+ }
5624
+ function parseAnchor(value) {
5625
+ if (!isObject3(value)) {
5626
+ throw new Document2CommandError("invalid_command", "anchor must be an object");
5627
+ }
5628
+ const hasEnd = value.end === true;
5629
+ const afterBlockId2 = typeof value.after_block_id === "string" ? value.after_block_id.trim() : "";
5630
+ const hasAfterBlock = afterBlockId2.length > 0;
5631
+ if (hasEnd && hasAfterBlock) {
5632
+ throw new Document2CommandError("invalid_command", "anchor cannot contain both end and after_block_id");
5633
+ }
5634
+ if (hasEnd) {
5635
+ return { end: true };
5636
+ }
5637
+ if (hasAfterBlock) {
5638
+ return { after_block_id: afterBlockId2 };
5639
+ }
5640
+ throw new Document2CommandError("invalid_command", "anchor requires end: true or a non-empty after_block_id");
5641
+ }
5642
+ function optionalString(value, field) {
5643
+ if (!(field in value)) {
5644
+ return void 0;
5645
+ }
5646
+ if (typeof value[field] !== "string") {
5647
+ throw new Document2CommandError("invalid_command", `${field} must be a string when provided`);
5648
+ }
5649
+ return value[field];
5650
+ }
5651
+ function parseTextBlockKind(value) {
5652
+ if (value === "section" || value === "subsection" || value === "subsubsection" || value === "paragraph") {
5653
+ return value;
5654
+ }
5655
+ throw new Document2CommandError("invalid_command", "kind must be section, subsection, subsubsection, or paragraph");
5656
+ }
5657
+ function parseCommand2(value) {
5658
+ if (!isObject3(value) || typeof value.op !== "string") {
5659
+ throw new Document2CommandError("invalid_command", "command requires an op");
5660
+ }
5661
+ if (value.op === "insert_text_block") {
5662
+ return {
5663
+ op: value.op,
5664
+ kind: parseTextBlockKind(value.kind),
5665
+ text: requireString2(value.text, "text"),
5666
+ anchor: parseAnchor(value.anchor)
5667
+ };
5668
+ }
5669
+ if (value.op === "replace_text_block") {
5670
+ return {
5671
+ op: value.op,
5672
+ block_id: requireString2(value.block_id, "block_id", false).trim(),
5673
+ text: requireString2(value.text, "text")
5674
+ };
5675
+ }
5676
+ if (value.op === "remove_block") {
5677
+ return {
5678
+ op: value.op,
5679
+ block_id: requireString2(value.block_id, "block_id", false).trim()
5680
+ };
5681
+ }
5682
+ if (value.op === "insert_figure") {
5683
+ const figureValue = optionalString(value, "value");
5684
+ const caption = optionalString(value, "caption");
5685
+ const label = optionalString(value, "label");
5686
+ return {
5687
+ op: value.op,
5688
+ asset_id: requireString2(value.asset_id, "asset_id", false).trim(),
5689
+ ...figureValue !== void 0 ? { value: figureValue } : {},
5690
+ ...caption !== void 0 ? { caption } : {},
5691
+ ...label !== void 0 ? { label } : {},
5692
+ anchor: parseAnchor(value.anchor)
5693
+ };
5694
+ }
5695
+ throw new Document2CommandError("invalid_command", `Unsupported document command: ${value.op}`);
5696
+ }
5697
+ function parseDocument(json) {
5698
+ try {
5699
+ return fromDocumentJson2(json);
5700
+ } catch (error) {
5701
+ const message = error instanceof Error ? error.message : "Invalid DocumentObject JSON";
5702
+ throw new Document2CommandError("invalid_document", message);
5703
+ }
5704
+ }
5705
+ function commandForKind(kind) {
5706
+ if (kind === "section") {
5707
+ return "\\section";
5708
+ }
5709
+ if (kind === "subsection") {
5710
+ return "\\subsection";
5711
+ }
5712
+ if (kind === "subsubsection") {
5713
+ return "\\subsubsection";
5714
+ }
5715
+ return "\\paragraph";
5716
+ }
5717
+ function insertionIndex(document2, anchor) {
5718
+ if ("end" in anchor) {
5719
+ return document2.blocks.length;
5720
+ }
5721
+ const index = document2.blocks.findIndex((block) => block.id === anchor.after_block_id);
5722
+ if (index < 0) {
5723
+ throw new Document2CommandError("anchor_not_found", `Anchor block was not found: ${anchor.after_block_id}`);
5724
+ }
5725
+ return index + 1;
5726
+ }
5727
+ function afterBlockId(document2, index) {
5728
+ return index > 0 ? document2.blocks[index - 1]?.id ?? null : null;
5729
+ }
5730
+ function insertedBlock(document2, index) {
5731
+ const block = document2.blocks[index];
5732
+ if (!block) {
5733
+ throw new Document2CommandError("invalid_command", "Document command did not insert a block");
5734
+ }
5735
+ return block;
5736
+ }
5737
+ function insertTextBlock(document2, command) {
5738
+ const index = insertionIndex(document2, command.anchor);
5739
+ const next = addDocument2TextBlock(document2, commandForKind(command.kind), afterBlockId(document2, index));
5740
+ const block = insertedBlock(next, index);
5741
+ if (block.kind !== "textBlock") {
5742
+ throw new Document2CommandError("block_kind_mismatch", "Inserted block is not a text block");
5743
+ }
5744
+ block.field = createInlineField2(command.text);
5745
+ return next;
5746
+ }
5747
+ function replaceTextBlock(document2, command) {
5748
+ const index = document2.blocks.findIndex((block2) => block2.id === command.block_id);
5749
+ if (index < 0) {
5750
+ throw new Document2CommandError("block_not_found", `Document block was not found: ${command.block_id}`);
5751
+ }
5752
+ const block = document2.blocks[index];
5753
+ if (!block || block.kind !== "textBlock") {
5754
+ throw new Document2CommandError("block_kind_mismatch", `Document block is not a text block: ${command.block_id}`);
5755
+ }
5756
+ if (block.field.tokens.some((token) => token.kind !== "text" || token.style !== void 0)) {
5757
+ throw new Document2CommandError(
5758
+ "unsupported_inline_content",
5759
+ "Whole-block replacement is not supported for formatted or structured inline content"
5760
+ );
5761
+ }
5762
+ const next = parseDocument(toDocumentJson2(document2));
5763
+ const nextBlock = next.blocks[index];
5764
+ if (!nextBlock || nextBlock.kind !== "textBlock") {
5765
+ throw new Document2CommandError("block_kind_mismatch", `Document block is not a text block: ${command.block_id}`);
5766
+ }
5767
+ nextBlock.field = createInlineField2(command.text);
5768
+ return next;
5769
+ }
5770
+ function removeBlock(document2, command) {
5771
+ if (!document2.blocks.some((block) => block.id === command.block_id)) {
5772
+ throw new Document2CommandError("block_not_found", `Document block was not found: ${command.block_id}`);
5773
+ }
5774
+ return removeDocument2BlockById(document2, command.block_id);
5775
+ }
5776
+ function insertFigure(document2, command) {
5777
+ const index = insertionIndex(document2, command.anchor);
5778
+ const next = addDocument2ImageBlock(
5779
+ document2,
5780
+ { assetId: command.asset_id, ...command.value !== void 0 ? { value: command.value } : {} },
5781
+ afterBlockId(document2, index)
5782
+ );
5783
+ const block = insertedBlock(next, index);
5784
+ if (block.kind !== "image") {
5785
+ throw new Document2CommandError("block_kind_mismatch", "Inserted block is not a figure");
5786
+ }
5787
+ if (command.caption !== void 0) {
5788
+ block.caption = command.caption;
5789
+ block.captionEnabled = command.caption.trim().length > 0;
5790
+ }
5791
+ if (command.label !== void 0) {
5792
+ block.label = command.label;
5793
+ block.labelEnabled = command.label.trim().length > 0;
5794
+ }
5795
+ return next;
5796
+ }
5797
+ function applyDocument2Command(json, commandInput) {
5798
+ const document2 = parseDocument(json);
5799
+ const command = parseCommand2(commandInput);
5800
+ let next;
5801
+ if (command.op === "insert_text_block") {
5802
+ next = insertTextBlock(document2, command);
5803
+ } else if (command.op === "replace_text_block") {
5804
+ next = replaceTextBlock(document2, command);
5805
+ } else if (command.op === "remove_block") {
5806
+ next = removeBlock(document2, command);
5807
+ } else {
5808
+ next = insertFigure(document2, command);
5809
+ }
5810
+ return toDocumentJson2(next);
5811
+ }
5812
+
5813
+ // src/document2/outline.ts
5814
+ var EXCERPT_LIMIT = 160;
5815
+ function normalizeExcerpt(value) {
5816
+ const normalized = value.replace(/\s+/g, " ").trim();
5817
+ if (normalized.length <= EXCERPT_LIMIT) {
5818
+ return normalized;
5819
+ }
5820
+ return `${normalized.slice(0, EXCERPT_LIMIT - 3)}...`;
5821
+ }
5822
+ function outlineKind(command) {
5823
+ if (command === "\\section") {
5824
+ return "section";
5825
+ }
5826
+ if (command === "\\subsection") {
5827
+ return "subsection";
5828
+ }
5829
+ if (command === "\\subsubsection") {
5830
+ return "subsubsection";
5831
+ }
5832
+ if (command === "\\paragraph") {
5833
+ return "paragraph";
5834
+ }
5835
+ if (command === "\\begin{itemize}" || command === "\\begin{enumerate}") {
5836
+ return "list";
5837
+ }
5838
+ if (command === "\\begin{tabular}") {
5839
+ return "table";
5840
+ }
5841
+ if (command === "\\includegraphics") {
5842
+ return "figure";
5843
+ }
5844
+ if (command === "\\begin{thebibliography}" || command === "\\bibliography") {
5845
+ return "bibliography";
5846
+ }
5847
+ return "raw";
5848
+ }
5849
+ function blockExcerpt(block) {
5850
+ if (block.command === "\\begin{itemize}" || block.command === "\\begin{enumerate}") {
5851
+ return normalizeExcerpt((block.items ?? []).map((item) => item.value).join(" "));
5852
+ }
5853
+ if (block.command === "\\begin{tabular}") {
5854
+ return normalizeExcerpt((block.rows ?? []).flat().join(" "));
5855
+ }
5856
+ if (block.command === "\\includegraphics") {
5857
+ return normalizeExcerpt(block.caption || block.asset_id || block.value || "");
5858
+ }
5859
+ return normalizeExcerpt(block.value ?? "");
5860
+ }
5861
+ function document2Outline(json) {
5862
+ if (!json || json.node_type !== "DocumentObject" || !Array.isArray(json.blocks)) {
5863
+ throw new Document2CommandError("invalid_document", "DocumentObject requires a blocks array");
5864
+ }
5865
+ const usedIds = /* @__PURE__ */ new Set();
5866
+ return json.blocks.map((block) => {
5867
+ if (!block || typeof block !== "object" || typeof block.command !== "string") {
5868
+ throw new Document2CommandError("invalid_document", "Document outline requires blocks with string commands");
5869
+ }
5870
+ const id = typeof block.id === "string" ? block.id.trim() : "";
5871
+ if (id.length === 0) {
5872
+ throw new Document2CommandError("missing_block_id", "Document outline requires canonical block IDs");
5873
+ }
5874
+ if (usedIds.has(id)) {
5875
+ throw new Document2CommandError("duplicate_block_id", `Duplicate document block ID: ${id}`);
5876
+ }
5877
+ usedIds.add(id);
5878
+ return {
5879
+ id,
5880
+ kind: outlineKind(block.command),
5881
+ command: block.command,
5882
+ excerpt: blockExcerpt(block)
5883
+ };
5884
+ });
5885
+ }
5886
+
5367
5887
  // src/document2/keys.ts
5368
5888
  var DOCUMENT2_KEY_PATTERN = /^[\p{L}\p{M}0-9:._-]+$/u;
5369
5889
  function normalizeDocument2Key(key) {
@@ -5848,155 +6368,6 @@ ${body}
5848
6368
  `;
5849
6369
  }
5850
6370
 
5851
- // src/document2/exportJson.ts
5852
- function serializeField(field) {
5853
- let value = "";
5854
- let hasPersistedMath = false;
5855
- const formats = [];
5856
- const mathObjects = [];
5857
- for (const token of field.tokens) {
5858
- if (token.kind === "text") {
5859
- const start = value.length;
5860
- value += token.text;
5861
- if (token.text.length > 0 && (token.style?.bold || token.style?.italic || token.style?.underline)) {
5862
- formats.push({
5863
- start,
5864
- end: value.length,
5865
- ...token.style.bold ? { bold: true } : {},
5866
- ...token.style.italic ? { italic: true } : {},
5867
- ...token.style.underline ? { underline: true } : {}
5868
- });
5869
- }
5870
- continue;
5871
- }
5872
- if (token.kind === "cite") {
5873
- value += citeTokenLatex(token.keys);
5874
- continue;
5875
- }
5876
- if (token.kind === "ref") {
5877
- value += refTokenLatex(token.keys, token.refCommand);
5878
- continue;
5879
- }
5880
- value += token.source;
5881
- if (!token.math || token.sourceOwner === "raw") {
5882
- if (token.labelEnabled !== void 0 || token.label !== void 0) {
5883
- hasPersistedMath = true;
5884
- mathObjects.push({
5885
- node_type: "RawMathObject",
5886
- ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
5887
- ...token.label !== void 0 ? { label: token.label } : {}
5888
- });
5889
- } else {
5890
- mathObjects.push(null);
5891
- }
5892
- continue;
5893
- }
5894
- hasPersistedMath = true;
5895
- mathObjects.push({
5896
- ...toMathObjectJson(token.math),
5897
- ...token.sourceSide ? { source_side: token.sourceSide } : {},
5898
- source_owner: token.sourceOwner,
5899
- ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
5900
- ...token.label !== void 0 ? { label: token.label } : {}
5901
- });
5902
- }
5903
- return { value, formats, mathObjects, hasPersistedMath };
5904
- }
5905
- function fieldJson(field) {
5906
- const serialized = serializeField(field);
5907
- return {
5908
- value: serialized.value,
5909
- ...serialized.formats.length > 0 ? { formats: serialized.formats } : {},
5910
- ...serialized.hasPersistedMath ? { math_objects: serialized.mathObjects } : {}
5911
- };
5912
- }
5913
- function listItemJson(item) {
5914
- const field = fieldJson(item.field);
5915
- return {
5916
- value: field.value ?? "",
5917
- ...field.formats ? { formats: field.formats } : {},
5918
- ...field.math_objects ? { math_objects: field.math_objects } : {},
5919
- ...item.blocks.length > 0 ? { blocks: item.blocks.map(blockJson) } : {}
5920
- };
5921
- }
5922
- function blockJson(block) {
5923
- if (block.kind === "textBlock") {
5924
- return {
5925
- command: block.command,
5926
- ...fieldJson(block.field),
5927
- ...block.command === "\\paragraph" ? { centered: block.centered === true } : {}
5928
- };
5929
- }
5930
- if (block.kind === "list") {
5931
- return {
5932
- command: block.command,
5933
- closing: block.closing,
5934
- items: block.items.map(listItemJson)
5935
- };
5936
- }
5937
- if (block.kind === "table") {
5938
- const fields = block.rows.map((row) => row.map(serializeField));
5939
- const hasPersistedMath = fields.some((row) => row.some((field) => field.hasPersistedMath));
5940
- return {
5941
- command: block.command,
5942
- closing: block.closing,
5943
- columns: block.columns,
5944
- rows: fields.map((row) => row.map((field) => field.value)),
5945
- ...fields.some((row) => row.some((field) => field.formats.length > 0)) ? { cell_formats: fields.map((row) => row.map((field) => field.formats)) } : {},
5946
- ...hasPersistedMath ? { math_objects: fields.flatMap((row) => row.flatMap((field) => field.mathObjects)) } : {},
5947
- centered: block.centered,
5948
- caption_enabled: block.captionEnabled,
5949
- caption: block.caption,
5950
- label_enabled: block.labelEnabled,
5951
- label: block.label
5952
- };
5953
- }
5954
- if (block.kind === "image") {
5955
- return {
5956
- command: block.command,
5957
- value: block.value,
5958
- ...block.assetId !== void 0 ? { asset_id: block.assetId } : {},
5959
- options: { ...block.options },
5960
- centered: block.centered,
5961
- caption_enabled: block.captionEnabled,
5962
- caption: block.caption,
5963
- label_enabled: block.labelEnabled,
5964
- label: block.label
5965
- };
5966
- }
5967
- if (block.kind === "bibliography") {
5968
- return { command: block.command, closing: block.closing };
5969
- }
5970
- return { command: block.command, value: block.value };
5971
- }
5972
- function referenceJson(reference) {
5973
- return {
5974
- key: reference.key,
5975
- authors: reference.authors,
5976
- title: reference.title,
5977
- year: reference.year,
5978
- url: reference.url,
5979
- venue: reference.venue,
5980
- field_separator: reference.fieldSeparator
5981
- };
5982
- }
5983
- function toDocumentJson2(document2) {
5984
- if (document2.nodeType !== "DocumentObject" || !Array.isArray(document2.blocks)) {
5985
- throw new Error("toDocumentJson2 requires a live Document2Node");
5986
- }
5987
- return {
5988
- node_type: "DocumentObject",
5989
- meta: {
5990
- title: document2.meta.title,
5991
- authors: document2.meta.authors,
5992
- date: { ...document2.meta.date },
5993
- abstract: document2.meta.abstract
5994
- },
5995
- references: document2.references.map(referenceJson),
5996
- blocks: document2.blocks.map(blockJson)
5997
- };
5998
- }
5999
-
6000
6371
  // src/document2/history.ts
6001
6372
  var DEFAULT_DOCUMENT2_HISTORY_MAX_DEPTH = 100;
6002
6373
  function createDocument2History(maxDepth = DEFAULT_DOCUMENT2_HISTORY_MAX_DEPTH) {
@@ -6275,6 +6646,7 @@ function document2Preview(document2, output = "svg", equationSide = "arabic", pr
6275
6646
  DEFAULT_DOCUMENT2_HISTORY_MAX_DEPTH,
6276
6647
  DEFAULT_HIJRI_DATE,
6277
6648
  DEFAULT_REFERENCE_FIELD_SEPARATOR,
6649
+ Document2CommandError,
6278
6650
  HIJRI_MONTH_IDS,
6279
6651
  HIJRI_YEAR_MAX,
6280
6652
  HIJRI_YEAR_MIN,
@@ -6285,6 +6657,7 @@ function document2Preview(document2, output = "svg", equationSide = "arabic", pr
6285
6657
  addDocument2Reference,
6286
6658
  addDocument2TableBlock,
6287
6659
  addDocument2TextBlock,
6660
+ applyDocument2Command,
6288
6661
  applyFloatMetaPatch,
6289
6662
  arabicXeLatexPreambleCmd,
6290
6663
  arabicXeLatexPreambleFont,
@@ -6300,6 +6673,7 @@ function document2Preview(document2, output = "svg", equationSide = "arabic", pr
6300
6673
  butexMaghribiDisplayLatex,
6301
6674
  butexMaghribiDisplayTex,
6302
6675
  citeTokenLatex,
6676
+ clearDocument2ImageAsset,
6303
6677
  cloneDocument2Meta,
6304
6678
  cloneDocument2Node,
6305
6679
  collectDocument2Labels,
@@ -6319,6 +6693,7 @@ function document2Preview(document2, output = "svg", equationSide = "arabic", pr
6319
6693
  document2HistoryCanUndo,
6320
6694
  document2KeyError,
6321
6695
  document2Latex,
6696
+ document2Outline,
6322
6697
  document2Preview,
6323
6698
  emptyBlockSelection,
6324
6699
  emptyDocument2Meta,
@@ -6385,6 +6760,7 @@ function document2Preview(document2, output = "svg", equationSide = "arabic", pr
6385
6760
  toggleBlockInSelection,
6386
6761
  toggleTextTokenStyle,
6387
6762
  updateCiteTokenKeys,
6763
+ updateDocument2ImageAsset,
6388
6764
  updateDocument2ImageMeta,
6389
6765
  updateDocument2ImageValue,
6390
6766
  updateDocument2Meta,