@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.
- package/README.md +8 -2
- package/dist/document2-cli.js +984 -280
- package/dist/document2-cli.js.map +1 -1
- package/dist/document2.d.mts +156 -8
- package/dist/document2.d.ts +156 -8
- package/dist/document2.js +1054 -240
- package/dist/document2.js.map +1 -1
- package/dist/document2.mjs +1054 -240
- package/dist/document2.mjs.map +1 -1
- package/dist/react-document2.d.mts +31 -0
- package/dist/react-document2.d.ts +31 -0
- package/dist/react-document2.js +201 -86
- package/dist/react-document2.js.map +1 -1
- package/dist/react-document2.mjs +201 -86
- package/dist/react-document2.mjs.map +1 -1
- package/package.json +1 -1
package/dist/document2-cli.js
CHANGED
|
@@ -107,14 +107,6 @@ function normalizeDocument2Meta(value) {
|
|
|
107
107
|
abstract: typeof raw.abstract === "string" ? raw.abstract : ""
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
|
-
function cloneDocument2Meta(meta) {
|
|
111
|
-
return {
|
|
112
|
-
title: meta.title,
|
|
113
|
-
authors: meta.authors,
|
|
114
|
-
date: { ...meta.date },
|
|
115
|
-
abstract: meta.abstract
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
110
|
|
|
119
111
|
// src/document2/citations.ts
|
|
120
112
|
var DEFAULT_REFERENCE_FIELD_SEPARATOR = "\u060C";
|
|
@@ -596,6 +588,76 @@ function refTokenLatex(keys, refCommand) {
|
|
|
596
588
|
return `${command}{${keys.join(",")}}`;
|
|
597
589
|
}
|
|
598
590
|
|
|
591
|
+
// src/document2/inlineIdentity.ts
|
|
592
|
+
function createDocument2IdentityState() {
|
|
593
|
+
return {
|
|
594
|
+
blockIds: /* @__PURE__ */ new Set(),
|
|
595
|
+
itemIds: /* @__PURE__ */ new Set(),
|
|
596
|
+
fieldIds: /* @__PURE__ */ new Set(),
|
|
597
|
+
tokenIds: /* @__PURE__ */ new Set()
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function importedOrGeneratedId(value, prefix, used) {
|
|
601
|
+
const imported = typeof value === "string" ? value.trim() : "";
|
|
602
|
+
if (imported.length > 0 && !used.has(imported)) {
|
|
603
|
+
used.add(imported);
|
|
604
|
+
return imported;
|
|
605
|
+
}
|
|
606
|
+
let generated = document2Id(prefix);
|
|
607
|
+
while (used.has(generated)) {
|
|
608
|
+
generated = document2Id(prefix);
|
|
609
|
+
}
|
|
610
|
+
used.add(generated);
|
|
611
|
+
return generated;
|
|
612
|
+
}
|
|
613
|
+
function inlineTokenSource(token) {
|
|
614
|
+
if (token.kind === "text") {
|
|
615
|
+
return token.text;
|
|
616
|
+
}
|
|
617
|
+
if (token.kind === "math") {
|
|
618
|
+
return token.source;
|
|
619
|
+
}
|
|
620
|
+
if (token.kind === "cite") {
|
|
621
|
+
return citeTokenLatex(token.keys);
|
|
622
|
+
}
|
|
623
|
+
return refTokenLatex(token.keys, token.refCommand);
|
|
624
|
+
}
|
|
625
|
+
function inlineIdsFromField(field) {
|
|
626
|
+
let offset = 0;
|
|
627
|
+
const tokens = field.tokens.map((token) => {
|
|
628
|
+
const start = offset;
|
|
629
|
+
offset += inlineTokenSource(token).length;
|
|
630
|
+
return { id: token.id, kind: token.kind, start, end: offset };
|
|
631
|
+
});
|
|
632
|
+
return { field_id: field.id, tokens };
|
|
633
|
+
}
|
|
634
|
+
function isAlignedSidecar(value, field) {
|
|
635
|
+
if (typeof value !== "object" || value === null || !Array.isArray(value.tokens)) {
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
const tokens = value.tokens;
|
|
639
|
+
const expected = inlineIdsFromField(field).tokens;
|
|
640
|
+
if (tokens.length !== expected.length) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
return tokens.every((token, index) => {
|
|
644
|
+
const expectedToken = expected[index];
|
|
645
|
+
return typeof token === "object" && token !== null && expectedToken !== void 0 && token.kind === expectedToken.kind && token.start === expectedToken.start && token.end === expectedToken.end;
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
function applyImportedInlineIds(field, sidecar, state) {
|
|
649
|
+
field.id = importedOrGeneratedId(
|
|
650
|
+
typeof sidecar === "object" && sidecar !== null ? sidecar.field_id : void 0,
|
|
651
|
+
"field",
|
|
652
|
+
state.fieldIds
|
|
653
|
+
);
|
|
654
|
+
const aligned = isAlignedSidecar(sidecar, field);
|
|
655
|
+
field.tokens.forEach((token, index) => {
|
|
656
|
+
token.id = importedOrGeneratedId(aligned ? sidecar.tokens[index]?.id : void 0, token.kind, state.tokenIds);
|
|
657
|
+
});
|
|
658
|
+
return field;
|
|
659
|
+
}
|
|
660
|
+
|
|
599
661
|
// src/document2/inlineScanner.ts
|
|
600
662
|
var MATH_ENVIRONMENTS = /* @__PURE__ */ new Set([
|
|
601
663
|
"equation",
|
|
@@ -674,17 +736,13 @@ function citeSpan(value, start) {
|
|
|
674
736
|
function refSpan(value, start) {
|
|
675
737
|
const eqrefPrefix = "\\eqref{";
|
|
676
738
|
const refPrefix = "\\ref{";
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (
|
|
680
|
-
refCommand = "eqref";
|
|
681
|
-
prefix = eqrefPrefix;
|
|
682
|
-
} else if (value.startsWith(refPrefix, start) && !isEscaped(value, start)) {
|
|
683
|
-
refCommand = "ref";
|
|
684
|
-
prefix = refPrefix;
|
|
685
|
-
} else {
|
|
739
|
+
const isEqref = value.startsWith(eqrefPrefix, start) && !isEscaped(value, start);
|
|
740
|
+
const isRef = value.startsWith(refPrefix, start) && !isEscaped(value, start);
|
|
741
|
+
if (!isEqref && !isRef) {
|
|
686
742
|
return null;
|
|
687
743
|
}
|
|
744
|
+
const refCommand = isEqref ? "eqref" : "ref";
|
|
745
|
+
const prefix = isEqref ? eqrefPrefix : refPrefix;
|
|
688
746
|
const openBrace = start + prefix.length - 1;
|
|
689
747
|
if (value[openBrace] !== "{") {
|
|
690
748
|
return null;
|
|
@@ -743,7 +801,14 @@ function mathSpanAt(value, i) {
|
|
|
743
801
|
return null;
|
|
744
802
|
}
|
|
745
803
|
function detectMathSpans2(value) {
|
|
746
|
-
return detectInlineSpans2(value).filter((span) => span.kind === "math").map((
|
|
804
|
+
return detectInlineSpans2(value).filter((span) => span.kind === "math").map((span) => ({
|
|
805
|
+
start: span.start,
|
|
806
|
+
end: span.end,
|
|
807
|
+
source: span.source,
|
|
808
|
+
opening: span.opening,
|
|
809
|
+
closing: span.closing,
|
|
810
|
+
display: span.display
|
|
811
|
+
}));
|
|
747
812
|
}
|
|
748
813
|
function detectInlineSpans2(value) {
|
|
749
814
|
const spans = [];
|
|
@@ -797,17 +862,13 @@ function asBlockJson(value) {
|
|
|
797
862
|
return value;
|
|
798
863
|
}
|
|
799
864
|
function blockIdFromJson(json, state) {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
let generated = document2Id("block");
|
|
806
|
-
while (state.used.has(generated)) {
|
|
807
|
-
generated = document2Id("block");
|
|
865
|
+
return importedOrGeneratedId(json.id, "block", state.blockIds);
|
|
866
|
+
}
|
|
867
|
+
function metadataFromJson(value) {
|
|
868
|
+
if (!isObject2(value) || value.source !== "agent" && value.source !== "user") {
|
|
869
|
+
return {};
|
|
808
870
|
}
|
|
809
|
-
|
|
810
|
-
return generated;
|
|
871
|
+
return { metadata: { source: value.source } };
|
|
811
872
|
}
|
|
812
873
|
function pushDiagnostic(diagnostics, options, path, message) {
|
|
813
874
|
if (options.strict) {
|
|
@@ -990,44 +1051,54 @@ function pushFormattedTextTokens(tokens, text, sourceStart, formats) {
|
|
|
990
1051
|
function closingForList(command) {
|
|
991
1052
|
return command === "\\begin{itemize}" ? "\\end{itemize}" : "\\end{enumerate}";
|
|
992
1053
|
}
|
|
993
|
-
function parseTextBlock(json, options, path, diagnostics,
|
|
1054
|
+
function parseTextBlock(json, options, path, diagnostics, identities) {
|
|
994
1055
|
const value = requireString(json.value, `${json.command} requires string value`);
|
|
995
1056
|
return {
|
|
996
|
-
id: blockIdFromJson(json,
|
|
1057
|
+
id: blockIdFromJson(json, identities),
|
|
997
1058
|
kind: "textBlock",
|
|
998
1059
|
command: json.command,
|
|
999
|
-
field:
|
|
1060
|
+
field: applyImportedInlineIds(
|
|
1061
|
+
createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
|
|
1062
|
+
json.inline_ids,
|
|
1063
|
+
identities
|
|
1064
|
+
),
|
|
1065
|
+
...metadataFromJson(json.metadata),
|
|
1000
1066
|
...json.centered === true ? { centered: true } : {}
|
|
1001
1067
|
};
|
|
1002
1068
|
}
|
|
1003
|
-
function parseListItem(json, options, path, diagnostics,
|
|
1069
|
+
function parseListItem(json, options, path, diagnostics, identities) {
|
|
1004
1070
|
const value = requireString(json.value, "Document list item requires string value");
|
|
1005
1071
|
const blocksJson = Array.isArray(json.blocks) ? json.blocks : [];
|
|
1006
1072
|
return {
|
|
1007
|
-
id:
|
|
1008
|
-
field:
|
|
1073
|
+
id: importedOrGeneratedId(json.id, "item", identities.itemIds),
|
|
1074
|
+
field: applyImportedInlineIds(
|
|
1075
|
+
createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
|
|
1076
|
+
json.inline_ids,
|
|
1077
|
+
identities
|
|
1078
|
+
),
|
|
1009
1079
|
blocks: blocksJson.map(
|
|
1010
|
-
(block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics,
|
|
1080
|
+
(block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics, identities)
|
|
1011
1081
|
)
|
|
1012
1082
|
};
|
|
1013
1083
|
}
|
|
1014
|
-
function parseListBlock(json, options, path, diagnostics,
|
|
1084
|
+
function parseListBlock(json, options, path, diagnostics, identities) {
|
|
1015
1085
|
if (!Array.isArray(json.items)) {
|
|
1016
1086
|
throw new Error(`${json.command} requires items array`);
|
|
1017
1087
|
}
|
|
1018
1088
|
const command = json.command;
|
|
1019
1089
|
const closing = closingForList(command);
|
|
1020
1090
|
return {
|
|
1021
|
-
id: blockIdFromJson(json,
|
|
1091
|
+
id: blockIdFromJson(json, identities),
|
|
1022
1092
|
kind: "list",
|
|
1023
1093
|
command,
|
|
1024
1094
|
closing,
|
|
1025
1095
|
items: json.items.map(
|
|
1026
|
-
(item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics,
|
|
1027
|
-
)
|
|
1096
|
+
(item, index) => parseListItem(item, options, `${path}.items[${String(index)}]`, diagnostics, identities)
|
|
1097
|
+
),
|
|
1098
|
+
...metadataFromJson(json.metadata)
|
|
1028
1099
|
};
|
|
1029
1100
|
}
|
|
1030
|
-
function parseTableBlock(json, options, path, diagnostics,
|
|
1101
|
+
function parseTableBlock(json, options, path, diagnostics, identities) {
|
|
1031
1102
|
if (!Array.isArray(json.rows)) {
|
|
1032
1103
|
throw new Error("\\begin{tabular} requires rows array");
|
|
1033
1104
|
}
|
|
@@ -1043,75 +1114,84 @@ function parseTableBlock(json, options, path, diagnostics, blockIds) {
|
|
|
1043
1114
|
const cellMathObjects = mathObjects.slice(mathObjectIndex, mathObjectIndex + spanCount);
|
|
1044
1115
|
mathObjectIndex += spanCount;
|
|
1045
1116
|
const cellFormats = json.cell_formats?.[rowIndex]?.[columnIndex] ?? [];
|
|
1046
|
-
return
|
|
1117
|
+
return applyImportedInlineIds(
|
|
1118
|
+
createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics, cellFormats),
|
|
1119
|
+
json.cell_inline_ids?.[rowIndex]?.[columnIndex],
|
|
1120
|
+
identities
|
|
1121
|
+
);
|
|
1047
1122
|
});
|
|
1048
1123
|
});
|
|
1049
1124
|
if (mathObjects.length > 0 && mathObjects.length !== mathObjectIndex) {
|
|
1050
1125
|
pushDiagnostic(diagnostics, options, path, `math_objects count mismatch: detected ${String(mathObjectIndex)}, got ${String(mathObjects.length)}`);
|
|
1051
1126
|
}
|
|
1052
1127
|
return {
|
|
1053
|
-
id: blockIdFromJson(json,
|
|
1128
|
+
id: blockIdFromJson(json, identities),
|
|
1054
1129
|
kind: "table",
|
|
1055
1130
|
command: "\\begin{tabular}",
|
|
1056
1131
|
closing: "\\end{tabular}",
|
|
1057
1132
|
columns: typeof json.columns === "string" ? json.columns : "",
|
|
1058
1133
|
rows,
|
|
1059
|
-
...parseFloatMetaFromJson(json)
|
|
1134
|
+
...parseFloatMetaFromJson(json),
|
|
1135
|
+
...metadataFromJson(json.metadata)
|
|
1060
1136
|
};
|
|
1061
1137
|
}
|
|
1062
|
-
function parseImageBlock(json,
|
|
1138
|
+
function parseImageBlock(json, identities) {
|
|
1063
1139
|
const assetId = typeof json.asset_id === "string" && json.asset_id.length > 0 ? json.asset_id : void 0;
|
|
1064
1140
|
const value = assetId !== void 0 ? typeof json.value === "string" ? json.value : "" : requireString(json.value, "\\includegraphics requires string value");
|
|
1065
1141
|
return {
|
|
1066
|
-
id: blockIdFromJson(json,
|
|
1142
|
+
id: blockIdFromJson(json, identities),
|
|
1067
1143
|
kind: "image",
|
|
1068
1144
|
command: "\\includegraphics",
|
|
1069
1145
|
value,
|
|
1070
1146
|
...assetId !== void 0 ? { assetId } : {},
|
|
1071
1147
|
options: isRecordOfStrings(json.options) ? json.options : {},
|
|
1072
|
-
...parseFloatMetaFromJson(json)
|
|
1148
|
+
...parseFloatMetaFromJson(json),
|
|
1149
|
+
...metadataFromJson(json.metadata)
|
|
1073
1150
|
};
|
|
1074
1151
|
}
|
|
1075
|
-
function parseRawBlock(json,
|
|
1152
|
+
function parseRawBlock(json, identities) {
|
|
1076
1153
|
return {
|
|
1077
|
-
id: blockIdFromJson(json,
|
|
1154
|
+
id: blockIdFromJson(json, identities),
|
|
1078
1155
|
kind: "raw",
|
|
1079
1156
|
command: "\\raw",
|
|
1080
|
-
value: typeof json.value === "string" ? json.value : ""
|
|
1157
|
+
value: typeof json.value === "string" ? json.value : "",
|
|
1158
|
+
...metadataFromJson(json.metadata)
|
|
1081
1159
|
};
|
|
1082
1160
|
}
|
|
1083
|
-
function parseBibliographyBlock(json,
|
|
1161
|
+
function parseBibliographyBlock(json, identities) {
|
|
1084
1162
|
return {
|
|
1085
|
-
id: blockIdFromJson(json,
|
|
1163
|
+
id: blockIdFromJson(json, identities),
|
|
1086
1164
|
kind: "bibliography",
|
|
1087
1165
|
command: "\\begin{thebibliography}",
|
|
1088
|
-
closing: "\\end{thebibliography}"
|
|
1166
|
+
closing: "\\end{thebibliography}",
|
|
1167
|
+
...metadataFromJson(json.metadata)
|
|
1089
1168
|
};
|
|
1090
1169
|
}
|
|
1091
|
-
function parseBlock(json, options, path, diagnostics,
|
|
1170
|
+
function parseBlock(json, options, path, diagnostics, identities) {
|
|
1092
1171
|
if (TEXT_COMMANDS.has(json.command)) {
|
|
1093
|
-
return parseTextBlock(json, options, path, diagnostics,
|
|
1172
|
+
return parseTextBlock(json, options, path, diagnostics, identities);
|
|
1094
1173
|
}
|
|
1095
1174
|
if (LIST_COMMANDS.has(json.command)) {
|
|
1096
|
-
return parseListBlock(json, options, path, diagnostics,
|
|
1175
|
+
return parseListBlock(json, options, path, diagnostics, identities);
|
|
1097
1176
|
}
|
|
1098
1177
|
if (json.command === "\\begin{tabular}") {
|
|
1099
|
-
return parseTableBlock(json, options, path, diagnostics,
|
|
1178
|
+
return parseTableBlock(json, options, path, diagnostics, identities);
|
|
1100
1179
|
}
|
|
1101
1180
|
if (json.command === "\\includegraphics") {
|
|
1102
|
-
return parseImageBlock(json,
|
|
1181
|
+
return parseImageBlock(json, identities);
|
|
1103
1182
|
}
|
|
1104
1183
|
if (json.command === "\\begin{thebibliography}" || json.command === "\\bibliography") {
|
|
1105
|
-
return parseBibliographyBlock(json,
|
|
1184
|
+
return parseBibliographyBlock(json, identities);
|
|
1106
1185
|
}
|
|
1107
1186
|
if (json.command === "\\raw") {
|
|
1108
|
-
return parseRawBlock(json,
|
|
1187
|
+
return parseRawBlock(json, identities);
|
|
1109
1188
|
}
|
|
1110
1189
|
return {
|
|
1111
|
-
id: blockIdFromJson(json,
|
|
1190
|
+
id: blockIdFromJson(json, identities),
|
|
1112
1191
|
kind: "raw",
|
|
1113
1192
|
command: "\\raw",
|
|
1114
|
-
value: typeof json.value === "string" ? json.value : json.command
|
|
1193
|
+
value: typeof json.value === "string" ? json.value : json.command,
|
|
1194
|
+
...metadataFromJson(json.metadata)
|
|
1115
1195
|
};
|
|
1116
1196
|
}
|
|
1117
1197
|
function fromDocumentJson2(json, options = {}) {
|
|
@@ -1122,13 +1202,13 @@ function fromDocumentJson2(json, options = {}) {
|
|
|
1122
1202
|
throw new Error("DocumentObject requires blocks array");
|
|
1123
1203
|
}
|
|
1124
1204
|
const diagnostics = [];
|
|
1125
|
-
const
|
|
1205
|
+
const identities = createDocument2IdentityState();
|
|
1126
1206
|
return {
|
|
1127
1207
|
nodeType: "DocumentObject",
|
|
1128
1208
|
meta: normalizeDocument2Meta(json.meta),
|
|
1129
1209
|
references: parseReferences(json.references),
|
|
1130
1210
|
blocks: json.blocks.map(
|
|
1131
|
-
(block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics,
|
|
1211
|
+
(block, index) => parseBlock(asBlockJson(block), options, `$.blocks[${String(index)}]`, diagnostics, identities)
|
|
1132
1212
|
),
|
|
1133
1213
|
diagnostics
|
|
1134
1214
|
};
|
|
@@ -2446,97 +2526,762 @@ var CHAR_FONT_EXPORT_TEX = Object.fromEntries(
|
|
|
2446
2526
|
NON_DEFAULT_CHARACTER_FONT_SPECS.map((font) => [font.id, font.macro])
|
|
2447
2527
|
);
|
|
2448
2528
|
|
|
2449
|
-
// src/document2/
|
|
2450
|
-
|
|
2451
|
-
|
|
2529
|
+
// src/document2/jsonCommandTypes.ts
|
|
2530
|
+
var Document2CommandError = class extends Error {
|
|
2531
|
+
code;
|
|
2532
|
+
constructor(code, message) {
|
|
2533
|
+
super(message);
|
|
2534
|
+
this.name = "Document2CommandError";
|
|
2535
|
+
this.code = code;
|
|
2536
|
+
}
|
|
2537
|
+
};
|
|
2538
|
+
|
|
2539
|
+
// src/document2/jsonCommandHelpers.ts
|
|
2540
|
+
function isObject3(value) {
|
|
2541
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2452
2542
|
}
|
|
2453
|
-
function
|
|
2454
|
-
|
|
2543
|
+
function requireString2(value, field, allowEmpty = true) {
|
|
2544
|
+
if (typeof value !== "string" || !allowEmpty && value.trim().length === 0) {
|
|
2545
|
+
throw new Document2CommandError("invalid_command", `${field} must be a${allowEmpty ? "" : " non-empty"} string`);
|
|
2546
|
+
}
|
|
2547
|
+
return value;
|
|
2455
2548
|
}
|
|
2456
|
-
function
|
|
2457
|
-
if (
|
|
2458
|
-
return
|
|
2549
|
+
function optionalString(value, field) {
|
|
2550
|
+
if (!(field in value)) {
|
|
2551
|
+
return void 0;
|
|
2459
2552
|
}
|
|
2460
|
-
if (
|
|
2553
|
+
if (typeof value[field] !== "string") {
|
|
2554
|
+
throw new Document2CommandError("invalid_command", `${field} must be a string when provided`);
|
|
2555
|
+
}
|
|
2556
|
+
return value[field];
|
|
2557
|
+
}
|
|
2558
|
+
function requireBoolean(value, field) {
|
|
2559
|
+
if (typeof value !== "boolean") {
|
|
2560
|
+
throw new Document2CommandError("invalid_command", `${field} must be a boolean`);
|
|
2561
|
+
}
|
|
2562
|
+
return value;
|
|
2563
|
+
}
|
|
2564
|
+
function requireIndex(value, field) {
|
|
2565
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
2566
|
+
throw new Document2CommandError("invalid_command", `${field} must be a non-negative integer`);
|
|
2567
|
+
}
|
|
2568
|
+
return value;
|
|
2569
|
+
}
|
|
2570
|
+
function parseMetadata(value) {
|
|
2571
|
+
if (value === void 0) {
|
|
2572
|
+
return void 0;
|
|
2573
|
+
}
|
|
2574
|
+
if (!isObject3(value)) {
|
|
2575
|
+
throw new Document2CommandError("invalid_command", "metadata must be an object");
|
|
2576
|
+
}
|
|
2577
|
+
if (value.source === void 0) {
|
|
2578
|
+
return void 0;
|
|
2579
|
+
}
|
|
2580
|
+
if (value.source !== "agent" && value.source !== "user") {
|
|
2581
|
+
throw new Document2CommandError("invalid_command", "metadata.source must be agent or user");
|
|
2582
|
+
}
|
|
2583
|
+
return { source: value.source };
|
|
2584
|
+
}
|
|
2585
|
+
function parseBlockAnchor(value) {
|
|
2586
|
+
if (!isObject3(value)) {
|
|
2587
|
+
throw new Document2CommandError("invalid_anchor", "anchor must be an object");
|
|
2588
|
+
}
|
|
2589
|
+
const before = typeof value.before_block_id === "string" ? value.before_block_id.trim() : "";
|
|
2590
|
+
const after = typeof value.after_block_id === "string" ? value.after_block_id.trim() : "";
|
|
2591
|
+
const choices = Number(before.length > 0) + Number(after.length > 0) + Number(value.end === true);
|
|
2592
|
+
if (choices !== 1) {
|
|
2593
|
+
throw new Document2CommandError("invalid_anchor", "anchor requires exactly one block position");
|
|
2594
|
+
}
|
|
2595
|
+
if (before) {
|
|
2596
|
+
return { before_block_id: before };
|
|
2597
|
+
}
|
|
2598
|
+
if (after) {
|
|
2599
|
+
return { after_block_id: after };
|
|
2600
|
+
}
|
|
2601
|
+
return { end: true };
|
|
2602
|
+
}
|
|
2603
|
+
function parseListItemAnchor(value) {
|
|
2604
|
+
if (!isObject3(value)) {
|
|
2605
|
+
throw new Document2CommandError("invalid_anchor", "anchor must be an object");
|
|
2606
|
+
}
|
|
2607
|
+
const before = typeof value.before_item_id === "string" ? value.before_item_id.trim() : "";
|
|
2608
|
+
const after = typeof value.after_item_id === "string" ? value.after_item_id.trim() : "";
|
|
2609
|
+
const choices = Number(before.length > 0) + Number(after.length > 0) + Number(value.end === true);
|
|
2610
|
+
if (choices !== 1) {
|
|
2611
|
+
throw new Document2CommandError("invalid_anchor", "anchor requires exactly one list-item position");
|
|
2612
|
+
}
|
|
2613
|
+
if (before) {
|
|
2614
|
+
return { before_item_id: before };
|
|
2615
|
+
}
|
|
2616
|
+
if (after) {
|
|
2617
|
+
return { after_item_id: after };
|
|
2618
|
+
}
|
|
2619
|
+
return { end: true };
|
|
2620
|
+
}
|
|
2621
|
+
function blockInsertionIndex(document2, anchor) {
|
|
2622
|
+
if ("end" in anchor) {
|
|
2623
|
+
return document2.blocks.length;
|
|
2624
|
+
}
|
|
2625
|
+
const id = "before_block_id" in anchor ? anchor.before_block_id : anchor.after_block_id;
|
|
2626
|
+
const index = document2.blocks.findIndex((block) => block.id === id);
|
|
2627
|
+
if (index < 0) {
|
|
2628
|
+
throw new Document2CommandError("anchor_not_found", "Anchor block was not found");
|
|
2629
|
+
}
|
|
2630
|
+
return "before_block_id" in anchor ? index : index + 1;
|
|
2631
|
+
}
|
|
2632
|
+
function itemInsertionIndex(list, anchor) {
|
|
2633
|
+
if ("end" in anchor) {
|
|
2634
|
+
return list.items.length;
|
|
2635
|
+
}
|
|
2636
|
+
const id = "before_item_id" in anchor ? anchor.before_item_id : anchor.after_item_id;
|
|
2637
|
+
const index = list.items.findIndex((item) => item.id === id);
|
|
2638
|
+
if (index < 0) {
|
|
2639
|
+
throw new Document2CommandError("anchor_not_found", "Anchor list item was not found");
|
|
2640
|
+
}
|
|
2641
|
+
return "before_item_id" in anchor ? index : index + 1;
|
|
2642
|
+
}
|
|
2643
|
+
function findField(blocks, fieldId) {
|
|
2644
|
+
for (const block of blocks) {
|
|
2645
|
+
if (block.kind === "textBlock" && block.field.id === fieldId) {
|
|
2646
|
+
return block.field;
|
|
2647
|
+
}
|
|
2648
|
+
if (block.kind === "list") {
|
|
2649
|
+
for (const item of block.items) {
|
|
2650
|
+
if (item.field.id === fieldId) {
|
|
2651
|
+
return item.field;
|
|
2652
|
+
}
|
|
2653
|
+
const nested = findField(item.blocks, fieldId);
|
|
2654
|
+
if (nested) {
|
|
2655
|
+
return nested;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
if (block.kind === "table") {
|
|
2660
|
+
for (const row of block.rows) {
|
|
2661
|
+
const cell = row.find((field) => field.id === fieldId);
|
|
2662
|
+
if (cell) {
|
|
2663
|
+
return cell;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return null;
|
|
2669
|
+
}
|
|
2670
|
+
function findBlock(blocks, blockId) {
|
|
2671
|
+
for (const block of blocks) {
|
|
2672
|
+
if (block.id === blockId) {
|
|
2673
|
+
return block;
|
|
2674
|
+
}
|
|
2675
|
+
if (block.kind === "list") {
|
|
2676
|
+
for (const item of block.items) {
|
|
2677
|
+
const nested = findBlock(item.blocks, blockId);
|
|
2678
|
+
if (nested) {
|
|
2679
|
+
return nested;
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
return null;
|
|
2685
|
+
}
|
|
2686
|
+
function collectIdentitySets(blocks, sets) {
|
|
2687
|
+
for (const block of blocks) {
|
|
2688
|
+
sets.blocks.add(block.id);
|
|
2689
|
+
if (block.kind === "textBlock") {
|
|
2690
|
+
sets.fields.add(block.field.id);
|
|
2691
|
+
block.field.tokens.forEach((token) => sets.tokens.add(token.id));
|
|
2692
|
+
} else if (block.kind === "list") {
|
|
2693
|
+
for (const item of block.items) {
|
|
2694
|
+
sets.items.add(item.id);
|
|
2695
|
+
sets.fields.add(item.field.id);
|
|
2696
|
+
item.field.tokens.forEach((token) => sets.tokens.add(token.id));
|
|
2697
|
+
collectIdentitySets(item.blocks, sets);
|
|
2698
|
+
}
|
|
2699
|
+
} else if (block.kind === "table") {
|
|
2700
|
+
for (const row of block.rows) {
|
|
2701
|
+
for (const field of row) {
|
|
2702
|
+
sets.fields.add(field.id);
|
|
2703
|
+
field.tokens.forEach((token) => sets.tokens.add(token.id));
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
function documentIdentitySets(document2) {
|
|
2710
|
+
const sets = {
|
|
2711
|
+
blocks: /* @__PURE__ */ new Set(),
|
|
2712
|
+
items: /* @__PURE__ */ new Set(),
|
|
2713
|
+
fields: /* @__PURE__ */ new Set(),
|
|
2714
|
+
tokens: /* @__PURE__ */ new Set()
|
|
2715
|
+
};
|
|
2716
|
+
collectIdentitySets(document2.blocks, sets);
|
|
2717
|
+
return sets;
|
|
2718
|
+
}
|
|
2719
|
+
function unusedGeneratedId(prefix, used) {
|
|
2720
|
+
let id = document2Id(prefix);
|
|
2721
|
+
while (used.has(id)) {
|
|
2722
|
+
id = document2Id(prefix);
|
|
2723
|
+
}
|
|
2724
|
+
return id;
|
|
2725
|
+
}
|
|
2726
|
+
function newBlockId(document2) {
|
|
2727
|
+
return unusedGeneratedId("block", documentIdentitySets(document2).blocks);
|
|
2728
|
+
}
|
|
2729
|
+
function newItemId(document2) {
|
|
2730
|
+
return unusedGeneratedId("item", documentIdentitySets(document2).items);
|
|
2731
|
+
}
|
|
2732
|
+
function newTokenId(document2, prefix) {
|
|
2733
|
+
return unusedGeneratedId(prefix, documentIdentitySets(document2).tokens);
|
|
2734
|
+
}
|
|
2735
|
+
function createCommandInlineField(document2, text) {
|
|
2736
|
+
const field = createInlineField2(text);
|
|
2737
|
+
const used = documentIdentitySets(document2);
|
|
2738
|
+
field.id = unusedGeneratedId("field", used.fields);
|
|
2739
|
+
field.tokens.forEach((token) => {
|
|
2740
|
+
token.id = unusedGeneratedId(token.kind, used.tokens);
|
|
2741
|
+
used.tokens.add(token.id);
|
|
2742
|
+
});
|
|
2743
|
+
return field;
|
|
2744
|
+
}
|
|
2745
|
+
function requireList(document2, listId) {
|
|
2746
|
+
const block = findBlock(document2.blocks, listId);
|
|
2747
|
+
if (!block) {
|
|
2748
|
+
throw new Document2CommandError("list_not_found", "Document list was not found");
|
|
2749
|
+
}
|
|
2750
|
+
if (block.kind !== "list") {
|
|
2751
|
+
throw new Document2CommandError("block_kind_mismatch", "Target block is not a list");
|
|
2752
|
+
}
|
|
2753
|
+
return block;
|
|
2754
|
+
}
|
|
2755
|
+
function requireTable(document2, tableId) {
|
|
2756
|
+
const block = findBlock(document2.blocks, tableId);
|
|
2757
|
+
if (!block) {
|
|
2758
|
+
throw new Document2CommandError("table_not_found", "Document table was not found");
|
|
2759
|
+
}
|
|
2760
|
+
if (block.kind !== "table") {
|
|
2761
|
+
throw new Document2CommandError("block_kind_mismatch", "Target block is not a table");
|
|
2762
|
+
}
|
|
2763
|
+
return block;
|
|
2764
|
+
}
|
|
2765
|
+
function fieldHasStructuredContent(field) {
|
|
2766
|
+
return field.tokens.some((token) => token.kind !== "text" || token.style !== void 0);
|
|
2767
|
+
}
|
|
2768
|
+
function replacePlainField(document2, field, text) {
|
|
2769
|
+
if (fieldHasStructuredContent(field)) {
|
|
2770
|
+
throw new Document2CommandError(
|
|
2771
|
+
"unsupported_inline_content",
|
|
2772
|
+
"Whole-field replacement is not supported for formatted or structured inline content"
|
|
2773
|
+
);
|
|
2774
|
+
}
|
|
2775
|
+
const next = createCommandInlineField(document2, text);
|
|
2776
|
+
field.tokens = next.tokens;
|
|
2777
|
+
}
|
|
2778
|
+
function textStylesEqual2(a, b) {
|
|
2779
|
+
return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
|
|
2780
|
+
}
|
|
2781
|
+
function compactTextTokens2(tokens, emptyTokenId = document2Id("text")) {
|
|
2782
|
+
const compacted = [];
|
|
2783
|
+
for (const token of tokens) {
|
|
2784
|
+
const previous = compacted[compacted.length - 1];
|
|
2785
|
+
if (previous?.kind === "text" && token.kind === "text" && textStylesEqual2(previous.style, token.style)) {
|
|
2786
|
+
previous.text += token.text;
|
|
2787
|
+
} else {
|
|
2788
|
+
compacted.push(token);
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
return compacted.length > 0 ? compacted : [{ id: emptyTokenId, kind: "text", text: "" }];
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
// src/document2/inlineJsonCommands.ts
|
|
2795
|
+
function parseInlineAnchor(value) {
|
|
2796
|
+
if (!isObject3(value)) {
|
|
2797
|
+
throw new Document2CommandError("invalid_anchor", "anchor must be an object");
|
|
2798
|
+
}
|
|
2799
|
+
const before = typeof value.before_token_id === "string" ? value.before_token_id.trim() : "";
|
|
2800
|
+
const after = typeof value.after_token_id === "string" ? value.after_token_id.trim() : "";
|
|
2801
|
+
const choices = Number(before.length > 0) + Number(after.length > 0) + Number(value.start === true) + Number(value.end === true);
|
|
2802
|
+
if (choices !== 1) {
|
|
2803
|
+
throw new Document2CommandError("invalid_anchor", "anchor requires exactly one inline-token position");
|
|
2804
|
+
}
|
|
2805
|
+
if (before) {
|
|
2806
|
+
return { before_token_id: before };
|
|
2807
|
+
}
|
|
2808
|
+
if (after) {
|
|
2809
|
+
return { after_token_id: after };
|
|
2810
|
+
}
|
|
2811
|
+
return value.start === true ? { start: true } : { end: true };
|
|
2812
|
+
}
|
|
2813
|
+
function parseStyle(value) {
|
|
2814
|
+
if (value === void 0) {
|
|
2815
|
+
return void 0;
|
|
2816
|
+
}
|
|
2817
|
+
if (!isObject3(value)) {
|
|
2818
|
+
throw new Document2CommandError("invalid_inline_token", "text token style must be an object");
|
|
2819
|
+
}
|
|
2820
|
+
const style = {};
|
|
2821
|
+
for (const key of ["bold", "italic", "underline"]) {
|
|
2822
|
+
if (value[key] !== void 0 && value[key] !== true) {
|
|
2823
|
+
throw new Document2CommandError("invalid_inline_token", `text token style.${key} must be true when provided`);
|
|
2824
|
+
}
|
|
2825
|
+
if (value[key] === true) {
|
|
2826
|
+
style[key] = true;
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
return style.bold || style.italic || style.underline ? style : void 0;
|
|
2830
|
+
}
|
|
2831
|
+
function normalizedKeys(value) {
|
|
2832
|
+
if (!Array.isArray(value)) {
|
|
2833
|
+
throw new Document2CommandError("invalid_inline_token", "citation/reference keys must be an array");
|
|
2834
|
+
}
|
|
2835
|
+
const keys = [];
|
|
2836
|
+
for (const entry of value) {
|
|
2837
|
+
if (typeof entry !== "string") {
|
|
2838
|
+
throw new Document2CommandError("invalid_inline_token", "citation/reference keys must be strings");
|
|
2839
|
+
}
|
|
2840
|
+
const key = entry.trim();
|
|
2841
|
+
if (key.length > 0 && !keys.includes(key)) {
|
|
2842
|
+
keys.push(key);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
if (keys.length === 0) {
|
|
2846
|
+
throw new Document2CommandError("invalid_inline_token", "citation/reference requires at least one non-empty key");
|
|
2847
|
+
}
|
|
2848
|
+
return keys;
|
|
2849
|
+
}
|
|
2850
|
+
function parseMathObject(value, source) {
|
|
2851
|
+
if (!isObject3(value) || value.node_type !== "MathObject") {
|
|
2852
|
+
throw new Document2CommandError("math_object_mismatch", "math_object must be a structured MathObject");
|
|
2853
|
+
}
|
|
2854
|
+
const spans = detectInlineSpans2(source);
|
|
2855
|
+
const span = spans[0];
|
|
2856
|
+
if (spans.length !== 1 || span?.kind !== "math" || span.start !== 0 || span.end !== source.length) {
|
|
2857
|
+
throw new Document2CommandError("math_object_mismatch", "math source must contain exactly one complete delimited span");
|
|
2858
|
+
}
|
|
2859
|
+
if (value.math_mode !== span.opening || value.closing !== span.closing) {
|
|
2860
|
+
throw new Document2CommandError("math_object_mismatch", "math_object delimiters do not match math source");
|
|
2861
|
+
}
|
|
2862
|
+
try {
|
|
2863
|
+
fromMathObjectJson(value, value.source_side === "arabic" ? "arabic" : "english");
|
|
2864
|
+
} catch {
|
|
2865
|
+
throw new Document2CommandError("math_object_mismatch", "math_object must contain a valid structured equation");
|
|
2866
|
+
}
|
|
2867
|
+
return value;
|
|
2868
|
+
}
|
|
2869
|
+
function parseToken(value) {
|
|
2870
|
+
if (!isObject3(value) || typeof value.kind !== "string") {
|
|
2871
|
+
throw new Document2CommandError("invalid_inline_token", "token requires a kind");
|
|
2872
|
+
}
|
|
2873
|
+
if (value.kind === "text") {
|
|
2874
|
+
const text = requireString2(value.text, "token.text");
|
|
2875
|
+
if (detectInlineSpans2(text).length > 0) {
|
|
2876
|
+
throw new Document2CommandError("invalid_inline_token", "text token contains structured inline syntax");
|
|
2877
|
+
}
|
|
2878
|
+
const style = parseStyle(value.style);
|
|
2879
|
+
return { kind: "text", text, ...style ? { style } : {} };
|
|
2880
|
+
}
|
|
2881
|
+
if (value.kind === "cite") {
|
|
2882
|
+
return { kind: "cite", keys: normalizedKeys(value.keys) };
|
|
2883
|
+
}
|
|
2884
|
+
if (value.kind === "ref") {
|
|
2885
|
+
if (value.ref_command !== "ref" && value.ref_command !== "eqref") {
|
|
2886
|
+
throw new Document2CommandError("invalid_inline_token", "ref_command must be ref or eqref");
|
|
2887
|
+
}
|
|
2888
|
+
return { kind: "ref", keys: normalizedKeys(value.keys), ref_command: value.ref_command };
|
|
2889
|
+
}
|
|
2890
|
+
if (value.kind === "math") {
|
|
2891
|
+
const source = requireString2(value.source, "token.source", false);
|
|
2892
|
+
return { kind: "math", source, math_object: parseMathObject(value.math_object, source) };
|
|
2893
|
+
}
|
|
2894
|
+
throw new Document2CommandError("invalid_inline_token", "Unsupported inline token kind");
|
|
2895
|
+
}
|
|
2896
|
+
function parseInlineCommand(value) {
|
|
2897
|
+
if (value.op === "insert_inline_token") {
|
|
2461
2898
|
return {
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
blocks: item.blocks.map(cloneBlock)
|
|
2467
|
-
}))
|
|
2899
|
+
op: value.op,
|
|
2900
|
+
field_id: requireString2(value.field_id, "field_id", false).trim(),
|
|
2901
|
+
token: parseToken(value.token),
|
|
2902
|
+
anchor: parseInlineAnchor(value.anchor)
|
|
2468
2903
|
};
|
|
2469
2904
|
}
|
|
2470
|
-
if (
|
|
2471
|
-
return {
|
|
2905
|
+
if (value.op === "replace_inline_token") {
|
|
2906
|
+
return {
|
|
2907
|
+
op: value.op,
|
|
2908
|
+
field_id: requireString2(value.field_id, "field_id", false).trim(),
|
|
2909
|
+
token_id: requireString2(value.token_id, "token_id", false).trim(),
|
|
2910
|
+
token: parseToken(value.token)
|
|
2911
|
+
};
|
|
2472
2912
|
}
|
|
2473
|
-
if (
|
|
2474
|
-
return {
|
|
2913
|
+
if (value.op === "remove_inline_token") {
|
|
2914
|
+
return {
|
|
2915
|
+
op: value.op,
|
|
2916
|
+
field_id: requireString2(value.field_id, "field_id", false).trim(),
|
|
2917
|
+
token_id: requireString2(value.token_id, "token_id", false).trim()
|
|
2918
|
+
};
|
|
2475
2919
|
}
|
|
2476
|
-
|
|
2920
|
+
throw new Document2CommandError("invalid_command", "Unsupported inline command");
|
|
2477
2921
|
}
|
|
2478
|
-
function
|
|
2922
|
+
function liveToken(input, id) {
|
|
2923
|
+
if (input.kind === "text") {
|
|
2924
|
+
return { id, kind: "text", text: input.text, ...input.style ? { style: { ...input.style } } : {} };
|
|
2925
|
+
}
|
|
2926
|
+
if (input.kind === "cite") {
|
|
2927
|
+
return { id, kind: "cite", keys: [...input.keys] };
|
|
2928
|
+
}
|
|
2929
|
+
if (input.kind === "ref") {
|
|
2930
|
+
return { id, kind: "ref", keys: [...input.keys], refCommand: input.ref_command };
|
|
2931
|
+
}
|
|
2932
|
+
const mathObject = input.math_object;
|
|
2933
|
+
const spans = detectInlineSpans2(input.source);
|
|
2934
|
+
const span = spans[0];
|
|
2935
|
+
if (!span || span.kind !== "math") {
|
|
2936
|
+
throw new Document2CommandError("math_object_mismatch", "math source is not a complete delimited span");
|
|
2937
|
+
}
|
|
2479
2938
|
return {
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2939
|
+
id,
|
|
2940
|
+
kind: "math",
|
|
2941
|
+
display: span.display,
|
|
2942
|
+
opening: span.opening,
|
|
2943
|
+
closing: span.closing,
|
|
2944
|
+
source: input.source,
|
|
2945
|
+
sourceSide: mathObject.source_side === "arabic" ? "arabic" : "english",
|
|
2946
|
+
math: fromMathObjectJson(mathObject, mathObject.source_side === "arabic" ? "arabic" : "english"),
|
|
2947
|
+
editable: true,
|
|
2948
|
+
sourceOwner: mathObject.source_owner === "editor" ? "editor" : "imported-structured",
|
|
2949
|
+
...mathObject.label_enabled !== void 0 ? { labelEnabled: mathObject.label_enabled } : {},
|
|
2950
|
+
...mathObject.label !== void 0 ? { label: mathObject.label } : {}
|
|
2485
2951
|
};
|
|
2486
2952
|
}
|
|
2487
|
-
function
|
|
2488
|
-
if (
|
|
2489
|
-
|
|
2953
|
+
function insertionIndex(tokens, anchor) {
|
|
2954
|
+
if ("start" in anchor) {
|
|
2955
|
+
return 0;
|
|
2956
|
+
}
|
|
2957
|
+
if ("end" in anchor) {
|
|
2958
|
+
return tokens.length;
|
|
2959
|
+
}
|
|
2960
|
+
const id = "before_token_id" in anchor ? anchor.before_token_id : anchor.after_token_id;
|
|
2961
|
+
const index = tokens.findIndex((token) => token.id === id);
|
|
2962
|
+
if (index < 0) {
|
|
2963
|
+
throw new Document2CommandError("token_not_found", "Anchor token was not found");
|
|
2964
|
+
}
|
|
2965
|
+
return "before_token_id" in anchor ? index : index + 1;
|
|
2966
|
+
}
|
|
2967
|
+
function applyInlineCommand(document2, command) {
|
|
2968
|
+
const field = findField(document2.blocks, command.field_id);
|
|
2969
|
+
if (!field) {
|
|
2970
|
+
throw new Document2CommandError("field_not_found", "Inline field was not found");
|
|
2971
|
+
}
|
|
2972
|
+
if (command.op === "insert_inline_token") {
|
|
2973
|
+
const index2 = insertionIndex(field.tokens, command.anchor);
|
|
2974
|
+
field.tokens.splice(index2, 0, liveToken(command.token, newTokenId(document2, command.token.kind)));
|
|
2490
2975
|
return;
|
|
2491
2976
|
}
|
|
2492
|
-
const index =
|
|
2977
|
+
const index = field.tokens.findIndex((token) => token.id === command.token_id);
|
|
2493
2978
|
if (index < 0) {
|
|
2494
|
-
|
|
2979
|
+
throw new Document2CommandError("token_not_found", "Inline token was not found");
|
|
2980
|
+
}
|
|
2981
|
+
if (command.op === "replace_inline_token") {
|
|
2982
|
+
field.tokens[index] = liveToken(command.token, command.token_id);
|
|
2495
2983
|
return;
|
|
2496
2984
|
}
|
|
2497
|
-
|
|
2985
|
+
field.tokens = compactTextTokens2(
|
|
2986
|
+
[...field.tokens.slice(0, index), ...field.tokens.slice(index + 1)],
|
|
2987
|
+
newTokenId(document2, "text")
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
// src/document2/listJsonCommands.ts
|
|
2992
|
+
function stringArray(value, field) {
|
|
2993
|
+
if (!Array.isArray(value) || value.length === 0 || value.some((entry) => typeof entry !== "string")) {
|
|
2994
|
+
throw new Document2CommandError("invalid_command", `${field} must be a non-empty string array`);
|
|
2995
|
+
}
|
|
2996
|
+
return [...value];
|
|
2997
|
+
}
|
|
2998
|
+
function parseListCommand(value) {
|
|
2999
|
+
if (value.op === "insert_list") {
|
|
3000
|
+
const metadata = parseMetadata(value.metadata);
|
|
3001
|
+
return {
|
|
3002
|
+
op: value.op,
|
|
3003
|
+
ordered: requireBoolean(value.ordered, "ordered"),
|
|
3004
|
+
items: stringArray(value.items, "items"),
|
|
3005
|
+
anchor: parseBlockAnchor(value.anchor),
|
|
3006
|
+
...metadata ? { metadata } : {}
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
if (value.op === "insert_list_item") {
|
|
3010
|
+
return {
|
|
3011
|
+
op: value.op,
|
|
3012
|
+
list_id: requireString2(value.list_id, "list_id", false).trim(),
|
|
3013
|
+
text: requireString2(value.text, "text"),
|
|
3014
|
+
anchor: parseListItemAnchor(value.anchor)
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
3017
|
+
if (value.op === "replace_list_item") {
|
|
3018
|
+
return {
|
|
3019
|
+
op: value.op,
|
|
3020
|
+
list_id: requireString2(value.list_id, "list_id", false).trim(),
|
|
3021
|
+
item_id: requireString2(value.item_id, "item_id", false).trim(),
|
|
3022
|
+
text: requireString2(value.text, "text")
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
if (value.op === "remove_list_item") {
|
|
3026
|
+
return {
|
|
3027
|
+
op: value.op,
|
|
3028
|
+
list_id: requireString2(value.list_id, "list_id", false).trim(),
|
|
3029
|
+
item_id: requireString2(value.item_id, "item_id", false).trim()
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
if (value.op === "move_list_item") {
|
|
3033
|
+
return {
|
|
3034
|
+
op: value.op,
|
|
3035
|
+
list_id: requireString2(value.list_id, "list_id", false).trim(),
|
|
3036
|
+
item_id: requireString2(value.item_id, "item_id", false).trim(),
|
|
3037
|
+
anchor: parseListItemAnchor(value.anchor)
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
throw new Document2CommandError("invalid_command", "Unsupported list command");
|
|
2498
3041
|
}
|
|
2499
|
-
function
|
|
2500
|
-
|
|
2501
|
-
insertBlockAfter(next.blocks, afterBlockId2, block);
|
|
2502
|
-
return next;
|
|
3042
|
+
function newItem(document2, text) {
|
|
3043
|
+
return { id: newItemId(document2), field: createCommandInlineField(document2, text), blocks: [] };
|
|
2503
3044
|
}
|
|
2504
|
-
function
|
|
3045
|
+
function insertList(document2, command) {
|
|
3046
|
+
const ordered = command.ordered;
|
|
2505
3047
|
const block = {
|
|
2506
|
-
id:
|
|
2507
|
-
kind: "
|
|
2508
|
-
command,
|
|
2509
|
-
|
|
2510
|
-
|
|
3048
|
+
id: newBlockId(document2),
|
|
3049
|
+
kind: "list",
|
|
3050
|
+
command: ordered ? "\\begin{enumerate}" : "\\begin{itemize}",
|
|
3051
|
+
closing: ordered ? "\\end{enumerate}" : "\\end{itemize}",
|
|
3052
|
+
items: command.items.map((text) => newItem(document2, text)),
|
|
3053
|
+
...command.metadata ? { metadata: { ...command.metadata } } : {}
|
|
2511
3054
|
};
|
|
2512
|
-
|
|
3055
|
+
document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block);
|
|
2513
3056
|
}
|
|
2514
|
-
function
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
3057
|
+
function applyListCommand(document2, command) {
|
|
3058
|
+
if (command.op === "insert_list") {
|
|
3059
|
+
insertList(document2, command);
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
const list = requireList(document2, command.list_id);
|
|
3063
|
+
if (command.op === "insert_list_item") {
|
|
3064
|
+
list.items.splice(itemInsertionIndex(list, command.anchor), 0, newItem(document2, command.text));
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
const itemIndex = list.items.findIndex((item2) => item2.id === command.item_id);
|
|
3068
|
+
if (itemIndex < 0) {
|
|
3069
|
+
throw new Document2CommandError("item_not_found", "Document list item was not found");
|
|
3070
|
+
}
|
|
3071
|
+
if (command.op === "replace_list_item") {
|
|
3072
|
+
replacePlainField(document2, list.items[itemIndex].field, command.text);
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
if (command.op === "remove_list_item") {
|
|
3076
|
+
if (list.items.length === 1) {
|
|
3077
|
+
throw new Document2CommandError("minimum_structure", "A list must retain at least one item");
|
|
3078
|
+
}
|
|
3079
|
+
list.items.splice(itemIndex, 1);
|
|
3080
|
+
return;
|
|
3081
|
+
}
|
|
3082
|
+
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;
|
|
3083
|
+
if (anchorId === command.item_id) {
|
|
3084
|
+
throw new Document2CommandError("invalid_anchor", "A list item cannot be moved relative to itself");
|
|
3085
|
+
}
|
|
3086
|
+
const [item] = list.items.splice(itemIndex, 1);
|
|
3087
|
+
if (!item) {
|
|
3088
|
+
throw new Document2CommandError("item_not_found", "Document list item was not found");
|
|
3089
|
+
}
|
|
3090
|
+
list.items.splice(itemInsertionIndex(list, command.anchor), 0, item);
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
// src/document2/tableJsonCommands.ts
|
|
3094
|
+
function stringArray2(value, field) {
|
|
3095
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
3096
|
+
throw new Document2CommandError("invalid_command", `${field} must be a string array`);
|
|
3097
|
+
}
|
|
3098
|
+
return [...value];
|
|
2518
3099
|
}
|
|
2519
|
-
function
|
|
2520
|
-
if (
|
|
2521
|
-
|
|
3100
|
+
function rectangularRows(value) {
|
|
3101
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
3102
|
+
throw new Document2CommandError("invalid_table_shape", "rows must be a non-empty array");
|
|
2522
3103
|
}
|
|
2523
|
-
|
|
2524
|
-
|
|
3104
|
+
const rows = value.map((row, index) => stringArray2(row, `rows[${String(index)}]`));
|
|
3105
|
+
const columnCount = rows[0]?.length ?? 0;
|
|
3106
|
+
if (columnCount === 0 || rows.some((row) => row.length !== columnCount)) {
|
|
3107
|
+
throw new Document2CommandError("invalid_table_shape", "Table rows must form a non-empty rectangle");
|
|
2525
3108
|
}
|
|
2526
|
-
return
|
|
3109
|
+
return rows;
|
|
2527
3110
|
}
|
|
2528
|
-
function
|
|
2529
|
-
const
|
|
3111
|
+
function columns(value) {
|
|
3112
|
+
const result = requireString2(value, "columns", false);
|
|
3113
|
+
if (result.trim().length === 0) {
|
|
3114
|
+
throw new Document2CommandError("invalid_table_shape", "columns must be a non-empty LaTeX specification");
|
|
3115
|
+
}
|
|
3116
|
+
return result;
|
|
3117
|
+
}
|
|
3118
|
+
function parseTableCommand(value) {
|
|
3119
|
+
if (value.op === "insert_table") {
|
|
3120
|
+
const caption = optionalString(value, "caption");
|
|
3121
|
+
const label = optionalString(value, "label");
|
|
3122
|
+
const metadata = parseMetadata(value.metadata);
|
|
3123
|
+
return {
|
|
3124
|
+
op: value.op,
|
|
3125
|
+
rows: rectangularRows(value.rows),
|
|
3126
|
+
columns: columns(value.columns),
|
|
3127
|
+
...caption !== void 0 ? { caption } : {},
|
|
3128
|
+
...label !== void 0 ? { label } : {},
|
|
3129
|
+
anchor: parseBlockAnchor(value.anchor),
|
|
3130
|
+
...metadata ? { metadata } : {}
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
if (value.op === "replace_table_cell") {
|
|
3134
|
+
return {
|
|
3135
|
+
op: value.op,
|
|
3136
|
+
table_id: requireString2(value.table_id, "table_id", false).trim(),
|
|
3137
|
+
row_index: requireIndex(value.row_index, "row_index"),
|
|
3138
|
+
column_index: requireIndex(value.column_index, "column_index"),
|
|
3139
|
+
text: requireString2(value.text, "text")
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
if (value.op === "insert_table_row") {
|
|
3143
|
+
return {
|
|
3144
|
+
op: value.op,
|
|
3145
|
+
table_id: requireString2(value.table_id, "table_id", false).trim(),
|
|
3146
|
+
index: requireIndex(value.index, "index"),
|
|
3147
|
+
values: stringArray2(value.values, "values")
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
if (value.op === "remove_table_row") {
|
|
3151
|
+
return {
|
|
3152
|
+
op: value.op,
|
|
3153
|
+
table_id: requireString2(value.table_id, "table_id", false).trim(),
|
|
3154
|
+
index: requireIndex(value.index, "index")
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
if (value.op === "move_table_row") {
|
|
3158
|
+
return {
|
|
3159
|
+
op: value.op,
|
|
3160
|
+
table_id: requireString2(value.table_id, "table_id", false).trim(),
|
|
3161
|
+
from_index: requireIndex(value.from_index, "from_index"),
|
|
3162
|
+
to_index: requireIndex(value.to_index, "to_index")
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
if (value.op === "insert_table_column") {
|
|
3166
|
+
return {
|
|
3167
|
+
op: value.op,
|
|
3168
|
+
table_id: requireString2(value.table_id, "table_id", false).trim(),
|
|
3169
|
+
index: requireIndex(value.index, "index"),
|
|
3170
|
+
values: stringArray2(value.values, "values"),
|
|
3171
|
+
columns: columns(value.columns)
|
|
3172
|
+
};
|
|
3173
|
+
}
|
|
3174
|
+
if (value.op === "remove_table_column") {
|
|
3175
|
+
return {
|
|
3176
|
+
op: value.op,
|
|
3177
|
+
table_id: requireString2(value.table_id, "table_id", false).trim(),
|
|
3178
|
+
index: requireIndex(value.index, "index"),
|
|
3179
|
+
columns: columns(value.columns)
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
if (value.op === "move_table_column") {
|
|
3183
|
+
return {
|
|
3184
|
+
op: value.op,
|
|
3185
|
+
table_id: requireString2(value.table_id, "table_id", false).trim(),
|
|
3186
|
+
from_index: requireIndex(value.from_index, "from_index"),
|
|
3187
|
+
to_index: requireIndex(value.to_index, "to_index"),
|
|
3188
|
+
columns: columns(value.columns)
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
throw new Document2CommandError("invalid_command", "Unsupported table command");
|
|
3192
|
+
}
|
|
3193
|
+
function insertTable(document2, command) {
|
|
2530
3194
|
const block = {
|
|
2531
|
-
id:
|
|
2532
|
-
kind: "
|
|
2533
|
-
command: "\\
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
...defaultFloatMeta()
|
|
3195
|
+
id: newBlockId(document2),
|
|
3196
|
+
kind: "table",
|
|
3197
|
+
command: "\\begin{tabular}",
|
|
3198
|
+
closing: "\\end{tabular}",
|
|
3199
|
+
columns: command.columns,
|
|
3200
|
+
rows: command.rows.map((row) => row.map((cell) => createCommandInlineField(document2, cell))),
|
|
3201
|
+
...defaultFloatMeta(),
|
|
3202
|
+
...command.caption !== void 0 ? { caption: command.caption, captionEnabled: command.caption.trim().length > 0 } : {},
|
|
3203
|
+
...command.label !== void 0 ? { label: command.label, labelEnabled: command.label.trim().length > 0 } : {},
|
|
3204
|
+
...command.metadata ? { metadata: { ...command.metadata } } : {}
|
|
2538
3205
|
};
|
|
2539
|
-
|
|
3206
|
+
document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block);
|
|
3207
|
+
}
|
|
3208
|
+
function checkCell(table, rowIndex, columnIndex) {
|
|
3209
|
+
if (rowIndex >= table.rows.length || columnIndex >= (table.rows[rowIndex]?.length ?? 0)) {
|
|
3210
|
+
throw new Document2CommandError("index_out_of_range", "Table cell index is out of range");
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
function checkExistingIndex(index, length, field) {
|
|
3214
|
+
if (index >= length) {
|
|
3215
|
+
throw new Document2CommandError("index_out_of_range", `${field} is out of range`);
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
function applyTableCommand(document2, command) {
|
|
3219
|
+
if (command.op === "insert_table") {
|
|
3220
|
+
insertTable(document2, command);
|
|
3221
|
+
return;
|
|
3222
|
+
}
|
|
3223
|
+
const table = requireTable(document2, command.table_id);
|
|
3224
|
+
const columnCount = table.rows[0]?.length ?? 0;
|
|
3225
|
+
if (table.rows.length === 0 || columnCount === 0 || table.rows.some((row) => row.length !== columnCount)) {
|
|
3226
|
+
throw new Document2CommandError("invalid_table_shape", "Target table must be a non-empty rectangle");
|
|
3227
|
+
}
|
|
3228
|
+
if (command.op === "replace_table_cell") {
|
|
3229
|
+
checkCell(table, command.row_index, command.column_index);
|
|
3230
|
+
replacePlainField(document2, table.rows[command.row_index][command.column_index], command.text);
|
|
3231
|
+
return;
|
|
3232
|
+
}
|
|
3233
|
+
if (command.op === "insert_table_row") {
|
|
3234
|
+
if (command.index > table.rows.length) {
|
|
3235
|
+
throw new Document2CommandError("index_out_of_range", "Table row insertion index is out of range");
|
|
3236
|
+
}
|
|
3237
|
+
if (command.values.length !== columnCount) {
|
|
3238
|
+
throw new Document2CommandError("invalid_table_shape", "Inserted row must match the current column count");
|
|
3239
|
+
}
|
|
3240
|
+
table.rows.splice(command.index, 0, command.values.map((value) => createCommandInlineField(document2, value)));
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
if (command.op === "remove_table_row") {
|
|
3244
|
+
checkExistingIndex(command.index, table.rows.length, "Table row index");
|
|
3245
|
+
if (table.rows.length === 1) {
|
|
3246
|
+
throw new Document2CommandError("minimum_structure", "A table must retain at least one row");
|
|
3247
|
+
}
|
|
3248
|
+
table.rows.splice(command.index, 1);
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
if (command.op === "move_table_row") {
|
|
3252
|
+
checkExistingIndex(command.from_index, table.rows.length, "Table source row index");
|
|
3253
|
+
checkExistingIndex(command.to_index, table.rows.length, "Table target row index");
|
|
3254
|
+
const [row] = table.rows.splice(command.from_index, 1);
|
|
3255
|
+
table.rows.splice(command.to_index, 0, row);
|
|
3256
|
+
return;
|
|
3257
|
+
}
|
|
3258
|
+
if (command.op === "insert_table_column") {
|
|
3259
|
+
if (command.index > columnCount) {
|
|
3260
|
+
throw new Document2CommandError("index_out_of_range", "Table column insertion index is out of range");
|
|
3261
|
+
}
|
|
3262
|
+
if (command.values.length !== table.rows.length) {
|
|
3263
|
+
throw new Document2CommandError("invalid_table_shape", "Inserted column requires one value per row");
|
|
3264
|
+
}
|
|
3265
|
+
table.rows.forEach((row, index) => row.splice(command.index, 0, createCommandInlineField(document2, command.values[index])));
|
|
3266
|
+
table.columns = command.columns;
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3269
|
+
if (command.op === "remove_table_column") {
|
|
3270
|
+
checkExistingIndex(command.index, columnCount, "Table column index");
|
|
3271
|
+
if (columnCount === 1) {
|
|
3272
|
+
throw new Document2CommandError("minimum_structure", "A table must retain at least one column");
|
|
3273
|
+
}
|
|
3274
|
+
table.rows.forEach((row) => row.splice(command.index, 1));
|
|
3275
|
+
table.columns = command.columns;
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3278
|
+
checkExistingIndex(command.from_index, columnCount, "Table source column index");
|
|
3279
|
+
checkExistingIndex(command.to_index, columnCount, "Table target column index");
|
|
3280
|
+
table.rows.forEach((row) => {
|
|
3281
|
+
const [cell] = row.splice(command.from_index, 1);
|
|
3282
|
+
row.splice(command.to_index, 0, cell);
|
|
3283
|
+
});
|
|
3284
|
+
table.columns = command.columns;
|
|
2540
3285
|
}
|
|
2541
3286
|
|
|
2542
3287
|
// src/document2/exportJson.ts
|
|
@@ -2591,12 +3336,13 @@ function serializeField(field) {
|
|
|
2591
3336
|
...token.label !== void 0 ? { label: token.label } : {}
|
|
2592
3337
|
});
|
|
2593
3338
|
}
|
|
2594
|
-
return { value, formats, mathObjects, hasPersistedMath };
|
|
3339
|
+
return { value, formats, mathObjects, hasPersistedMath, inlineIds: inlineIdsFromField(field) };
|
|
2595
3340
|
}
|
|
2596
3341
|
function fieldJson(field) {
|
|
2597
3342
|
const serialized = serializeField(field);
|
|
2598
3343
|
return {
|
|
2599
3344
|
value: serialized.value,
|
|
3345
|
+
inline_ids: serialized.inlineIds,
|
|
2600
3346
|
...serialized.formats.length > 0 ? { formats: serialized.formats } : {},
|
|
2601
3347
|
...serialized.hasPersistedMath ? { math_objects: serialized.mathObjects } : {}
|
|
2602
3348
|
};
|
|
@@ -2604,7 +3350,9 @@ function fieldJson(field) {
|
|
|
2604
3350
|
function listItemJson(item) {
|
|
2605
3351
|
const field = fieldJson(item.field);
|
|
2606
3352
|
return {
|
|
3353
|
+
id: item.id,
|
|
2607
3354
|
value: field.value ?? "",
|
|
3355
|
+
inline_ids: field.inline_ids,
|
|
2608
3356
|
...field.formats ? { formats: field.formats } : {},
|
|
2609
3357
|
...field.math_objects ? { math_objects: field.math_objects } : {},
|
|
2610
3358
|
...item.blocks.length > 0 ? { blocks: item.blocks.map(blockJson) } : {}
|
|
@@ -2616,6 +3364,7 @@ function blockJson(block) {
|
|
|
2616
3364
|
id: block.id,
|
|
2617
3365
|
command: block.command,
|
|
2618
3366
|
...fieldJson(block.field),
|
|
3367
|
+
...block.metadata ? { metadata: { ...block.metadata } } : {},
|
|
2619
3368
|
...block.command === "\\paragraph" ? { centered: block.centered === true } : {}
|
|
2620
3369
|
};
|
|
2621
3370
|
}
|
|
@@ -2624,7 +3373,8 @@ function blockJson(block) {
|
|
|
2624
3373
|
id: block.id,
|
|
2625
3374
|
command: block.command,
|
|
2626
3375
|
closing: block.closing,
|
|
2627
|
-
items: block.items.map(listItemJson)
|
|
3376
|
+
items: block.items.map(listItemJson),
|
|
3377
|
+
...block.metadata ? { metadata: { ...block.metadata } } : {}
|
|
2628
3378
|
};
|
|
2629
3379
|
}
|
|
2630
3380
|
if (block.kind === "table") {
|
|
@@ -2636,13 +3386,15 @@ function blockJson(block) {
|
|
|
2636
3386
|
closing: block.closing,
|
|
2637
3387
|
columns: block.columns,
|
|
2638
3388
|
rows: fields.map((row) => row.map((field) => field.value)),
|
|
3389
|
+
cell_inline_ids: fields.map((row) => row.map((field) => field.inlineIds)),
|
|
2639
3390
|
...fields.some((row) => row.some((field) => field.formats.length > 0)) ? { cell_formats: fields.map((row) => row.map((field) => field.formats)) } : {},
|
|
2640
3391
|
...hasPersistedMath ? { math_objects: fields.flatMap((row) => row.flatMap((field) => field.mathObjects)) } : {},
|
|
2641
3392
|
centered: block.centered,
|
|
2642
3393
|
caption_enabled: block.captionEnabled,
|
|
2643
3394
|
caption: block.caption,
|
|
2644
3395
|
label_enabled: block.labelEnabled,
|
|
2645
|
-
label: block.label
|
|
3396
|
+
label: block.label,
|
|
3397
|
+
...block.metadata ? { metadata: { ...block.metadata } } : {}
|
|
2646
3398
|
};
|
|
2647
3399
|
}
|
|
2648
3400
|
if (block.kind === "image") {
|
|
@@ -2656,13 +3408,24 @@ function blockJson(block) {
|
|
|
2656
3408
|
caption_enabled: block.captionEnabled,
|
|
2657
3409
|
caption: block.caption,
|
|
2658
3410
|
label_enabled: block.labelEnabled,
|
|
2659
|
-
label: block.label
|
|
3411
|
+
label: block.label,
|
|
3412
|
+
...block.metadata ? { metadata: { ...block.metadata } } : {}
|
|
2660
3413
|
};
|
|
2661
3414
|
}
|
|
2662
3415
|
if (block.kind === "bibliography") {
|
|
2663
|
-
return {
|
|
3416
|
+
return {
|
|
3417
|
+
id: block.id,
|
|
3418
|
+
command: block.command,
|
|
3419
|
+
closing: block.closing,
|
|
3420
|
+
...block.metadata ? { metadata: { ...block.metadata } } : {}
|
|
3421
|
+
};
|
|
2664
3422
|
}
|
|
2665
|
-
return {
|
|
3423
|
+
return {
|
|
3424
|
+
id: block.id,
|
|
3425
|
+
command: block.command,
|
|
3426
|
+
value: block.value,
|
|
3427
|
+
...block.metadata ? { metadata: { ...block.metadata } } : {}
|
|
3428
|
+
};
|
|
2666
3429
|
}
|
|
2667
3430
|
function referenceJson(reference) {
|
|
2668
3431
|
return {
|
|
@@ -2693,49 +3456,14 @@ function toDocumentJson2(document2) {
|
|
|
2693
3456
|
}
|
|
2694
3457
|
|
|
2695
3458
|
// src/document2/jsonCommands.ts
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
constructor(code, message) {
|
|
2699
|
-
super(message);
|
|
2700
|
-
this.name = "Document2CommandError";
|
|
2701
|
-
this.code = code;
|
|
2702
|
-
}
|
|
2703
|
-
};
|
|
2704
|
-
function isObject3(value) {
|
|
2705
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3459
|
+
function isInlineCommand(command) {
|
|
3460
|
+
return command.op === "insert_inline_token" || command.op === "replace_inline_token" || command.op === "remove_inline_token";
|
|
2706
3461
|
}
|
|
2707
|
-
function
|
|
2708
|
-
|
|
2709
|
-
throw new Document2CommandError("invalid_command", `${field} must be a${allowEmpty ? "" : " non-empty"} string`);
|
|
2710
|
-
}
|
|
2711
|
-
return value;
|
|
3462
|
+
function isListCommand(command) {
|
|
3463
|
+
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";
|
|
2712
3464
|
}
|
|
2713
|
-
function
|
|
2714
|
-
|
|
2715
|
-
throw new Document2CommandError("invalid_command", "anchor must be an object");
|
|
2716
|
-
}
|
|
2717
|
-
const hasEnd = value.end === true;
|
|
2718
|
-
const afterBlockId2 = typeof value.after_block_id === "string" ? value.after_block_id.trim() : "";
|
|
2719
|
-
const hasAfterBlock = afterBlockId2.length > 0;
|
|
2720
|
-
if (hasEnd && hasAfterBlock) {
|
|
2721
|
-
throw new Document2CommandError("invalid_command", "anchor cannot contain both end and after_block_id");
|
|
2722
|
-
}
|
|
2723
|
-
if (hasEnd) {
|
|
2724
|
-
return { end: true };
|
|
2725
|
-
}
|
|
2726
|
-
if (hasAfterBlock) {
|
|
2727
|
-
return { after_block_id: afterBlockId2 };
|
|
2728
|
-
}
|
|
2729
|
-
throw new Document2CommandError("invalid_command", "anchor requires end: true or a non-empty after_block_id");
|
|
2730
|
-
}
|
|
2731
|
-
function optionalString(value, field) {
|
|
2732
|
-
if (!(field in value)) {
|
|
2733
|
-
return void 0;
|
|
2734
|
-
}
|
|
2735
|
-
if (typeof value[field] !== "string") {
|
|
2736
|
-
throw new Document2CommandError("invalid_command", `${field} must be a string when provided`);
|
|
2737
|
-
}
|
|
2738
|
-
return value[field];
|
|
3465
|
+
function isTableCommand(command) {
|
|
3466
|
+
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";
|
|
2739
3467
|
}
|
|
2740
3468
|
function parseTextBlockKind(value) {
|
|
2741
3469
|
if (value === "section" || value === "subsection" || value === "subsubsection" || value === "paragraph") {
|
|
@@ -2743,16 +3471,15 @@ function parseTextBlockKind(value) {
|
|
|
2743
3471
|
}
|
|
2744
3472
|
throw new Document2CommandError("invalid_command", "kind must be section, subsection, subsubsection, or paragraph");
|
|
2745
3473
|
}
|
|
2746
|
-
function
|
|
2747
|
-
if (!isObject3(value) || typeof value.op !== "string") {
|
|
2748
|
-
throw new Document2CommandError("invalid_command", "command requires an op");
|
|
2749
|
-
}
|
|
3474
|
+
function parseBasicCommand(value) {
|
|
2750
3475
|
if (value.op === "insert_text_block") {
|
|
3476
|
+
const metadata = parseMetadata(value.metadata);
|
|
2751
3477
|
return {
|
|
2752
3478
|
op: value.op,
|
|
2753
3479
|
kind: parseTextBlockKind(value.kind),
|
|
2754
3480
|
text: requireString2(value.text, "text"),
|
|
2755
|
-
anchor:
|
|
3481
|
+
anchor: parseBlockAnchor(value.anchor),
|
|
3482
|
+
...metadata ? { metadata } : {}
|
|
2756
3483
|
};
|
|
2757
3484
|
}
|
|
2758
3485
|
if (value.op === "replace_text_block") {
|
|
@@ -2763,140 +3490,118 @@ function parseCommand2(value) {
|
|
|
2763
3490
|
};
|
|
2764
3491
|
}
|
|
2765
3492
|
if (value.op === "remove_block") {
|
|
2766
|
-
return {
|
|
2767
|
-
op: value.op,
|
|
2768
|
-
block_id: requireString2(value.block_id, "block_id", false).trim()
|
|
2769
|
-
};
|
|
3493
|
+
return { op: value.op, block_id: requireString2(value.block_id, "block_id", false).trim() };
|
|
2770
3494
|
}
|
|
2771
3495
|
if (value.op === "insert_figure") {
|
|
2772
3496
|
const figureValue = optionalString(value, "value");
|
|
2773
3497
|
const caption = optionalString(value, "caption");
|
|
2774
3498
|
const label = optionalString(value, "label");
|
|
3499
|
+
const metadata = parseMetadata(value.metadata);
|
|
2775
3500
|
return {
|
|
2776
3501
|
op: value.op,
|
|
2777
3502
|
asset_id: requireString2(value.asset_id, "asset_id", false).trim(),
|
|
2778
3503
|
...figureValue !== void 0 ? { value: figureValue } : {},
|
|
2779
3504
|
...caption !== void 0 ? { caption } : {},
|
|
2780
3505
|
...label !== void 0 ? { label } : {},
|
|
2781
|
-
anchor:
|
|
3506
|
+
anchor: parseBlockAnchor(value.anchor),
|
|
3507
|
+
...metadata ? { metadata } : {}
|
|
2782
3508
|
};
|
|
2783
3509
|
}
|
|
2784
|
-
throw new Document2CommandError("invalid_command",
|
|
3510
|
+
throw new Document2CommandError("invalid_command", "Unsupported block command");
|
|
3511
|
+
}
|
|
3512
|
+
function parseCommand2(value) {
|
|
3513
|
+
if (!isObject3(value) || typeof value.op !== "string") {
|
|
3514
|
+
throw new Document2CommandError("invalid_command", "command requires an op");
|
|
3515
|
+
}
|
|
3516
|
+
if (value.op === "insert_inline_token" || value.op === "replace_inline_token" || value.op === "remove_inline_token") {
|
|
3517
|
+
return parseInlineCommand(value);
|
|
3518
|
+
}
|
|
3519
|
+
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") {
|
|
3520
|
+
return parseListCommand(value);
|
|
3521
|
+
}
|
|
3522
|
+
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") {
|
|
3523
|
+
return parseTableCommand(value);
|
|
3524
|
+
}
|
|
3525
|
+
if (value.op === "insert_text_block" || value.op === "replace_text_block" || value.op === "remove_block" || value.op === "insert_figure") {
|
|
3526
|
+
return parseBasicCommand(value);
|
|
3527
|
+
}
|
|
3528
|
+
throw new Document2CommandError("invalid_command", "Unsupported document command");
|
|
2785
3529
|
}
|
|
2786
3530
|
function parseDocument(json) {
|
|
2787
3531
|
try {
|
|
2788
3532
|
return fromDocumentJson2(json);
|
|
2789
|
-
} catch
|
|
2790
|
-
|
|
2791
|
-
throw new Document2CommandError("invalid_document", message);
|
|
3533
|
+
} catch {
|
|
3534
|
+
throw new Document2CommandError("invalid_document", "Document JSON is invalid");
|
|
2792
3535
|
}
|
|
2793
3536
|
}
|
|
2794
|
-
function
|
|
3537
|
+
function textCommand(kind) {
|
|
2795
3538
|
if (kind === "section") {
|
|
2796
3539
|
return "\\section";
|
|
2797
3540
|
}
|
|
2798
3541
|
if (kind === "subsection") {
|
|
2799
3542
|
return "\\subsection";
|
|
2800
3543
|
}
|
|
2801
|
-
|
|
2802
|
-
return "\\subsubsection";
|
|
2803
|
-
}
|
|
2804
|
-
return "\\paragraph";
|
|
3544
|
+
return kind === "subsubsection" ? "\\subsubsection" : "\\paragraph";
|
|
2805
3545
|
}
|
|
2806
|
-
function
|
|
2807
|
-
if ("
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
}
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
}
|
|
2819
|
-
function insertedBlock(document2, index) {
|
|
2820
|
-
const block = document2.blocks[index];
|
|
2821
|
-
if (!block) {
|
|
2822
|
-
throw new Document2CommandError("invalid_command", "Document command did not insert a block");
|
|
2823
|
-
}
|
|
2824
|
-
return block;
|
|
2825
|
-
}
|
|
2826
|
-
function insertTextBlock(document2, command) {
|
|
2827
|
-
const index = insertionIndex(document2, command.anchor);
|
|
2828
|
-
const next = addDocument2TextBlock(document2, commandForKind(command.kind), afterBlockId(document2, index));
|
|
2829
|
-
const block = insertedBlock(next, index);
|
|
2830
|
-
if (block.kind !== "textBlock") {
|
|
2831
|
-
throw new Document2CommandError("block_kind_mismatch", "Inserted block is not a text block");
|
|
2832
|
-
}
|
|
2833
|
-
block.field = createInlineField2(command.text);
|
|
2834
|
-
return next;
|
|
2835
|
-
}
|
|
2836
|
-
function replaceTextBlock(document2, command) {
|
|
2837
|
-
const index = document2.blocks.findIndex((block2) => block2.id === command.block_id);
|
|
2838
|
-
if (index < 0) {
|
|
2839
|
-
throw new Document2CommandError("block_not_found", `Document block was not found: ${command.block_id}`);
|
|
2840
|
-
}
|
|
2841
|
-
const block = document2.blocks[index];
|
|
2842
|
-
if (!block || block.kind !== "textBlock") {
|
|
2843
|
-
throw new Document2CommandError("block_kind_mismatch", `Document block is not a text block: ${command.block_id}`);
|
|
2844
|
-
}
|
|
2845
|
-
if (block.field.tokens.some((token) => token.kind !== "text" || token.style !== void 0)) {
|
|
2846
|
-
throw new Document2CommandError(
|
|
2847
|
-
"unsupported_inline_content",
|
|
2848
|
-
"Whole-block replacement is not supported for formatted or structured inline content"
|
|
2849
|
-
);
|
|
2850
|
-
}
|
|
2851
|
-
const next = parseDocument(toDocumentJson2(document2));
|
|
2852
|
-
const nextBlock = next.blocks[index];
|
|
2853
|
-
if (!nextBlock || nextBlock.kind !== "textBlock") {
|
|
2854
|
-
throw new Document2CommandError("block_kind_mismatch", `Document block is not a text block: ${command.block_id}`);
|
|
2855
|
-
}
|
|
2856
|
-
nextBlock.field = createInlineField2(command.text);
|
|
2857
|
-
return next;
|
|
2858
|
-
}
|
|
2859
|
-
function removeBlock(document2, command) {
|
|
2860
|
-
if (!document2.blocks.some((block) => block.id === command.block_id)) {
|
|
2861
|
-
throw new Document2CommandError("block_not_found", `Document block was not found: ${command.block_id}`);
|
|
2862
|
-
}
|
|
2863
|
-
return removeDocument2BlockById(document2, command.block_id);
|
|
2864
|
-
}
|
|
2865
|
-
function insertFigure(document2, command) {
|
|
2866
|
-
const index = insertionIndex(document2, command.anchor);
|
|
2867
|
-
const next = addDocument2ImageBlock(
|
|
2868
|
-
document2,
|
|
2869
|
-
{ assetId: command.asset_id, ...command.value !== void 0 ? { value: command.value } : {} },
|
|
2870
|
-
afterBlockId(document2, index)
|
|
2871
|
-
);
|
|
2872
|
-
const block = insertedBlock(next, index);
|
|
2873
|
-
if (block.kind !== "image") {
|
|
2874
|
-
throw new Document2CommandError("block_kind_mismatch", "Inserted block is not a figure");
|
|
3546
|
+
function applyBasicCommand(document2, command) {
|
|
3547
|
+
if (command.op === "insert_text_block") {
|
|
3548
|
+
const block2 = {
|
|
3549
|
+
id: newBlockId(document2),
|
|
3550
|
+
kind: "textBlock",
|
|
3551
|
+
command: textCommand(command.kind),
|
|
3552
|
+
field: createCommandInlineField(document2, command.text),
|
|
3553
|
+
...command.kind === "paragraph" ? { centered: false } : {},
|
|
3554
|
+
...command.metadata ? { metadata: { ...command.metadata } } : {}
|
|
3555
|
+
};
|
|
3556
|
+
document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block2);
|
|
3557
|
+
return;
|
|
2875
3558
|
}
|
|
2876
|
-
if (command.
|
|
2877
|
-
|
|
2878
|
-
|
|
3559
|
+
if (command.op === "replace_text_block") {
|
|
3560
|
+
const block2 = document2.blocks.find((entry) => entry.id === command.block_id);
|
|
3561
|
+
if (!block2) {
|
|
3562
|
+
throw new Document2CommandError("block_not_found", "Document block was not found");
|
|
3563
|
+
}
|
|
3564
|
+
if (block2.kind !== "textBlock") {
|
|
3565
|
+
throw new Document2CommandError("block_kind_mismatch", "Document block is not a text block");
|
|
3566
|
+
}
|
|
3567
|
+
replacePlainField(document2, block2.field, command.text);
|
|
3568
|
+
return;
|
|
2879
3569
|
}
|
|
2880
|
-
if (command.
|
|
2881
|
-
|
|
2882
|
-
|
|
3570
|
+
if (command.op === "remove_block") {
|
|
3571
|
+
const index = document2.blocks.findIndex((block2) => block2.id === command.block_id);
|
|
3572
|
+
if (index < 0) {
|
|
3573
|
+
throw new Document2CommandError("block_not_found", "Document block was not found");
|
|
3574
|
+
}
|
|
3575
|
+
document2.blocks.splice(index, 1);
|
|
3576
|
+
return;
|
|
2883
3577
|
}
|
|
2884
|
-
|
|
3578
|
+
const block = {
|
|
3579
|
+
id: newBlockId(document2),
|
|
3580
|
+
kind: "image",
|
|
3581
|
+
command: "\\includegraphics",
|
|
3582
|
+
value: command.value ?? command.asset_id,
|
|
3583
|
+
assetId: command.asset_id,
|
|
3584
|
+
options: { width: "0.8\\columnwidth" },
|
|
3585
|
+
...defaultFloatMeta(),
|
|
3586
|
+
...command.caption !== void 0 ? { caption: command.caption, captionEnabled: command.caption.trim().length > 0 } : {},
|
|
3587
|
+
...command.label !== void 0 ? { label: command.label, labelEnabled: command.label.trim().length > 0 } : {},
|
|
3588
|
+
...command.metadata ? { metadata: { ...command.metadata } } : {}
|
|
3589
|
+
};
|
|
3590
|
+
document2.blocks.splice(blockInsertionIndex(document2, command.anchor), 0, block);
|
|
2885
3591
|
}
|
|
2886
3592
|
function applyDocument2Command(json, commandInput) {
|
|
2887
3593
|
const document2 = parseDocument(json);
|
|
2888
3594
|
const command = parseCommand2(commandInput);
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
next = removeBlock(document2, command);
|
|
3595
|
+
if (isInlineCommand(command)) {
|
|
3596
|
+
applyInlineCommand(document2, command);
|
|
3597
|
+
} else if (isListCommand(command)) {
|
|
3598
|
+
applyListCommand(document2, command);
|
|
3599
|
+
} else if (isTableCommand(command)) {
|
|
3600
|
+
applyTableCommand(document2, command);
|
|
2896
3601
|
} else {
|
|
2897
|
-
|
|
3602
|
+
applyBasicCommand(document2, command);
|
|
2898
3603
|
}
|
|
2899
|
-
return toDocumentJson2(
|
|
3604
|
+
return toDocumentJson2(document2);
|
|
2900
3605
|
}
|
|
2901
3606
|
|
|
2902
3607
|
// src/document2/outline.ts
|
|
@@ -3041,9 +3746,8 @@ function asDocument2Command(value) {
|
|
|
3041
3746
|
function normalizeDocument(document2) {
|
|
3042
3747
|
try {
|
|
3043
3748
|
return toDocumentJson2(fromDocumentJson2(document2));
|
|
3044
|
-
} catch
|
|
3045
|
-
|
|
3046
|
-
throw new Document2CommandError("invalid_document", message);
|
|
3749
|
+
} catch {
|
|
3750
|
+
throw new Document2CommandError("invalid_document", "Document JSON is invalid");
|
|
3047
3751
|
}
|
|
3048
3752
|
}
|
|
3049
3753
|
function executeDocument2WorkerRequest(input) {
|