@drghaliasri/butex 6.0.1 → 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/document2.js CHANGED
@@ -1135,6 +1135,76 @@ function applyFloatMetaPatch(block, patch) {
1135
1135
  }
1136
1136
  }
1137
1137
 
1138
+ // src/document2/inlineIdentity.ts
1139
+ function createDocument2IdentityState() {
1140
+ return {
1141
+ blockIds: /* @__PURE__ */ new Set(),
1142
+ itemIds: /* @__PURE__ */ new Set(),
1143
+ fieldIds: /* @__PURE__ */ new Set(),
1144
+ tokenIds: /* @__PURE__ */ new Set()
1145
+ };
1146
+ }
1147
+ function importedOrGeneratedId(value, prefix, used) {
1148
+ const imported = typeof value === "string" ? value.trim() : "";
1149
+ if (imported.length > 0 && !used.has(imported)) {
1150
+ used.add(imported);
1151
+ return imported;
1152
+ }
1153
+ let generated = document2Id(prefix);
1154
+ while (used.has(generated)) {
1155
+ generated = document2Id(prefix);
1156
+ }
1157
+ used.add(generated);
1158
+ return generated;
1159
+ }
1160
+ function inlineTokenSource(token) {
1161
+ if (token.kind === "text") {
1162
+ return token.text;
1163
+ }
1164
+ if (token.kind === "math") {
1165
+ return token.source;
1166
+ }
1167
+ if (token.kind === "cite") {
1168
+ return citeTokenLatex(token.keys);
1169
+ }
1170
+ return refTokenLatex(token.keys, token.refCommand);
1171
+ }
1172
+ function inlineIdsFromField(field) {
1173
+ let offset = 0;
1174
+ const tokens = field.tokens.map((token) => {
1175
+ const start = offset;
1176
+ offset += inlineTokenSource(token).length;
1177
+ return { id: token.id, kind: token.kind, start, end: offset };
1178
+ });
1179
+ return { field_id: field.id, tokens };
1180
+ }
1181
+ function isAlignedSidecar(value, field) {
1182
+ if (typeof value !== "object" || value === null || !Array.isArray(value.tokens)) {
1183
+ return false;
1184
+ }
1185
+ const tokens = value.tokens;
1186
+ const expected = inlineIdsFromField(field).tokens;
1187
+ if (tokens.length !== expected.length) {
1188
+ return false;
1189
+ }
1190
+ return tokens.every((token, index) => {
1191
+ const expectedToken = expected[index];
1192
+ return typeof token === "object" && token !== null && expectedToken !== void 0 && token.kind === expectedToken.kind && token.start === expectedToken.start && token.end === expectedToken.end;
1193
+ });
1194
+ }
1195
+ function applyImportedInlineIds(field, sidecar, state) {
1196
+ field.id = importedOrGeneratedId(
1197
+ typeof sidecar === "object" && sidecar !== null ? sidecar.field_id : void 0,
1198
+ "field",
1199
+ state.fieldIds
1200
+ );
1201
+ const aligned = isAlignedSidecar(sidecar, field);
1202
+ field.tokens.forEach((token, index) => {
1203
+ token.id = importedOrGeneratedId(aligned ? sidecar.tokens[index]?.id : void 0, token.kind, state.tokenIds);
1204
+ });
1205
+ return field;
1206
+ }
1207
+
1138
1208
  // src/document2/inlineScanner.ts
1139
1209
  var MATH_ENVIRONMENTS = /* @__PURE__ */ new Set([
1140
1210
  "equation",
@@ -1213,17 +1283,13 @@ function citeSpan(value, start) {
1213
1283
  function refSpan(value, start) {
1214
1284
  const eqrefPrefix = "\\eqref{";
1215
1285
  const refPrefix = "\\ref{";
1216
- let refCommand = "ref";
1217
- let prefix = refPrefix;
1218
- if (value.startsWith(eqrefPrefix, start) && !isEscaped(value, start)) {
1219
- refCommand = "eqref";
1220
- prefix = eqrefPrefix;
1221
- } else if (value.startsWith(refPrefix, start) && !isEscaped(value, start)) {
1222
- refCommand = "ref";
1223
- prefix = refPrefix;
1224
- } else {
1286
+ const isEqref = value.startsWith(eqrefPrefix, start) && !isEscaped(value, start);
1287
+ const isRef = value.startsWith(refPrefix, start) && !isEscaped(value, start);
1288
+ if (!isEqref && !isRef) {
1225
1289
  return null;
1226
1290
  }
1291
+ const refCommand = isEqref ? "eqref" : "ref";
1292
+ const prefix = isEqref ? eqrefPrefix : refPrefix;
1227
1293
  const openBrace = start + prefix.length - 1;
1228
1294
  if (value[openBrace] !== "{") {
1229
1295
  return null;
@@ -1282,7 +1348,14 @@ function mathSpanAt(value, i) {
1282
1348
  return null;
1283
1349
  }
1284
1350
  function detectMathSpans2(value) {
1285
- return detectInlineSpans2(value).filter((span) => span.kind === "math").map(({ kind: _kind, ...span }) => span);
1351
+ return detectInlineSpans2(value).filter((span) => span.kind === "math").map((span) => ({
1352
+ start: span.start,
1353
+ end: span.end,
1354
+ source: span.source,
1355
+ opening: span.opening,
1356
+ closing: span.closing,
1357
+ display: span.display
1358
+ }));
1286
1359
  }
1287
1360
  function detectInlineSpans2(value) {
1288
1361
  const spans = [];
@@ -1336,17 +1409,13 @@ function asBlockJson(value) {
1336
1409
  return value;
1337
1410
  }
1338
1411
  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");
1412
+ return importedOrGeneratedId(json.id, "block", state.blockIds);
1413
+ }
1414
+ function metadataFromJson(value) {
1415
+ if (!isObject2(value) || value.source !== "agent" && value.source !== "user") {
1416
+ return {};
1347
1417
  }
1348
- state.used.add(generated);
1349
- return generated;
1418
+ return { metadata: { source: value.source } };
1350
1419
  }
1351
1420
  function pushDiagnostic(diagnostics, options, path, message) {
1352
1421
  if (options.strict) {
@@ -1529,44 +1598,54 @@ function pushFormattedTextTokens(tokens, text, sourceStart, formats) {
1529
1598
  function closingForList(command) {
1530
1599
  return command === "\\begin{itemize}" ? "\\end{itemize}" : "\\end{enumerate}";
1531
1600
  }
1532
- function parseTextBlock(json, options, path, diagnostics, blockIds) {
1601
+ function parseTextBlock(json, options, path, diagnostics, identities) {
1533
1602
  const value = requireString(json.value, `${json.command} requires string value`);
1534
1603
  return {
1535
- id: blockIdFromJson(json, blockIds),
1604
+ id: blockIdFromJson(json, identities),
1536
1605
  kind: "textBlock",
1537
1606
  command: json.command,
1538
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1607
+ field: applyImportedInlineIds(
1608
+ createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1609
+ json.inline_ids,
1610
+ identities
1611
+ ),
1612
+ ...metadataFromJson(json.metadata),
1539
1613
  ...json.centered === true ? { centered: true } : {}
1540
1614
  };
1541
1615
  }
1542
- function parseListItem(json, options, path, diagnostics, blockIds) {
1616
+ function parseListItem(json, options, path, diagnostics, identities) {
1543
1617
  const value = requireString(json.value, "Document list item requires string value");
1544
1618
  const blocksJson = Array.isArray(json.blocks) ? json.blocks : [];
1545
1619
  return {
1546
- id: document2Id("item"),
1547
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1620
+ id: importedOrGeneratedId(json.id, "item", identities.itemIds),
1621
+ field: applyImportedInlineIds(
1622
+ createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1623
+ json.inline_ids,
1624
+ identities
1625
+ ),
1548
1626
  blocks: blocksJson.map(
1549
- (block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics, blockIds)
1627
+ (block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics, identities)
1550
1628
  )
1551
1629
  };
1552
1630
  }
1553
- function parseListBlock(json, options, path, diagnostics, blockIds) {
1631
+ function parseListBlock(json, options, path, diagnostics, identities) {
1554
1632
  if (!Array.isArray(json.items)) {
1555
1633
  throw new Error(`${json.command} requires items array`);
1556
1634
  }
1557
1635
  const command = json.command;
1558
1636
  const closing = closingForList(command);
1559
1637
  return {
1560
- id: blockIdFromJson(json, blockIds),
1638
+ id: blockIdFromJson(json, identities),
1561
1639
  kind: "list",
1562
1640
  command,
1563
1641
  closing,
1564
1642
  items: json.items.map(
1565
- (item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics, blockIds)
1566
- )
1643
+ (item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics, identities)
1644
+ ),
1645
+ ...metadataFromJson(json.metadata)
1567
1646
  };
1568
1647
  }
1569
- function parseTableBlock(json, options, path, diagnostics, blockIds) {
1648
+ function parseTableBlock(json, options, path, diagnostics, identities) {
1570
1649
  if (!Array.isArray(json.rows)) {
1571
1650
  throw new Error("\\begin{tabular} requires rows array");
1572
1651
  }
@@ -1582,75 +1661,84 @@ function parseTableBlock(json, options, path, diagnostics, blockIds) {
1582
1661
  const cellMathObjects = mathObjects.slice(mathObjectIndex, mathObjectIndex + spanCount);
1583
1662
  mathObjectIndex += spanCount;
1584
1663
  const cellFormats = json.cell_formats?.[rowIndex]?.[columnIndex] ?? [];
1585
- return createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics, cellFormats);
1664
+ return applyImportedInlineIds(
1665
+ createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics, cellFormats),
1666
+ json.cell_inline_ids?.[rowIndex]?.[columnIndex],
1667
+ identities
1668
+ );
1586
1669
  });
1587
1670
  });
1588
1671
  if (mathObjects.length > 0 && mathObjects.length !== mathObjectIndex) {
1589
1672
  pushDiagnostic(diagnostics, options, path, `math_objects count mismatch: detected ${String(mathObjectIndex)}, got ${String(mathObjects.length)}`);
1590
1673
  }
1591
1674
  return {
1592
- id: blockIdFromJson(json, blockIds),
1675
+ id: blockIdFromJson(json, identities),
1593
1676
  kind: "table",
1594
1677
  command: "\\begin{tabular}",
1595
1678
  closing: "\\end{tabular}",
1596
1679
  columns: typeof json.columns === "string" ? json.columns : "",
1597
1680
  rows,
1598
- ...parseFloatMetaFromJson(json)
1681
+ ...parseFloatMetaFromJson(json),
1682
+ ...metadataFromJson(json.metadata)
1599
1683
  };
1600
1684
  }
1601
- function parseImageBlock(json, blockIds) {
1685
+ function parseImageBlock(json, identities) {
1602
1686
  const assetId = typeof json.asset_id === "string" && json.asset_id.length > 0 ? json.asset_id : void 0;
1603
1687
  const value = assetId !== void 0 ? typeof json.value === "string" ? json.value : "" : requireString(json.value, "\\includegraphics requires string value");
1604
1688
  return {
1605
- id: blockIdFromJson(json, blockIds),
1689
+ id: blockIdFromJson(json, identities),
1606
1690
  kind: "image",
1607
1691
  command: "\\includegraphics",
1608
1692
  value,
1609
1693
  ...assetId !== void 0 ? { assetId } : {},
1610
1694
  options: isRecordOfStrings(json.options) ? json.options : {},
1611
- ...parseFloatMetaFromJson(json)
1695
+ ...parseFloatMetaFromJson(json),
1696
+ ...metadataFromJson(json.metadata)
1612
1697
  };
1613
1698
  }
1614
- function parseRawBlock(json, blockIds) {
1699
+ function parseRawBlock(json, identities) {
1615
1700
  return {
1616
- id: blockIdFromJson(json, blockIds),
1701
+ id: blockIdFromJson(json, identities),
1617
1702
  kind: "raw",
1618
1703
  command: "\\raw",
1619
- value: typeof json.value === "string" ? json.value : ""
1704
+ value: typeof json.value === "string" ? json.value : "",
1705
+ ...metadataFromJson(json.metadata)
1620
1706
  };
1621
1707
  }
1622
- function parseBibliographyBlock(json, blockIds) {
1708
+ function parseBibliographyBlock(json, identities) {
1623
1709
  return {
1624
- id: blockIdFromJson(json, blockIds),
1710
+ id: blockIdFromJson(json, identities),
1625
1711
  kind: "bibliography",
1626
1712
  command: "\\begin{thebibliography}",
1627
- closing: "\\end{thebibliography}"
1713
+ closing: "\\end{thebibliography}",
1714
+ ...metadataFromJson(json.metadata)
1628
1715
  };
1629
1716
  }
1630
- function parseBlock(json, options, path, diagnostics, blockIds) {
1717
+ function parseBlock(json, options, path, diagnostics, identities) {
1631
1718
  if (TEXT_COMMANDS.has(json.command)) {
1632
- return parseTextBlock(json, options, path, diagnostics, blockIds);
1719
+ return parseTextBlock(json, options, path, diagnostics, identities);
1633
1720
  }
1634
1721
  if (LIST_COMMANDS.has(json.command)) {
1635
- return parseListBlock(json, options, path, diagnostics, blockIds);
1722
+ return parseListBlock(json, options, path, diagnostics, identities);
1636
1723
  }
1637
1724
  if (json.command === "\\begin{tabular}") {
1638
- return parseTableBlock(json, options, path, diagnostics, blockIds);
1725
+ return parseTableBlock(json, options, path, diagnostics, identities);
1639
1726
  }
1640
1727
  if (json.command === "\\includegraphics") {
1641
- return parseImageBlock(json, blockIds);
1728
+ return parseImageBlock(json, identities);
1642
1729
  }
1643
1730
  if (json.command === "\\begin{thebibliography}" || json.command === "\\bibliography") {
1644
- return parseBibliographyBlock(json, blockIds);
1731
+ return parseBibliographyBlock(json, identities);
1645
1732
  }
1646
1733
  if (json.command === "\\raw") {
1647
- return parseRawBlock(json, blockIds);
1734
+ return parseRawBlock(json, identities);
1648
1735
  }
1649
1736
  return {
1650
- id: blockIdFromJson(json, blockIds),
1737
+ id: blockIdFromJson(json, identities),
1651
1738
  kind: "raw",
1652
1739
  command: "\\raw",
1653
- value: typeof json.value === "string" ? json.value : json.command
1740
+ value: typeof json.value === "string" ? json.value : json.command,
1741
+ ...metadataFromJson(json.metadata)
1654
1742
  };
1655
1743
  }
1656
1744
  function fromDocumentJson2(json, options = {}) {
@@ -1661,13 +1749,13 @@ function fromDocumentJson2(json, options = {}) {
1661
1749
  throw new Error("DocumentObject requires blocks array");
1662
1750
  }
1663
1751
  const diagnostics = [];
1664
- const blockIds = { used: /* @__PURE__ */ new Set() };
1752
+ const identities = createDocument2IdentityState();
1665
1753
  return {
1666
1754
  nodeType: "DocumentObject",
1667
1755
  meta: normalizeDocument2Meta(json.meta),
1668
1756
  references: parseReferences(json.references),
1669
1757
  blocks: json.blocks.map(
1670
- (block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics, blockIds)
1758
+ (block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics, identities)
1671
1759
  ),
1672
1760
  diagnostics
1673
1761
  };
@@ -2885,9 +2973,9 @@ function createAccentNode(accentId) {
2885
2973
  baseExpr: createEditorChain()
2886
2974
  };
2887
2975
  }
2888
- function createGridEnvNode(envName, rows, columns, columnAlignments) {
2976
+ function createGridEnvNode(envName, rows, columns2, columnAlignments) {
2889
2977
  const safeRows = Math.max(1, Math.min(12, Math.trunc(rows)));
2890
- const safeColumns = Math.max(1, Math.min(12, Math.trunc(columns)));
2978
+ const safeColumns = Math.max(1, Math.min(12, Math.trunc(columns2)));
2891
2979
  const matrixStyles = /* @__PURE__ */ new Set(["matrix", "pmatrix", "bmatrix", "Bmatrix", "vmatrix", "Vmatrix"]);
2892
2980
  const isMatrix = matrixStyles.has(envName) && envName !== "array" && envName !== "aligned";
2893
2981
  return {
@@ -4156,11 +4244,11 @@ function envToEditorNode(node) {
4156
4244
  }
4157
4245
  const rowCells = node.lines.map(splitEnvLine);
4158
4246
  const rows = Math.max(1, rowCells.length);
4159
- const columns = Math.max(1, ...rowCells.map((row) => row.length));
4160
- const env = createGridEnvNode(parsed.envName, rows, columns, parsed.alignments);
4247
+ const columns2 = Math.max(1, ...rowCells.map((row) => row.length));
4248
+ const env = createGridEnvNode(parsed.envName, rows, columns2, parsed.alignments);
4161
4249
  for (let rowIndex = 0; rowIndex < rows; rowIndex += 1) {
4162
4250
  const row = rowCells[rowIndex] ?? [];
4163
- for (let columnIndex = 0; columnIndex < columns; columnIndex += 1) {
4251
+ for (let columnIndex = 0; columnIndex < columns2; columnIndex += 1) {
4164
4252
  const cell = chainToEditorChain(row[columnIndex] ?? new ChainNode());
4165
4253
  if (!cell.editable) {
4166
4254
  return cell;
@@ -4170,7 +4258,7 @@ function envToEditorNode(node) {
4170
4258
  }
4171
4259
  if (parsed.envName === "array") {
4172
4260
  env.columnAlignments = Array.from(
4173
- { length: columns },
4261
+ { length: columns2 },
4174
4262
  (_, index) => parsed.alignments?.[index] ?? "c"
4175
4263
  );
4176
4264
  }
@@ -4648,12 +4736,14 @@ function cloneField(field) {
4648
4736
  return { id: field.id, tokens: field.tokens.map(cloneToken) };
4649
4737
  }
4650
4738
  function cloneBlock(block) {
4739
+ const metadata = block.metadata ? { metadata: { ...block.metadata } } : {};
4651
4740
  if (block.kind === "textBlock") {
4652
- return { ...block, field: cloneField(block.field) };
4741
+ return { ...block, ...metadata, field: cloneField(block.field) };
4653
4742
  }
4654
4743
  if (block.kind === "list") {
4655
4744
  return {
4656
4745
  ...block,
4746
+ ...metadata,
4657
4747
  items: block.items.map((item) => ({
4658
4748
  ...item,
4659
4749
  field: cloneField(item.field),
@@ -4662,12 +4752,12 @@ function cloneBlock(block) {
4662
4752
  };
4663
4753
  }
4664
4754
  if (block.kind === "table") {
4665
- return { ...block, rows: block.rows.map((row) => row.map(cloneField)) };
4755
+ return { ...block, ...metadata, rows: block.rows.map((row) => row.map(cloneField)) };
4666
4756
  }
4667
4757
  if (block.kind === "image") {
4668
- return { ...block, options: { ...block.options } };
4758
+ return { ...block, ...metadata, options: { ...block.options } };
4669
4759
  }
4670
- return { ...block };
4760
+ return { ...block, ...metadata };
4671
4761
  }
4672
4762
  function cloneDocument(document2) {
4673
4763
  return {
@@ -4752,21 +4842,21 @@ function splitTextForInsertion(token) {
4752
4842
  function cloneDocument2Node(document2) {
4753
4843
  return cloneDocument(document2);
4754
4844
  }
4755
- function insertBlockAfter(blocks, afterBlockId2, block) {
4756
- if (!afterBlockId2) {
4845
+ function insertBlockAfter(blocks, afterBlockId, block) {
4846
+ if (!afterBlockId) {
4757
4847
  blocks.push(block);
4758
4848
  return;
4759
4849
  }
4760
- const index = blocks.findIndex((entry) => entry.id === afterBlockId2);
4850
+ const index = blocks.findIndex((entry) => entry.id === afterBlockId);
4761
4851
  if (index < 0) {
4762
4852
  blocks.push(block);
4763
4853
  return;
4764
4854
  }
4765
4855
  blocks.splice(index + 1, 0, block);
4766
4856
  }
4767
- function insertDocument2BlockAfter(document2, afterBlockId2, block) {
4857
+ function insertDocument2BlockAfter(document2, afterBlockId, block) {
4768
4858
  const next = cloneDocument(document2);
4769
- insertBlockAfter(next.blocks, afterBlockId2, block);
4859
+ insertBlockAfter(next.blocks, afterBlockId, block);
4770
4860
  return next;
4771
4861
  }
4772
4862
  function moveDocument2BlockById(document2, blockId, direction) {
@@ -4986,7 +5076,7 @@ function replaceMathTokenFromSession(document2, tokenId, session, opening, closi
4986
5076
  });
4987
5077
  return next;
4988
5078
  }
4989
- function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId2) {
5079
+ function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId) {
4990
5080
  const block = {
4991
5081
  id: document2Id("block"),
4992
5082
  kind: "textBlock",
@@ -4994,7 +5084,7 @@ function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId2
4994
5084
  field: createInlineField2(""),
4995
5085
  ...command === "\\paragraph" ? { centered: false } : {}
4996
5086
  };
4997
- return insertDocument2BlockAfter(document2, afterBlockId2, block);
5087
+ return insertDocument2BlockAfter(document2, afterBlockId, block);
4998
5088
  }
4999
5089
  function updateDocument2TextBlockCentered(document2, blockId, centered) {
5000
5090
  const next = cloneDocument(document2);
@@ -5033,10 +5123,10 @@ function newListBlock(ordered) {
5033
5123
  items: [{ id: document2Id("item"), field: createInlineField2(""), blocks: [] }]
5034
5124
  };
5035
5125
  }
5036
- function addDocument2ListBlock(document2, ordered, afterBlockId2) {
5037
- return insertDocument2BlockAfter(document2, afterBlockId2, newListBlock(ordered));
5126
+ function addDocument2ListBlock(document2, ordered, afterBlockId) {
5127
+ return insertDocument2BlockAfter(document2, afterBlockId, newListBlock(ordered));
5038
5128
  }
5039
- function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCount = 3, afterBlockId2) {
5129
+ function addDocument2TableBlock(document2, columns2 = "lll", rowCount = 3, colCount = 3, afterBlockId) {
5040
5130
  const rows = Math.max(1, rowCount);
5041
5131
  const cols = Math.max(1, colCount);
5042
5132
  const tableRows = [];
@@ -5052,11 +5142,11 @@ function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCou
5052
5142
  kind: "table",
5053
5143
  command: "\\begin{tabular}",
5054
5144
  closing: "\\end{tabular}",
5055
- columns: columns || "l".repeat(cols),
5145
+ columns: columns2 || "l".repeat(cols),
5056
5146
  rows: tableRows,
5057
5147
  ...defaultFloatMeta()
5058
5148
  };
5059
- return insertDocument2BlockAfter(document2, afterBlockId2, block);
5149
+ return insertDocument2BlockAfter(document2, afterBlockId, block);
5060
5150
  }
5061
5151
  function normalizeImageInput(srcOrAsset) {
5062
5152
  if (typeof srcOrAsset === "string") {
@@ -5067,7 +5157,7 @@ function normalizeImageInput(srcOrAsset) {
5067
5157
  }
5068
5158
  return { value: "" };
5069
5159
  }
5070
- function addDocument2ImageBlock(document2, srcOrAsset, afterBlockId2) {
5160
+ function addDocument2ImageBlock(document2, srcOrAsset, afterBlockId) {
5071
5161
  const image = normalizeImageInput(srcOrAsset);
5072
5162
  const block = {
5073
5163
  id: document2Id("block"),
@@ -5078,7 +5168,7 @@ function addDocument2ImageBlock(document2, srcOrAsset, afterBlockId2) {
5078
5168
  options: { width: "0.8\\columnwidth" },
5079
5169
  ...defaultFloatMeta()
5080
5170
  };
5081
- return insertDocument2BlockAfter(document2, afterBlockId2, block);
5171
+ return insertDocument2BlockAfter(document2, afterBlockId, block);
5082
5172
  }
5083
5173
  function updateDocument2ImageValue(document2, blockId, value) {
5084
5174
  const next = cloneDocument(document2);
@@ -5368,7 +5458,7 @@ function removeCiteTokenById(document2, tokenId) {
5368
5458
  });
5369
5459
  return next;
5370
5460
  }
5371
- function ensureDocument2BibliographyBlock(document2, afterBlockId2) {
5461
+ function ensureDocument2BibliographyBlock(document2, afterBlockId) {
5372
5462
  if (document2.blocks.some((block2) => block2.kind === "bibliography")) {
5373
5463
  return document2;
5374
5464
  }
@@ -5378,7 +5468,7 @@ function ensureDocument2BibliographyBlock(document2, afterBlockId2) {
5378
5468
  command: "\\begin{thebibliography}",
5379
5469
  closing: "\\end{thebibliography}"
5380
5470
  };
5381
- return insertDocument2BlockAfter(document2, afterBlockId2 ?? null, block);
5471
+ return insertDocument2BlockAfter(document2, afterBlockId ?? null, block);
5382
5472
  }
5383
5473
  function addDocument2Reference(document2, partial = {}) {
5384
5474
  const next = cloneDocument(document2);
@@ -5450,6 +5540,764 @@ function updateDocument2Meta(document2, patch) {
5450
5540
  return next;
5451
5541
  }
5452
5542
 
5543
+ // src/document2/jsonCommandTypes.ts
5544
+ var Document2CommandError = class extends Error {
5545
+ code;
5546
+ constructor(code, message) {
5547
+ super(message);
5548
+ this.name = "Document2CommandError";
5549
+ this.code = code;
5550
+ }
5551
+ };
5552
+
5553
+ // src/document2/jsonCommandHelpers.ts
5554
+ function isObject3(value) {
5555
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5556
+ }
5557
+ function requireString2(value, field, allowEmpty = true) {
5558
+ if (typeof value !== "string" || !allowEmpty && value.trim().length === 0) {
5559
+ throw new Document2CommandError("invalid_command", `${field} must be a${allowEmpty ? "" : " non-empty"} string`);
5560
+ }
5561
+ return value;
5562
+ }
5563
+ function optionalString(value, field) {
5564
+ if (!(field in value)) {
5565
+ return void 0;
5566
+ }
5567
+ if (typeof value[field] !== "string") {
5568
+ throw new Document2CommandError("invalid_command", `${field} must be a string when provided`);
5569
+ }
5570
+ return value[field];
5571
+ }
5572
+ function requireBoolean(value, field) {
5573
+ if (typeof value !== "boolean") {
5574
+ throw new Document2CommandError("invalid_command", `${field} must be a boolean`);
5575
+ }
5576
+ return value;
5577
+ }
5578
+ function requireIndex(value, field) {
5579
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
5580
+ throw new Document2CommandError("invalid_command", `${field} must be a non-negative integer`);
5581
+ }
5582
+ return value;
5583
+ }
5584
+ function parseMetadata(value) {
5585
+ if (value === void 0) {
5586
+ return void 0;
5587
+ }
5588
+ if (!isObject3(value)) {
5589
+ throw new Document2CommandError("invalid_command", "metadata must be an object");
5590
+ }
5591
+ if (value.source === void 0) {
5592
+ return void 0;
5593
+ }
5594
+ if (value.source !== "agent" && value.source !== "user") {
5595
+ throw new Document2CommandError("invalid_command", "metadata.source must be agent or user");
5596
+ }
5597
+ return { source: value.source };
5598
+ }
5599
+ function parseBlockAnchor(value) {
5600
+ if (!isObject3(value)) {
5601
+ throw new Document2CommandError("invalid_anchor", "anchor must be an object");
5602
+ }
5603
+ const before = typeof value.before_block_id === "string" ? value.before_block_id.trim() : "";
5604
+ const after = typeof value.after_block_id === "string" ? value.after_block_id.trim() : "";
5605
+ const choices = Number(before.length > 0) + Number(after.length > 0) + Number(value.end === true);
5606
+ if (choices !== 1) {
5607
+ throw new Document2CommandError("invalid_anchor", "anchor requires exactly one block position");
5608
+ }
5609
+ if (before) {
5610
+ return { before_block_id: before };
5611
+ }
5612
+ if (after) {
5613
+ return { after_block_id: after };
5614
+ }
5615
+ return { end: true };
5616
+ }
5617
+ function parseListItemAnchor(value) {
5618
+ if (!isObject3(value)) {
5619
+ throw new Document2CommandError("invalid_anchor", "anchor must be an object");
5620
+ }
5621
+ const before = typeof value.before_item_id === "string" ? value.before_item_id.trim() : "";
5622
+ const after = typeof value.after_item_id === "string" ? value.after_item_id.trim() : "";
5623
+ const choices = Number(before.length > 0) + Number(after.length > 0) + Number(value.end === true);
5624
+ if (choices !== 1) {
5625
+ throw new Document2CommandError("invalid_anchor", "anchor requires exactly one list-item position");
5626
+ }
5627
+ if (before) {
5628
+ return { before_item_id: before };
5629
+ }
5630
+ if (after) {
5631
+ return { after_item_id: after };
5632
+ }
5633
+ return { end: true };
5634
+ }
5635
+ function blockInsertionIndex(document2, anchor) {
5636
+ if ("end" in anchor) {
5637
+ return document2.blocks.length;
5638
+ }
5639
+ const id = "before_block_id" in anchor ? anchor.before_block_id : anchor.after_block_id;
5640
+ const index = document2.blocks.findIndex((block) => block.id === id);
5641
+ if (index < 0) {
5642
+ throw new Document2CommandError("anchor_not_found", "Anchor block was not found");
5643
+ }
5644
+ return "before_block_id" in anchor ? index : index + 1;
5645
+ }
5646
+ function itemInsertionIndex(list, anchor) {
5647
+ if ("end" in anchor) {
5648
+ return list.items.length;
5649
+ }
5650
+ const id = "before_item_id" in anchor ? anchor.before_item_id : anchor.after_item_id;
5651
+ const index = list.items.findIndex((item) => item.id === id);
5652
+ if (index < 0) {
5653
+ throw new Document2CommandError("anchor_not_found", "Anchor list item was not found");
5654
+ }
5655
+ return "before_item_id" in anchor ? index : index + 1;
5656
+ }
5657
+ function findField(blocks, fieldId) {
5658
+ for (const block of blocks) {
5659
+ if (block.kind === "textBlock" && block.field.id === fieldId) {
5660
+ return block.field;
5661
+ }
5662
+ if (block.kind === "list") {
5663
+ for (const item of block.items) {
5664
+ if (item.field.id === fieldId) {
5665
+ return item.field;
5666
+ }
5667
+ const nested = findField(item.blocks, fieldId);
5668
+ if (nested) {
5669
+ return nested;
5670
+ }
5671
+ }
5672
+ }
5673
+ if (block.kind === "table") {
5674
+ for (const row of block.rows) {
5675
+ const cell = row.find((field) => field.id === fieldId);
5676
+ if (cell) {
5677
+ return cell;
5678
+ }
5679
+ }
5680
+ }
5681
+ }
5682
+ return null;
5683
+ }
5684
+ function findBlock(blocks, blockId) {
5685
+ for (const block of blocks) {
5686
+ if (block.id === blockId) {
5687
+ return block;
5688
+ }
5689
+ if (block.kind === "list") {
5690
+ for (const item of block.items) {
5691
+ const nested = findBlock(item.blocks, blockId);
5692
+ if (nested) {
5693
+ return nested;
5694
+ }
5695
+ }
5696
+ }
5697
+ }
5698
+ return null;
5699
+ }
5700
+ function collectIdentitySets(blocks, sets) {
5701
+ for (const block of blocks) {
5702
+ sets.blocks.add(block.id);
5703
+ if (block.kind === "textBlock") {
5704
+ sets.fields.add(block.field.id);
5705
+ block.field.tokens.forEach((token) => sets.tokens.add(token.id));
5706
+ } else if (block.kind === "list") {
5707
+ for (const item of block.items) {
5708
+ sets.items.add(item.id);
5709
+ sets.fields.add(item.field.id);
5710
+ item.field.tokens.forEach((token) => sets.tokens.add(token.id));
5711
+ collectIdentitySets(item.blocks, sets);
5712
+ }
5713
+ } else if (block.kind === "table") {
5714
+ for (const row of block.rows) {
5715
+ for (const field of row) {
5716
+ sets.fields.add(field.id);
5717
+ field.tokens.forEach((token) => sets.tokens.add(token.id));
5718
+ }
5719
+ }
5720
+ }
5721
+ }
5722
+ }
5723
+ function documentIdentitySets(document2) {
5724
+ const sets = {
5725
+ blocks: /* @__PURE__ */ new Set(),
5726
+ items: /* @__PURE__ */ new Set(),
5727
+ fields: /* @__PURE__ */ new Set(),
5728
+ tokens: /* @__PURE__ */ new Set()
5729
+ };
5730
+ collectIdentitySets(document2.blocks, sets);
5731
+ return sets;
5732
+ }
5733
+ function unusedGeneratedId(prefix, used) {
5734
+ let id = document2Id(prefix);
5735
+ while (used.has(id)) {
5736
+ id = document2Id(prefix);
5737
+ }
5738
+ return id;
5739
+ }
5740
+ function newBlockId(document2) {
5741
+ return unusedGeneratedId("block", documentIdentitySets(document2).blocks);
5742
+ }
5743
+ function newItemId(document2) {
5744
+ return unusedGeneratedId("item", documentIdentitySets(document2).items);
5745
+ }
5746
+ function newTokenId(document2, prefix) {
5747
+ return unusedGeneratedId(prefix, documentIdentitySets(document2).tokens);
5748
+ }
5749
+ function createCommandInlineField(document2, text) {
5750
+ const field = createInlineField2(text);
5751
+ const used = documentIdentitySets(document2);
5752
+ field.id = unusedGeneratedId("field", used.fields);
5753
+ field.tokens.forEach((token) => {
5754
+ token.id = unusedGeneratedId(token.kind, used.tokens);
5755
+ used.tokens.add(token.id);
5756
+ });
5757
+ return field;
5758
+ }
5759
+ function requireList(document2, listId) {
5760
+ const block = findBlock(document2.blocks, listId);
5761
+ if (!block) {
5762
+ throw new Document2CommandError("list_not_found", "Document list was not found");
5763
+ }
5764
+ if (block.kind !== "list") {
5765
+ throw new Document2CommandError("block_kind_mismatch", "Target block is not a list");
5766
+ }
5767
+ return block;
5768
+ }
5769
+ function requireTable(document2, tableId) {
5770
+ const block = findBlock(document2.blocks, tableId);
5771
+ if (!block) {
5772
+ throw new Document2CommandError("table_not_found", "Document table was not found");
5773
+ }
5774
+ if (block.kind !== "table") {
5775
+ throw new Document2CommandError("block_kind_mismatch", "Target block is not a table");
5776
+ }
5777
+ return block;
5778
+ }
5779
+ function fieldHasStructuredContent(field) {
5780
+ return field.tokens.some((token) => token.kind !== "text" || token.style !== void 0);
5781
+ }
5782
+ function replacePlainField(document2, field, text) {
5783
+ if (fieldHasStructuredContent(field)) {
5784
+ throw new Document2CommandError(
5785
+ "unsupported_inline_content",
5786
+ "Whole-field replacement is not supported for formatted or structured inline content"
5787
+ );
5788
+ }
5789
+ const next = createCommandInlineField(document2, text);
5790
+ field.tokens = next.tokens;
5791
+ }
5792
+ function textStylesEqual3(a, b) {
5793
+ return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
5794
+ }
5795
+ function compactTextTokens2(tokens, emptyTokenId = document2Id("text")) {
5796
+ const compacted = [];
5797
+ for (const token of tokens) {
5798
+ const previous = compacted[compacted.length - 1];
5799
+ if (previous?.kind === "text" && token.kind === "text" && textStylesEqual3(previous.style, token.style)) {
5800
+ previous.text += token.text;
5801
+ } else {
5802
+ compacted.push(token);
5803
+ }
5804
+ }
5805
+ return compacted.length > 0 ? compacted : [{ id: emptyTokenId, kind: "text", text: "" }];
5806
+ }
5807
+
5808
+ // src/document2/inlineJsonCommands.ts
5809
+ function parseInlineAnchor(value) {
5810
+ if (!isObject3(value)) {
5811
+ throw new Document2CommandError("invalid_anchor", "anchor must be an object");
5812
+ }
5813
+ const before = typeof value.before_token_id === "string" ? value.before_token_id.trim() : "";
5814
+ const after = typeof value.after_token_id === "string" ? value.after_token_id.trim() : "";
5815
+ const choices = Number(before.length > 0) + Number(after.length > 0) + Number(value.start === true) + Number(value.end === true);
5816
+ if (choices !== 1) {
5817
+ throw new Document2CommandError("invalid_anchor", "anchor requires exactly one inline-token position");
5818
+ }
5819
+ if (before) {
5820
+ return { before_token_id: before };
5821
+ }
5822
+ if (after) {
5823
+ return { after_token_id: after };
5824
+ }
5825
+ return value.start === true ? { start: true } : { end: true };
5826
+ }
5827
+ function parseStyle(value) {
5828
+ if (value === void 0) {
5829
+ return void 0;
5830
+ }
5831
+ if (!isObject3(value)) {
5832
+ throw new Document2CommandError("invalid_inline_token", "text token style must be an object");
5833
+ }
5834
+ const style = {};
5835
+ for (const key of ["bold", "italic", "underline"]) {
5836
+ if (value[key] !== void 0 && value[key] !== true) {
5837
+ throw new Document2CommandError("invalid_inline_token", `text token style.${key} must be true when provided`);
5838
+ }
5839
+ if (value[key] === true) {
5840
+ style[key] = true;
5841
+ }
5842
+ }
5843
+ return style.bold || style.italic || style.underline ? style : void 0;
5844
+ }
5845
+ function normalizedKeys(value) {
5846
+ if (!Array.isArray(value)) {
5847
+ throw new Document2CommandError("invalid_inline_token", "citation/reference keys must be an array");
5848
+ }
5849
+ const keys = [];
5850
+ for (const entry of value) {
5851
+ if (typeof entry !== "string") {
5852
+ throw new Document2CommandError("invalid_inline_token", "citation/reference keys must be strings");
5853
+ }
5854
+ const key = entry.trim();
5855
+ if (key.length > 0 && !keys.includes(key)) {
5856
+ keys.push(key);
5857
+ }
5858
+ }
5859
+ if (keys.length === 0) {
5860
+ throw new Document2CommandError("invalid_inline_token", "citation/reference requires at least one non-empty key");
5861
+ }
5862
+ return keys;
5863
+ }
5864
+ function parseMathObject(value, source) {
5865
+ if (!isObject3(value) || value.node_type !== "MathObject") {
5866
+ throw new Document2CommandError("math_object_mismatch", "math_object must be a structured MathObject");
5867
+ }
5868
+ const spans = detectInlineSpans2(source);
5869
+ const span = spans[0];
5870
+ if (spans.length !== 1 || span?.kind !== "math" || span.start !== 0 || span.end !== source.length) {
5871
+ throw new Document2CommandError("math_object_mismatch", "math source must contain exactly one complete delimited span");
5872
+ }
5873
+ if (value.math_mode !== span.opening || value.closing !== span.closing) {
5874
+ throw new Document2CommandError("math_object_mismatch", "math_object delimiters do not match math source");
5875
+ }
5876
+ try {
5877
+ fromMathObjectJson(value, value.source_side === "arabic" ? "arabic" : "english");
5878
+ } catch {
5879
+ throw new Document2CommandError("math_object_mismatch", "math_object must contain a valid structured equation");
5880
+ }
5881
+ return value;
5882
+ }
5883
+ function parseToken(value) {
5884
+ if (!isObject3(value) || typeof value.kind !== "string") {
5885
+ throw new Document2CommandError("invalid_inline_token", "token requires a kind");
5886
+ }
5887
+ if (value.kind === "text") {
5888
+ const text = requireString2(value.text, "token.text");
5889
+ if (detectInlineSpans2(text).length > 0) {
5890
+ throw new Document2CommandError("invalid_inline_token", "text token contains structured inline syntax");
5891
+ }
5892
+ const style = parseStyle(value.style);
5893
+ return { kind: "text", text, ...style ? { style } : {} };
5894
+ }
5895
+ if (value.kind === "cite") {
5896
+ return { kind: "cite", keys: normalizedKeys(value.keys) };
5897
+ }
5898
+ if (value.kind === "ref") {
5899
+ if (value.ref_command !== "ref" && value.ref_command !== "eqref") {
5900
+ throw new Document2CommandError("invalid_inline_token", "ref_command must be ref or eqref");
5901
+ }
5902
+ return { kind: "ref", keys: normalizedKeys(value.keys), ref_command: value.ref_command };
5903
+ }
5904
+ if (value.kind === "math") {
5905
+ const source = requireString2(value.source, "token.source", false);
5906
+ return { kind: "math", source, math_object: parseMathObject(value.math_object, source) };
5907
+ }
5908
+ throw new Document2CommandError("invalid_inline_token", "Unsupported inline token kind");
5909
+ }
5910
+ function parseInlineCommand(value) {
5911
+ if (value.op === "insert_inline_token") {
5912
+ return {
5913
+ op: value.op,
5914
+ field_id: requireString2(value.field_id, "field_id", false).trim(),
5915
+ token: parseToken(value.token),
5916
+ anchor: parseInlineAnchor(value.anchor)
5917
+ };
5918
+ }
5919
+ if (value.op === "replace_inline_token") {
5920
+ return {
5921
+ op: value.op,
5922
+ field_id: requireString2(value.field_id, "field_id", false).trim(),
5923
+ token_id: requireString2(value.token_id, "token_id", false).trim(),
5924
+ token: parseToken(value.token)
5925
+ };
5926
+ }
5927
+ if (value.op === "remove_inline_token") {
5928
+ return {
5929
+ op: value.op,
5930
+ field_id: requireString2(value.field_id, "field_id", false).trim(),
5931
+ token_id: requireString2(value.token_id, "token_id", false).trim()
5932
+ };
5933
+ }
5934
+ throw new Document2CommandError("invalid_command", "Unsupported inline command");
5935
+ }
5936
+ function liveToken(input, id) {
5937
+ if (input.kind === "text") {
5938
+ return { id, kind: "text", text: input.text, ...input.style ? { style: { ...input.style } } : {} };
5939
+ }
5940
+ if (input.kind === "cite") {
5941
+ return { id, kind: "cite", keys: [...input.keys] };
5942
+ }
5943
+ if (input.kind === "ref") {
5944
+ return { id, kind: "ref", keys: [...input.keys], refCommand: input.ref_command };
5945
+ }
5946
+ const mathObject = input.math_object;
5947
+ const spans = detectInlineSpans2(input.source);
5948
+ const span = spans[0];
5949
+ if (!span || span.kind !== "math") {
5950
+ throw new Document2CommandError("math_object_mismatch", "math source is not a complete delimited span");
5951
+ }
5952
+ return {
5953
+ id,
5954
+ kind: "math",
5955
+ display: span.display,
5956
+ opening: span.opening,
5957
+ closing: span.closing,
5958
+ source: input.source,
5959
+ sourceSide: mathObject.source_side === "arabic" ? "arabic" : "english",
5960
+ math: fromMathObjectJson(mathObject, mathObject.source_side === "arabic" ? "arabic" : "english"),
5961
+ editable: true,
5962
+ sourceOwner: mathObject.source_owner === "editor" ? "editor" : "imported-structured",
5963
+ ...mathObject.label_enabled !== void 0 ? { labelEnabled: mathObject.label_enabled } : {},
5964
+ ...mathObject.label !== void 0 ? { label: mathObject.label } : {}
5965
+ };
5966
+ }
5967
+ function insertionIndex(tokens, anchor) {
5968
+ if ("start" in anchor) {
5969
+ return 0;
5970
+ }
5971
+ if ("end" in anchor) {
5972
+ return tokens.length;
5973
+ }
5974
+ const id = "before_token_id" in anchor ? anchor.before_token_id : anchor.after_token_id;
5975
+ const index = tokens.findIndex((token) => token.id === id);
5976
+ if (index < 0) {
5977
+ throw new Document2CommandError("token_not_found", "Anchor token was not found");
5978
+ }
5979
+ return "before_token_id" in anchor ? index : index + 1;
5980
+ }
5981
+ function applyInlineCommand(document2, command) {
5982
+ const field = findField(document2.blocks, command.field_id);
5983
+ if (!field) {
5984
+ throw new Document2CommandError("field_not_found", "Inline field was not found");
5985
+ }
5986
+ if (command.op === "insert_inline_token") {
5987
+ const index2 = insertionIndex(field.tokens, command.anchor);
5988
+ field.tokens.splice(index2, 0, liveToken(command.token, newTokenId(document2, command.token.kind)));
5989
+ return;
5990
+ }
5991
+ const index = field.tokens.findIndex((token) => token.id === command.token_id);
5992
+ if (index < 0) {
5993
+ throw new Document2CommandError("token_not_found", "Inline token was not found");
5994
+ }
5995
+ if (command.op === "replace_inline_token") {
5996
+ field.tokens[index] = liveToken(command.token, command.token_id);
5997
+ return;
5998
+ }
5999
+ field.tokens = compactTextTokens2(
6000
+ [...field.tokens.slice(0, index), ...field.tokens.slice(index + 1)],
6001
+ newTokenId(document2, "text")
6002
+ );
6003
+ }
6004
+
6005
+ // src/document2/listJsonCommands.ts
6006
+ function stringArray(value, field) {
6007
+ if (!Array.isArray(value) || value.length === 0 || value.some((entry) => typeof entry !== "string")) {
6008
+ throw new Document2CommandError("invalid_command", `${field} must be a non-empty string array`);
6009
+ }
6010
+ return [...value];
6011
+ }
6012
+ function parseListCommand(value) {
6013
+ if (value.op === "insert_list") {
6014
+ const metadata = parseMetadata(value.metadata);
6015
+ return {
6016
+ op: value.op,
6017
+ ordered: requireBoolean(value.ordered, "ordered"),
6018
+ items: stringArray(value.items, "items"),
6019
+ anchor: parseBlockAnchor(value.anchor),
6020
+ ...metadata ? { metadata } : {}
6021
+ };
6022
+ }
6023
+ if (value.op === "insert_list_item") {
6024
+ return {
6025
+ op: value.op,
6026
+ list_id: requireString2(value.list_id, "list_id", false).trim(),
6027
+ text: requireString2(value.text, "text"),
6028
+ anchor: parseListItemAnchor(value.anchor)
6029
+ };
6030
+ }
6031
+ if (value.op === "replace_list_item") {
6032
+ return {
6033
+ op: value.op,
6034
+ list_id: requireString2(value.list_id, "list_id", false).trim(),
6035
+ item_id: requireString2(value.item_id, "item_id", false).trim(),
6036
+ text: requireString2(value.text, "text")
6037
+ };
6038
+ }
6039
+ if (value.op === "remove_list_item") {
6040
+ return {
6041
+ op: value.op,
6042
+ list_id: requireString2(value.list_id, "list_id", false).trim(),
6043
+ item_id: requireString2(value.item_id, "item_id", false).trim()
6044
+ };
6045
+ }
6046
+ if (value.op === "move_list_item") {
6047
+ return {
6048
+ op: value.op,
6049
+ list_id: requireString2(value.list_id, "list_id", false).trim(),
6050
+ item_id: requireString2(value.item_id, "item_id", false).trim(),
6051
+ anchor: parseListItemAnchor(value.anchor)
6052
+ };
6053
+ }
6054
+ throw new Document2CommandError("invalid_command", "Unsupported list command");
6055
+ }
6056
+ function newItem(document2, text) {
6057
+ return { id: newItemId(document2), field: createCommandInlineField(document2, text), blocks: [] };
6058
+ }
6059
+ function insertList(document2, command) {
6060
+ const ordered = command.ordered;
6061
+ const block = {
6062
+ id: newBlockId(document2),
6063
+ kind: "list",
6064
+ command: ordered ? "\\begin{enumerate}" : "\\begin{itemize}",
6065
+ closing: ordered ? "\\end{enumerate}" : "\\end{itemize}",
6066
+ items: command.items.map((text) => newItem(document2, text)),
6067
+ ...command.metadata ? { metadata: { ...command.metadata } } : {}
6068
+ };
6069
+ document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block);
6070
+ }
6071
+ function applyListCommand(document2, command) {
6072
+ if (command.op === "insert_list") {
6073
+ insertList(document2, command);
6074
+ return;
6075
+ }
6076
+ const list = requireList(document2, command.list_id);
6077
+ if (command.op === "insert_list_item") {
6078
+ list.items.splice(itemInsertionIndex(list, command.anchor), 0, newItem(document2, command.text));
6079
+ return;
6080
+ }
6081
+ const itemIndex = list.items.findIndex((item2) => item2.id === command.item_id);
6082
+ if (itemIndex < 0) {
6083
+ throw new Document2CommandError("item_not_found", "Document list item was not found");
6084
+ }
6085
+ if (command.op === "replace_list_item") {
6086
+ replacePlainField(document2, list.items[itemIndex].field, command.text);
6087
+ return;
6088
+ }
6089
+ if (command.op === "remove_list_item") {
6090
+ if (list.items.length === 1) {
6091
+ throw new Document2CommandError("minimum_structure", "A list must retain at least one item");
6092
+ }
6093
+ list.items.splice(itemIndex, 1);
6094
+ return;
6095
+ }
6096
+ const anchorId = "before_item_id" in command.anchor ? command.anchor.before_item_id : "after_item_id" in command.anchor ? command.anchor.after_item_id : null;
6097
+ if (anchorId === command.item_id) {
6098
+ throw new Document2CommandError("invalid_anchor", "A list item cannot be moved relative to itself");
6099
+ }
6100
+ const [item] = list.items.splice(itemIndex, 1);
6101
+ if (!item) {
6102
+ throw new Document2CommandError("item_not_found", "Document list item was not found");
6103
+ }
6104
+ list.items.splice(itemInsertionIndex(list, command.anchor), 0, item);
6105
+ }
6106
+
6107
+ // src/document2/tableJsonCommands.ts
6108
+ function stringArray2(value, field) {
6109
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
6110
+ throw new Document2CommandError("invalid_command", `${field} must be a string array`);
6111
+ }
6112
+ return [...value];
6113
+ }
6114
+ function rectangularRows(value) {
6115
+ if (!Array.isArray(value) || value.length === 0) {
6116
+ throw new Document2CommandError("invalid_table_shape", "rows must be a non-empty array");
6117
+ }
6118
+ const rows = value.map((row, index) => stringArray2(row, `rows[${String(index)}]`));
6119
+ const columnCount = rows[0]?.length ?? 0;
6120
+ if (columnCount === 0 || rows.some((row) => row.length !== columnCount)) {
6121
+ throw new Document2CommandError("invalid_table_shape", "Table rows must form a non-empty rectangle");
6122
+ }
6123
+ return rows;
6124
+ }
6125
+ function columns(value) {
6126
+ const result = requireString2(value, "columns", false);
6127
+ if (result.trim().length === 0) {
6128
+ throw new Document2CommandError("invalid_table_shape", "columns must be a non-empty LaTeX specification");
6129
+ }
6130
+ return result;
6131
+ }
6132
+ function parseTableCommand(value) {
6133
+ if (value.op === "insert_table") {
6134
+ const caption = optionalString(value, "caption");
6135
+ const label = optionalString(value, "label");
6136
+ const metadata = parseMetadata(value.metadata);
6137
+ return {
6138
+ op: value.op,
6139
+ rows: rectangularRows(value.rows),
6140
+ columns: columns(value.columns),
6141
+ ...caption !== void 0 ? { caption } : {},
6142
+ ...label !== void 0 ? { label } : {},
6143
+ anchor: parseBlockAnchor(value.anchor),
6144
+ ...metadata ? { metadata } : {}
6145
+ };
6146
+ }
6147
+ if (value.op === "replace_table_cell") {
6148
+ return {
6149
+ op: value.op,
6150
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6151
+ row_index: requireIndex(value.row_index, "row_index"),
6152
+ column_index: requireIndex(value.column_index, "column_index"),
6153
+ text: requireString2(value.text, "text")
6154
+ };
6155
+ }
6156
+ if (value.op === "insert_table_row") {
6157
+ return {
6158
+ op: value.op,
6159
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6160
+ index: requireIndex(value.index, "index"),
6161
+ values: stringArray2(value.values, "values")
6162
+ };
6163
+ }
6164
+ if (value.op === "remove_table_row") {
6165
+ return {
6166
+ op: value.op,
6167
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6168
+ index: requireIndex(value.index, "index")
6169
+ };
6170
+ }
6171
+ if (value.op === "move_table_row") {
6172
+ return {
6173
+ op: value.op,
6174
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6175
+ from_index: requireIndex(value.from_index, "from_index"),
6176
+ to_index: requireIndex(value.to_index, "to_index")
6177
+ };
6178
+ }
6179
+ if (value.op === "insert_table_column") {
6180
+ return {
6181
+ op: value.op,
6182
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6183
+ index: requireIndex(value.index, "index"),
6184
+ values: stringArray2(value.values, "values"),
6185
+ columns: columns(value.columns)
6186
+ };
6187
+ }
6188
+ if (value.op === "remove_table_column") {
6189
+ return {
6190
+ op: value.op,
6191
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6192
+ index: requireIndex(value.index, "index"),
6193
+ columns: columns(value.columns)
6194
+ };
6195
+ }
6196
+ if (value.op === "move_table_column") {
6197
+ return {
6198
+ op: value.op,
6199
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6200
+ from_index: requireIndex(value.from_index, "from_index"),
6201
+ to_index: requireIndex(value.to_index, "to_index"),
6202
+ columns: columns(value.columns)
6203
+ };
6204
+ }
6205
+ throw new Document2CommandError("invalid_command", "Unsupported table command");
6206
+ }
6207
+ function insertTable(document2, command) {
6208
+ const block = {
6209
+ id: newBlockId(document2),
6210
+ kind: "table",
6211
+ command: "\\begin{tabular}",
6212
+ closing: "\\end{tabular}",
6213
+ columns: command.columns,
6214
+ rows: command.rows.map((row) => row.map((cell) => createCommandInlineField(document2, cell))),
6215
+ ...defaultFloatMeta(),
6216
+ ...command.caption !== void 0 ? { caption: command.caption, captionEnabled: command.caption.trim().length > 0 } : {},
6217
+ ...command.label !== void 0 ? { label: command.label, labelEnabled: command.label.trim().length > 0 } : {},
6218
+ ...command.metadata ? { metadata: { ...command.metadata } } : {}
6219
+ };
6220
+ document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block);
6221
+ }
6222
+ function checkCell(table, rowIndex, columnIndex) {
6223
+ if (rowIndex >= table.rows.length || columnIndex >= (table.rows[rowIndex]?.length ?? 0)) {
6224
+ throw new Document2CommandError("index_out_of_range", "Table cell index is out of range");
6225
+ }
6226
+ }
6227
+ function checkExistingIndex(index, length, field) {
6228
+ if (index >= length) {
6229
+ throw new Document2CommandError("index_out_of_range", `${field} is out of range`);
6230
+ }
6231
+ }
6232
+ function applyTableCommand(document2, command) {
6233
+ if (command.op === "insert_table") {
6234
+ insertTable(document2, command);
6235
+ return;
6236
+ }
6237
+ const table = requireTable(document2, command.table_id);
6238
+ const columnCount = table.rows[0]?.length ?? 0;
6239
+ if (table.rows.length === 0 || columnCount === 0 || table.rows.some((row) => row.length !== columnCount)) {
6240
+ throw new Document2CommandError("invalid_table_shape", "Target table must be a non-empty rectangle");
6241
+ }
6242
+ if (command.op === "replace_table_cell") {
6243
+ checkCell(table, command.row_index, command.column_index);
6244
+ replacePlainField(document2, table.rows[command.row_index][command.column_index], command.text);
6245
+ return;
6246
+ }
6247
+ if (command.op === "insert_table_row") {
6248
+ if (command.index > table.rows.length) {
6249
+ throw new Document2CommandError("index_out_of_range", "Table row insertion index is out of range");
6250
+ }
6251
+ if (command.values.length !== columnCount) {
6252
+ throw new Document2CommandError("invalid_table_shape", "Inserted row must match the current column count");
6253
+ }
6254
+ table.rows.splice(command.index, 0, command.values.map((value) => createCommandInlineField(document2, value)));
6255
+ return;
6256
+ }
6257
+ if (command.op === "remove_table_row") {
6258
+ checkExistingIndex(command.index, table.rows.length, "Table row index");
6259
+ if (table.rows.length === 1) {
6260
+ throw new Document2CommandError("minimum_structure", "A table must retain at least one row");
6261
+ }
6262
+ table.rows.splice(command.index, 1);
6263
+ return;
6264
+ }
6265
+ if (command.op === "move_table_row") {
6266
+ checkExistingIndex(command.from_index, table.rows.length, "Table source row index");
6267
+ checkExistingIndex(command.to_index, table.rows.length, "Table target row index");
6268
+ const [row] = table.rows.splice(command.from_index, 1);
6269
+ table.rows.splice(command.to_index, 0, row);
6270
+ return;
6271
+ }
6272
+ if (command.op === "insert_table_column") {
6273
+ if (command.index > columnCount) {
6274
+ throw new Document2CommandError("index_out_of_range", "Table column insertion index is out of range");
6275
+ }
6276
+ if (command.values.length !== table.rows.length) {
6277
+ throw new Document2CommandError("invalid_table_shape", "Inserted column requires one value per row");
6278
+ }
6279
+ table.rows.forEach((row, index) => row.splice(command.index, 0, createCommandInlineField(document2, command.values[index])));
6280
+ table.columns = command.columns;
6281
+ return;
6282
+ }
6283
+ if (command.op === "remove_table_column") {
6284
+ checkExistingIndex(command.index, columnCount, "Table column index");
6285
+ if (columnCount === 1) {
6286
+ throw new Document2CommandError("minimum_structure", "A table must retain at least one column");
6287
+ }
6288
+ table.rows.forEach((row) => row.splice(command.index, 1));
6289
+ table.columns = command.columns;
6290
+ return;
6291
+ }
6292
+ checkExistingIndex(command.from_index, columnCount, "Table source column index");
6293
+ checkExistingIndex(command.to_index, columnCount, "Table target column index");
6294
+ table.rows.forEach((row) => {
6295
+ const [cell] = row.splice(command.from_index, 1);
6296
+ row.splice(command.to_index, 0, cell);
6297
+ });
6298
+ table.columns = command.columns;
6299
+ }
6300
+
5453
6301
  // src/document2/exportJson.ts
5454
6302
  function serializeField(field) {
5455
6303
  let value = "";
@@ -5502,12 +6350,13 @@ function serializeField(field) {
5502
6350
  ...token.label !== void 0 ? { label: token.label } : {}
5503
6351
  });
5504
6352
  }
5505
- return { value, formats, mathObjects, hasPersistedMath };
6353
+ return { value, formats, mathObjects, hasPersistedMath, inlineIds: inlineIdsFromField(field) };
5506
6354
  }
5507
6355
  function fieldJson(field) {
5508
6356
  const serialized = serializeField(field);
5509
6357
  return {
5510
6358
  value: serialized.value,
6359
+ inline_ids: serialized.inlineIds,
5511
6360
  ...serialized.formats.length > 0 ? { formats: serialized.formats } : {},
5512
6361
  ...serialized.hasPersistedMath ? { math_objects: serialized.mathObjects } : {}
5513
6362
  };
@@ -5515,7 +6364,9 @@ function fieldJson(field) {
5515
6364
  function listItemJson(item) {
5516
6365
  const field = fieldJson(item.field);
5517
6366
  return {
6367
+ id: item.id,
5518
6368
  value: field.value ?? "",
6369
+ inline_ids: field.inline_ids,
5519
6370
  ...field.formats ? { formats: field.formats } : {},
5520
6371
  ...field.math_objects ? { math_objects: field.math_objects } : {},
5521
6372
  ...item.blocks.length > 0 ? { blocks: item.blocks.map(blockJson) } : {}
@@ -5527,6 +6378,7 @@ function blockJson(block) {
5527
6378
  id: block.id,
5528
6379
  command: block.command,
5529
6380
  ...fieldJson(block.field),
6381
+ ...block.metadata ? { metadata: { ...block.metadata } } : {},
5530
6382
  ...block.command === "\\paragraph" ? { centered: block.centered === true } : {}
5531
6383
  };
5532
6384
  }
@@ -5535,7 +6387,8 @@ function blockJson(block) {
5535
6387
  id: block.id,
5536
6388
  command: block.command,
5537
6389
  closing: block.closing,
5538
- items: block.items.map(listItemJson)
6390
+ items: block.items.map(listItemJson),
6391
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
5539
6392
  };
5540
6393
  }
5541
6394
  if (block.kind === "table") {
@@ -5547,13 +6400,15 @@ function blockJson(block) {
5547
6400
  closing: block.closing,
5548
6401
  columns: block.columns,
5549
6402
  rows: fields.map((row) => row.map((field) => field.value)),
6403
+ cell_inline_ids: fields.map((row) => row.map((field) => field.inlineIds)),
5550
6404
  ...fields.some((row) => row.some((field) => field.formats.length > 0)) ? { cell_formats: fields.map((row) => row.map((field) => field.formats)) } : {},
5551
6405
  ...hasPersistedMath ? { math_objects: fields.flatMap((row) => row.flatMap((field) => field.mathObjects)) } : {},
5552
6406
  centered: block.centered,
5553
6407
  caption_enabled: block.captionEnabled,
5554
6408
  caption: block.caption,
5555
6409
  label_enabled: block.labelEnabled,
5556
- label: block.label
6410
+ label: block.label,
6411
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
5557
6412
  };
5558
6413
  }
5559
6414
  if (block.kind === "image") {
@@ -5567,13 +6422,24 @@ function blockJson(block) {
5567
6422
  caption_enabled: block.captionEnabled,
5568
6423
  caption: block.caption,
5569
6424
  label_enabled: block.labelEnabled,
5570
- label: block.label
6425
+ label: block.label,
6426
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
5571
6427
  };
5572
6428
  }
5573
6429
  if (block.kind === "bibliography") {
5574
- return { id: block.id, command: block.command, closing: block.closing };
6430
+ return {
6431
+ id: block.id,
6432
+ command: block.command,
6433
+ closing: block.closing,
6434
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6435
+ };
5575
6436
  }
5576
- return { id: block.id, command: block.command, value: block.value };
6437
+ return {
6438
+ id: block.id,
6439
+ command: block.command,
6440
+ value: block.value,
6441
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6442
+ };
5577
6443
  }
5578
6444
  function referenceJson(reference) {
5579
6445
  return {
@@ -5604,49 +6470,14 @@ function toDocumentJson2(document2) {
5604
6470
  }
5605
6471
 
5606
6472
  // 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);
6473
+ function isInlineCommand(command) {
6474
+ return command.op === "insert_inline_token" || command.op === "replace_inline_token" || command.op === "remove_inline_token";
5617
6475
  }
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;
6476
+ function isListCommand(command) {
6477
+ return command.op === "insert_list" || command.op === "insert_list_item" || command.op === "replace_list_item" || command.op === "remove_list_item" || command.op === "move_list_item";
5623
6478
  }
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];
6479
+ function isTableCommand(command) {
6480
+ return command.op === "insert_table" || command.op === "replace_table_cell" || command.op === "insert_table_row" || command.op === "remove_table_row" || command.op === "move_table_row" || command.op === "insert_table_column" || command.op === "remove_table_column" || command.op === "move_table_column";
5650
6481
  }
5651
6482
  function parseTextBlockKind(value) {
5652
6483
  if (value === "section" || value === "subsection" || value === "subsubsection" || value === "paragraph") {
@@ -5654,16 +6485,15 @@ function parseTextBlockKind(value) {
5654
6485
  }
5655
6486
  throw new Document2CommandError("invalid_command", "kind must be section, subsection, subsubsection, or paragraph");
5656
6487
  }
5657
- function parseCommand2(value) {
5658
- if (!isObject3(value) || typeof value.op !== "string") {
5659
- throw new Document2CommandError("invalid_command", "command requires an op");
5660
- }
6488
+ function parseBasicCommand(value) {
5661
6489
  if (value.op === "insert_text_block") {
6490
+ const metadata = parseMetadata(value.metadata);
5662
6491
  return {
5663
6492
  op: value.op,
5664
6493
  kind: parseTextBlockKind(value.kind),
5665
6494
  text: requireString2(value.text, "text"),
5666
- anchor: parseAnchor(value.anchor)
6495
+ anchor: parseBlockAnchor(value.anchor),
6496
+ ...metadata ? { metadata } : {}
5667
6497
  };
5668
6498
  }
5669
6499
  if (value.op === "replace_text_block") {
@@ -5674,140 +6504,118 @@ function parseCommand2(value) {
5674
6504
  };
5675
6505
  }
5676
6506
  if (value.op === "remove_block") {
5677
- return {
5678
- op: value.op,
5679
- block_id: requireString2(value.block_id, "block_id", false).trim()
5680
- };
6507
+ return { op: value.op, block_id: requireString2(value.block_id, "block_id", false).trim() };
5681
6508
  }
5682
6509
  if (value.op === "insert_figure") {
5683
6510
  const figureValue = optionalString(value, "value");
5684
6511
  const caption = optionalString(value, "caption");
5685
6512
  const label = optionalString(value, "label");
6513
+ const metadata = parseMetadata(value.metadata);
5686
6514
  return {
5687
6515
  op: value.op,
5688
6516
  asset_id: requireString2(value.asset_id, "asset_id", false).trim(),
5689
6517
  ...figureValue !== void 0 ? { value: figureValue } : {},
5690
6518
  ...caption !== void 0 ? { caption } : {},
5691
6519
  ...label !== void 0 ? { label } : {},
5692
- anchor: parseAnchor(value.anchor)
6520
+ anchor: parseBlockAnchor(value.anchor),
6521
+ ...metadata ? { metadata } : {}
5693
6522
  };
5694
6523
  }
5695
- throw new Document2CommandError("invalid_command", `Unsupported document command: ${value.op}`);
6524
+ throw new Document2CommandError("invalid_command", "Unsupported block command");
6525
+ }
6526
+ function parseCommand2(value) {
6527
+ if (!isObject3(value) || typeof value.op !== "string") {
6528
+ throw new Document2CommandError("invalid_command", "command requires an op");
6529
+ }
6530
+ if (value.op === "insert_inline_token" || value.op === "replace_inline_token" || value.op === "remove_inline_token") {
6531
+ return parseInlineCommand(value);
6532
+ }
6533
+ if (value.op === "insert_list" || value.op === "insert_list_item" || value.op === "replace_list_item" || value.op === "remove_list_item" || value.op === "move_list_item") {
6534
+ return parseListCommand(value);
6535
+ }
6536
+ if (value.op === "insert_table" || value.op === "replace_table_cell" || value.op === "insert_table_row" || value.op === "remove_table_row" || value.op === "move_table_row" || value.op === "insert_table_column" || value.op === "remove_table_column" || value.op === "move_table_column") {
6537
+ return parseTableCommand(value);
6538
+ }
6539
+ if (value.op === "insert_text_block" || value.op === "replace_text_block" || value.op === "remove_block" || value.op === "insert_figure") {
6540
+ return parseBasicCommand(value);
6541
+ }
6542
+ throw new Document2CommandError("invalid_command", "Unsupported document command");
5696
6543
  }
5697
6544
  function parseDocument(json) {
5698
6545
  try {
5699
6546
  return fromDocumentJson2(json);
5700
- } catch (error) {
5701
- const message = error instanceof Error ? error.message : "Invalid DocumentObject JSON";
5702
- throw new Document2CommandError("invalid_document", message);
6547
+ } catch {
6548
+ throw new Document2CommandError("invalid_document", "Document JSON is invalid");
5703
6549
  }
5704
6550
  }
5705
- function commandForKind(kind) {
6551
+ function textCommand(kind) {
5706
6552
  if (kind === "section") {
5707
6553
  return "\\section";
5708
6554
  }
5709
6555
  if (kind === "subsection") {
5710
6556
  return "\\subsection";
5711
6557
  }
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;
6558
+ return kind === "subsubsection" ? "\\subsubsection" : "\\paragraph";
5729
6559
  }
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");
6560
+ function applyBasicCommand(document2, command) {
6561
+ if (command.op === "insert_text_block") {
6562
+ const block2 = {
6563
+ id: newBlockId(document2),
6564
+ kind: "textBlock",
6565
+ command: textCommand(command.kind),
6566
+ field: createCommandInlineField(document2, command.text),
6567
+ ...command.kind === "paragraph" ? { centered: false } : {},
6568
+ ...command.metadata ? { metadata: { ...command.metadata } } : {}
6569
+ };
6570
+ document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block2);
6571
+ return;
5786
6572
  }
5787
- if (command.caption !== void 0) {
5788
- block.caption = command.caption;
5789
- block.captionEnabled = command.caption.trim().length > 0;
6573
+ if (command.op === "replace_text_block") {
6574
+ const block2 = document2.blocks.find((entry) => entry.id === command.block_id);
6575
+ if (!block2) {
6576
+ throw new Document2CommandError("block_not_found", "Document block was not found");
6577
+ }
6578
+ if (block2.kind !== "textBlock") {
6579
+ throw new Document2CommandError("block_kind_mismatch", "Document block is not a text block");
6580
+ }
6581
+ replacePlainField(document2, block2.field, command.text);
6582
+ return;
5790
6583
  }
5791
- if (command.label !== void 0) {
5792
- block.label = command.label;
5793
- block.labelEnabled = command.label.trim().length > 0;
6584
+ if (command.op === "remove_block") {
6585
+ const index = document2.blocks.findIndex((block2) => block2.id === command.block_id);
6586
+ if (index < 0) {
6587
+ throw new Document2CommandError("block_not_found", "Document block was not found");
6588
+ }
6589
+ document2.blocks.splice(index, 1);
6590
+ return;
5794
6591
  }
5795
- return next;
6592
+ const block = {
6593
+ id: newBlockId(document2),
6594
+ kind: "image",
6595
+ command: "\\includegraphics",
6596
+ value: command.value ?? command.asset_id,
6597
+ assetId: command.asset_id,
6598
+ options: { width: "0.8\\columnwidth" },
6599
+ ...defaultFloatMeta(),
6600
+ ...command.caption !== void 0 ? { caption: command.caption, captionEnabled: command.caption.trim().length > 0 } : {},
6601
+ ...command.label !== void 0 ? { label: command.label, labelEnabled: command.label.trim().length > 0 } : {},
6602
+ ...command.metadata ? { metadata: { ...command.metadata } } : {}
6603
+ };
6604
+ document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block);
5796
6605
  }
5797
6606
  function applyDocument2Command(json, commandInput) {
5798
6607
  const document2 = parseDocument(json);
5799
6608
  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);
6609
+ if (isInlineCommand(command)) {
6610
+ applyInlineCommand(document2, command);
6611
+ } else if (isListCommand(command)) {
6612
+ applyListCommand(document2, command);
6613
+ } else if (isTableCommand(command)) {
6614
+ applyTableCommand(document2, command);
5807
6615
  } else {
5808
- next = insertFigure(document2, command);
6616
+ applyBasicCommand(document2, command);
5809
6617
  }
5810
- return toDocumentJson2(next);
6618
+ return toDocumentJson2(document2);
5811
6619
  }
5812
6620
 
5813
6621
  // src/document2/outline.ts
@@ -6507,19 +7315,21 @@ function previewInlines(field, islands, output, equationSide, document2, preview
6507
7315
  }
6508
7316
  function textBlockPreview(block, islands, output, equationSide, document2, previewOptions) {
6509
7317
  const inlines = previewInlines(block.field, islands, output, equationSide, document2, previewOptions);
7318
+ const metadata = block.metadata ? { metadata: { ...block.metadata } } : {};
6510
7319
  if (block.command === "\\section") {
6511
- return { kind: "heading", id: block.id, level: 1, inlines };
7320
+ return { kind: "heading", id: block.id, level: 1, inlines, ...metadata };
6512
7321
  }
6513
7322
  if (block.command === "\\subsection") {
6514
- return { kind: "heading", id: block.id, level: 2, inlines };
7323
+ return { kind: "heading", id: block.id, level: 2, inlines, ...metadata };
6515
7324
  }
6516
7325
  if (block.command === "\\subsubsection") {
6517
- return { kind: "heading", id: block.id, level: 3, inlines };
7326
+ return { kind: "heading", id: block.id, level: 3, inlines, ...metadata };
6518
7327
  }
6519
7328
  return {
6520
7329
  kind: "paragraph",
6521
7330
  id: block.id,
6522
7331
  inlines,
7332
+ ...metadata,
6523
7333
  ...block.centered ? { centered: true } : {}
6524
7334
  };
6525
7335
  }
@@ -6550,7 +7360,8 @@ function blockPreview(block, islands, output, equationSide, document2, previewOp
6550
7360
  id: item.id,
6551
7361
  inlines: previewInlines(item.field, islands, output, equationSide, document2, previewOptions),
6552
7362
  blocks: item.blocks.map((child) => blockPreview(child, islands, output, equationSide, document2, previewOptions))
6553
- }))
7363
+ })),
7364
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6554
7365
  };
6555
7366
  }
6556
7367
  if (block.kind === "table") {
@@ -6560,7 +7371,8 @@ function blockPreview(block, islands, output, equationSide, document2, previewOp
6560
7371
  id: block.id,
6561
7372
  rows: block.rows.map((row) => row.map((cell) => previewInlines(cell, islands, output, equationSide, document2, previewOptions))),
6562
7373
  ...block.captionEnabled && block.caption.trim().length > 0 ? { caption: block.caption.trim() } : {},
6563
- ...label.length > 0 ? { label, numberLabel: floatNumberLabel(document2, label, previewOptions) } : {}
7374
+ ...label.length > 0 ? { label, numberLabel: floatNumberLabel(document2, label, previewOptions) } : {},
7375
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6564
7376
  };
6565
7377
  }
6566
7378
  if (block.kind === "image") {
@@ -6572,7 +7384,8 @@ function blockPreview(block, islands, output, equationSide, document2, previewOp
6572
7384
  ...block.assetId !== void 0 ? { assetId: block.assetId } : {},
6573
7385
  options: block.options,
6574
7386
  ...block.captionEnabled && block.caption.trim().length > 0 ? { caption: block.caption.trim() } : {},
6575
- ...label.length > 0 ? { label, numberLabel: floatNumberLabel(document2, label, previewOptions) } : {}
7387
+ ...label.length > 0 ? { label, numberLabel: floatNumberLabel(document2, label, previewOptions) } : {},
7388
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6576
7389
  };
6577
7390
  }
6578
7391
  if (block.kind === "bibliography") {
@@ -6592,10 +7405,11 @@ function blockPreview(block, islands, output, equationSide, document2, previewOp
6592
7405
  url: reference.url,
6593
7406
  venue: reference.venue,
6594
7407
  fieldSeparator: reference.fieldSeparator
6595
- }))
7408
+ })),
7409
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6596
7410
  };
6597
7411
  }
6598
- return { kind: "omit", id: block.id };
7412
+ return { kind: "omit", id: block.id, ...block.metadata ? { metadata: { ...block.metadata } } : {} };
6599
7413
  }
6600
7414
  function articleChromePreview(document2, uiLocale, previewOptions) {
6601
7415
  const meta = document2.meta ?? emptyDocument2Meta();