@drghaliasri/butex 6.0.0 → 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.
@@ -978,6 +978,76 @@ function applyFloatMetaPatch(block, patch) {
978
978
  }
979
979
  }
980
980
 
981
+ // src/document2/inlineIdentity.ts
982
+ function createDocument2IdentityState() {
983
+ return {
984
+ blockIds: /* @__PURE__ */ new Set(),
985
+ itemIds: /* @__PURE__ */ new Set(),
986
+ fieldIds: /* @__PURE__ */ new Set(),
987
+ tokenIds: /* @__PURE__ */ new Set()
988
+ };
989
+ }
990
+ function importedOrGeneratedId(value, prefix, used) {
991
+ const imported = typeof value === "string" ? value.trim() : "";
992
+ if (imported.length > 0 && !used.has(imported)) {
993
+ used.add(imported);
994
+ return imported;
995
+ }
996
+ let generated = document2Id(prefix);
997
+ while (used.has(generated)) {
998
+ generated = document2Id(prefix);
999
+ }
1000
+ used.add(generated);
1001
+ return generated;
1002
+ }
1003
+ function inlineTokenSource(token) {
1004
+ if (token.kind === "text") {
1005
+ return token.text;
1006
+ }
1007
+ if (token.kind === "math") {
1008
+ return token.source;
1009
+ }
1010
+ if (token.kind === "cite") {
1011
+ return citeTokenLatex(token.keys);
1012
+ }
1013
+ return refTokenLatex(token.keys, token.refCommand);
1014
+ }
1015
+ function inlineIdsFromField(field) {
1016
+ let offset = 0;
1017
+ const tokens = field.tokens.map((token) => {
1018
+ const start = offset;
1019
+ offset += inlineTokenSource(token).length;
1020
+ return { id: token.id, kind: token.kind, start, end: offset };
1021
+ });
1022
+ return { field_id: field.id, tokens };
1023
+ }
1024
+ function isAlignedSidecar(value, field) {
1025
+ if (typeof value !== "object" || value === null || !Array.isArray(value.tokens)) {
1026
+ return false;
1027
+ }
1028
+ const tokens = value.tokens;
1029
+ const expected = inlineIdsFromField(field).tokens;
1030
+ if (tokens.length !== expected.length) {
1031
+ return false;
1032
+ }
1033
+ return tokens.every((token, index) => {
1034
+ const expectedToken = expected[index];
1035
+ return typeof token === "object" && token !== null && expectedToken !== void 0 && token.kind === expectedToken.kind && token.start === expectedToken.start && token.end === expectedToken.end;
1036
+ });
1037
+ }
1038
+ function applyImportedInlineIds(field, sidecar, state) {
1039
+ field.id = importedOrGeneratedId(
1040
+ typeof sidecar === "object" && sidecar !== null ? sidecar.field_id : void 0,
1041
+ "field",
1042
+ state.fieldIds
1043
+ );
1044
+ const aligned = isAlignedSidecar(sidecar, field);
1045
+ field.tokens.forEach((token, index) => {
1046
+ token.id = importedOrGeneratedId(aligned ? sidecar.tokens[index]?.id : void 0, token.kind, state.tokenIds);
1047
+ });
1048
+ return field;
1049
+ }
1050
+
981
1051
  // src/document2/inlineScanner.ts
982
1052
  var MATH_ENVIRONMENTS = /* @__PURE__ */ new Set([
983
1053
  "equation",
@@ -1056,17 +1126,13 @@ function citeSpan(value, start) {
1056
1126
  function refSpan(value, start) {
1057
1127
  const eqrefPrefix = "\\eqref{";
1058
1128
  const refPrefix = "\\ref{";
1059
- let refCommand = "ref";
1060
- let prefix = refPrefix;
1061
- if (value.startsWith(eqrefPrefix, start) && !isEscaped(value, start)) {
1062
- refCommand = "eqref";
1063
- prefix = eqrefPrefix;
1064
- } else if (value.startsWith(refPrefix, start) && !isEscaped(value, start)) {
1065
- refCommand = "ref";
1066
- prefix = refPrefix;
1067
- } else {
1129
+ const isEqref = value.startsWith(eqrefPrefix, start) && !isEscaped(value, start);
1130
+ const isRef = value.startsWith(refPrefix, start) && !isEscaped(value, start);
1131
+ if (!isEqref && !isRef) {
1068
1132
  return null;
1069
1133
  }
1134
+ const refCommand = isEqref ? "eqref" : "ref";
1135
+ const prefix = isEqref ? eqrefPrefix : refPrefix;
1070
1136
  const openBrace = start + prefix.length - 1;
1071
1137
  if (value[openBrace] !== "{") {
1072
1138
  return null;
@@ -1125,7 +1191,14 @@ function mathSpanAt(value, i) {
1125
1191
  return null;
1126
1192
  }
1127
1193
  function detectMathSpans2(value) {
1128
- return detectInlineSpans2(value).filter((span) => span.kind === "math").map(({ kind: _kind, ...span }) => span);
1194
+ return detectInlineSpans2(value).filter((span) => span.kind === "math").map((span) => ({
1195
+ start: span.start,
1196
+ end: span.end,
1197
+ source: span.source,
1198
+ opening: span.opening,
1199
+ closing: span.closing,
1200
+ display: span.display
1201
+ }));
1129
1202
  }
1130
1203
  function detectInlineSpans2(value) {
1131
1204
  const spans = [];
@@ -1179,17 +1252,13 @@ function asBlockJson(value) {
1179
1252
  return value;
1180
1253
  }
1181
1254
  function blockIdFromJson(json, state) {
1182
- const imported = typeof json.id === "string" ? json.id.trim() : "";
1183
- if (imported.length > 0 && !state.used.has(imported)) {
1184
- state.used.add(imported);
1185
- return imported;
1186
- }
1187
- let generated = document2Id("block");
1188
- while (state.used.has(generated)) {
1189
- generated = document2Id("block");
1255
+ return importedOrGeneratedId(json.id, "block", state.blockIds);
1256
+ }
1257
+ function metadataFromJson(value) {
1258
+ if (!isObject2(value) || value.source !== "agent" && value.source !== "user") {
1259
+ return {};
1190
1260
  }
1191
- state.used.add(generated);
1192
- return generated;
1261
+ return { metadata: { source: value.source } };
1193
1262
  }
1194
1263
  function pushDiagnostic(diagnostics, options, path, message) {
1195
1264
  if (options.strict) {
@@ -1372,44 +1441,54 @@ function pushFormattedTextTokens(tokens, text, sourceStart, formats) {
1372
1441
  function closingForList(command) {
1373
1442
  return command === "\\begin{itemize}" ? "\\end{itemize}" : "\\end{enumerate}";
1374
1443
  }
1375
- function parseTextBlock(json, options, path, diagnostics, blockIds) {
1444
+ function parseTextBlock(json, options, path, diagnostics, identities) {
1376
1445
  const value = requireString(json.value, `${json.command} requires string value`);
1377
1446
  return {
1378
- id: blockIdFromJson(json, blockIds),
1447
+ id: blockIdFromJson(json, identities),
1379
1448
  kind: "textBlock",
1380
1449
  command: json.command,
1381
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1450
+ field: applyImportedInlineIds(
1451
+ createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1452
+ json.inline_ids,
1453
+ identities
1454
+ ),
1455
+ ...metadataFromJson(json.metadata),
1382
1456
  ...json.centered === true ? { centered: true } : {}
1383
1457
  };
1384
1458
  }
1385
- function parseListItem(json, options, path, diagnostics, blockIds) {
1459
+ function parseListItem(json, options, path, diagnostics, identities) {
1386
1460
  const value = requireString(json.value, "Document list item requires string value");
1387
1461
  const blocksJson = Array.isArray(json.blocks) ? json.blocks : [];
1388
1462
  return {
1389
- id: document2Id("item"),
1390
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1463
+ id: importedOrGeneratedId(json.id, "item", identities.itemIds),
1464
+ field: applyImportedInlineIds(
1465
+ createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1466
+ json.inline_ids,
1467
+ identities
1468
+ ),
1391
1469
  blocks: blocksJson.map(
1392
- (block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics, blockIds)
1470
+ (block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics, identities)
1393
1471
  )
1394
1472
  };
1395
1473
  }
1396
- function parseListBlock(json, options, path, diagnostics, blockIds) {
1474
+ function parseListBlock(json, options, path, diagnostics, identities) {
1397
1475
  if (!Array.isArray(json.items)) {
1398
1476
  throw new Error(`${json.command} requires items array`);
1399
1477
  }
1400
1478
  const command = json.command;
1401
1479
  const closing = closingForList(command);
1402
1480
  return {
1403
- id: blockIdFromJson(json, blockIds),
1481
+ id: blockIdFromJson(json, identities),
1404
1482
  kind: "list",
1405
1483
  command,
1406
1484
  closing,
1407
1485
  items: json.items.map(
1408
- (item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics, blockIds)
1409
- )
1486
+ (item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics, identities)
1487
+ ),
1488
+ ...metadataFromJson(json.metadata)
1410
1489
  };
1411
1490
  }
1412
- function parseTableBlock(json, options, path, diagnostics, blockIds) {
1491
+ function parseTableBlock(json, options, path, diagnostics, identities) {
1413
1492
  if (!Array.isArray(json.rows)) {
1414
1493
  throw new Error("\\begin{tabular} requires rows array");
1415
1494
  }
@@ -1425,75 +1504,84 @@ function parseTableBlock(json, options, path, diagnostics, blockIds) {
1425
1504
  const cellMathObjects = mathObjects.slice(mathObjectIndex, mathObjectIndex + spanCount);
1426
1505
  mathObjectIndex += spanCount;
1427
1506
  const cellFormats = json.cell_formats?.[rowIndex]?.[columnIndex] ?? [];
1428
- return createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics, cellFormats);
1507
+ return applyImportedInlineIds(
1508
+ createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics, cellFormats),
1509
+ json.cell_inline_ids?.[rowIndex]?.[columnIndex],
1510
+ identities
1511
+ );
1429
1512
  });
1430
1513
  });
1431
1514
  if (mathObjects.length > 0 && mathObjects.length !== mathObjectIndex) {
1432
1515
  pushDiagnostic(diagnostics, options, path, `math_objects count mismatch: detected ${String(mathObjectIndex)}, got ${String(mathObjects.length)}`);
1433
1516
  }
1434
1517
  return {
1435
- id: blockIdFromJson(json, blockIds),
1518
+ id: blockIdFromJson(json, identities),
1436
1519
  kind: "table",
1437
1520
  command: "\\begin{tabular}",
1438
1521
  closing: "\\end{tabular}",
1439
1522
  columns: typeof json.columns === "string" ? json.columns : "",
1440
1523
  rows,
1441
- ...parseFloatMetaFromJson(json)
1524
+ ...parseFloatMetaFromJson(json),
1525
+ ...metadataFromJson(json.metadata)
1442
1526
  };
1443
1527
  }
1444
- function parseImageBlock(json, blockIds) {
1528
+ function parseImageBlock(json, identities) {
1445
1529
  const assetId = typeof json.asset_id === "string" && json.asset_id.length > 0 ? json.asset_id : void 0;
1446
1530
  const value = assetId !== void 0 ? typeof json.value === "string" ? json.value : "" : requireString(json.value, "\\includegraphics requires string value");
1447
1531
  return {
1448
- id: blockIdFromJson(json, blockIds),
1532
+ id: blockIdFromJson(json, identities),
1449
1533
  kind: "image",
1450
1534
  command: "\\includegraphics",
1451
1535
  value,
1452
1536
  ...assetId !== void 0 ? { assetId } : {},
1453
1537
  options: isRecordOfStrings(json.options) ? json.options : {},
1454
- ...parseFloatMetaFromJson(json)
1538
+ ...parseFloatMetaFromJson(json),
1539
+ ...metadataFromJson(json.metadata)
1455
1540
  };
1456
1541
  }
1457
- function parseRawBlock(json, blockIds) {
1542
+ function parseRawBlock(json, identities) {
1458
1543
  return {
1459
- id: blockIdFromJson(json, blockIds),
1544
+ id: blockIdFromJson(json, identities),
1460
1545
  kind: "raw",
1461
1546
  command: "\\raw",
1462
- value: typeof json.value === "string" ? json.value : ""
1547
+ value: typeof json.value === "string" ? json.value : "",
1548
+ ...metadataFromJson(json.metadata)
1463
1549
  };
1464
1550
  }
1465
- function parseBibliographyBlock(json, blockIds) {
1551
+ function parseBibliographyBlock(json, identities) {
1466
1552
  return {
1467
- id: blockIdFromJson(json, blockIds),
1553
+ id: blockIdFromJson(json, identities),
1468
1554
  kind: "bibliography",
1469
1555
  command: "\\begin{thebibliography}",
1470
- closing: "\\end{thebibliography}"
1556
+ closing: "\\end{thebibliography}",
1557
+ ...metadataFromJson(json.metadata)
1471
1558
  };
1472
1559
  }
1473
- function parseBlock(json, options, path, diagnostics, blockIds) {
1560
+ function parseBlock(json, options, path, diagnostics, identities) {
1474
1561
  if (TEXT_COMMANDS.has(json.command)) {
1475
- return parseTextBlock(json, options, path, diagnostics, blockIds);
1562
+ return parseTextBlock(json, options, path, diagnostics, identities);
1476
1563
  }
1477
1564
  if (LIST_COMMANDS.has(json.command)) {
1478
- return parseListBlock(json, options, path, diagnostics, blockIds);
1565
+ return parseListBlock(json, options, path, diagnostics, identities);
1479
1566
  }
1480
1567
  if (json.command === "\\begin{tabular}") {
1481
- return parseTableBlock(json, options, path, diagnostics, blockIds);
1568
+ return parseTableBlock(json, options, path, diagnostics, identities);
1482
1569
  }
1483
1570
  if (json.command === "\\includegraphics") {
1484
- return parseImageBlock(json, blockIds);
1571
+ return parseImageBlock(json, identities);
1485
1572
  }
1486
1573
  if (json.command === "\\begin{thebibliography}" || json.command === "\\bibliography") {
1487
- return parseBibliographyBlock(json, blockIds);
1574
+ return parseBibliographyBlock(json, identities);
1488
1575
  }
1489
1576
  if (json.command === "\\raw") {
1490
- return parseRawBlock(json, blockIds);
1577
+ return parseRawBlock(json, identities);
1491
1578
  }
1492
1579
  return {
1493
- id: blockIdFromJson(json, blockIds),
1580
+ id: blockIdFromJson(json, identities),
1494
1581
  kind: "raw",
1495
1582
  command: "\\raw",
1496
- value: typeof json.value === "string" ? json.value : json.command
1583
+ value: typeof json.value === "string" ? json.value : json.command,
1584
+ ...metadataFromJson(json.metadata)
1497
1585
  };
1498
1586
  }
1499
1587
  function fromDocumentJson2(json, options = {}) {
@@ -1504,13 +1592,13 @@ function fromDocumentJson2(json, options = {}) {
1504
1592
  throw new Error("DocumentObject requires blocks array");
1505
1593
  }
1506
1594
  const diagnostics = [];
1507
- const blockIds = { used: /* @__PURE__ */ new Set() };
1595
+ const identities = createDocument2IdentityState();
1508
1596
  return {
1509
1597
  nodeType: "DocumentObject",
1510
1598
  meta: normalizeDocument2Meta(json.meta),
1511
1599
  references: parseReferences(json.references),
1512
1600
  blocks: json.blocks.map(
1513
- (block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics, blockIds)
1601
+ (block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics, identities)
1514
1602
  ),
1515
1603
  diagnostics
1516
1604
  };
@@ -2728,9 +2816,9 @@ function createAccentNode(accentId) {
2728
2816
  baseExpr: createEditorChain()
2729
2817
  };
2730
2818
  }
2731
- function createGridEnvNode(envName, rows, columns, columnAlignments) {
2819
+ function createGridEnvNode(envName, rows, columns2, columnAlignments) {
2732
2820
  const safeRows = Math.max(1, Math.min(12, Math.trunc(rows)));
2733
- const safeColumns = Math.max(1, Math.min(12, Math.trunc(columns)));
2821
+ const safeColumns = Math.max(1, Math.min(12, Math.trunc(columns2)));
2734
2822
  const matrixStyles = /* @__PURE__ */ new Set(["matrix", "pmatrix", "bmatrix", "Bmatrix", "vmatrix", "Vmatrix"]);
2735
2823
  const isMatrix = matrixStyles.has(envName) && envName !== "array" && envName !== "aligned";
2736
2824
  return {
@@ -3999,11 +4087,11 @@ function envToEditorNode(node) {
3999
4087
  }
4000
4088
  const rowCells = node.lines.map(splitEnvLine);
4001
4089
  const rows = Math.max(1, rowCells.length);
4002
- const columns = Math.max(1, ...rowCells.map((row) => row.length));
4003
- const env = createGridEnvNode(parsed.envName, rows, columns, parsed.alignments);
4090
+ const columns2 = Math.max(1, ...rowCells.map((row) => row.length));
4091
+ const env = createGridEnvNode(parsed.envName, rows, columns2, parsed.alignments);
4004
4092
  for (let rowIndex = 0; rowIndex < rows; rowIndex += 1) {
4005
4093
  const row = rowCells[rowIndex] ?? [];
4006
- for (let columnIndex = 0; columnIndex < columns; columnIndex += 1) {
4094
+ for (let columnIndex = 0; columnIndex < columns2; columnIndex += 1) {
4007
4095
  const cell = chainToEditorChain(row[columnIndex] ?? new ChainNode());
4008
4096
  if (!cell.editable) {
4009
4097
  return cell;
@@ -4013,7 +4101,7 @@ function envToEditorNode(node) {
4013
4101
  }
4014
4102
  if (parsed.envName === "array") {
4015
4103
  env.columnAlignments = Array.from(
4016
- { length: columns },
4104
+ { length: columns2 },
4017
4105
  (_, index) => parsed.alignments?.[index] ?? "c"
4018
4106
  );
4019
4107
  }
@@ -4491,12 +4579,14 @@ function cloneField(field) {
4491
4579
  return { id: field.id, tokens: field.tokens.map(cloneToken) };
4492
4580
  }
4493
4581
  function cloneBlock(block) {
4582
+ const metadata = block.metadata ? { metadata: { ...block.metadata } } : {};
4494
4583
  if (block.kind === "textBlock") {
4495
- return { ...block, field: cloneField(block.field) };
4584
+ return { ...block, ...metadata, field: cloneField(block.field) };
4496
4585
  }
4497
4586
  if (block.kind === "list") {
4498
4587
  return {
4499
4588
  ...block,
4589
+ ...metadata,
4500
4590
  items: block.items.map((item) => ({
4501
4591
  ...item,
4502
4592
  field: cloneField(item.field),
@@ -4505,12 +4595,12 @@ function cloneBlock(block) {
4505
4595
  };
4506
4596
  }
4507
4597
  if (block.kind === "table") {
4508
- return { ...block, rows: block.rows.map((row) => row.map(cloneField)) };
4598
+ return { ...block, ...metadata, rows: block.rows.map((row) => row.map(cloneField)) };
4509
4599
  }
4510
4600
  if (block.kind === "image") {
4511
- return { ...block, options: { ...block.options } };
4601
+ return { ...block, ...metadata, options: { ...block.options } };
4512
4602
  }
4513
- return { ...block };
4603
+ return { ...block, ...metadata };
4514
4604
  }
4515
4605
  function cloneDocument(document2) {
4516
4606
  return {
@@ -4595,21 +4685,21 @@ function splitTextForInsertion(token) {
4595
4685
  function cloneDocument2Node(document2) {
4596
4686
  return cloneDocument(document2);
4597
4687
  }
4598
- function insertBlockAfter(blocks, afterBlockId2, block) {
4599
- if (!afterBlockId2) {
4688
+ function insertBlockAfter(blocks, afterBlockId, block) {
4689
+ if (!afterBlockId) {
4600
4690
  blocks.push(block);
4601
4691
  return;
4602
4692
  }
4603
- const index = blocks.findIndex((entry) => entry.id === afterBlockId2);
4693
+ const index = blocks.findIndex((entry) => entry.id === afterBlockId);
4604
4694
  if (index < 0) {
4605
4695
  blocks.push(block);
4606
4696
  return;
4607
4697
  }
4608
4698
  blocks.splice(index + 1, 0, block);
4609
4699
  }
4610
- function insertDocument2BlockAfter(document2, afterBlockId2, block) {
4700
+ function insertDocument2BlockAfter(document2, afterBlockId, block) {
4611
4701
  const next = cloneDocument(document2);
4612
- insertBlockAfter(next.blocks, afterBlockId2, block);
4702
+ insertBlockAfter(next.blocks, afterBlockId, block);
4613
4703
  return next;
4614
4704
  }
4615
4705
  function moveDocument2BlockById(document2, blockId, direction) {
@@ -4829,7 +4919,7 @@ function replaceMathTokenFromSession(document2, tokenId, session, opening, closi
4829
4919
  });
4830
4920
  return next;
4831
4921
  }
4832
- function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId2) {
4922
+ function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId) {
4833
4923
  const block = {
4834
4924
  id: document2Id("block"),
4835
4925
  kind: "textBlock",
@@ -4837,7 +4927,7 @@ function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId2
4837
4927
  field: createInlineField2(""),
4838
4928
  ...command === "\\paragraph" ? { centered: false } : {}
4839
4929
  };
4840
- return insertDocument2BlockAfter(document2, afterBlockId2, block);
4930
+ return insertDocument2BlockAfter(document2, afterBlockId, block);
4841
4931
  }
4842
4932
  function updateDocument2TextBlockCentered(document2, blockId, centered) {
4843
4933
  const next = cloneDocument(document2);
@@ -4876,10 +4966,10 @@ function newListBlock(ordered) {
4876
4966
  items: [{ id: document2Id("item"), field: createInlineField2(""), blocks: [] }]
4877
4967
  };
4878
4968
  }
4879
- function addDocument2ListBlock(document2, ordered, afterBlockId2) {
4880
- return insertDocument2BlockAfter(document2, afterBlockId2, newListBlock(ordered));
4969
+ function addDocument2ListBlock(document2, ordered, afterBlockId) {
4970
+ return insertDocument2BlockAfter(document2, afterBlockId, newListBlock(ordered));
4881
4971
  }
4882
- function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCount = 3, afterBlockId2) {
4972
+ function addDocument2TableBlock(document2, columns2 = "lll", rowCount = 3, colCount = 3, afterBlockId) {
4883
4973
  const rows = Math.max(1, rowCount);
4884
4974
  const cols = Math.max(1, colCount);
4885
4975
  const tableRows = [];
@@ -4895,11 +4985,11 @@ function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCou
4895
4985
  kind: "table",
4896
4986
  command: "\\begin{tabular}",
4897
4987
  closing: "\\end{tabular}",
4898
- columns: columns || "l".repeat(cols),
4988
+ columns: columns2 || "l".repeat(cols),
4899
4989
  rows: tableRows,
4900
4990
  ...defaultFloatMeta()
4901
4991
  };
4902
- return insertDocument2BlockAfter(document2, afterBlockId2, block);
4992
+ return insertDocument2BlockAfter(document2, afterBlockId, block);
4903
4993
  }
4904
4994
  function normalizeImageInput(srcOrAsset) {
4905
4995
  if (typeof srcOrAsset === "string") {
@@ -4910,7 +5000,7 @@ function normalizeImageInput(srcOrAsset) {
4910
5000
  }
4911
5001
  return { value: "" };
4912
5002
  }
4913
- function addDocument2ImageBlock(document2, srcOrAsset, afterBlockId2) {
5003
+ function addDocument2ImageBlock(document2, srcOrAsset, afterBlockId) {
4914
5004
  const image = normalizeImageInput(srcOrAsset);
4915
5005
  const block = {
4916
5006
  id: document2Id("block"),
@@ -4921,7 +5011,7 @@ function addDocument2ImageBlock(document2, srcOrAsset, afterBlockId2) {
4921
5011
  options: { width: "0.8\\columnwidth" },
4922
5012
  ...defaultFloatMeta()
4923
5013
  };
4924
- return insertDocument2BlockAfter(document2, afterBlockId2, block);
5014
+ return insertDocument2BlockAfter(document2, afterBlockId, block);
4925
5015
  }
4926
5016
  function updateDocument2ImageValue(document2, blockId, value) {
4927
5017
  const next = cloneDocument(document2);
@@ -5211,7 +5301,7 @@ function removeCiteTokenById(document2, tokenId) {
5211
5301
  });
5212
5302
  return next;
5213
5303
  }
5214
- function ensureDocument2BibliographyBlock(document2, afterBlockId2) {
5304
+ function ensureDocument2BibliographyBlock(document2, afterBlockId) {
5215
5305
  if (document2.blocks.some((block2) => block2.kind === "bibliography")) {
5216
5306
  return document2;
5217
5307
  }
@@ -5221,7 +5311,7 @@ function ensureDocument2BibliographyBlock(document2, afterBlockId2) {
5221
5311
  command: "\\begin{thebibliography}",
5222
5312
  closing: "\\end{thebibliography}"
5223
5313
  };
5224
- return insertDocument2BlockAfter(document2, afterBlockId2 ?? null, block);
5314
+ return insertDocument2BlockAfter(document2, afterBlockId ?? null, block);
5225
5315
  }
5226
5316
  function addDocument2Reference(document2, partial = {}) {
5227
5317
  const next = cloneDocument(document2);
@@ -5293,6 +5383,764 @@ function updateDocument2Meta(document2, patch) {
5293
5383
  return next;
5294
5384
  }
5295
5385
 
5386
+ // src/document2/jsonCommandTypes.ts
5387
+ var Document2CommandError = class extends Error {
5388
+ code;
5389
+ constructor(code, message) {
5390
+ super(message);
5391
+ this.name = "Document2CommandError";
5392
+ this.code = code;
5393
+ }
5394
+ };
5395
+
5396
+ // src/document2/jsonCommandHelpers.ts
5397
+ function isObject3(value) {
5398
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5399
+ }
5400
+ function requireString2(value, field, allowEmpty = true) {
5401
+ if (typeof value !== "string" || !allowEmpty && value.trim().length === 0) {
5402
+ throw new Document2CommandError("invalid_command", `${field} must be a${allowEmpty ? "" : " non-empty"} string`);
5403
+ }
5404
+ return value;
5405
+ }
5406
+ function optionalString(value, field) {
5407
+ if (!(field in value)) {
5408
+ return void 0;
5409
+ }
5410
+ if (typeof value[field] !== "string") {
5411
+ throw new Document2CommandError("invalid_command", `${field} must be a string when provided`);
5412
+ }
5413
+ return value[field];
5414
+ }
5415
+ function requireBoolean(value, field) {
5416
+ if (typeof value !== "boolean") {
5417
+ throw new Document2CommandError("invalid_command", `${field} must be a boolean`);
5418
+ }
5419
+ return value;
5420
+ }
5421
+ function requireIndex(value, field) {
5422
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
5423
+ throw new Document2CommandError("invalid_command", `${field} must be a non-negative integer`);
5424
+ }
5425
+ return value;
5426
+ }
5427
+ function parseMetadata(value) {
5428
+ if (value === void 0) {
5429
+ return void 0;
5430
+ }
5431
+ if (!isObject3(value)) {
5432
+ throw new Document2CommandError("invalid_command", "metadata must be an object");
5433
+ }
5434
+ if (value.source === void 0) {
5435
+ return void 0;
5436
+ }
5437
+ if (value.source !== "agent" && value.source !== "user") {
5438
+ throw new Document2CommandError("invalid_command", "metadata.source must be agent or user");
5439
+ }
5440
+ return { source: value.source };
5441
+ }
5442
+ function parseBlockAnchor(value) {
5443
+ if (!isObject3(value)) {
5444
+ throw new Document2CommandError("invalid_anchor", "anchor must be an object");
5445
+ }
5446
+ const before = typeof value.before_block_id === "string" ? value.before_block_id.trim() : "";
5447
+ const after = typeof value.after_block_id === "string" ? value.after_block_id.trim() : "";
5448
+ const choices = Number(before.length > 0) + Number(after.length > 0) + Number(value.end === true);
5449
+ if (choices !== 1) {
5450
+ throw new Document2CommandError("invalid_anchor", "anchor requires exactly one block position");
5451
+ }
5452
+ if (before) {
5453
+ return { before_block_id: before };
5454
+ }
5455
+ if (after) {
5456
+ return { after_block_id: after };
5457
+ }
5458
+ return { end: true };
5459
+ }
5460
+ function parseListItemAnchor(value) {
5461
+ if (!isObject3(value)) {
5462
+ throw new Document2CommandError("invalid_anchor", "anchor must be an object");
5463
+ }
5464
+ const before = typeof value.before_item_id === "string" ? value.before_item_id.trim() : "";
5465
+ const after = typeof value.after_item_id === "string" ? value.after_item_id.trim() : "";
5466
+ const choices = Number(before.length > 0) + Number(after.length > 0) + Number(value.end === true);
5467
+ if (choices !== 1) {
5468
+ throw new Document2CommandError("invalid_anchor", "anchor requires exactly one list-item position");
5469
+ }
5470
+ if (before) {
5471
+ return { before_item_id: before };
5472
+ }
5473
+ if (after) {
5474
+ return { after_item_id: after };
5475
+ }
5476
+ return { end: true };
5477
+ }
5478
+ function blockInsertionIndex(document2, anchor) {
5479
+ if ("end" in anchor) {
5480
+ return document2.blocks.length;
5481
+ }
5482
+ const id = "before_block_id" in anchor ? anchor.before_block_id : anchor.after_block_id;
5483
+ const index = document2.blocks.findIndex((block) => block.id === id);
5484
+ if (index < 0) {
5485
+ throw new Document2CommandError("anchor_not_found", "Anchor block was not found");
5486
+ }
5487
+ return "before_block_id" in anchor ? index : index + 1;
5488
+ }
5489
+ function itemInsertionIndex(list, anchor) {
5490
+ if ("end" in anchor) {
5491
+ return list.items.length;
5492
+ }
5493
+ const id = "before_item_id" in anchor ? anchor.before_item_id : anchor.after_item_id;
5494
+ const index = list.items.findIndex((item) => item.id === id);
5495
+ if (index < 0) {
5496
+ throw new Document2CommandError("anchor_not_found", "Anchor list item was not found");
5497
+ }
5498
+ return "before_item_id" in anchor ? index : index + 1;
5499
+ }
5500
+ function findField(blocks, fieldId) {
5501
+ for (const block of blocks) {
5502
+ if (block.kind === "textBlock" && block.field.id === fieldId) {
5503
+ return block.field;
5504
+ }
5505
+ if (block.kind === "list") {
5506
+ for (const item of block.items) {
5507
+ if (item.field.id === fieldId) {
5508
+ return item.field;
5509
+ }
5510
+ const nested = findField(item.blocks, fieldId);
5511
+ if (nested) {
5512
+ return nested;
5513
+ }
5514
+ }
5515
+ }
5516
+ if (block.kind === "table") {
5517
+ for (const row of block.rows) {
5518
+ const cell = row.find((field) => field.id === fieldId);
5519
+ if (cell) {
5520
+ return cell;
5521
+ }
5522
+ }
5523
+ }
5524
+ }
5525
+ return null;
5526
+ }
5527
+ function findBlock(blocks, blockId) {
5528
+ for (const block of blocks) {
5529
+ if (block.id === blockId) {
5530
+ return block;
5531
+ }
5532
+ if (block.kind === "list") {
5533
+ for (const item of block.items) {
5534
+ const nested = findBlock(item.blocks, blockId);
5535
+ if (nested) {
5536
+ return nested;
5537
+ }
5538
+ }
5539
+ }
5540
+ }
5541
+ return null;
5542
+ }
5543
+ function collectIdentitySets(blocks, sets) {
5544
+ for (const block of blocks) {
5545
+ sets.blocks.add(block.id);
5546
+ if (block.kind === "textBlock") {
5547
+ sets.fields.add(block.field.id);
5548
+ block.field.tokens.forEach((token) => sets.tokens.add(token.id));
5549
+ } else if (block.kind === "list") {
5550
+ for (const item of block.items) {
5551
+ sets.items.add(item.id);
5552
+ sets.fields.add(item.field.id);
5553
+ item.field.tokens.forEach((token) => sets.tokens.add(token.id));
5554
+ collectIdentitySets(item.blocks, sets);
5555
+ }
5556
+ } else if (block.kind === "table") {
5557
+ for (const row of block.rows) {
5558
+ for (const field of row) {
5559
+ sets.fields.add(field.id);
5560
+ field.tokens.forEach((token) => sets.tokens.add(token.id));
5561
+ }
5562
+ }
5563
+ }
5564
+ }
5565
+ }
5566
+ function documentIdentitySets(document2) {
5567
+ const sets = {
5568
+ blocks: /* @__PURE__ */ new Set(),
5569
+ items: /* @__PURE__ */ new Set(),
5570
+ fields: /* @__PURE__ */ new Set(),
5571
+ tokens: /* @__PURE__ */ new Set()
5572
+ };
5573
+ collectIdentitySets(document2.blocks, sets);
5574
+ return sets;
5575
+ }
5576
+ function unusedGeneratedId(prefix, used) {
5577
+ let id = document2Id(prefix);
5578
+ while (used.has(id)) {
5579
+ id = document2Id(prefix);
5580
+ }
5581
+ return id;
5582
+ }
5583
+ function newBlockId(document2) {
5584
+ return unusedGeneratedId("block", documentIdentitySets(document2).blocks);
5585
+ }
5586
+ function newItemId(document2) {
5587
+ return unusedGeneratedId("item", documentIdentitySets(document2).items);
5588
+ }
5589
+ function newTokenId(document2, prefix) {
5590
+ return unusedGeneratedId(prefix, documentIdentitySets(document2).tokens);
5591
+ }
5592
+ function createCommandInlineField(document2, text) {
5593
+ const field = createInlineField2(text);
5594
+ const used = documentIdentitySets(document2);
5595
+ field.id = unusedGeneratedId("field", used.fields);
5596
+ field.tokens.forEach((token) => {
5597
+ token.id = unusedGeneratedId(token.kind, used.tokens);
5598
+ used.tokens.add(token.id);
5599
+ });
5600
+ return field;
5601
+ }
5602
+ function requireList(document2, listId) {
5603
+ const block = findBlock(document2.blocks, listId);
5604
+ if (!block) {
5605
+ throw new Document2CommandError("list_not_found", "Document list was not found");
5606
+ }
5607
+ if (block.kind !== "list") {
5608
+ throw new Document2CommandError("block_kind_mismatch", "Target block is not a list");
5609
+ }
5610
+ return block;
5611
+ }
5612
+ function requireTable(document2, tableId) {
5613
+ const block = findBlock(document2.blocks, tableId);
5614
+ if (!block) {
5615
+ throw new Document2CommandError("table_not_found", "Document table was not found");
5616
+ }
5617
+ if (block.kind !== "table") {
5618
+ throw new Document2CommandError("block_kind_mismatch", "Target block is not a table");
5619
+ }
5620
+ return block;
5621
+ }
5622
+ function fieldHasStructuredContent(field) {
5623
+ return field.tokens.some((token) => token.kind !== "text" || token.style !== void 0);
5624
+ }
5625
+ function replacePlainField(document2, field, text) {
5626
+ if (fieldHasStructuredContent(field)) {
5627
+ throw new Document2CommandError(
5628
+ "unsupported_inline_content",
5629
+ "Whole-field replacement is not supported for formatted or structured inline content"
5630
+ );
5631
+ }
5632
+ const next = createCommandInlineField(document2, text);
5633
+ field.tokens = next.tokens;
5634
+ }
5635
+ function textStylesEqual3(a, b) {
5636
+ return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
5637
+ }
5638
+ function compactTextTokens2(tokens, emptyTokenId = document2Id("text")) {
5639
+ const compacted = [];
5640
+ for (const token of tokens) {
5641
+ const previous = compacted[compacted.length - 1];
5642
+ if (previous?.kind === "text" && token.kind === "text" && textStylesEqual3(previous.style, token.style)) {
5643
+ previous.text += token.text;
5644
+ } else {
5645
+ compacted.push(token);
5646
+ }
5647
+ }
5648
+ return compacted.length > 0 ? compacted : [{ id: emptyTokenId, kind: "text", text: "" }];
5649
+ }
5650
+
5651
+ // src/document2/inlineJsonCommands.ts
5652
+ function parseInlineAnchor(value) {
5653
+ if (!isObject3(value)) {
5654
+ throw new Document2CommandError("invalid_anchor", "anchor must be an object");
5655
+ }
5656
+ const before = typeof value.before_token_id === "string" ? value.before_token_id.trim() : "";
5657
+ const after = typeof value.after_token_id === "string" ? value.after_token_id.trim() : "";
5658
+ const choices = Number(before.length > 0) + Number(after.length > 0) + Number(value.start === true) + Number(value.end === true);
5659
+ if (choices !== 1) {
5660
+ throw new Document2CommandError("invalid_anchor", "anchor requires exactly one inline-token position");
5661
+ }
5662
+ if (before) {
5663
+ return { before_token_id: before };
5664
+ }
5665
+ if (after) {
5666
+ return { after_token_id: after };
5667
+ }
5668
+ return value.start === true ? { start: true } : { end: true };
5669
+ }
5670
+ function parseStyle(value) {
5671
+ if (value === void 0) {
5672
+ return void 0;
5673
+ }
5674
+ if (!isObject3(value)) {
5675
+ throw new Document2CommandError("invalid_inline_token", "text token style must be an object");
5676
+ }
5677
+ const style = {};
5678
+ for (const key of ["bold", "italic", "underline"]) {
5679
+ if (value[key] !== void 0 && value[key] !== true) {
5680
+ throw new Document2CommandError("invalid_inline_token", `text token style.${key} must be true when provided`);
5681
+ }
5682
+ if (value[key] === true) {
5683
+ style[key] = true;
5684
+ }
5685
+ }
5686
+ return style.bold || style.italic || style.underline ? style : void 0;
5687
+ }
5688
+ function normalizedKeys(value) {
5689
+ if (!Array.isArray(value)) {
5690
+ throw new Document2CommandError("invalid_inline_token", "citation/reference keys must be an array");
5691
+ }
5692
+ const keys = [];
5693
+ for (const entry of value) {
5694
+ if (typeof entry !== "string") {
5695
+ throw new Document2CommandError("invalid_inline_token", "citation/reference keys must be strings");
5696
+ }
5697
+ const key = entry.trim();
5698
+ if (key.length > 0 && !keys.includes(key)) {
5699
+ keys.push(key);
5700
+ }
5701
+ }
5702
+ if (keys.length === 0) {
5703
+ throw new Document2CommandError("invalid_inline_token", "citation/reference requires at least one non-empty key");
5704
+ }
5705
+ return keys;
5706
+ }
5707
+ function parseMathObject(value, source) {
5708
+ if (!isObject3(value) || value.node_type !== "MathObject") {
5709
+ throw new Document2CommandError("math_object_mismatch", "math_object must be a structured MathObject");
5710
+ }
5711
+ const spans = detectInlineSpans2(source);
5712
+ const span = spans[0];
5713
+ if (spans.length !== 1 || span?.kind !== "math" || span.start !== 0 || span.end !== source.length) {
5714
+ throw new Document2CommandError("math_object_mismatch", "math source must contain exactly one complete delimited span");
5715
+ }
5716
+ if (value.math_mode !== span.opening || value.closing !== span.closing) {
5717
+ throw new Document2CommandError("math_object_mismatch", "math_object delimiters do not match math source");
5718
+ }
5719
+ try {
5720
+ fromMathObjectJson(value, value.source_side === "arabic" ? "arabic" : "english");
5721
+ } catch {
5722
+ throw new Document2CommandError("math_object_mismatch", "math_object must contain a valid structured equation");
5723
+ }
5724
+ return value;
5725
+ }
5726
+ function parseToken(value) {
5727
+ if (!isObject3(value) || typeof value.kind !== "string") {
5728
+ throw new Document2CommandError("invalid_inline_token", "token requires a kind");
5729
+ }
5730
+ if (value.kind === "text") {
5731
+ const text = requireString2(value.text, "token.text");
5732
+ if (detectInlineSpans2(text).length > 0) {
5733
+ throw new Document2CommandError("invalid_inline_token", "text token contains structured inline syntax");
5734
+ }
5735
+ const style = parseStyle(value.style);
5736
+ return { kind: "text", text, ...style ? { style } : {} };
5737
+ }
5738
+ if (value.kind === "cite") {
5739
+ return { kind: "cite", keys: normalizedKeys(value.keys) };
5740
+ }
5741
+ if (value.kind === "ref") {
5742
+ if (value.ref_command !== "ref" && value.ref_command !== "eqref") {
5743
+ throw new Document2CommandError("invalid_inline_token", "ref_command must be ref or eqref");
5744
+ }
5745
+ return { kind: "ref", keys: normalizedKeys(value.keys), ref_command: value.ref_command };
5746
+ }
5747
+ if (value.kind === "math") {
5748
+ const source = requireString2(value.source, "token.source", false);
5749
+ return { kind: "math", source, math_object: parseMathObject(value.math_object, source) };
5750
+ }
5751
+ throw new Document2CommandError("invalid_inline_token", "Unsupported inline token kind");
5752
+ }
5753
+ function parseInlineCommand(value) {
5754
+ if (value.op === "insert_inline_token") {
5755
+ return {
5756
+ op: value.op,
5757
+ field_id: requireString2(value.field_id, "field_id", false).trim(),
5758
+ token: parseToken(value.token),
5759
+ anchor: parseInlineAnchor(value.anchor)
5760
+ };
5761
+ }
5762
+ if (value.op === "replace_inline_token") {
5763
+ return {
5764
+ op: value.op,
5765
+ field_id: requireString2(value.field_id, "field_id", false).trim(),
5766
+ token_id: requireString2(value.token_id, "token_id", false).trim(),
5767
+ token: parseToken(value.token)
5768
+ };
5769
+ }
5770
+ if (value.op === "remove_inline_token") {
5771
+ return {
5772
+ op: value.op,
5773
+ field_id: requireString2(value.field_id, "field_id", false).trim(),
5774
+ token_id: requireString2(value.token_id, "token_id", false).trim()
5775
+ };
5776
+ }
5777
+ throw new Document2CommandError("invalid_command", "Unsupported inline command");
5778
+ }
5779
+ function liveToken(input, id) {
5780
+ if (input.kind === "text") {
5781
+ return { id, kind: "text", text: input.text, ...input.style ? { style: { ...input.style } } : {} };
5782
+ }
5783
+ if (input.kind === "cite") {
5784
+ return { id, kind: "cite", keys: [...input.keys] };
5785
+ }
5786
+ if (input.kind === "ref") {
5787
+ return { id, kind: "ref", keys: [...input.keys], refCommand: input.ref_command };
5788
+ }
5789
+ const mathObject = input.math_object;
5790
+ const spans = detectInlineSpans2(input.source);
5791
+ const span = spans[0];
5792
+ if (!span || span.kind !== "math") {
5793
+ throw new Document2CommandError("math_object_mismatch", "math source is not a complete delimited span");
5794
+ }
5795
+ return {
5796
+ id,
5797
+ kind: "math",
5798
+ display: span.display,
5799
+ opening: span.opening,
5800
+ closing: span.closing,
5801
+ source: input.source,
5802
+ sourceSide: mathObject.source_side === "arabic" ? "arabic" : "english",
5803
+ math: fromMathObjectJson(mathObject, mathObject.source_side === "arabic" ? "arabic" : "english"),
5804
+ editable: true,
5805
+ sourceOwner: mathObject.source_owner === "editor" ? "editor" : "imported-structured",
5806
+ ...mathObject.label_enabled !== void 0 ? { labelEnabled: mathObject.label_enabled } : {},
5807
+ ...mathObject.label !== void 0 ? { label: mathObject.label } : {}
5808
+ };
5809
+ }
5810
+ function insertionIndex(tokens, anchor) {
5811
+ if ("start" in anchor) {
5812
+ return 0;
5813
+ }
5814
+ if ("end" in anchor) {
5815
+ return tokens.length;
5816
+ }
5817
+ const id = "before_token_id" in anchor ? anchor.before_token_id : anchor.after_token_id;
5818
+ const index = tokens.findIndex((token) => token.id === id);
5819
+ if (index < 0) {
5820
+ throw new Document2CommandError("token_not_found", "Anchor token was not found");
5821
+ }
5822
+ return "before_token_id" in anchor ? index : index + 1;
5823
+ }
5824
+ function applyInlineCommand(document2, command) {
5825
+ const field = findField(document2.blocks, command.field_id);
5826
+ if (!field) {
5827
+ throw new Document2CommandError("field_not_found", "Inline field was not found");
5828
+ }
5829
+ if (command.op === "insert_inline_token") {
5830
+ const index2 = insertionIndex(field.tokens, command.anchor);
5831
+ field.tokens.splice(index2, 0, liveToken(command.token, newTokenId(document2, command.token.kind)));
5832
+ return;
5833
+ }
5834
+ const index = field.tokens.findIndex((token) => token.id === command.token_id);
5835
+ if (index < 0) {
5836
+ throw new Document2CommandError("token_not_found", "Inline token was not found");
5837
+ }
5838
+ if (command.op === "replace_inline_token") {
5839
+ field.tokens[index] = liveToken(command.token, command.token_id);
5840
+ return;
5841
+ }
5842
+ field.tokens = compactTextTokens2(
5843
+ [...field.tokens.slice(0, index), ...field.tokens.slice(index + 1)],
5844
+ newTokenId(document2, "text")
5845
+ );
5846
+ }
5847
+
5848
+ // src/document2/listJsonCommands.ts
5849
+ function stringArray(value, field) {
5850
+ if (!Array.isArray(value) || value.length === 0 || value.some((entry) => typeof entry !== "string")) {
5851
+ throw new Document2CommandError("invalid_command", `${field} must be a non-empty string array`);
5852
+ }
5853
+ return [...value];
5854
+ }
5855
+ function parseListCommand(value) {
5856
+ if (value.op === "insert_list") {
5857
+ const metadata = parseMetadata(value.metadata);
5858
+ return {
5859
+ op: value.op,
5860
+ ordered: requireBoolean(value.ordered, "ordered"),
5861
+ items: stringArray(value.items, "items"),
5862
+ anchor: parseBlockAnchor(value.anchor),
5863
+ ...metadata ? { metadata } : {}
5864
+ };
5865
+ }
5866
+ if (value.op === "insert_list_item") {
5867
+ return {
5868
+ op: value.op,
5869
+ list_id: requireString2(value.list_id, "list_id", false).trim(),
5870
+ text: requireString2(value.text, "text"),
5871
+ anchor: parseListItemAnchor(value.anchor)
5872
+ };
5873
+ }
5874
+ if (value.op === "replace_list_item") {
5875
+ return {
5876
+ op: value.op,
5877
+ list_id: requireString2(value.list_id, "list_id", false).trim(),
5878
+ item_id: requireString2(value.item_id, "item_id", false).trim(),
5879
+ text: requireString2(value.text, "text")
5880
+ };
5881
+ }
5882
+ if (value.op === "remove_list_item") {
5883
+ return {
5884
+ op: value.op,
5885
+ list_id: requireString2(value.list_id, "list_id", false).trim(),
5886
+ item_id: requireString2(value.item_id, "item_id", false).trim()
5887
+ };
5888
+ }
5889
+ if (value.op === "move_list_item") {
5890
+ return {
5891
+ op: value.op,
5892
+ list_id: requireString2(value.list_id, "list_id", false).trim(),
5893
+ item_id: requireString2(value.item_id, "item_id", false).trim(),
5894
+ anchor: parseListItemAnchor(value.anchor)
5895
+ };
5896
+ }
5897
+ throw new Document2CommandError("invalid_command", "Unsupported list command");
5898
+ }
5899
+ function newItem(document2, text) {
5900
+ return { id: newItemId(document2), field: createCommandInlineField(document2, text), blocks: [] };
5901
+ }
5902
+ function insertList(document2, command) {
5903
+ const ordered = command.ordered;
5904
+ const block = {
5905
+ id: newBlockId(document2),
5906
+ kind: "list",
5907
+ command: ordered ? "\\begin{enumerate}" : "\\begin{itemize}",
5908
+ closing: ordered ? "\\end{enumerate}" : "\\end{itemize}",
5909
+ items: command.items.map((text) => newItem(document2, text)),
5910
+ ...command.metadata ? { metadata: { ...command.metadata } } : {}
5911
+ };
5912
+ document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block);
5913
+ }
5914
+ function applyListCommand(document2, command) {
5915
+ if (command.op === "insert_list") {
5916
+ insertList(document2, command);
5917
+ return;
5918
+ }
5919
+ const list = requireList(document2, command.list_id);
5920
+ if (command.op === "insert_list_item") {
5921
+ list.items.splice(itemInsertionIndex(list, command.anchor), 0, newItem(document2, command.text));
5922
+ return;
5923
+ }
5924
+ const itemIndex = list.items.findIndex((item2) => item2.id === command.item_id);
5925
+ if (itemIndex < 0) {
5926
+ throw new Document2CommandError("item_not_found", "Document list item was not found");
5927
+ }
5928
+ if (command.op === "replace_list_item") {
5929
+ replacePlainField(document2, list.items[itemIndex].field, command.text);
5930
+ return;
5931
+ }
5932
+ if (command.op === "remove_list_item") {
5933
+ if (list.items.length === 1) {
5934
+ throw new Document2CommandError("minimum_structure", "A list must retain at least one item");
5935
+ }
5936
+ list.items.splice(itemIndex, 1);
5937
+ return;
5938
+ }
5939
+ 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;
5940
+ if (anchorId === command.item_id) {
5941
+ throw new Document2CommandError("invalid_anchor", "A list item cannot be moved relative to itself");
5942
+ }
5943
+ const [item] = list.items.splice(itemIndex, 1);
5944
+ if (!item) {
5945
+ throw new Document2CommandError("item_not_found", "Document list item was not found");
5946
+ }
5947
+ list.items.splice(itemInsertionIndex(list, command.anchor), 0, item);
5948
+ }
5949
+
5950
+ // src/document2/tableJsonCommands.ts
5951
+ function stringArray2(value, field) {
5952
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
5953
+ throw new Document2CommandError("invalid_command", `${field} must be a string array`);
5954
+ }
5955
+ return [...value];
5956
+ }
5957
+ function rectangularRows(value) {
5958
+ if (!Array.isArray(value) || value.length === 0) {
5959
+ throw new Document2CommandError("invalid_table_shape", "rows must be a non-empty array");
5960
+ }
5961
+ const rows = value.map((row, index) => stringArray2(row, `rows[${String(index)}]`));
5962
+ const columnCount = rows[0]?.length ?? 0;
5963
+ if (columnCount === 0 || rows.some((row) => row.length !== columnCount)) {
5964
+ throw new Document2CommandError("invalid_table_shape", "Table rows must form a non-empty rectangle");
5965
+ }
5966
+ return rows;
5967
+ }
5968
+ function columns(value) {
5969
+ const result = requireString2(value, "columns", false);
5970
+ if (result.trim().length === 0) {
5971
+ throw new Document2CommandError("invalid_table_shape", "columns must be a non-empty LaTeX specification");
5972
+ }
5973
+ return result;
5974
+ }
5975
+ function parseTableCommand(value) {
5976
+ if (value.op === "insert_table") {
5977
+ const caption = optionalString(value, "caption");
5978
+ const label = optionalString(value, "label");
5979
+ const metadata = parseMetadata(value.metadata);
5980
+ return {
5981
+ op: value.op,
5982
+ rows: rectangularRows(value.rows),
5983
+ columns: columns(value.columns),
5984
+ ...caption !== void 0 ? { caption } : {},
5985
+ ...label !== void 0 ? { label } : {},
5986
+ anchor: parseBlockAnchor(value.anchor),
5987
+ ...metadata ? { metadata } : {}
5988
+ };
5989
+ }
5990
+ if (value.op === "replace_table_cell") {
5991
+ return {
5992
+ op: value.op,
5993
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
5994
+ row_index: requireIndex(value.row_index, "row_index"),
5995
+ column_index: requireIndex(value.column_index, "column_index"),
5996
+ text: requireString2(value.text, "text")
5997
+ };
5998
+ }
5999
+ if (value.op === "insert_table_row") {
6000
+ return {
6001
+ op: value.op,
6002
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6003
+ index: requireIndex(value.index, "index"),
6004
+ values: stringArray2(value.values, "values")
6005
+ };
6006
+ }
6007
+ if (value.op === "remove_table_row") {
6008
+ return {
6009
+ op: value.op,
6010
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6011
+ index: requireIndex(value.index, "index")
6012
+ };
6013
+ }
6014
+ if (value.op === "move_table_row") {
6015
+ return {
6016
+ op: value.op,
6017
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6018
+ from_index: requireIndex(value.from_index, "from_index"),
6019
+ to_index: requireIndex(value.to_index, "to_index")
6020
+ };
6021
+ }
6022
+ if (value.op === "insert_table_column") {
6023
+ return {
6024
+ op: value.op,
6025
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6026
+ index: requireIndex(value.index, "index"),
6027
+ values: stringArray2(value.values, "values"),
6028
+ columns: columns(value.columns)
6029
+ };
6030
+ }
6031
+ if (value.op === "remove_table_column") {
6032
+ return {
6033
+ op: value.op,
6034
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6035
+ index: requireIndex(value.index, "index"),
6036
+ columns: columns(value.columns)
6037
+ };
6038
+ }
6039
+ if (value.op === "move_table_column") {
6040
+ return {
6041
+ op: value.op,
6042
+ table_id: requireString2(value.table_id, "table_id", false).trim(),
6043
+ from_index: requireIndex(value.from_index, "from_index"),
6044
+ to_index: requireIndex(value.to_index, "to_index"),
6045
+ columns: columns(value.columns)
6046
+ };
6047
+ }
6048
+ throw new Document2CommandError("invalid_command", "Unsupported table command");
6049
+ }
6050
+ function insertTable(document2, command) {
6051
+ const block = {
6052
+ id: newBlockId(document2),
6053
+ kind: "table",
6054
+ command: "\\begin{tabular}",
6055
+ closing: "\\end{tabular}",
6056
+ columns: command.columns,
6057
+ rows: command.rows.map((row) => row.map((cell) => createCommandInlineField(document2, cell))),
6058
+ ...defaultFloatMeta(),
6059
+ ...command.caption !== void 0 ? { caption: command.caption, captionEnabled: command.caption.trim().length > 0 } : {},
6060
+ ...command.label !== void 0 ? { label: command.label, labelEnabled: command.label.trim().length > 0 } : {},
6061
+ ...command.metadata ? { metadata: { ...command.metadata } } : {}
6062
+ };
6063
+ document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block);
6064
+ }
6065
+ function checkCell(table, rowIndex, columnIndex) {
6066
+ if (rowIndex >= table.rows.length || columnIndex >= (table.rows[rowIndex]?.length ?? 0)) {
6067
+ throw new Document2CommandError("index_out_of_range", "Table cell index is out of range");
6068
+ }
6069
+ }
6070
+ function checkExistingIndex(index, length, field) {
6071
+ if (index >= length) {
6072
+ throw new Document2CommandError("index_out_of_range", `${field} is out of range`);
6073
+ }
6074
+ }
6075
+ function applyTableCommand(document2, command) {
6076
+ if (command.op === "insert_table") {
6077
+ insertTable(document2, command);
6078
+ return;
6079
+ }
6080
+ const table = requireTable(document2, command.table_id);
6081
+ const columnCount = table.rows[0]?.length ?? 0;
6082
+ if (table.rows.length === 0 || columnCount === 0 || table.rows.some((row) => row.length !== columnCount)) {
6083
+ throw new Document2CommandError("invalid_table_shape", "Target table must be a non-empty rectangle");
6084
+ }
6085
+ if (command.op === "replace_table_cell") {
6086
+ checkCell(table, command.row_index, command.column_index);
6087
+ replacePlainField(document2, table.rows[command.row_index][command.column_index], command.text);
6088
+ return;
6089
+ }
6090
+ if (command.op === "insert_table_row") {
6091
+ if (command.index > table.rows.length) {
6092
+ throw new Document2CommandError("index_out_of_range", "Table row insertion index is out of range");
6093
+ }
6094
+ if (command.values.length !== columnCount) {
6095
+ throw new Document2CommandError("invalid_table_shape", "Inserted row must match the current column count");
6096
+ }
6097
+ table.rows.splice(command.index, 0, command.values.map((value) => createCommandInlineField(document2, value)));
6098
+ return;
6099
+ }
6100
+ if (command.op === "remove_table_row") {
6101
+ checkExistingIndex(command.index, table.rows.length, "Table row index");
6102
+ if (table.rows.length === 1) {
6103
+ throw new Document2CommandError("minimum_structure", "A table must retain at least one row");
6104
+ }
6105
+ table.rows.splice(command.index, 1);
6106
+ return;
6107
+ }
6108
+ if (command.op === "move_table_row") {
6109
+ checkExistingIndex(command.from_index, table.rows.length, "Table source row index");
6110
+ checkExistingIndex(command.to_index, table.rows.length, "Table target row index");
6111
+ const [row] = table.rows.splice(command.from_index, 1);
6112
+ table.rows.splice(command.to_index, 0, row);
6113
+ return;
6114
+ }
6115
+ if (command.op === "insert_table_column") {
6116
+ if (command.index > columnCount) {
6117
+ throw new Document2CommandError("index_out_of_range", "Table column insertion index is out of range");
6118
+ }
6119
+ if (command.values.length !== table.rows.length) {
6120
+ throw new Document2CommandError("invalid_table_shape", "Inserted column requires one value per row");
6121
+ }
6122
+ table.rows.forEach((row, index) => row.splice(command.index, 0, createCommandInlineField(document2, command.values[index])));
6123
+ table.columns = command.columns;
6124
+ return;
6125
+ }
6126
+ if (command.op === "remove_table_column") {
6127
+ checkExistingIndex(command.index, columnCount, "Table column index");
6128
+ if (columnCount === 1) {
6129
+ throw new Document2CommandError("minimum_structure", "A table must retain at least one column");
6130
+ }
6131
+ table.rows.forEach((row) => row.splice(command.index, 1));
6132
+ table.columns = command.columns;
6133
+ return;
6134
+ }
6135
+ checkExistingIndex(command.from_index, columnCount, "Table source column index");
6136
+ checkExistingIndex(command.to_index, columnCount, "Table target column index");
6137
+ table.rows.forEach((row) => {
6138
+ const [cell] = row.splice(command.from_index, 1);
6139
+ row.splice(command.to_index, 0, cell);
6140
+ });
6141
+ table.columns = command.columns;
6142
+ }
6143
+
5296
6144
  // src/document2/exportJson.ts
5297
6145
  function serializeField(field) {
5298
6146
  let value = "";
@@ -5345,12 +6193,13 @@ function serializeField(field) {
5345
6193
  ...token.label !== void 0 ? { label: token.label } : {}
5346
6194
  });
5347
6195
  }
5348
- return { value, formats, mathObjects, hasPersistedMath };
6196
+ return { value, formats, mathObjects, hasPersistedMath, inlineIds: inlineIdsFromField(field) };
5349
6197
  }
5350
6198
  function fieldJson(field) {
5351
6199
  const serialized = serializeField(field);
5352
6200
  return {
5353
6201
  value: serialized.value,
6202
+ inline_ids: serialized.inlineIds,
5354
6203
  ...serialized.formats.length > 0 ? { formats: serialized.formats } : {},
5355
6204
  ...serialized.hasPersistedMath ? { math_objects: serialized.mathObjects } : {}
5356
6205
  };
@@ -5358,7 +6207,9 @@ function fieldJson(field) {
5358
6207
  function listItemJson(item) {
5359
6208
  const field = fieldJson(item.field);
5360
6209
  return {
6210
+ id: item.id,
5361
6211
  value: field.value ?? "",
6212
+ inline_ids: field.inline_ids,
5362
6213
  ...field.formats ? { formats: field.formats } : {},
5363
6214
  ...field.math_objects ? { math_objects: field.math_objects } : {},
5364
6215
  ...item.blocks.length > 0 ? { blocks: item.blocks.map(blockJson) } : {}
@@ -5370,6 +6221,7 @@ function blockJson(block) {
5370
6221
  id: block.id,
5371
6222
  command: block.command,
5372
6223
  ...fieldJson(block.field),
6224
+ ...block.metadata ? { metadata: { ...block.metadata } } : {},
5373
6225
  ...block.command === "\\paragraph" ? { centered: block.centered === true } : {}
5374
6226
  };
5375
6227
  }
@@ -5378,7 +6230,8 @@ function blockJson(block) {
5378
6230
  id: block.id,
5379
6231
  command: block.command,
5380
6232
  closing: block.closing,
5381
- items: block.items.map(listItemJson)
6233
+ items: block.items.map(listItemJson),
6234
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
5382
6235
  };
5383
6236
  }
5384
6237
  if (block.kind === "table") {
@@ -5390,13 +6243,15 @@ function blockJson(block) {
5390
6243
  closing: block.closing,
5391
6244
  columns: block.columns,
5392
6245
  rows: fields.map((row) => row.map((field) => field.value)),
6246
+ cell_inline_ids: fields.map((row) => row.map((field) => field.inlineIds)),
5393
6247
  ...fields.some((row) => row.some((field) => field.formats.length > 0)) ? { cell_formats: fields.map((row) => row.map((field) => field.formats)) } : {},
5394
6248
  ...hasPersistedMath ? { math_objects: fields.flatMap((row) => row.flatMap((field) => field.mathObjects)) } : {},
5395
6249
  centered: block.centered,
5396
6250
  caption_enabled: block.captionEnabled,
5397
6251
  caption: block.caption,
5398
6252
  label_enabled: block.labelEnabled,
5399
- label: block.label
6253
+ label: block.label,
6254
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
5400
6255
  };
5401
6256
  }
5402
6257
  if (block.kind === "image") {
@@ -5410,13 +6265,24 @@ function blockJson(block) {
5410
6265
  caption_enabled: block.captionEnabled,
5411
6266
  caption: block.caption,
5412
6267
  label_enabled: block.labelEnabled,
5413
- label: block.label
6268
+ label: block.label,
6269
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
5414
6270
  };
5415
6271
  }
5416
6272
  if (block.kind === "bibliography") {
5417
- return { id: block.id, command: block.command, closing: block.closing };
6273
+ return {
6274
+ id: block.id,
6275
+ command: block.command,
6276
+ closing: block.closing,
6277
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6278
+ };
5418
6279
  }
5419
- return { id: block.id, command: block.command, value: block.value };
6280
+ return {
6281
+ id: block.id,
6282
+ command: block.command,
6283
+ value: block.value,
6284
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6285
+ };
5420
6286
  }
5421
6287
  function referenceJson(reference) {
5422
6288
  return {
@@ -5447,49 +6313,14 @@ function toDocumentJson2(document2) {
5447
6313
  }
5448
6314
 
5449
6315
  // src/document2/jsonCommands.ts
5450
- var Document2CommandError = class extends Error {
5451
- code;
5452
- constructor(code, message) {
5453
- super(message);
5454
- this.name = "Document2CommandError";
5455
- this.code = code;
5456
- }
5457
- };
5458
- function isObject3(value) {
5459
- return typeof value === "object" && value !== null && !Array.isArray(value);
6316
+ function isInlineCommand(command) {
6317
+ return command.op === "insert_inline_token" || command.op === "replace_inline_token" || command.op === "remove_inline_token";
5460
6318
  }
5461
- function requireString2(value, field, allowEmpty = true) {
5462
- if (typeof value !== "string" || !allowEmpty && value.trim().length === 0) {
5463
- throw new Document2CommandError("invalid_command", `${field} must be a${allowEmpty ? "" : " non-empty"} string`);
5464
- }
5465
- return value;
6319
+ function isListCommand(command) {
6320
+ 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";
5466
6321
  }
5467
- function parseAnchor(value) {
5468
- if (!isObject3(value)) {
5469
- throw new Document2CommandError("invalid_command", "anchor must be an object");
5470
- }
5471
- const hasEnd = value.end === true;
5472
- const afterBlockId2 = typeof value.after_block_id === "string" ? value.after_block_id.trim() : "";
5473
- const hasAfterBlock = afterBlockId2.length > 0;
5474
- if (hasEnd && hasAfterBlock) {
5475
- throw new Document2CommandError("invalid_command", "anchor cannot contain both end and after_block_id");
5476
- }
5477
- if (hasEnd) {
5478
- return { end: true };
5479
- }
5480
- if (hasAfterBlock) {
5481
- return { after_block_id: afterBlockId2 };
5482
- }
5483
- throw new Document2CommandError("invalid_command", "anchor requires end: true or a non-empty after_block_id");
5484
- }
5485
- function optionalString(value, field) {
5486
- if (!(field in value)) {
5487
- return void 0;
5488
- }
5489
- if (typeof value[field] !== "string") {
5490
- throw new Document2CommandError("invalid_command", `${field} must be a string when provided`);
5491
- }
5492
- return value[field];
6322
+ function isTableCommand(command) {
6323
+ 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";
5493
6324
  }
5494
6325
  function parseTextBlockKind(value) {
5495
6326
  if (value === "section" || value === "subsection" || value === "subsubsection" || value === "paragraph") {
@@ -5497,16 +6328,15 @@ function parseTextBlockKind(value) {
5497
6328
  }
5498
6329
  throw new Document2CommandError("invalid_command", "kind must be section, subsection, subsubsection, or paragraph");
5499
6330
  }
5500
- function parseCommand2(value) {
5501
- if (!isObject3(value) || typeof value.op !== "string") {
5502
- throw new Document2CommandError("invalid_command", "command requires an op");
5503
- }
6331
+ function parseBasicCommand(value) {
5504
6332
  if (value.op === "insert_text_block") {
6333
+ const metadata = parseMetadata(value.metadata);
5505
6334
  return {
5506
6335
  op: value.op,
5507
6336
  kind: parseTextBlockKind(value.kind),
5508
6337
  text: requireString2(value.text, "text"),
5509
- anchor: parseAnchor(value.anchor)
6338
+ anchor: parseBlockAnchor(value.anchor),
6339
+ ...metadata ? { metadata } : {}
5510
6340
  };
5511
6341
  }
5512
6342
  if (value.op === "replace_text_block") {
@@ -5517,140 +6347,118 @@ function parseCommand2(value) {
5517
6347
  };
5518
6348
  }
5519
6349
  if (value.op === "remove_block") {
5520
- return {
5521
- op: value.op,
5522
- block_id: requireString2(value.block_id, "block_id", false).trim()
5523
- };
6350
+ return { op: value.op, block_id: requireString2(value.block_id, "block_id", false).trim() };
5524
6351
  }
5525
6352
  if (value.op === "insert_figure") {
5526
6353
  const figureValue = optionalString(value, "value");
5527
6354
  const caption = optionalString(value, "caption");
5528
6355
  const label = optionalString(value, "label");
6356
+ const metadata = parseMetadata(value.metadata);
5529
6357
  return {
5530
6358
  op: value.op,
5531
6359
  asset_id: requireString2(value.asset_id, "asset_id", false).trim(),
5532
6360
  ...figureValue !== void 0 ? { value: figureValue } : {},
5533
6361
  ...caption !== void 0 ? { caption } : {},
5534
6362
  ...label !== void 0 ? { label } : {},
5535
- anchor: parseAnchor(value.anchor)
6363
+ anchor: parseBlockAnchor(value.anchor),
6364
+ ...metadata ? { metadata } : {}
5536
6365
  };
5537
6366
  }
5538
- throw new Document2CommandError("invalid_command", `Unsupported document command: ${value.op}`);
6367
+ throw new Document2CommandError("invalid_command", "Unsupported block command");
6368
+ }
6369
+ function parseCommand2(value) {
6370
+ if (!isObject3(value) || typeof value.op !== "string") {
6371
+ throw new Document2CommandError("invalid_command", "command requires an op");
6372
+ }
6373
+ if (value.op === "insert_inline_token" || value.op === "replace_inline_token" || value.op === "remove_inline_token") {
6374
+ return parseInlineCommand(value);
6375
+ }
6376
+ 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") {
6377
+ return parseListCommand(value);
6378
+ }
6379
+ 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") {
6380
+ return parseTableCommand(value);
6381
+ }
6382
+ if (value.op === "insert_text_block" || value.op === "replace_text_block" || value.op === "remove_block" || value.op === "insert_figure") {
6383
+ return parseBasicCommand(value);
6384
+ }
6385
+ throw new Document2CommandError("invalid_command", "Unsupported document command");
5539
6386
  }
5540
6387
  function parseDocument(json) {
5541
6388
  try {
5542
6389
  return fromDocumentJson2(json);
5543
- } catch (error) {
5544
- const message = error instanceof Error ? error.message : "Invalid DocumentObject JSON";
5545
- throw new Document2CommandError("invalid_document", message);
6390
+ } catch {
6391
+ throw new Document2CommandError("invalid_document", "Document JSON is invalid");
5546
6392
  }
5547
6393
  }
5548
- function commandForKind(kind) {
6394
+ function textCommand(kind) {
5549
6395
  if (kind === "section") {
5550
6396
  return "\\section";
5551
6397
  }
5552
6398
  if (kind === "subsection") {
5553
6399
  return "\\subsection";
5554
6400
  }
5555
- if (kind === "subsubsection") {
5556
- return "\\subsubsection";
5557
- }
5558
- return "\\paragraph";
5559
- }
5560
- function insertionIndex(document2, anchor) {
5561
- if ("end" in anchor) {
5562
- return document2.blocks.length;
5563
- }
5564
- const index = document2.blocks.findIndex((block) => block.id === anchor.after_block_id);
5565
- if (index < 0) {
5566
- throw new Document2CommandError("anchor_not_found", `Anchor block was not found: ${anchor.after_block_id}`);
5567
- }
5568
- return index + 1;
5569
- }
5570
- function afterBlockId(document2, index) {
5571
- return index > 0 ? document2.blocks[index - 1]?.id ?? null : null;
6401
+ return kind === "subsubsection" ? "\\subsubsection" : "\\paragraph";
5572
6402
  }
5573
- function insertedBlock(document2, index) {
5574
- const block = document2.blocks[index];
5575
- if (!block) {
5576
- throw new Document2CommandError("invalid_command", "Document command did not insert a block");
5577
- }
5578
- return block;
5579
- }
5580
- function insertTextBlock(document2, command) {
5581
- const index = insertionIndex(document2, command.anchor);
5582
- const next = addDocument2TextBlock(document2, commandForKind(command.kind), afterBlockId(document2, index));
5583
- const block = insertedBlock(next, index);
5584
- if (block.kind !== "textBlock") {
5585
- throw new Document2CommandError("block_kind_mismatch", "Inserted block is not a text block");
5586
- }
5587
- block.field = createInlineField2(command.text);
5588
- return next;
5589
- }
5590
- function replaceTextBlock(document2, command) {
5591
- const index = document2.blocks.findIndex((block2) => block2.id === command.block_id);
5592
- if (index < 0) {
5593
- throw new Document2CommandError("block_not_found", `Document block was not found: ${command.block_id}`);
5594
- }
5595
- const block = document2.blocks[index];
5596
- if (!block || block.kind !== "textBlock") {
5597
- throw new Document2CommandError("block_kind_mismatch", `Document block is not a text block: ${command.block_id}`);
5598
- }
5599
- if (block.field.tokens.some((token) => token.kind !== "text" || token.style !== void 0)) {
5600
- throw new Document2CommandError(
5601
- "unsupported_inline_content",
5602
- "Whole-block replacement is not supported for formatted or structured inline content"
5603
- );
5604
- }
5605
- const next = parseDocument(toDocumentJson2(document2));
5606
- const nextBlock = next.blocks[index];
5607
- if (!nextBlock || nextBlock.kind !== "textBlock") {
5608
- throw new Document2CommandError("block_kind_mismatch", `Document block is not a text block: ${command.block_id}`);
5609
- }
5610
- nextBlock.field = createInlineField2(command.text);
5611
- return next;
5612
- }
5613
- function removeBlock(document2, command) {
5614
- if (!document2.blocks.some((block) => block.id === command.block_id)) {
5615
- throw new Document2CommandError("block_not_found", `Document block was not found: ${command.block_id}`);
5616
- }
5617
- return removeDocument2BlockById(document2, command.block_id);
5618
- }
5619
- function insertFigure(document2, command) {
5620
- const index = insertionIndex(document2, command.anchor);
5621
- const next = addDocument2ImageBlock(
5622
- document2,
5623
- { assetId: command.asset_id, ...command.value !== void 0 ? { value: command.value } : {} },
5624
- afterBlockId(document2, index)
5625
- );
5626
- const block = insertedBlock(next, index);
5627
- if (block.kind !== "image") {
5628
- throw new Document2CommandError("block_kind_mismatch", "Inserted block is not a figure");
6403
+ function applyBasicCommand(document2, command) {
6404
+ if (command.op === "insert_text_block") {
6405
+ const block2 = {
6406
+ id: newBlockId(document2),
6407
+ kind: "textBlock",
6408
+ command: textCommand(command.kind),
6409
+ field: createCommandInlineField(document2, command.text),
6410
+ ...command.kind === "paragraph" ? { centered: false } : {},
6411
+ ...command.metadata ? { metadata: { ...command.metadata } } : {}
6412
+ };
6413
+ document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block2);
6414
+ return;
5629
6415
  }
5630
- if (command.caption !== void 0) {
5631
- block.caption = command.caption;
5632
- block.captionEnabled = command.caption.trim().length > 0;
6416
+ if (command.op === "replace_text_block") {
6417
+ const block2 = document2.blocks.find((entry) => entry.id === command.block_id);
6418
+ if (!block2) {
6419
+ throw new Document2CommandError("block_not_found", "Document block was not found");
6420
+ }
6421
+ if (block2.kind !== "textBlock") {
6422
+ throw new Document2CommandError("block_kind_mismatch", "Document block is not a text block");
6423
+ }
6424
+ replacePlainField(document2, block2.field, command.text);
6425
+ return;
5633
6426
  }
5634
- if (command.label !== void 0) {
5635
- block.label = command.label;
5636
- block.labelEnabled = command.label.trim().length > 0;
6427
+ if (command.op === "remove_block") {
6428
+ const index = document2.blocks.findIndex((block2) => block2.id === command.block_id);
6429
+ if (index < 0) {
6430
+ throw new Document2CommandError("block_not_found", "Document block was not found");
6431
+ }
6432
+ document2.blocks.splice(index, 1);
6433
+ return;
5637
6434
  }
5638
- return next;
6435
+ const block = {
6436
+ id: newBlockId(document2),
6437
+ kind: "image",
6438
+ command: "\\includegraphics",
6439
+ value: command.value ?? command.asset_id,
6440
+ assetId: command.asset_id,
6441
+ options: { width: "0.8\\columnwidth" },
6442
+ ...defaultFloatMeta(),
6443
+ ...command.caption !== void 0 ? { caption: command.caption, captionEnabled: command.caption.trim().length > 0 } : {},
6444
+ ...command.label !== void 0 ? { label: command.label, labelEnabled: command.label.trim().length > 0 } : {},
6445
+ ...command.metadata ? { metadata: { ...command.metadata } } : {}
6446
+ };
6447
+ document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block);
5639
6448
  }
5640
6449
  function applyDocument2Command(json, commandInput) {
5641
6450
  const document2 = parseDocument(json);
5642
6451
  const command = parseCommand2(commandInput);
5643
- let next;
5644
- if (command.op === "insert_text_block") {
5645
- next = insertTextBlock(document2, command);
5646
- } else if (command.op === "replace_text_block") {
5647
- next = replaceTextBlock(document2, command);
5648
- } else if (command.op === "remove_block") {
5649
- next = removeBlock(document2, command);
6452
+ if (isInlineCommand(command)) {
6453
+ applyInlineCommand(document2, command);
6454
+ } else if (isListCommand(command)) {
6455
+ applyListCommand(document2, command);
6456
+ } else if (isTableCommand(command)) {
6457
+ applyTableCommand(document2, command);
5650
6458
  } else {
5651
- next = insertFigure(document2, command);
6459
+ applyBasicCommand(document2, command);
5652
6460
  }
5653
- return toDocumentJson2(next);
6461
+ return toDocumentJson2(document2);
5654
6462
  }
5655
6463
 
5656
6464
  // src/document2/outline.ts
@@ -6350,19 +7158,21 @@ function previewInlines(field, islands, output, equationSide, document2, preview
6350
7158
  }
6351
7159
  function textBlockPreview(block, islands, output, equationSide, document2, previewOptions) {
6352
7160
  const inlines = previewInlines(block.field, islands, output, equationSide, document2, previewOptions);
7161
+ const metadata = block.metadata ? { metadata: { ...block.metadata } } : {};
6353
7162
  if (block.command === "\\section") {
6354
- return { kind: "heading", id: block.id, level: 1, inlines };
7163
+ return { kind: "heading", id: block.id, level: 1, inlines, ...metadata };
6355
7164
  }
6356
7165
  if (block.command === "\\subsection") {
6357
- return { kind: "heading", id: block.id, level: 2, inlines };
7166
+ return { kind: "heading", id: block.id, level: 2, inlines, ...metadata };
6358
7167
  }
6359
7168
  if (block.command === "\\subsubsection") {
6360
- return { kind: "heading", id: block.id, level: 3, inlines };
7169
+ return { kind: "heading", id: block.id, level: 3, inlines, ...metadata };
6361
7170
  }
6362
7171
  return {
6363
7172
  kind: "paragraph",
6364
7173
  id: block.id,
6365
7174
  inlines,
7175
+ ...metadata,
6366
7176
  ...block.centered ? { centered: true } : {}
6367
7177
  };
6368
7178
  }
@@ -6393,7 +7203,8 @@ function blockPreview(block, islands, output, equationSide, document2, previewOp
6393
7203
  id: item.id,
6394
7204
  inlines: previewInlines(item.field, islands, output, equationSide, document2, previewOptions),
6395
7205
  blocks: item.blocks.map((child) => blockPreview(child, islands, output, equationSide, document2, previewOptions))
6396
- }))
7206
+ })),
7207
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6397
7208
  };
6398
7209
  }
6399
7210
  if (block.kind === "table") {
@@ -6403,7 +7214,8 @@ function blockPreview(block, islands, output, equationSide, document2, previewOp
6403
7214
  id: block.id,
6404
7215
  rows: block.rows.map((row) => row.map((cell) => previewInlines(cell, islands, output, equationSide, document2, previewOptions))),
6405
7216
  ...block.captionEnabled && block.caption.trim().length > 0 ? { caption: block.caption.trim() } : {},
6406
- ...label.length > 0 ? { label, numberLabel: floatNumberLabel(document2, label, previewOptions) } : {}
7217
+ ...label.length > 0 ? { label, numberLabel: floatNumberLabel(document2, label, previewOptions) } : {},
7218
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6407
7219
  };
6408
7220
  }
6409
7221
  if (block.kind === "image") {
@@ -6415,7 +7227,8 @@ function blockPreview(block, islands, output, equationSide, document2, previewOp
6415
7227
  ...block.assetId !== void 0 ? { assetId: block.assetId } : {},
6416
7228
  options: block.options,
6417
7229
  ...block.captionEnabled && block.caption.trim().length > 0 ? { caption: block.caption.trim() } : {},
6418
- ...label.length > 0 ? { label, numberLabel: floatNumberLabel(document2, label, previewOptions) } : {}
7230
+ ...label.length > 0 ? { label, numberLabel: floatNumberLabel(document2, label, previewOptions) } : {},
7231
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6419
7232
  };
6420
7233
  }
6421
7234
  if (block.kind === "bibliography") {
@@ -6435,10 +7248,11 @@ function blockPreview(block, islands, output, equationSide, document2, previewOp
6435
7248
  url: reference.url,
6436
7249
  venue: reference.venue,
6437
7250
  fieldSeparator: reference.fieldSeparator
6438
- }))
7251
+ })),
7252
+ ...block.metadata ? { metadata: { ...block.metadata } } : {}
6439
7253
  };
6440
7254
  }
6441
- return { kind: "omit", id: block.id };
7255
+ return { kind: "omit", id: block.id, ...block.metadata ? { metadata: { ...block.metadata } } : {} };
6442
7256
  }
6443
7257
  function articleChromePreview(document2, uiLocale, previewOptions) {
6444
7258
  const meta = document2.meta ?? emptyDocument2Meta();