@drghaliasri/butex 5.6.1 → 6.0.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.
@@ -38,8 +38,9 @@ var import_react = require("react");
38
38
 
39
39
  // src/document2/ids.ts
40
40
  var nextId = 1;
41
+ var instanceId = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
41
42
  function document2Id(prefix) {
42
- const id = `${prefix}_${String(nextId)}`;
43
+ const id = `${prefix}_${instanceId}_${String(nextId)}`;
43
44
  nextId += 1;
44
45
  return id;
45
46
  }
@@ -1204,6 +1205,19 @@ function asBlockJson(value) {
1204
1205
  }
1205
1206
  return value;
1206
1207
  }
1208
+ function blockIdFromJson(json, state) {
1209
+ const imported = typeof json.id === "string" ? json.id.trim() : "";
1210
+ if (imported.length > 0 && !state.used.has(imported)) {
1211
+ state.used.add(imported);
1212
+ return imported;
1213
+ }
1214
+ let generated = document2Id("block");
1215
+ while (state.used.has(generated)) {
1216
+ generated = document2Id("block");
1217
+ }
1218
+ state.used.add(generated);
1219
+ return generated;
1220
+ }
1207
1221
  function pushDiagnostic(diagnostics, options, path, message) {
1208
1222
  if (options.strict) {
1209
1223
  throw new Error(message);
@@ -1385,40 +1399,44 @@ function pushFormattedTextTokens(tokens, text, sourceStart, formats) {
1385
1399
  function closingForList(command) {
1386
1400
  return command === "\\begin{itemize}" ? "\\end{itemize}" : "\\end{enumerate}";
1387
1401
  }
1388
- function parseTextBlock(json, options, path, diagnostics) {
1402
+ function parseTextBlock(json, options, path, diagnostics, blockIds) {
1389
1403
  const value = requireString(json.value, `${json.command} requires string value`);
1390
1404
  return {
1391
- id: document2Id("block"),
1405
+ id: blockIdFromJson(json, blockIds),
1392
1406
  kind: "textBlock",
1393
1407
  command: json.command,
1394
1408
  field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1395
1409
  ...json.centered === true ? { centered: true } : {}
1396
1410
  };
1397
1411
  }
1398
- function parseListItem(json, options, path, diagnostics) {
1412
+ function parseListItem(json, options, path, diagnostics, blockIds) {
1399
1413
  const value = requireString(json.value, "Document list item requires string value");
1400
1414
  const blocksJson = Array.isArray(json.blocks) ? json.blocks : [];
1401
1415
  return {
1402
1416
  id: document2Id("item"),
1403
1417
  field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1404
- blocks: blocksJson.map((block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics))
1418
+ blocks: blocksJson.map(
1419
+ (block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics, blockIds)
1420
+ )
1405
1421
  };
1406
1422
  }
1407
- function parseListBlock(json, options, path, diagnostics) {
1423
+ function parseListBlock(json, options, path, diagnostics, blockIds) {
1408
1424
  if (!Array.isArray(json.items)) {
1409
1425
  throw new Error(`${json.command} requires items array`);
1410
1426
  }
1411
1427
  const command = json.command;
1412
1428
  const closing = closingForList(command);
1413
1429
  return {
1414
- id: document2Id("block"),
1430
+ id: blockIdFromJson(json, blockIds),
1415
1431
  kind: "list",
1416
1432
  command,
1417
1433
  closing,
1418
- items: json.items.map((item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics))
1434
+ items: json.items.map(
1435
+ (item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics, blockIds)
1436
+ )
1419
1437
  };
1420
1438
  }
1421
- function parseTableBlock(json, options, path, diagnostics) {
1439
+ function parseTableBlock(json, options, path, diagnostics, blockIds) {
1422
1440
  if (!Array.isArray(json.rows)) {
1423
1441
  throw new Error("\\begin{tabular} requires rows array");
1424
1442
  }
@@ -1441,7 +1459,7 @@ function parseTableBlock(json, options, path, diagnostics) {
1441
1459
  pushDiagnostic(diagnostics, options, path, `math_objects count mismatch: detected ${String(mathObjectIndex)}, got ${String(mathObjects.length)}`);
1442
1460
  }
1443
1461
  return {
1444
- id: document2Id("block"),
1462
+ id: blockIdFromJson(json, blockIds),
1445
1463
  kind: "table",
1446
1464
  command: "\\begin{tabular}",
1447
1465
  closing: "\\end{tabular}",
@@ -1450,11 +1468,11 @@ function parseTableBlock(json, options, path, diagnostics) {
1450
1468
  ...parseFloatMetaFromJson(json)
1451
1469
  };
1452
1470
  }
1453
- function parseImageBlock(json) {
1471
+ function parseImageBlock(json, blockIds) {
1454
1472
  const assetId = typeof json.asset_id === "string" && json.asset_id.length > 0 ? json.asset_id : void 0;
1455
1473
  const value = assetId !== void 0 ? typeof json.value === "string" ? json.value : "" : requireString(json.value, "\\includegraphics requires string value");
1456
1474
  return {
1457
- id: document2Id("block"),
1475
+ id: blockIdFromJson(json, blockIds),
1458
1476
  kind: "image",
1459
1477
  command: "\\includegraphics",
1460
1478
  value,
@@ -1463,43 +1481,43 @@ function parseImageBlock(json) {
1463
1481
  ...parseFloatMetaFromJson(json)
1464
1482
  };
1465
1483
  }
1466
- function parseRawBlock(json) {
1484
+ function parseRawBlock(json, blockIds) {
1467
1485
  return {
1468
- id: document2Id("block"),
1486
+ id: blockIdFromJson(json, blockIds),
1469
1487
  kind: "raw",
1470
1488
  command: "\\raw",
1471
1489
  value: typeof json.value === "string" ? json.value : ""
1472
1490
  };
1473
1491
  }
1474
- function parseBibliographyBlock() {
1492
+ function parseBibliographyBlock(json, blockIds) {
1475
1493
  return {
1476
- id: document2Id("block"),
1494
+ id: blockIdFromJson(json, blockIds),
1477
1495
  kind: "bibliography",
1478
1496
  command: "\\begin{thebibliography}",
1479
1497
  closing: "\\end{thebibliography}"
1480
1498
  };
1481
1499
  }
1482
- function parseBlock(json, options, path, diagnostics) {
1500
+ function parseBlock(json, options, path, diagnostics, blockIds) {
1483
1501
  if (TEXT_COMMANDS.has(json.command)) {
1484
- return parseTextBlock(json, options, path, diagnostics);
1502
+ return parseTextBlock(json, options, path, diagnostics, blockIds);
1485
1503
  }
1486
1504
  if (LIST_COMMANDS.has(json.command)) {
1487
- return parseListBlock(json, options, path, diagnostics);
1505
+ return parseListBlock(json, options, path, diagnostics, blockIds);
1488
1506
  }
1489
1507
  if (json.command === "\\begin{tabular}") {
1490
- return parseTableBlock(json, options, path, diagnostics);
1508
+ return parseTableBlock(json, options, path, diagnostics, blockIds);
1491
1509
  }
1492
1510
  if (json.command === "\\includegraphics") {
1493
- return parseImageBlock(json);
1511
+ return parseImageBlock(json, blockIds);
1494
1512
  }
1495
1513
  if (json.command === "\\begin{thebibliography}" || json.command === "\\bibliography") {
1496
- return parseBibliographyBlock();
1514
+ return parseBibliographyBlock(json, blockIds);
1497
1515
  }
1498
1516
  if (json.command === "\\raw") {
1499
- return parseRawBlock(json);
1517
+ return parseRawBlock(json, blockIds);
1500
1518
  }
1501
1519
  return {
1502
- id: document2Id("block"),
1520
+ id: blockIdFromJson(json, blockIds),
1503
1521
  kind: "raw",
1504
1522
  command: "\\raw",
1505
1523
  value: typeof json.value === "string" ? json.value : json.command
@@ -1513,11 +1531,14 @@ function fromDocumentJson2(json, options = {}) {
1513
1531
  throw new Error("DocumentObject requires blocks array");
1514
1532
  }
1515
1533
  const diagnostics = [];
1534
+ const blockIds = { used: /* @__PURE__ */ new Set() };
1516
1535
  return {
1517
1536
  nodeType: "DocumentObject",
1518
1537
  meta: normalizeDocument2Meta(json.meta),
1519
1538
  references: parseReferences(json.references),
1520
- blocks: json.blocks.map((block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics)),
1539
+ blocks: json.blocks.map(
1540
+ (block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics, blockIds)
1541
+ ),
1521
1542
  diagnostics
1522
1543
  };
1523
1544
  }
@@ -8690,12 +8711,23 @@ function addDocument2TableBlock(document2, columns = "lll", rowCount = 3, colCou
8690
8711
  };
8691
8712
  return insertDocument2BlockAfter(document2, afterBlockId, block);
8692
8713
  }
8693
- function addDocument2ImageBlock(document2, src = "", afterBlockId) {
8714
+ function normalizeImageInput(srcOrAsset) {
8715
+ if (typeof srcOrAsset === "string") {
8716
+ return srcOrAsset.length > 0 ? { value: srcOrAsset, assetId: srcOrAsset } : { value: "" };
8717
+ }
8718
+ if (srcOrAsset && srcOrAsset.assetId.length > 0) {
8719
+ return { value: srcOrAsset.value ?? srcOrAsset.assetId, assetId: srcOrAsset.assetId };
8720
+ }
8721
+ return { value: "" };
8722
+ }
8723
+ function addDocument2ImageBlock(document2, srcOrAsset, afterBlockId) {
8724
+ const image = normalizeImageInput(srcOrAsset);
8694
8725
  const block = {
8695
8726
  id: document2Id("block"),
8696
8727
  kind: "image",
8697
8728
  command: "\\includegraphics",
8698
- value: src,
8729
+ value: image.value,
8730
+ ...image.assetId !== void 0 ? { assetId: image.assetId } : {},
8699
8731
  options: { width: "0.8\\columnwidth" },
8700
8732
  ...defaultFloatMeta()
8701
8733
  };
@@ -8722,6 +8754,55 @@ function updateDocument2ImageValue(document2, blockId, value) {
8722
8754
  visit(next.blocks);
8723
8755
  return next;
8724
8756
  }
8757
+ function updateDocument2ImageAsset(document2, blockId, asset) {
8758
+ const next = cloneDocument(document2);
8759
+ const image = normalizeImageInput(asset);
8760
+ function visit(blocks) {
8761
+ for (const block of blocks) {
8762
+ if (block.id === blockId && block.kind === "image") {
8763
+ block.value = image.value;
8764
+ if (image.assetId !== void 0) {
8765
+ block.assetId = image.assetId;
8766
+ } else {
8767
+ delete block.assetId;
8768
+ }
8769
+ return true;
8770
+ }
8771
+ if (block.kind === "list") {
8772
+ for (const item of block.items) {
8773
+ if (visit(item.blocks)) {
8774
+ return true;
8775
+ }
8776
+ }
8777
+ }
8778
+ }
8779
+ return false;
8780
+ }
8781
+ visit(next.blocks);
8782
+ return next;
8783
+ }
8784
+ function clearDocument2ImageAsset(document2, blockId) {
8785
+ const next = cloneDocument(document2);
8786
+ function visit(blocks) {
8787
+ for (const block of blocks) {
8788
+ if (block.id === blockId && block.kind === "image") {
8789
+ block.value = "";
8790
+ delete block.assetId;
8791
+ return true;
8792
+ }
8793
+ if (block.kind === "list") {
8794
+ for (const item of block.items) {
8795
+ if (visit(item.blocks)) {
8796
+ return true;
8797
+ }
8798
+ }
8799
+ }
8800
+ }
8801
+ return false;
8802
+ }
8803
+ visit(next.blocks);
8804
+ return next;
8805
+ }
8725
8806
  function updateDocument2ImageMeta(document2, blockId, patch) {
8726
8807
  const next = cloneDocument(document2);
8727
8808
  function visit(blocks) {
@@ -9017,6 +9098,159 @@ function updateDocument2Meta(document2, patch) {
9017
9098
  return next;
9018
9099
  }
9019
9100
 
9101
+ // src/document2/exportJson.ts
9102
+ function serializeField(field) {
9103
+ let value = "";
9104
+ let hasPersistedMath = false;
9105
+ const formats = [];
9106
+ const mathObjects = [];
9107
+ for (const token of field.tokens) {
9108
+ if (token.kind === "text") {
9109
+ const start = value.length;
9110
+ value += token.text;
9111
+ if (token.text.length > 0 && (token.style?.bold || token.style?.italic || token.style?.underline)) {
9112
+ formats.push({
9113
+ start,
9114
+ end: value.length,
9115
+ ...token.style.bold ? { bold: true } : {},
9116
+ ...token.style.italic ? { italic: true } : {},
9117
+ ...token.style.underline ? { underline: true } : {}
9118
+ });
9119
+ }
9120
+ continue;
9121
+ }
9122
+ if (token.kind === "cite") {
9123
+ value += citeTokenLatex(token.keys);
9124
+ continue;
9125
+ }
9126
+ if (token.kind === "ref") {
9127
+ value += refTokenLatex(token.keys, token.refCommand);
9128
+ continue;
9129
+ }
9130
+ value += token.source;
9131
+ if (!token.math || token.sourceOwner === "raw") {
9132
+ if (token.labelEnabled !== void 0 || token.label !== void 0) {
9133
+ hasPersistedMath = true;
9134
+ mathObjects.push({
9135
+ node_type: "RawMathObject",
9136
+ ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
9137
+ ...token.label !== void 0 ? { label: token.label } : {}
9138
+ });
9139
+ } else {
9140
+ mathObjects.push(null);
9141
+ }
9142
+ continue;
9143
+ }
9144
+ hasPersistedMath = true;
9145
+ mathObjects.push({
9146
+ ...toMathObjectJson(token.math),
9147
+ ...token.sourceSide ? { source_side: token.sourceSide } : {},
9148
+ source_owner: token.sourceOwner,
9149
+ ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
9150
+ ...token.label !== void 0 ? { label: token.label } : {}
9151
+ });
9152
+ }
9153
+ return { value, formats, mathObjects, hasPersistedMath };
9154
+ }
9155
+ function fieldJson(field) {
9156
+ const serialized = serializeField(field);
9157
+ return {
9158
+ value: serialized.value,
9159
+ ...serialized.formats.length > 0 ? { formats: serialized.formats } : {},
9160
+ ...serialized.hasPersistedMath ? { math_objects: serialized.mathObjects } : {}
9161
+ };
9162
+ }
9163
+ function listItemJson(item) {
9164
+ const field = fieldJson(item.field);
9165
+ return {
9166
+ value: field.value ?? "",
9167
+ ...field.formats ? { formats: field.formats } : {},
9168
+ ...field.math_objects ? { math_objects: field.math_objects } : {},
9169
+ ...item.blocks.length > 0 ? { blocks: item.blocks.map(blockJson) } : {}
9170
+ };
9171
+ }
9172
+ function blockJson(block) {
9173
+ if (block.kind === "textBlock") {
9174
+ return {
9175
+ id: block.id,
9176
+ command: block.command,
9177
+ ...fieldJson(block.field),
9178
+ ...block.command === "\\paragraph" ? { centered: block.centered === true } : {}
9179
+ };
9180
+ }
9181
+ if (block.kind === "list") {
9182
+ return {
9183
+ id: block.id,
9184
+ command: block.command,
9185
+ closing: block.closing,
9186
+ items: block.items.map(listItemJson)
9187
+ };
9188
+ }
9189
+ if (block.kind === "table") {
9190
+ const fields = block.rows.map((row) => row.map(serializeField));
9191
+ const hasPersistedMath = fields.some((row) => row.some((field) => field.hasPersistedMath));
9192
+ return {
9193
+ id: block.id,
9194
+ command: block.command,
9195
+ closing: block.closing,
9196
+ columns: block.columns,
9197
+ rows: fields.map((row) => row.map((field) => field.value)),
9198
+ ...fields.some((row) => row.some((field) => field.formats.length > 0)) ? { cell_formats: fields.map((row) => row.map((field) => field.formats)) } : {},
9199
+ ...hasPersistedMath ? { math_objects: fields.flatMap((row) => row.flatMap((field) => field.mathObjects)) } : {},
9200
+ centered: block.centered,
9201
+ caption_enabled: block.captionEnabled,
9202
+ caption: block.caption,
9203
+ label_enabled: block.labelEnabled,
9204
+ label: block.label
9205
+ };
9206
+ }
9207
+ if (block.kind === "image") {
9208
+ return {
9209
+ id: block.id,
9210
+ command: block.command,
9211
+ value: block.value,
9212
+ ...block.assetId !== void 0 ? { asset_id: block.assetId } : {},
9213
+ options: { ...block.options },
9214
+ centered: block.centered,
9215
+ caption_enabled: block.captionEnabled,
9216
+ caption: block.caption,
9217
+ label_enabled: block.labelEnabled,
9218
+ label: block.label
9219
+ };
9220
+ }
9221
+ if (block.kind === "bibliography") {
9222
+ return { id: block.id, command: block.command, closing: block.closing };
9223
+ }
9224
+ return { id: block.id, command: block.command, value: block.value };
9225
+ }
9226
+ function referenceJson(reference) {
9227
+ return {
9228
+ key: reference.key,
9229
+ authors: reference.authors,
9230
+ title: reference.title,
9231
+ year: reference.year,
9232
+ url: reference.url,
9233
+ venue: reference.venue,
9234
+ field_separator: reference.fieldSeparator
9235
+ };
9236
+ }
9237
+ function toDocumentJson2(document2) {
9238
+ if (document2.nodeType !== "DocumentObject" || !Array.isArray(document2.blocks)) {
9239
+ throw new Error("toDocumentJson2 requires a live Document2Node");
9240
+ }
9241
+ return {
9242
+ node_type: "DocumentObject",
9243
+ meta: {
9244
+ title: document2.meta.title,
9245
+ authors: document2.meta.authors,
9246
+ date: { ...document2.meta.date },
9247
+ abstract: document2.meta.abstract
9248
+ },
9249
+ references: document2.references.map(referenceJson),
9250
+ blocks: document2.blocks.map(blockJson)
9251
+ };
9252
+ }
9253
+
9020
9254
  // src/document2/keys.ts
9021
9255
  var DOCUMENT2_KEY_PATTERN = /^[\p{L}\p{M}0-9:._-]+$/u;
9022
9256
  function normalizeDocument2Key(key) {
@@ -9497,155 +9731,6 @@ ${body}
9497
9731
  `;
9498
9732
  }
9499
9733
 
9500
- // src/document2/exportJson.ts
9501
- function serializeField(field) {
9502
- let value = "";
9503
- let hasPersistedMath = false;
9504
- const formats = [];
9505
- const mathObjects = [];
9506
- for (const token of field.tokens) {
9507
- if (token.kind === "text") {
9508
- const start = value.length;
9509
- value += token.text;
9510
- if (token.text.length > 0 && (token.style?.bold || token.style?.italic || token.style?.underline)) {
9511
- formats.push({
9512
- start,
9513
- end: value.length,
9514
- ...token.style.bold ? { bold: true } : {},
9515
- ...token.style.italic ? { italic: true } : {},
9516
- ...token.style.underline ? { underline: true } : {}
9517
- });
9518
- }
9519
- continue;
9520
- }
9521
- if (token.kind === "cite") {
9522
- value += citeTokenLatex(token.keys);
9523
- continue;
9524
- }
9525
- if (token.kind === "ref") {
9526
- value += refTokenLatex(token.keys, token.refCommand);
9527
- continue;
9528
- }
9529
- value += token.source;
9530
- if (!token.math || token.sourceOwner === "raw") {
9531
- if (token.labelEnabled !== void 0 || token.label !== void 0) {
9532
- hasPersistedMath = true;
9533
- mathObjects.push({
9534
- node_type: "RawMathObject",
9535
- ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
9536
- ...token.label !== void 0 ? { label: token.label } : {}
9537
- });
9538
- } else {
9539
- mathObjects.push(null);
9540
- }
9541
- continue;
9542
- }
9543
- hasPersistedMath = true;
9544
- mathObjects.push({
9545
- ...toMathObjectJson(token.math),
9546
- ...token.sourceSide ? { source_side: token.sourceSide } : {},
9547
- source_owner: token.sourceOwner,
9548
- ...token.labelEnabled !== void 0 ? { label_enabled: token.labelEnabled } : {},
9549
- ...token.label !== void 0 ? { label: token.label } : {}
9550
- });
9551
- }
9552
- return { value, formats, mathObjects, hasPersistedMath };
9553
- }
9554
- function fieldJson(field) {
9555
- const serialized = serializeField(field);
9556
- return {
9557
- value: serialized.value,
9558
- ...serialized.formats.length > 0 ? { formats: serialized.formats } : {},
9559
- ...serialized.hasPersistedMath ? { math_objects: serialized.mathObjects } : {}
9560
- };
9561
- }
9562
- function listItemJson(item) {
9563
- const field = fieldJson(item.field);
9564
- return {
9565
- value: field.value ?? "",
9566
- ...field.formats ? { formats: field.formats } : {},
9567
- ...field.math_objects ? { math_objects: field.math_objects } : {},
9568
- ...item.blocks.length > 0 ? { blocks: item.blocks.map(blockJson) } : {}
9569
- };
9570
- }
9571
- function blockJson(block) {
9572
- if (block.kind === "textBlock") {
9573
- return {
9574
- command: block.command,
9575
- ...fieldJson(block.field),
9576
- ...block.command === "\\paragraph" ? { centered: block.centered === true } : {}
9577
- };
9578
- }
9579
- if (block.kind === "list") {
9580
- return {
9581
- command: block.command,
9582
- closing: block.closing,
9583
- items: block.items.map(listItemJson)
9584
- };
9585
- }
9586
- if (block.kind === "table") {
9587
- const fields = block.rows.map((row) => row.map(serializeField));
9588
- const hasPersistedMath = fields.some((row) => row.some((field) => field.hasPersistedMath));
9589
- return {
9590
- command: block.command,
9591
- closing: block.closing,
9592
- columns: block.columns,
9593
- rows: fields.map((row) => row.map((field) => field.value)),
9594
- ...fields.some((row) => row.some((field) => field.formats.length > 0)) ? { cell_formats: fields.map((row) => row.map((field) => field.formats)) } : {},
9595
- ...hasPersistedMath ? { math_objects: fields.flatMap((row) => row.flatMap((field) => field.mathObjects)) } : {},
9596
- centered: block.centered,
9597
- caption_enabled: block.captionEnabled,
9598
- caption: block.caption,
9599
- label_enabled: block.labelEnabled,
9600
- label: block.label
9601
- };
9602
- }
9603
- if (block.kind === "image") {
9604
- return {
9605
- command: block.command,
9606
- value: block.value,
9607
- ...block.assetId !== void 0 ? { asset_id: block.assetId } : {},
9608
- options: { ...block.options },
9609
- centered: block.centered,
9610
- caption_enabled: block.captionEnabled,
9611
- caption: block.caption,
9612
- label_enabled: block.labelEnabled,
9613
- label: block.label
9614
- };
9615
- }
9616
- if (block.kind === "bibliography") {
9617
- return { command: block.command, closing: block.closing };
9618
- }
9619
- return { command: block.command, value: block.value };
9620
- }
9621
- function referenceJson(reference) {
9622
- return {
9623
- key: reference.key,
9624
- authors: reference.authors,
9625
- title: reference.title,
9626
- year: reference.year,
9627
- url: reference.url,
9628
- venue: reference.venue,
9629
- field_separator: reference.fieldSeparator
9630
- };
9631
- }
9632
- function toDocumentJson2(document2) {
9633
- if (document2.nodeType !== "DocumentObject" || !Array.isArray(document2.blocks)) {
9634
- throw new Error("toDocumentJson2 requires a live Document2Node");
9635
- }
9636
- return {
9637
- node_type: "DocumentObject",
9638
- meta: {
9639
- title: document2.meta.title,
9640
- authors: document2.meta.authors,
9641
- date: { ...document2.meta.date },
9642
- abstract: document2.meta.abstract
9643
- },
9644
- references: document2.references.map(referenceJson),
9645
- blocks: document2.blocks.map(blockJson)
9646
- };
9647
- }
9648
-
9649
9734
  // src/document2/history.ts
9650
9735
  var DEFAULT_DOCUMENT2_HISTORY_MAX_DEPTH = 100;
9651
9736
  function createDocument2History(maxDepth = DEFAULT_DOCUMENT2_HISTORY_MAX_DEPTH) {
@@ -9991,6 +10076,15 @@ var DOCUMENT2_MESSAGES = {
9991
10076
  loadDocumentError: "\u062A\u0639\u0630\u0631 \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0645\u0633\u062A\u0646\u062F",
9992
10077
  runtimePersistenceError: "\u0644\u0627 \u064A\u0645\u0643\u0646 \u062A\u062D\u0645\u064A\u0644 Document2Node \u0628\u0639\u062F JSON.stringify. \u0627\u062D\u0641\u0638 Document2Json \u0628\u0627\u0633\u062A\u062E\u062F\u0627\u0645 toDocumentJson2.",
9993
10078
  emptyImagePath: "\u0644\u0627 \u064A\u0648\u062C\u062F \u0645\u0633\u0627\u0631 \u0635\u0648\u0631\u0629",
10079
+ noImageSelected: "\u0644\u0627 \u0635\u0648\u0631\u0629 \u0645\u062D\u062F\u062F\u0629",
10080
+ chooseImage: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0635\u0648\u0631\u0629",
10081
+ changeImage: "\u062A\u063A\u064A\u064A\u0631",
10082
+ removeImage: "\u0625\u0632\u0627\u0644\u0629",
10083
+ selectImageAsset: "\u0627\u062E\u062A\u064A\u0627\u0631 \u0635\u0648\u0631\u0629",
10084
+ noImageAssets: "\u0644\u0627 \u062A\u0648\u062C\u062F \u0635\u0648\u0631 \u0645\u062A\u0627\u062D\u0629",
10085
+ loadingImageAssets: "\u062C\u0627\u0631 \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0635\u0648\u0631\u2026",
10086
+ advancedImagePath: "\u062E\u064A\u0627\u0631\u0627\u062A \u0645\u062A\u0642\u062F\u0645\u0629",
10087
+ imagePreviewAlt: "\u0645\u0639\u0627\u064A\u0646\u0629 \u0627\u0644\u0635\u0648\u0631\u0629",
9994
10088
  blockSelection: "\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0643\u062A\u0644",
9995
10089
  selectBlock: "\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0643\u062A\u0644\u0629",
9996
10090
  selectedBlockCount: "\u0645\u062D\u062F\u062F\u0629",
@@ -10135,6 +10229,15 @@ var DOCUMENT2_MESSAGES = {
10135
10229
  loadDocumentError: "Could not load document",
10136
10230
  runtimePersistenceError: "A Document2Node cannot be loaded after JSON.stringify. Persist Document2Json with toDocumentJson2 instead.",
10137
10231
  emptyImagePath: "Empty image path",
10232
+ noImageSelected: "No image selected",
10233
+ chooseImage: "Choose image",
10234
+ changeImage: "Change",
10235
+ removeImage: "Remove",
10236
+ selectImageAsset: "Select image",
10237
+ noImageAssets: "No images available",
10238
+ loadingImageAssets: "Loading images\u2026",
10239
+ advancedImagePath: "Advanced",
10240
+ imagePreviewAlt: "Image preview",
10138
10241
  blockSelection: "Block selection",
10139
10242
  selectBlock: "Select block",
10140
10243
  selectedBlockCount: "selected",
@@ -11623,6 +11726,167 @@ function FloatMetaEditor({
11623
11726
  ] }) : null
11624
11727
  ] });
11625
11728
  }
11729
+ function ImageAssetEditor({
11730
+ block,
11731
+ uiLocale,
11732
+ messages,
11733
+ resolveImageUrl,
11734
+ onRequestImagePick,
11735
+ listImageAssets,
11736
+ renderImageBlockEditor,
11737
+ onSelectAsset,
11738
+ onClearAsset,
11739
+ onImageSrcChange,
11740
+ onBlockFocus
11741
+ }) {
11742
+ const [assets, setAssets] = (0, import_react4.useState)([]);
11743
+ const [assetsLoaded, setAssetsLoaded] = (0, import_react4.useState)(false);
11744
+ const [assetsLoading, setAssetsLoading] = (0, import_react4.useState)(false);
11745
+ const [advancedOpen, setAdvancedOpen] = (0, import_react4.useState)(false);
11746
+ const hasImage = block.value.length > 0 || Boolean(block.assetId);
11747
+ const resolvedUrl = hasImage ? resolveImageUrl ? resolveImageUrl({ assetId: block.assetId, value: block.value }) : block.value : "";
11748
+ const current = block.assetId ? { assetId: block.assetId, value: block.value } : null;
11749
+ const canUsePicker = Boolean(onRequestImagePick);
11750
+ (0, import_react4.useEffect)(() => {
11751
+ let alive = true;
11752
+ if (!listImageAssets || onRequestImagePick) {
11753
+ setAssets([]);
11754
+ setAssetsLoaded(false);
11755
+ setAssetsLoading(false);
11756
+ return () => {
11757
+ alive = false;
11758
+ };
11759
+ }
11760
+ setAssetsLoading(true);
11761
+ Promise.resolve(listImageAssets()).then((nextAssets) => {
11762
+ if (!alive) {
11763
+ return;
11764
+ }
11765
+ setAssets(nextAssets);
11766
+ setAssetsLoaded(true);
11767
+ }).catch(() => {
11768
+ if (!alive) {
11769
+ return;
11770
+ }
11771
+ setAssets([]);
11772
+ setAssetsLoaded(true);
11773
+ }).finally(() => {
11774
+ if (alive) {
11775
+ setAssetsLoading(false);
11776
+ }
11777
+ });
11778
+ return () => {
11779
+ alive = false;
11780
+ };
11781
+ }, [listImageAssets, onRequestImagePick]);
11782
+ async function requestImagePick() {
11783
+ if (!onRequestImagePick) {
11784
+ return;
11785
+ }
11786
+ const nextAsset = await onRequestImagePick({ blockId: block.id, current });
11787
+ if (nextAsset) {
11788
+ onSelectAsset?.(block.id, nextAsset);
11789
+ }
11790
+ }
11791
+ function selectListedAsset(assetId) {
11792
+ const nextAsset = assets.find((asset) => asset.assetId === assetId);
11793
+ if (nextAsset) {
11794
+ onSelectAsset?.(block.id, nextAsset);
11795
+ }
11796
+ }
11797
+ const imageFieldId = `butex-d2-img-${block.id}`;
11798
+ const selectId = `butex-d2-img-asset-${block.id}`;
11799
+ const canSelectListedAsset = !canUsePicker && assets.length > 0;
11800
+ function activateImagePicker() {
11801
+ if (canUsePicker) {
11802
+ void requestImagePick();
11803
+ return;
11804
+ }
11805
+ if (canSelectListedAsset && typeof document !== "undefined") {
11806
+ document.getElementById(selectId)?.focus();
11807
+ }
11808
+ }
11809
+ const defaultUi = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "butex-document2-widget__image-editor", children: [
11810
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: `butex-document2-widget__image-asset ${hasImage ? "butex-document2-widget__image-asset--filled" : "butex-document2-widget__image-asset--empty"}`, children: hasImage ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
11811
+ resolvedUrl ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { className: "butex-document2-widget__image-thumb", src: resolvedUrl, alt: messages.imagePreviewAlt }) : null,
11812
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "butex-document2-widget__image-asset-main", children: [
11813
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "butex-document2-widget__image-asset-title", dir: "ltr", children: block.assetId ?? block.value }),
11814
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "butex-document2-widget__image-actions", children: [
11815
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: activateImagePicker, disabled: !canUsePicker && !canSelectListedAsset, children: messages.changeImage }),
11816
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "butex-document2-widget__button--danger", onClick: () => onClearAsset?.(block.id), children: messages.removeImage })
11817
+ ] })
11818
+ ] })
11819
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "butex-document2-widget__image-empty-content", children: [
11820
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: messages.noImageSelected }),
11821
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: activateImagePicker, disabled: !canUsePicker && !canSelectListedAsset, children: messages.chooseImage })
11822
+ ] }) }),
11823
+ !canUsePicker ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { className: "butex-document2-widget__image-select", htmlFor: selectId, children: [
11824
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: messages.selectImageAsset }),
11825
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
11826
+ "select",
11827
+ {
11828
+ id: selectId,
11829
+ value: "",
11830
+ disabled: !canSelectListedAsset,
11831
+ onFocus: () => onBlockFocus?.(block.id),
11832
+ onChange: (event) => selectListedAsset(event.currentTarget.value),
11833
+ children: [
11834
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "", children: assetsLoading ? messages.loadingImageAssets : assetsLoaded && assets.length === 0 ? messages.noImageAssets : messages.selectImageAsset }),
11835
+ assets.map((asset) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: asset.assetId, children: asset.label ?? asset.value ?? asset.assetId }, asset.assetId))
11836
+ ]
11837
+ }
11838
+ )
11839
+ ] }) : null,
11840
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
11841
+ "details",
11842
+ {
11843
+ className: "butex-document2-widget__image-advanced",
11844
+ open: advancedOpen,
11845
+ onToggle: (event) => setAdvancedOpen(event.currentTarget.open),
11846
+ children: [
11847
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("summary", { children: messages.advancedImagePath }),
11848
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "butex-document2-widget__image-src-label", htmlFor: imageFieldId, children: messages.imagePath }),
11849
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
11850
+ "textarea",
11851
+ {
11852
+ id: imageFieldId,
11853
+ className: "butex-document2-widget__inline-text",
11854
+ value: block.value,
11855
+ dir: "ltr",
11856
+ "aria-label": messages.imagePathLabel,
11857
+ rows: 1,
11858
+ ref: (element) => {
11859
+ if (element) {
11860
+ element.style.height = "auto";
11861
+ element.style.height = `${String(element.scrollHeight)}px`;
11862
+ }
11863
+ },
11864
+ onFocus: () => onBlockFocus?.(block.id),
11865
+ onChange: (event) => {
11866
+ onImageSrcChange?.(block.id, event.currentTarget.value);
11867
+ event.currentTarget.style.height = "auto";
11868
+ event.currentTarget.style.height = `${String(event.currentTarget.scrollHeight)}px`;
11869
+ }
11870
+ }
11871
+ )
11872
+ ]
11873
+ }
11874
+ )
11875
+ ] });
11876
+ if (renderImageBlockEditor) {
11877
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: renderImageBlockEditor({
11878
+ blockId: block.id,
11879
+ assetId: block.assetId,
11880
+ value: block.value,
11881
+ resolvedUrl,
11882
+ uiLocale,
11883
+ onSelectAsset: (asset) => onSelectAsset?.(block.id, asset),
11884
+ onClearAsset: () => onClearAsset?.(block.id),
11885
+ defaultUi
11886
+ }) });
11887
+ }
11888
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: defaultUi });
11889
+ }
11626
11890
  function blockSummary(block, referenceCount, messages) {
11627
11891
  if (block.kind === "textBlock") {
11628
11892
  return inlineFieldSummary(block.field);
@@ -11671,6 +11935,12 @@ function BlockEditor({
11671
11935
  onRefFocus,
11672
11936
  onFieldBlur,
11673
11937
  onImageSrcChange,
11938
+ onImageAssetChange,
11939
+ onImageAssetClear,
11940
+ resolveImageUrl,
11941
+ onRequestImagePick,
11942
+ listImageAssets,
11943
+ renderImageBlockEditor,
11674
11944
  onFloatMetaChange,
11675
11945
  onParagraphCenteredChange,
11676
11946
  onAddListItem,
@@ -11813,6 +12083,12 @@ function BlockEditor({
11813
12083
  onCiteFocus,
11814
12084
  onFieldBlur,
11815
12085
  onImageSrcChange,
12086
+ onImageAssetChange,
12087
+ onImageAssetClear,
12088
+ resolveImageUrl,
12089
+ onRequestImagePick,
12090
+ listImageAssets,
12091
+ renderImageBlockEditor,
11816
12092
  onAddListItem,
11817
12093
  onRemoveListItem,
11818
12094
  onManageReferences
@@ -11851,31 +12127,22 @@ function BlockEditor({
11851
12127
  ] });
11852
12128
  }
11853
12129
  if (block.kind === "image") {
11854
- const imageFieldId = `butex-d2-img-${block.id}`;
11855
12130
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { className: chromeClass, children: [
11856
12131
  header,
11857
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "butex-document2-widget__image-src-label", htmlFor: imageFieldId, children: messages.imagePath }),
11858
12132
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
11859
- "textarea",
12133
+ ImageAssetEditor,
11860
12134
  {
11861
- id: imageFieldId,
11862
- className: "butex-document2-widget__inline-text",
11863
- value: block.value,
11864
- dir: "ltr",
11865
- "aria-label": messages.imagePathLabel,
11866
- rows: 1,
11867
- ref: (element) => {
11868
- if (element) {
11869
- element.style.height = "auto";
11870
- element.style.height = `${String(element.scrollHeight)}px`;
11871
- }
11872
- },
11873
- onFocus: () => onBlockFocus?.(block.id),
11874
- onChange: (event) => {
11875
- onImageSrcChange?.(block.id, event.currentTarget.value);
11876
- event.currentTarget.style.height = "auto";
11877
- event.currentTarget.style.height = `${String(event.currentTarget.scrollHeight)}px`;
11878
- }
12135
+ block,
12136
+ uiLocale,
12137
+ messages,
12138
+ resolveImageUrl,
12139
+ onRequestImagePick,
12140
+ listImageAssets,
12141
+ renderImageBlockEditor,
12142
+ onSelectAsset: onImageAssetChange,
12143
+ onClearAsset: onImageAssetClear,
12144
+ onImageSrcChange,
12145
+ onBlockFocus
11879
12146
  }
11880
12147
  ),
11881
12148
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
@@ -16750,6 +17017,94 @@ var DOCUMENT2_WIDGET_CSS = `
16750
17017
  border-color: var(--butex-document2-dev-border);
16751
17018
  }
16752
17019
 
17020
+ .butex-document2-widget__image-editor {
17021
+ display: grid;
17022
+ gap: 10px;
17023
+ min-width: 0;
17024
+ }
17025
+
17026
+ .butex-document2-widget__image-asset {
17027
+ align-items: center;
17028
+ border: 1px solid var(--butex-document2-border);
17029
+ border-radius: 8px;
17030
+ display: flex;
17031
+ gap: 12px;
17032
+ min-height: 128px;
17033
+ min-width: 0;
17034
+ padding: 12px;
17035
+ }
17036
+
17037
+ .butex-document2-widget__image-asset--empty {
17038
+ border-style: dashed;
17039
+ justify-content: center;
17040
+ }
17041
+
17042
+ .butex-document2-widget__image-empty-content,
17043
+ .butex-document2-widget__image-asset-main,
17044
+ .butex-document2-widget__image-select,
17045
+ .butex-document2-widget__image-advanced {
17046
+ display: grid;
17047
+ gap: 8px;
17048
+ min-width: 0;
17049
+ }
17050
+
17051
+ .butex-document2-widget__image-empty-content {
17052
+ justify-items: center;
17053
+ text-align: center;
17054
+ }
17055
+
17056
+ .butex-document2-widget__image-thumb {
17057
+ aspect-ratio: 4 / 3;
17058
+ background: var(--butex-document2-bg);
17059
+ border: 1px solid var(--butex-document2-border);
17060
+ border-radius: 8px;
17061
+ flex: 0 0 128px;
17062
+ max-width: 38%;
17063
+ object-fit: cover;
17064
+ width: 128px;
17065
+ }
17066
+
17067
+ .butex-document2-widget__image-asset-title {
17068
+ color: var(--butex-document2-muted);
17069
+ display: block;
17070
+ overflow: hidden;
17071
+ text-align: start;
17072
+ text-overflow: ellipsis;
17073
+ white-space: nowrap;
17074
+ }
17075
+
17076
+ .butex-document2-widget__image-actions {
17077
+ display: flex;
17078
+ flex-wrap: wrap;
17079
+ gap: 8px;
17080
+ }
17081
+
17082
+ .butex-document2-widget__image-select span,
17083
+ .butex-document2-widget__image-src-label {
17084
+ color: var(--butex-document2-muted);
17085
+ font-size: 0.86rem;
17086
+ }
17087
+
17088
+ .butex-document2-widget__image-select select {
17089
+ background: var(--butex-document2-input-bg);
17090
+ border: 1px solid var(--butex-document2-border);
17091
+ border-radius: 8px;
17092
+ color: var(--butex-document2-fg);
17093
+ min-height: 36px;
17094
+ padding: 6px 8px;
17095
+ }
17096
+
17097
+ .butex-document2-widget__image-select select:disabled {
17098
+ cursor: not-allowed;
17099
+ opacity: 0.55;
17100
+ }
17101
+
17102
+ .butex-document2-widget__image-advanced summary {
17103
+ color: var(--butex-document2-muted);
17104
+ cursor: pointer;
17105
+ font-size: 0.86rem;
17106
+ }
17107
+
16753
17108
  .butex-document2-widget__dev pre {
16754
17109
  background: var(--butex-document2-dev-code-bg);
16755
17110
  color: var(--butex-document2-dev-code-fg);
@@ -17643,6 +17998,9 @@ var ButexDocumentEditor2 = (0, import_react13.forwardRef)(
17643
17998
  digitForm: digitFormProp,
17644
17999
  onDigitFormChange,
17645
18000
  resolveImageUrl,
18001
+ onRequestImagePick,
18002
+ listImageAssets,
18003
+ renderImageBlockEditor,
17646
18004
  onDocumentChange,
17647
18005
  onDocumentJsonChange,
17648
18006
  onLatexChange
@@ -17843,10 +18201,10 @@ var ButexDocumentEditor2 = (0, import_react13.forwardRef)(
17843
18201
  );
17844
18202
  const afterBlockId = (0, import_react13.useCallback)(() => resolveInsertAfterBlockId(documentRef.current, editorFocusRef.current), []);
17845
18203
  const insertImageBlock = (0, import_react13.useCallback)(
17846
- (srcOrAssetId = "") => {
18204
+ (srcOrAsset = "") => {
17847
18205
  clearBlockSelection();
17848
18206
  const after = resolveInsertAfterBlockId(documentRef.current, editorFocusRef.current);
17849
- applyDocument(addDocument2ImageBlock(documentRef.current, srcOrAssetId, after), "immediate");
18207
+ applyDocument(addDocument2ImageBlock(documentRef.current, srcOrAsset, after), "immediate");
17850
18208
  },
17851
18209
  [applyDocument]
17852
18210
  );
@@ -17857,6 +18215,9 @@ var ButexDocumentEditor2 = (0, import_react13.forwardRef)(
17857
18215
  updateImageBlockValue(blockId, value) {
17858
18216
  applyDocument(updateDocument2ImageValue(documentRef.current, blockId, value), "immediate");
17859
18217
  },
18218
+ updateImageBlockAsset(blockId, asset) {
18219
+ applyDocument(updateDocument2ImageAsset(documentRef.current, blockId, asset), "immediate");
18220
+ },
17860
18221
  getDocumentJson() {
17861
18222
  return toDocumentJson2(documentRef.current);
17862
18223
  }
@@ -18412,6 +18773,18 @@ var ButexDocumentEditor2 = (0, import_react13.forwardRef)(
18412
18773
  clearBlockSelection();
18413
18774
  applyDocument(updateDocument2ImageValue(documentRef.current, blockId, value), "text");
18414
18775
  },
18776
+ onImageAssetChange: (blockId, asset) => {
18777
+ clearBlockSelection();
18778
+ applyDocument(updateDocument2ImageAsset(documentRef.current, blockId, asset), "immediate");
18779
+ },
18780
+ onImageAssetClear: (blockId) => {
18781
+ clearBlockSelection();
18782
+ applyDocument(clearDocument2ImageAsset(documentRef.current, blockId), "immediate");
18783
+ },
18784
+ resolveImageUrl,
18785
+ onRequestImagePick,
18786
+ listImageAssets,
18787
+ renderImageBlockEditor,
18415
18788
  onFloatMetaChange: (blockId, kind, patch) => {
18416
18789
  clearBlockSelection();
18417
18790
  applyDocument(