@drghaliasri/butex 5.6.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  // src/document2/ids.ts
2
2
  var nextId = 1;
3
+ var instanceId = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
3
4
  function document2Id(prefix) {
4
- const id = `${prefix}_${String(nextId)}`;
5
+ const id = `${prefix}_${instanceId}_${String(nextId)}`;
5
6
  nextId += 1;
6
7
  return id;
7
8
  }
@@ -1177,6 +1178,19 @@ function asBlockJson(value) {
1177
1178
  }
1178
1179
  return value;
1179
1180
  }
1181
+ 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");
1190
+ }
1191
+ state.used.add(generated);
1192
+ return generated;
1193
+ }
1180
1194
  function pushDiagnostic(diagnostics, options, path, message) {
1181
1195
  if (options.strict) {
1182
1196
  throw new Error(message);
@@ -1358,40 +1372,44 @@ function pushFormattedTextTokens(tokens, text, sourceStart, formats) {
1358
1372
  function closingForList(command) {
1359
1373
  return command === "\\begin{itemize}" ? "\\end{itemize}" : "\\end{enumerate}";
1360
1374
  }
1361
- function parseTextBlock(json, options, path, diagnostics) {
1375
+ function parseTextBlock(json, options, path, diagnostics, blockIds) {
1362
1376
  const value = requireString(json.value, `${json.command} requires string value`);
1363
1377
  return {
1364
- id: document2Id("block"),
1378
+ id: blockIdFromJson(json, blockIds),
1365
1379
  kind: "textBlock",
1366
1380
  command: json.command,
1367
1381
  field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1368
1382
  ...json.centered === true ? { centered: true } : {}
1369
1383
  };
1370
1384
  }
1371
- function parseListItem(json, options, path, diagnostics) {
1385
+ function parseListItem(json, options, path, diagnostics, blockIds) {
1372
1386
  const value = requireString(json.value, "Document list item requires string value");
1373
1387
  const blocksJson = Array.isArray(json.blocks) ? json.blocks : [];
1374
1388
  return {
1375
1389
  id: document2Id("item"),
1376
1390
  field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1377
- blocks: blocksJson.map((block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics))
1391
+ blocks: blocksJson.map(
1392
+ (block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics, blockIds)
1393
+ )
1378
1394
  };
1379
1395
  }
1380
- function parseListBlock(json, options, path, diagnostics) {
1396
+ function parseListBlock(json, options, path, diagnostics, blockIds) {
1381
1397
  if (!Array.isArray(json.items)) {
1382
1398
  throw new Error(`${json.command} requires items array`);
1383
1399
  }
1384
1400
  const command = json.command;
1385
1401
  const closing = closingForList(command);
1386
1402
  return {
1387
- id: document2Id("block"),
1403
+ id: blockIdFromJson(json, blockIds),
1388
1404
  kind: "list",
1389
1405
  command,
1390
1406
  closing,
1391
- items: json.items.map((item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics))
1407
+ items: json.items.map(
1408
+ (item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics, blockIds)
1409
+ )
1392
1410
  };
1393
1411
  }
1394
- function parseTableBlock(json, options, path, diagnostics) {
1412
+ function parseTableBlock(json, options, path, diagnostics, blockIds) {
1395
1413
  if (!Array.isArray(json.rows)) {
1396
1414
  throw new Error("\\begin{tabular} requires rows array");
1397
1415
  }
@@ -1414,7 +1432,7 @@ function parseTableBlock(json, options, path, diagnostics) {
1414
1432
  pushDiagnostic(diagnostics, options, path, `math_objects count mismatch: detected ${String(mathObjectIndex)}, got ${String(mathObjects.length)}`);
1415
1433
  }
1416
1434
  return {
1417
- id: document2Id("block"),
1435
+ id: blockIdFromJson(json, blockIds),
1418
1436
  kind: "table",
1419
1437
  command: "\\begin{tabular}",
1420
1438
  closing: "\\end{tabular}",
@@ -1423,11 +1441,11 @@ function parseTableBlock(json, options, path, diagnostics) {
1423
1441
  ...parseFloatMetaFromJson(json)
1424
1442
  };
1425
1443
  }
1426
- function parseImageBlock(json) {
1444
+ function parseImageBlock(json, blockIds) {
1427
1445
  const assetId = typeof json.asset_id === "string" && json.asset_id.length > 0 ? json.asset_id : void 0;
1428
1446
  const value = assetId !== void 0 ? typeof json.value === "string" ? json.value : "" : requireString(json.value, "\\includegraphics requires string value");
1429
1447
  return {
1430
- id: document2Id("block"),
1448
+ id: blockIdFromJson(json, blockIds),
1431
1449
  kind: "image",
1432
1450
  command: "\\includegraphics",
1433
1451
  value,
@@ -1436,43 +1454,43 @@ function parseImageBlock(json) {
1436
1454
  ...parseFloatMetaFromJson(json)
1437
1455
  };
1438
1456
  }
1439
- function parseRawBlock(json) {
1457
+ function parseRawBlock(json, blockIds) {
1440
1458
  return {
1441
- id: document2Id("block"),
1459
+ id: blockIdFromJson(json, blockIds),
1442
1460
  kind: "raw",
1443
1461
  command: "\\raw",
1444
1462
  value: typeof json.value === "string" ? json.value : ""
1445
1463
  };
1446
1464
  }
1447
- function parseBibliographyBlock() {
1465
+ function parseBibliographyBlock(json, blockIds) {
1448
1466
  return {
1449
- id: document2Id("block"),
1467
+ id: blockIdFromJson(json, blockIds),
1450
1468
  kind: "bibliography",
1451
1469
  command: "\\begin{thebibliography}",
1452
1470
  closing: "\\end{thebibliography}"
1453
1471
  };
1454
1472
  }
1455
- function parseBlock(json, options, path, diagnostics) {
1473
+ function parseBlock(json, options, path, diagnostics, blockIds) {
1456
1474
  if (TEXT_COMMANDS.has(json.command)) {
1457
- return parseTextBlock(json, options, path, diagnostics);
1475
+ return parseTextBlock(json, options, path, diagnostics, blockIds);
1458
1476
  }
1459
1477
  if (LIST_COMMANDS.has(json.command)) {
1460
- return parseListBlock(json, options, path, diagnostics);
1478
+ return parseListBlock(json, options, path, diagnostics, blockIds);
1461
1479
  }
1462
1480
  if (json.command === "\\begin{tabular}") {
1463
- return parseTableBlock(json, options, path, diagnostics);
1481
+ return parseTableBlock(json, options, path, diagnostics, blockIds);
1464
1482
  }
1465
1483
  if (json.command === "\\includegraphics") {
1466
- return parseImageBlock(json);
1484
+ return parseImageBlock(json, blockIds);
1467
1485
  }
1468
1486
  if (json.command === "\\begin{thebibliography}" || json.command === "\\bibliography") {
1469
- return parseBibliographyBlock();
1487
+ return parseBibliographyBlock(json, blockIds);
1470
1488
  }
1471
1489
  if (json.command === "\\raw") {
1472
- return parseRawBlock(json);
1490
+ return parseRawBlock(json, blockIds);
1473
1491
  }
1474
1492
  return {
1475
- id: document2Id("block"),
1493
+ id: blockIdFromJson(json, blockIds),
1476
1494
  kind: "raw",
1477
1495
  command: "\\raw",
1478
1496
  value: typeof json.value === "string" ? json.value : json.command
@@ -1486,11 +1504,14 @@ function fromDocumentJson2(json, options = {}) {
1486
1504
  throw new Error("DocumentObject requires blocks array");
1487
1505
  }
1488
1506
  const diagnostics = [];
1507
+ const blockIds = { used: /* @__PURE__ */ new Set() };
1489
1508
  return {
1490
1509
  nodeType: "DocumentObject",
1491
1510
  meta: normalizeDocument2Meta(json.meta),
1492
1511
  references: parseReferences(json.references),
1493
- blocks: json.blocks.map((block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics)),
1512
+ blocks: json.blocks.map(
1513
+ (block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics, blockIds)
1514
+ ),
1494
1515
  diagnostics
1495
1516
  };
1496
1517
  }
@@ -4574,21 +4595,21 @@ function splitTextForInsertion(token) {
4574
4595
  function cloneDocument2Node(document2) {
4575
4596
  return cloneDocument(document2);
4576
4597
  }
4577
- function insertBlockAfter(blocks, afterBlockId, block) {
4578
- if (!afterBlockId) {
4598
+ function insertBlockAfter(blocks, afterBlockId2, block) {
4599
+ if (!afterBlockId2) {
4579
4600
  blocks.push(block);
4580
4601
  return;
4581
4602
  }
4582
- const index = blocks.findIndex((entry) => entry.id === afterBlockId);
4603
+ const index = blocks.findIndex((entry) => entry.id === afterBlockId2);
4583
4604
  if (index < 0) {
4584
4605
  blocks.push(block);
4585
4606
  return;
4586
4607
  }
4587
4608
  blocks.splice(index + 1, 0, block);
4588
4609
  }
4589
- function insertDocument2BlockAfter(document2, afterBlockId, block) {
4610
+ function insertDocument2BlockAfter(document2, afterBlockId2, block) {
4590
4611
  const next = cloneDocument(document2);
4591
- insertBlockAfter(next.blocks, afterBlockId, block);
4612
+ insertBlockAfter(next.blocks, afterBlockId2, block);
4592
4613
  return next;
4593
4614
  }
4594
4615
  function moveDocument2BlockById(document2, blockId, direction) {
@@ -4808,7 +4829,7 @@ function replaceMathTokenFromSession(document2, tokenId, session, opening, closi
4808
4829
  });
4809
4830
  return next;
4810
4831
  }
4811
- function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId) {
4832
+ function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId2) {
4812
4833
  const block = {
4813
4834
  id: document2Id("block"),
4814
4835
  kind: "textBlock",
@@ -4816,7 +4837,7 @@ function addDocument2TextBlock(document2, command = "\\paragraph", afterBlockId)
4816
4837
  field: createInlineField2(""),
4817
4838
  ...command === "\\paragraph" ? { centered: false } : {}
4818
4839
  };
4819
- return insertDocument2BlockAfter(document2, afterBlockId, block);
4840
+ return insertDocument2BlockAfter(document2, afterBlockId2, block);
4820
4841
  }
4821
4842
  function updateDocument2TextBlockCentered(document2, blockId, centered) {
4822
4843
  const next = cloneDocument(document2);
@@ -4855,10 +4876,10 @@ function newListBlock(ordered) {
4855
4876
  items: [{ id: document2Id("item"), field: createInlineField2(""), blocks: [] }]
4856
4877
  };
4857
4878
  }
4858
- function addDocument2ListBlock(document2, ordered, afterBlockId) {
4859
- return insertDocument2BlockAfter(document2, afterBlockId, newListBlock(ordered));
4879
+ function addDocument2ListBlock(document2, ordered, afterBlockId2) {
4880
+ return insertDocument2BlockAfter(document2, afterBlockId2, newListBlock(ordered));
4860
4881
  }
4861
- function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCount = 3, afterBlockId) {
4882
+ function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCount = 3, afterBlockId2) {
4862
4883
  const rows = Math.max(1, rowCount);
4863
4884
  const cols = Math.max(1, colCount);
4864
4885
  const tableRows = [];
@@ -4878,18 +4899,29 @@ function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCou
4878
4899
  rows: tableRows,
4879
4900
  ...defaultFloatMeta()
4880
4901
  };
4881
- return insertDocument2BlockAfter(document2, afterBlockId, block);
4902
+ return insertDocument2BlockAfter(document2, afterBlockId2, block);
4903
+ }
4904
+ function normalizeImageInput(srcOrAsset) {
4905
+ if (typeof srcOrAsset === "string") {
4906
+ return srcOrAsset.length > 0 ? { value: srcOrAsset, assetId: srcOrAsset } : { value: "" };
4907
+ }
4908
+ if (srcOrAsset && srcOrAsset.assetId.length > 0) {
4909
+ return { value: srcOrAsset.value ?? srcOrAsset.assetId, assetId: srcOrAsset.assetId };
4910
+ }
4911
+ return { value: "" };
4882
4912
  }
4883
- function addDocument2ImageBlock(document2, src = "", afterBlockId) {
4913
+ function addDocument2ImageBlock(document2, srcOrAsset, afterBlockId2) {
4914
+ const image = normalizeImageInput(srcOrAsset);
4884
4915
  const block = {
4885
4916
  id: document2Id("block"),
4886
4917
  kind: "image",
4887
4918
  command: "\\includegraphics",
4888
- value: src,
4919
+ value: image.value,
4920
+ ...image.assetId !== void 0 ? { assetId: image.assetId } : {},
4889
4921
  options: { width: "0.8\\columnwidth" },
4890
4922
  ...defaultFloatMeta()
4891
4923
  };
4892
- return insertDocument2BlockAfter(document2, afterBlockId, block);
4924
+ return insertDocument2BlockAfter(document2, afterBlockId2, block);
4893
4925
  }
4894
4926
  function updateDocument2ImageValue(document2, blockId, value) {
4895
4927
  const next = cloneDocument(document2);
@@ -4912,6 +4944,55 @@ function updateDocument2ImageValue(document2, blockId, value) {
4912
4944
  visit(next.blocks);
4913
4945
  return next;
4914
4946
  }
4947
+ function updateDocument2ImageAsset(document2, blockId, asset) {
4948
+ const next = cloneDocument(document2);
4949
+ const image = normalizeImageInput(asset);
4950
+ function visit(blocks) {
4951
+ for (const block of blocks) {
4952
+ if (block.id === blockId && block.kind === "image") {
4953
+ block.value = image.value;
4954
+ if (image.assetId !== void 0) {
4955
+ block.assetId = image.assetId;
4956
+ } else {
4957
+ delete block.assetId;
4958
+ }
4959
+ return true;
4960
+ }
4961
+ if (block.kind === "list") {
4962
+ for (const item of block.items) {
4963
+ if (visit(item.blocks)) {
4964
+ return true;
4965
+ }
4966
+ }
4967
+ }
4968
+ }
4969
+ return false;
4970
+ }
4971
+ visit(next.blocks);
4972
+ return next;
4973
+ }
4974
+ function clearDocument2ImageAsset(document2, blockId) {
4975
+ const next = cloneDocument(document2);
4976
+ function visit(blocks) {
4977
+ for (const block of blocks) {
4978
+ if (block.id === blockId && block.kind === "image") {
4979
+ block.value = "";
4980
+ delete block.assetId;
4981
+ return true;
4982
+ }
4983
+ if (block.kind === "list") {
4984
+ for (const item of block.items) {
4985
+ if (visit(item.blocks)) {
4986
+ return true;
4987
+ }
4988
+ }
4989
+ }
4990
+ }
4991
+ return false;
4992
+ }
4993
+ visit(next.blocks);
4994
+ return next;
4995
+ }
4915
4996
  function updateDocument2ImageMeta(document2, blockId, patch) {
4916
4997
  const next = cloneDocument(document2);
4917
4998
  function visit(blocks) {
@@ -5130,7 +5211,7 @@ function removeCiteTokenById(document2, tokenId) {
5130
5211
  });
5131
5212
  return next;
5132
5213
  }
5133
- function ensureDocument2BibliographyBlock(document2, afterBlockId) {
5214
+ function ensureDocument2BibliographyBlock(document2, afterBlockId2) {
5134
5215
  if (document2.blocks.some((block2) => block2.kind === "bibliography")) {
5135
5216
  return document2;
5136
5217
  }
@@ -5140,7 +5221,7 @@ function ensureDocument2BibliographyBlock(document2, afterBlockId) {
5140
5221
  command: "\\begin{thebibliography}",
5141
5222
  closing: "\\end{thebibliography}"
5142
5223
  };
5143
- return insertDocument2BlockAfter(document2, afterBlockId ?? null, block);
5224
+ return insertDocument2BlockAfter(document2, afterBlockId2 ?? null, block);
5144
5225
  }
5145
5226
  function addDocument2Reference(document2, partial = {}) {
5146
5227
  const next = cloneDocument(document2);
@@ -5212,6 +5293,440 @@ function updateDocument2Meta(document2, patch) {
5212
5293
  return next;
5213
5294
  }
5214
5295
 
5296
+ // src/document2/exportJson.ts
5297
+ function serializeField(field) {
5298
+ let value = "";
5299
+ let hasPersistedMath = false;
5300
+ const formats = [];
5301
+ const mathObjects = [];
5302
+ for (const token of field.tokens) {
5303
+ if (token.kind === "text") {
5304
+ const start = value.length;
5305
+ value += token.text;
5306
+ if (token.text.length > 0 && (token.style?.bold || token.style?.italic || token.style?.underline)) {
5307
+ formats.push({
5308
+ start,
5309
+ end: value.length,
5310
+ ...token.style.bold ? { bold: true } : {},
5311
+ ...token.style.italic ? { italic: true } : {},
5312
+ ...token.style.underline ? { underline: true } : {}
5313
+ });
5314
+ }
5315
+ continue;
5316
+ }
5317
+ if (token.kind === "cite") {
5318
+ value += citeTokenLatex(token.keys);
5319
+ continue;
5320
+ }
5321
+ if (token.kind === "ref") {
5322
+ value += refTokenLatex(token.keys, token.refCommand);
5323
+ continue;
5324
+ }
5325
+ value += token.source;
5326
+ if (!token.math || token.sourceOwner === "raw") {
5327
+ if (token.labelEnabled !== void 0 || token.label !== void 0) {
5328
+ hasPersistedMath = true;
5329
+ mathObjects.push({
5330
+ node_type: "RawMathObject",
5331
+ ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
5332
+ ...token.label !== void 0 ? { label: token.label } : {}
5333
+ });
5334
+ } else {
5335
+ mathObjects.push(null);
5336
+ }
5337
+ continue;
5338
+ }
5339
+ hasPersistedMath = true;
5340
+ mathObjects.push({
5341
+ ...toMathObjectJson(token.math),
5342
+ ...token.sourceSide ? { source_side: token.sourceSide } : {},
5343
+ source_owner: token.sourceOwner,
5344
+ ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
5345
+ ...token.label !== void 0 ? { label: token.label } : {}
5346
+ });
5347
+ }
5348
+ return { value, formats, mathObjects, hasPersistedMath };
5349
+ }
5350
+ function fieldJson(field) {
5351
+ const serialized = serializeField(field);
5352
+ return {
5353
+ value: serialized.value,
5354
+ ...serialized.formats.length > 0 ? { formats: serialized.formats } : {},
5355
+ ...serialized.hasPersistedMath ? { math_objects: serialized.mathObjects } : {}
5356
+ };
5357
+ }
5358
+ function listItemJson(item) {
5359
+ const field = fieldJson(item.field);
5360
+ return {
5361
+ value: field.value ?? "",
5362
+ ...field.formats ? { formats: field.formats } : {},
5363
+ ...field.math_objects ? { math_objects: field.math_objects } : {},
5364
+ ...item.blocks.length > 0 ? { blocks: item.blocks.map(blockJson) } : {}
5365
+ };
5366
+ }
5367
+ function blockJson(block) {
5368
+ if (block.kind === "textBlock") {
5369
+ return {
5370
+ id: block.id,
5371
+ command: block.command,
5372
+ ...fieldJson(block.field),
5373
+ ...block.command === "\\paragraph" ? { centered: block.centered === true } : {}
5374
+ };
5375
+ }
5376
+ if (block.kind === "list") {
5377
+ return {
5378
+ id: block.id,
5379
+ command: block.command,
5380
+ closing: block.closing,
5381
+ items: block.items.map(listItemJson)
5382
+ };
5383
+ }
5384
+ if (block.kind === "table") {
5385
+ const fields = block.rows.map((row) => row.map(serializeField));
5386
+ const hasPersistedMath = fields.some((row) => row.some((field) => field.hasPersistedMath));
5387
+ return {
5388
+ id: block.id,
5389
+ command: block.command,
5390
+ closing: block.closing,
5391
+ columns: block.columns,
5392
+ rows: fields.map((row) => row.map((field) => field.value)),
5393
+ ...fields.some((row) => row.some((field) => field.formats.length > 0)) ? { cell_formats: fields.map((row) => row.map((field) => field.formats)) } : {},
5394
+ ...hasPersistedMath ? { math_objects: fields.flatMap((row) => row.flatMap((field) => field.mathObjects)) } : {},
5395
+ centered: block.centered,
5396
+ caption_enabled: block.captionEnabled,
5397
+ caption: block.caption,
5398
+ label_enabled: block.labelEnabled,
5399
+ label: block.label
5400
+ };
5401
+ }
5402
+ if (block.kind === "image") {
5403
+ return {
5404
+ id: block.id,
5405
+ command: block.command,
5406
+ value: block.value,
5407
+ ...block.assetId !== void 0 ? { asset_id: block.assetId } : {},
5408
+ options: { ...block.options },
5409
+ centered: block.centered,
5410
+ caption_enabled: block.captionEnabled,
5411
+ caption: block.caption,
5412
+ label_enabled: block.labelEnabled,
5413
+ label: block.label
5414
+ };
5415
+ }
5416
+ if (block.kind === "bibliography") {
5417
+ return { id: block.id, command: block.command, closing: block.closing };
5418
+ }
5419
+ return { id: block.id, command: block.command, value: block.value };
5420
+ }
5421
+ function referenceJson(reference) {
5422
+ return {
5423
+ key: reference.key,
5424
+ authors: reference.authors,
5425
+ title: reference.title,
5426
+ year: reference.year,
5427
+ url: reference.url,
5428
+ venue: reference.venue,
5429
+ field_separator: reference.fieldSeparator
5430
+ };
5431
+ }
5432
+ function toDocumentJson2(document2) {
5433
+ if (document2.nodeType !== "DocumentObject" || !Array.isArray(document2.blocks)) {
5434
+ throw new Error("toDocumentJson2 requires a live Document2Node");
5435
+ }
5436
+ return {
5437
+ node_type: "DocumentObject",
5438
+ meta: {
5439
+ title: document2.meta.title,
5440
+ authors: document2.meta.authors,
5441
+ date: { ...document2.meta.date },
5442
+ abstract: document2.meta.abstract
5443
+ },
5444
+ references: document2.references.map(referenceJson),
5445
+ blocks: document2.blocks.map(blockJson)
5446
+ };
5447
+ }
5448
+
5449
+ // 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);
5460
+ }
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;
5466
+ }
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];
5493
+ }
5494
+ function parseTextBlockKind(value) {
5495
+ if (value === "section" || value === "subsection" || value === "subsubsection" || value === "paragraph") {
5496
+ return value;
5497
+ }
5498
+ throw new Document2CommandError("invalid_command", "kind must be section, subsection, subsubsection, or paragraph");
5499
+ }
5500
+ function parseCommand2(value) {
5501
+ if (!isObject3(value) || typeof value.op !== "string") {
5502
+ throw new Document2CommandError("invalid_command", "command requires an op");
5503
+ }
5504
+ if (value.op === "insert_text_block") {
5505
+ return {
5506
+ op: value.op,
5507
+ kind: parseTextBlockKind(value.kind),
5508
+ text: requireString2(value.text, "text"),
5509
+ anchor: parseAnchor(value.anchor)
5510
+ };
5511
+ }
5512
+ if (value.op === "replace_text_block") {
5513
+ return {
5514
+ op: value.op,
5515
+ block_id: requireString2(value.block_id, "block_id", false).trim(),
5516
+ text: requireString2(value.text, "text")
5517
+ };
5518
+ }
5519
+ if (value.op === "remove_block") {
5520
+ return {
5521
+ op: value.op,
5522
+ block_id: requireString2(value.block_id, "block_id", false).trim()
5523
+ };
5524
+ }
5525
+ if (value.op === "insert_figure") {
5526
+ const figureValue = optionalString(value, "value");
5527
+ const caption = optionalString(value, "caption");
5528
+ const label = optionalString(value, "label");
5529
+ return {
5530
+ op: value.op,
5531
+ asset_id: requireString2(value.asset_id, "asset_id", false).trim(),
5532
+ ...figureValue !== void 0 ? { value: figureValue } : {},
5533
+ ...caption !== void 0 ? { caption } : {},
5534
+ ...label !== void 0 ? { label } : {},
5535
+ anchor: parseAnchor(value.anchor)
5536
+ };
5537
+ }
5538
+ throw new Document2CommandError("invalid_command", `Unsupported document command: ${value.op}`);
5539
+ }
5540
+ function parseDocument(json) {
5541
+ try {
5542
+ return fromDocumentJson2(json);
5543
+ } catch (error) {
5544
+ const message = error instanceof Error ? error.message : "Invalid DocumentObject JSON";
5545
+ throw new Document2CommandError("invalid_document", message);
5546
+ }
5547
+ }
5548
+ function commandForKind(kind) {
5549
+ if (kind === "section") {
5550
+ return "\\section";
5551
+ }
5552
+ if (kind === "subsection") {
5553
+ return "\\subsection";
5554
+ }
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;
5572
+ }
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");
5629
+ }
5630
+ if (command.caption !== void 0) {
5631
+ block.caption = command.caption;
5632
+ block.captionEnabled = command.caption.trim().length > 0;
5633
+ }
5634
+ if (command.label !== void 0) {
5635
+ block.label = command.label;
5636
+ block.labelEnabled = command.label.trim().length > 0;
5637
+ }
5638
+ return next;
5639
+ }
5640
+ function applyDocument2Command(json, commandInput) {
5641
+ const document2 = parseDocument(json);
5642
+ 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);
5650
+ } else {
5651
+ next = insertFigure(document2, command);
5652
+ }
5653
+ return toDocumentJson2(next);
5654
+ }
5655
+
5656
+ // src/document2/outline.ts
5657
+ var EXCERPT_LIMIT = 160;
5658
+ function normalizeExcerpt(value) {
5659
+ const normalized = value.replace(/\s+/g, " ").trim();
5660
+ if (normalized.length <= EXCERPT_LIMIT) {
5661
+ return normalized;
5662
+ }
5663
+ return `${normalized.slice(0, EXCERPT_LIMIT - 3)}...`;
5664
+ }
5665
+ function outlineKind(command) {
5666
+ if (command === "\\section") {
5667
+ return "section";
5668
+ }
5669
+ if (command === "\\subsection") {
5670
+ return "subsection";
5671
+ }
5672
+ if (command === "\\subsubsection") {
5673
+ return "subsubsection";
5674
+ }
5675
+ if (command === "\\paragraph") {
5676
+ return "paragraph";
5677
+ }
5678
+ if (command === "\\begin{itemize}" || command === "\\begin{enumerate}") {
5679
+ return "list";
5680
+ }
5681
+ if (command === "\\begin{tabular}") {
5682
+ return "table";
5683
+ }
5684
+ if (command === "\\includegraphics") {
5685
+ return "figure";
5686
+ }
5687
+ if (command === "\\begin{thebibliography}" || command === "\\bibliography") {
5688
+ return "bibliography";
5689
+ }
5690
+ return "raw";
5691
+ }
5692
+ function blockExcerpt(block) {
5693
+ if (block.command === "\\begin{itemize}" || block.command === "\\begin{enumerate}") {
5694
+ return normalizeExcerpt((block.items ?? []).map((item) => item.value).join(" "));
5695
+ }
5696
+ if (block.command === "\\begin{tabular}") {
5697
+ return normalizeExcerpt((block.rows ?? []).flat().join(" "));
5698
+ }
5699
+ if (block.command === "\\includegraphics") {
5700
+ return normalizeExcerpt(block.caption || block.asset_id || block.value || "");
5701
+ }
5702
+ return normalizeExcerpt(block.value ?? "");
5703
+ }
5704
+ function document2Outline(json) {
5705
+ if (!json || json.node_type !== "DocumentObject" || !Array.isArray(json.blocks)) {
5706
+ throw new Document2CommandError("invalid_document", "DocumentObject requires a blocks array");
5707
+ }
5708
+ const usedIds = /* @__PURE__ */ new Set();
5709
+ return json.blocks.map((block) => {
5710
+ if (!block || typeof block !== "object" || typeof block.command !== "string") {
5711
+ throw new Document2CommandError("invalid_document", "Document outline requires blocks with string commands");
5712
+ }
5713
+ const id = typeof block.id === "string" ? block.id.trim() : "";
5714
+ if (id.length === 0) {
5715
+ throw new Document2CommandError("missing_block_id", "Document outline requires canonical block IDs");
5716
+ }
5717
+ if (usedIds.has(id)) {
5718
+ throw new Document2CommandError("duplicate_block_id", `Duplicate document block ID: ${id}`);
5719
+ }
5720
+ usedIds.add(id);
5721
+ return {
5722
+ id,
5723
+ kind: outlineKind(block.command),
5724
+ command: block.command,
5725
+ excerpt: blockExcerpt(block)
5726
+ };
5727
+ });
5728
+ }
5729
+
5215
5730
  // src/document2/keys.ts
5216
5731
  var DOCUMENT2_KEY_PATTERN = /^[\p{L}\p{M}0-9:._-]+$/u;
5217
5732
  function normalizeDocument2Key(key) {
@@ -5696,155 +6211,6 @@ ${body}
5696
6211
  `;
5697
6212
  }
5698
6213
 
5699
- // src/document2/exportJson.ts
5700
- function serializeField(field) {
5701
- let value = "";
5702
- let hasPersistedMath = false;
5703
- const formats = [];
5704
- const mathObjects = [];
5705
- for (const token of field.tokens) {
5706
- if (token.kind === "text") {
5707
- const start = value.length;
5708
- value += token.text;
5709
- if (token.text.length > 0 && (token.style?.bold || token.style?.italic || token.style?.underline)) {
5710
- formats.push({
5711
- start,
5712
- end: value.length,
5713
- ...token.style.bold ? { bold: true } : {},
5714
- ...token.style.italic ? { italic: true } : {},
5715
- ...token.style.underline ? { underline: true } : {}
5716
- });
5717
- }
5718
- continue;
5719
- }
5720
- if (token.kind === "cite") {
5721
- value += citeTokenLatex(token.keys);
5722
- continue;
5723
- }
5724
- if (token.kind === "ref") {
5725
- value += refTokenLatex(token.keys, token.refCommand);
5726
- continue;
5727
- }
5728
- value += token.source;
5729
- if (!token.math || token.sourceOwner === "raw") {
5730
- if (token.labelEnabled !== void 0 || token.label !== void 0) {
5731
- hasPersistedMath = true;
5732
- mathObjects.push({
5733
- node_type: "RawMathObject",
5734
- ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
5735
- ...token.label !== void 0 ? { label: token.label } : {}
5736
- });
5737
- } else {
5738
- mathObjects.push(null);
5739
- }
5740
- continue;
5741
- }
5742
- hasPersistedMath = true;
5743
- mathObjects.push({
5744
- ...toMathObjectJson(token.math),
5745
- ...token.sourceSide ? { source_side: token.sourceSide } : {},
5746
- source_owner: token.sourceOwner,
5747
- ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
5748
- ...token.label !== void 0 ? { label: token.label } : {}
5749
- });
5750
- }
5751
- return { value, formats, mathObjects, hasPersistedMath };
5752
- }
5753
- function fieldJson(field) {
5754
- const serialized = serializeField(field);
5755
- return {
5756
- value: serialized.value,
5757
- ...serialized.formats.length > 0 ? { formats: serialized.formats } : {},
5758
- ...serialized.hasPersistedMath ? { math_objects: serialized.mathObjects } : {}
5759
- };
5760
- }
5761
- function listItemJson(item) {
5762
- const field = fieldJson(item.field);
5763
- return {
5764
- value: field.value ?? "",
5765
- ...field.formats ? { formats: field.formats } : {},
5766
- ...field.math_objects ? { math_objects: field.math_objects } : {},
5767
- ...item.blocks.length > 0 ? { blocks: item.blocks.map(blockJson) } : {}
5768
- };
5769
- }
5770
- function blockJson(block) {
5771
- if (block.kind === "textBlock") {
5772
- return {
5773
- command: block.command,
5774
- ...fieldJson(block.field),
5775
- ...block.command === "\\paragraph" ? { centered: block.centered === true } : {}
5776
- };
5777
- }
5778
- if (block.kind === "list") {
5779
- return {
5780
- command: block.command,
5781
- closing: block.closing,
5782
- items: block.items.map(listItemJson)
5783
- };
5784
- }
5785
- if (block.kind === "table") {
5786
- const fields = block.rows.map((row) => row.map(serializeField));
5787
- const hasPersistedMath = fields.some((row) => row.some((field) => field.hasPersistedMath));
5788
- return {
5789
- command: block.command,
5790
- closing: block.closing,
5791
- columns: block.columns,
5792
- rows: fields.map((row) => row.map((field) => field.value)),
5793
- ...fields.some((row) => row.some((field) => field.formats.length > 0)) ? { cell_formats: fields.map((row) => row.map((field) => field.formats)) } : {},
5794
- ...hasPersistedMath ? { math_objects: fields.flatMap((row) => row.flatMap((field) => field.mathObjects)) } : {},
5795
- centered: block.centered,
5796
- caption_enabled: block.captionEnabled,
5797
- caption: block.caption,
5798
- label_enabled: block.labelEnabled,
5799
- label: block.label
5800
- };
5801
- }
5802
- if (block.kind === "image") {
5803
- return {
5804
- command: block.command,
5805
- value: block.value,
5806
- ...block.assetId !== void 0 ? { asset_id: block.assetId } : {},
5807
- options: { ...block.options },
5808
- centered: block.centered,
5809
- caption_enabled: block.captionEnabled,
5810
- caption: block.caption,
5811
- label_enabled: block.labelEnabled,
5812
- label: block.label
5813
- };
5814
- }
5815
- if (block.kind === "bibliography") {
5816
- return { command: block.command, closing: block.closing };
5817
- }
5818
- return { command: block.command, value: block.value };
5819
- }
5820
- function referenceJson(reference) {
5821
- return {
5822
- key: reference.key,
5823
- authors: reference.authors,
5824
- title: reference.title,
5825
- year: reference.year,
5826
- url: reference.url,
5827
- venue: reference.venue,
5828
- field_separator: reference.fieldSeparator
5829
- };
5830
- }
5831
- function toDocumentJson2(document2) {
5832
- if (document2.nodeType !== "DocumentObject" || !Array.isArray(document2.blocks)) {
5833
- throw new Error("toDocumentJson2 requires a live Document2Node");
5834
- }
5835
- return {
5836
- node_type: "DocumentObject",
5837
- meta: {
5838
- title: document2.meta.title,
5839
- authors: document2.meta.authors,
5840
- date: { ...document2.meta.date },
5841
- abstract: document2.meta.abstract
5842
- },
5843
- references: document2.references.map(referenceJson),
5844
- blocks: document2.blocks.map(blockJson)
5845
- };
5846
- }
5847
-
5848
6214
  // src/document2/history.ts
5849
6215
  var DEFAULT_DOCUMENT2_HISTORY_MAX_DEPTH = 100;
5850
6216
  function createDocument2History(maxDepth = DEFAULT_DOCUMENT2_HISTORY_MAX_DEPTH) {
@@ -6122,6 +6488,7 @@ export {
6122
6488
  DEFAULT_DOCUMENT2_HISTORY_MAX_DEPTH,
6123
6489
  DEFAULT_HIJRI_DATE,
6124
6490
  DEFAULT_REFERENCE_FIELD_SEPARATOR,
6491
+ Document2CommandError,
6125
6492
  HIJRI_MONTH_IDS,
6126
6493
  HIJRI_YEAR_MAX,
6127
6494
  HIJRI_YEAR_MIN,
@@ -6132,6 +6499,7 @@ export {
6132
6499
  addDocument2Reference,
6133
6500
  addDocument2TableBlock,
6134
6501
  addDocument2TextBlock,
6502
+ applyDocument2Command,
6135
6503
  applyFloatMetaPatch,
6136
6504
  arabicXeLatexPreambleCmd,
6137
6505
  arabicXeLatexPreambleFont,
@@ -6147,6 +6515,7 @@ export {
6147
6515
  butexMaghribiDisplayLatex,
6148
6516
  butexMaghribiDisplayTex,
6149
6517
  citeTokenLatex,
6518
+ clearDocument2ImageAsset,
6150
6519
  cloneDocument2Meta,
6151
6520
  cloneDocument2Node,
6152
6521
  collectDocument2Labels,
@@ -6166,6 +6535,7 @@ export {
6166
6535
  document2HistoryCanUndo,
6167
6536
  document2KeyError,
6168
6537
  document2Latex,
6538
+ document2Outline,
6169
6539
  document2Preview,
6170
6540
  emptyBlockSelection,
6171
6541
  emptyDocument2Meta,
@@ -6232,6 +6602,7 @@ export {
6232
6602
  toggleBlockInSelection,
6233
6603
  toggleTextTokenStyle,
6234
6604
  updateCiteTokenKeys,
6605
+ updateDocument2ImageAsset,
6235
6606
  updateDocument2ImageMeta,
6236
6607
  updateDocument2ImageValue,
6237
6608
  updateDocument2Meta,