@stll/folio-core 0.31.2 → 0.32.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.
@@ -274,7 +274,19 @@ type FolioAIEditSkipReason = "missingBlock" | "changedBlock" | "ambiguousFind" |
274
274
  * replace, or replaceBlock's `text` matches the live block.
275
275
  * Filtered out so the reviewer doesn't see "X → X" cards.
276
276
  */
277
- "noopOperation";
277
+ "noopOperation" |
278
+ /**
279
+ * The batch carried a `precondition.documentVersion` that no longer
280
+ * matches the document the surface holds; every operation in the batch
281
+ * is skipped. Re-read the document and regenerate the edits.
282
+ */
283
+ "documentVersionMismatch" |
284
+ /**
285
+ * The surface holds no editable document right now (no editor mounted,
286
+ * no entity to attach suggestions to); the operation was neither applied
287
+ * nor queued.
288
+ */
289
+ "documentNotEditable";
278
290
  type FolioAIEditAppliedOperation = {
279
291
  id: string;
280
292
  commentId?: number;
@@ -6,6 +6,15 @@ declare const FOLIO_DOCUMENT_OPERATION_TYPES: readonly ["replaceInBlock", "repla
6
6
  declare const FOLIO_DOCUMENT_OPERATION_MODES: readonly ["direct", "tracked-changes", "suggested"];
7
7
  declare const FOLIO_DOCUMENT_OPERATION_STORIES: readonly ["main", "header", "footer", "footnote", "endnote"];
8
8
  declare const FOLIO_DOCUMENT_OPERATION_PRECONDITIONS: readonly ["blockTextHash"];
9
+ /**
10
+ * Batch-level preconditions. `documentVersion` is an opaque host token (an
11
+ * entity version id, a collaboration checkpoint) pinning the document the
12
+ * batch was authored against; whoever knows the live version (an agent
13
+ * bridge, a host applying the batch itself) compares it and skips the whole
14
+ * batch with `documentVersionMismatch` when it differs. The core apply path
15
+ * preserves the token but has no version to compare it to.
16
+ */
17
+ declare const FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS: readonly ["documentVersion"];
9
18
  declare const FOLIO_DOCUMENT_OPERATION_BATCH_MODES: readonly ["best-effort", "atomic"];
10
19
  type FolioDocumentOperation = FolioAIEditOperation;
11
20
  type FolioDocumentOperationMode = FolioAIEditApplyMode;
@@ -37,6 +46,7 @@ type FolioDocumentOperationCapabilities = {
37
46
  readonly batchModes: typeof FOLIO_DOCUMENT_OPERATION_BATCH_MODES;
38
47
  readonly dryRun: true;
39
48
  readonly preconditions: typeof FOLIO_DOCUMENT_OPERATION_PRECONDITIONS;
49
+ readonly batchPreconditions: typeof FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS;
40
50
  readonly stories: typeof FOLIO_DOCUMENT_OPERATION_STORIES;
41
51
  };
42
52
  declare const getFolioDocumentOperationCapabilities: () => FolioDocumentOperationCapabilities;
@@ -54,16 +64,49 @@ declare class InvalidFolioDocumentOperationBatchError extends InvalidFolioDocume
54
64
  reason: string;
55
65
  }> {}
56
66
  declare const assertSupportedFolioDocumentOperationVersion: (value: unknown) => typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
67
+ /** Batch-level guard; see {@link FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS}. */
68
+ type FolioDocumentOperationBatchPrecondition = {
69
+ readonly documentVersion: string;
70
+ };
57
71
  type FolioDocumentOperationBatch = {
58
72
  readonly version: typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
59
73
  readonly operations: readonly FolioDocumentOperation[];
60
74
  readonly mode?: FolioDocumentOperationMode;
61
75
  readonly atomic?: boolean;
62
76
  readonly dryRun?: boolean;
77
+ readonly precondition?: FolioDocumentOperationBatchPrecondition;
63
78
  };
79
+ /**
80
+ * Every property each operation type accepts on the wire; the parser
81
+ * rejects any other key. Exported so a lenient front door (an LLM tool
82
+ * decoder) can strip stray keys with the same knowledge instead of
83
+ * guessing, and so tool schemas can be derived from the contract.
84
+ */
85
+ declare const FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE: Readonly<{
86
+ readonly replaceInBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "find", "replace", "comment"];
87
+ readonly replaceRange: readonly ["id", "type", "range", "severity", "area", "precondition", "suggestionId", "replace", "comment"];
88
+ readonly commentOnRange: readonly ["id", "type", "range", "severity", "area", "precondition", "comment"];
89
+ readonly formatRange: readonly ["id", "type", "range", "severity", "area", "precondition", "suggestionId", "formatting"];
90
+ readonly insertAfterBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "text", "inheritFormatting", "pageBreakBefore", "styleId", "comment"];
91
+ readonly insertBeforeBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "text", "inheritFormatting", "pageBreakBefore", "styleId", "comment"];
92
+ readonly replaceBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "text", "preserveFormatting", "styleId", "comment"];
93
+ readonly deleteBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "comment"];
94
+ readonly commentOnBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "quote", "comment"];
95
+ readonly insertSignatureTable: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "position", "parties", "comment"];
96
+ readonly insertTableRow: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "position", "cellTexts"];
97
+ readonly deleteTableRow: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId"];
98
+ readonly insertTableColumn: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "position", "cellTexts"];
99
+ readonly deleteTableColumn: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId"];
100
+ readonly mergeTableCells: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "endBlockId", "rowCount"];
101
+ readonly splitTableCell: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId"];
102
+ }>;
64
103
  declare const parseFolioDocumentOperationBatch: (value: unknown) => FolioDocumentOperationBatch;
65
- type FolioDocumentOperationStatus = "committed" | "previewed" | "rejected";
66
- type FolioDocumentOperationRecovery = "refreshDocument" | "narrowMatch" | "changeMode" | "changeTarget" | "removeOperation" | "inspectBatch";
104
+ /**
105
+ * `queued` is reported by a surface that routes the batch into a host-owned
106
+ * review queue instead of applying it; the core apply path never produces it.
107
+ */
108
+ type FolioDocumentOperationStatus = "committed" | "previewed" | "rejected" | "queued";
109
+ type FolioDocumentOperationRecovery = "refreshDocument" | "narrowMatch" | "changeMode" | "changeTarget" | "removeOperation" | "inspectBatch" | "retryLater";
67
110
  type FolioDocumentOperationIssue = {
68
111
  operationId: string;
69
112
  operationIndex: number;
@@ -152,9 +195,12 @@ type FolioDocumentOperationUndoResult = {
152
195
  undoHandle: FolioDocumentOperationUndoHandle;
153
196
  reason: FolioDocumentOperationUndoFailureReason;
154
197
  };
155
- type FolioDocumentOperationResult = {
198
+ /** One operation a host surface accepted into its own review queue rather than applying. */
199
+ type FolioDocumentOperationQueuedOperation = {
200
+ id: string;
201
+ };
202
+ type FolioDocumentOperationResultBase = {
156
203
  version: typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
157
- status: FolioDocumentOperationStatus;
158
204
  applied: FolioAIEditAppliedOperation[];
159
205
  skipped: FolioAIEditSkippedOperation[];
160
206
  issues: FolioDocumentOperationIssue[];
@@ -163,6 +209,19 @@ type FolioDocumentOperationResult = {
163
209
  /** Present when the execution surface can undo this committed batch. */
164
210
  undoHandle: FolioDocumentOperationUndoHandle | null;
165
211
  };
212
+ /**
213
+ * Discriminated on `status`: only a host review-queue surface reports
214
+ * `"queued"`, and only that branch carries the `queued` list, so a consumer
215
+ * narrowing on the status can never meet a contradictory payload.
216
+ */
217
+ type FolioDocumentOperationResult = (FolioDocumentOperationResultBase & {
218
+ status: "queued";
219
+ /** Operations parked in the host review queue instead of applied. */
220
+ queued: FolioDocumentOperationQueuedOperation[];
221
+ }) | (FolioDocumentOperationResultBase & {
222
+ status: Exclude<FolioDocumentOperationStatus, "queued">;
223
+ queued?: never;
224
+ });
166
225
  declare const getFolioDocumentOperationIssues: (operations: readonly FolioDocumentOperation[], skipped: readonly FolioAIEditSkippedOperation[]) => FolioDocumentOperationIssue[];
167
226
  /** Build deterministic affected-target receipts from operations and their applied entries. */
168
227
  declare const getFolioDocumentOperationReceipts: (operations: readonly FolioDocumentOperation[], applied: readonly FolioAIEditAppliedOperation[]) => FolioDocumentOperationReceipt[];
@@ -177,4 +236,4 @@ type ApplyFolioDocumentOperationsOptions = {
177
236
  };
178
237
  declare const applyFolioDocumentOperations: ({ view, snapshot, batch, story, author, createCommentId, createUndoHandle }: ApplyFolioDocumentOperationsOptions) => FolioDocumentOperationResult;
179
238
  //#endregion
180
- export { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch };
239
+ export { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationBatchPrecondition, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationQueuedOperation, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch };
@@ -33,6 +33,15 @@ const FOLIO_DOCUMENT_OPERATION_STORIES = Object.freeze([
33
33
  "endnote"
34
34
  ]);
35
35
  const FOLIO_DOCUMENT_OPERATION_PRECONDITIONS = Object.freeze(["blockTextHash"]);
36
+ /**
37
+ * Batch-level preconditions. `documentVersion` is an opaque host token (an
38
+ * entity version id, a collaboration checkpoint) pinning the document the
39
+ * batch was authored against; whoever knows the live version (an agent
40
+ * bridge, a host applying the batch itself) compares it and skips the whole
41
+ * batch with `documentVersionMismatch` when it differs. The core apply path
42
+ * preserves the token but has no version to compare it to.
43
+ */
44
+ const FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS = Object.freeze(["documentVersion"]);
36
45
  const FOLIO_DOCUMENT_OPERATION_BATCH_MODES = Object.freeze(["best-effort", "atomic"]);
37
46
  const parsedFolioDocumentOperationBatches = /* @__PURE__ */ new WeakSet();
38
47
  const DIRECT_AND_TRACKED_MODES = Object.freeze(["direct", "tracked-changes"]);
@@ -64,6 +73,7 @@ const DOCUMENT_OPERATION_CAPABILITIES = Object.freeze({
64
73
  batchModes: FOLIO_DOCUMENT_OPERATION_BATCH_MODES,
65
74
  dryRun: true,
66
75
  preconditions: FOLIO_DOCUMENT_OPERATION_PRECONDITIONS,
76
+ batchPreconditions: FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS,
67
77
  stories: FOLIO_DOCUMENT_OPERATION_STORIES
68
78
  });
69
79
  const getFolioDocumentOperationCapabilities = () => DOCUMENT_OPERATION_CAPABILITIES;
@@ -224,6 +234,94 @@ const COMMON_OPERATION_KEYS = [
224
234
  "precondition",
225
235
  "suggestionId"
226
236
  ];
237
+ const RANGE_OPERATION_KEYS = [
238
+ "id",
239
+ "type",
240
+ "range",
241
+ "severity",
242
+ "area",
243
+ "precondition"
244
+ ];
245
+ /**
246
+ * Every property each operation type accepts on the wire; the parser
247
+ * rejects any other key. Exported so a lenient front door (an LLM tool
248
+ * decoder) can strip stray keys with the same knowledge instead of
249
+ * guessing, and so tool schemas can be derived from the contract.
250
+ */
251
+ const FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE = Object.freeze({
252
+ replaceInBlock: [
253
+ ...COMMON_OPERATION_KEYS,
254
+ "find",
255
+ "replace",
256
+ "comment"
257
+ ],
258
+ replaceRange: [
259
+ ...RANGE_OPERATION_KEYS,
260
+ "suggestionId",
261
+ "replace",
262
+ "comment"
263
+ ],
264
+ commentOnRange: [...RANGE_OPERATION_KEYS, "comment"],
265
+ formatRange: [
266
+ ...RANGE_OPERATION_KEYS,
267
+ "suggestionId",
268
+ "formatting"
269
+ ],
270
+ insertAfterBlock: [
271
+ ...COMMON_OPERATION_KEYS,
272
+ "text",
273
+ "inheritFormatting",
274
+ "pageBreakBefore",
275
+ "styleId",
276
+ "comment"
277
+ ],
278
+ insertBeforeBlock: [
279
+ ...COMMON_OPERATION_KEYS,
280
+ "text",
281
+ "inheritFormatting",
282
+ "pageBreakBefore",
283
+ "styleId",
284
+ "comment"
285
+ ],
286
+ replaceBlock: [
287
+ ...COMMON_OPERATION_KEYS,
288
+ "text",
289
+ "preserveFormatting",
290
+ "styleId",
291
+ "comment"
292
+ ],
293
+ deleteBlock: [...COMMON_OPERATION_KEYS, "comment"],
294
+ commentOnBlock: [
295
+ ...COMMON_OPERATION_KEYS,
296
+ "quote",
297
+ "comment"
298
+ ],
299
+ insertSignatureTable: [
300
+ ...COMMON_OPERATION_KEYS,
301
+ "position",
302
+ "parties",
303
+ "comment"
304
+ ],
305
+ insertTableRow: [
306
+ ...COMMON_OPERATION_KEYS,
307
+ "position",
308
+ "cellTexts"
309
+ ],
310
+ deleteTableRow: COMMON_OPERATION_KEYS,
311
+ insertTableColumn: [
312
+ ...COMMON_OPERATION_KEYS,
313
+ "position",
314
+ "cellTexts"
315
+ ],
316
+ deleteTableColumn: COMMON_OPERATION_KEYS,
317
+ mergeTableCells: [
318
+ ...COMMON_OPERATION_KEYS,
319
+ "endBlockId",
320
+ "rowCount"
321
+ ],
322
+ splitTableCell: COMMON_OPERATION_KEYS
323
+ });
324
+ const isFolioDocumentOperationType = (value) => Object.hasOwn(FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, value);
227
325
  const parseSignatureParties = (value, path) => {
228
326
  const parties = value["parties"];
229
327
  if (!Array.isArray(parties)) return invalidBatch(`${path}.parties`, "expected an array");
@@ -248,6 +346,8 @@ const parseDocumentOperation = (value, index) => {
248
346
  if (!isPlainObject(value)) return invalidBatch(path, "expected an object");
249
347
  const id = readString(value, "id", path);
250
348
  const type = readString(value, "type", path);
349
+ if (!isFolioDocumentOperationType(type)) return invalidBatch(`${path}.type`, `unsupported operation type "${type}"`);
350
+ assertAllowedKeys(value, path, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE[type]);
251
351
  const reviewMeta = readReviewMeta(value, path);
252
352
  const precondition = readOptionalPrecondition(value, path);
253
353
  const suggestionId = readOptionalString(value, "suggestionId", path);
@@ -257,37 +357,15 @@ const parseDocumentOperation = (value, index) => {
257
357
  ...precondition !== void 0 && { precondition },
258
358
  ...suggestionId !== void 0 && { suggestionId }
259
359
  };
260
- if (type === "replaceRange") {
261
- assertAllowedKeys(value, path, [
262
- "id",
263
- "type",
264
- "range",
265
- "replace",
266
- "comment",
267
- "severity",
268
- "area",
269
- "precondition",
270
- "suggestionId"
271
- ]);
272
- return {
273
- ...operationMeta,
274
- id,
275
- type,
276
- range: readTextRange(value, path),
277
- replace: readString(value, "replace", path),
278
- ...comment !== void 0 && { comment }
279
- };
280
- }
360
+ if (type === "replaceRange") return {
361
+ ...operationMeta,
362
+ id,
363
+ type,
364
+ range: readTextRange(value, path),
365
+ replace: readString(value, "replace", path),
366
+ ...comment !== void 0 && { comment }
367
+ };
281
368
  if (type === "commentOnRange") {
282
- assertAllowedKeys(value, path, [
283
- "id",
284
- "type",
285
- "range",
286
- "comment",
287
- "severity",
288
- "area",
289
- "precondition"
290
- ]);
291
369
  if (comment === void 0) return invalidBatch(`${path}.comment`, "expected an object");
292
370
  return {
293
371
  ...operationMeta,
@@ -297,52 +375,24 @@ const parseDocumentOperation = (value, index) => {
297
375
  comment
298
376
  };
299
377
  }
300
- if (type === "formatRange") {
301
- assertAllowedKeys(value, path, [
302
- "id",
303
- "type",
304
- "range",
305
- "formatting",
306
- "severity",
307
- "area",
308
- "precondition",
309
- "suggestionId"
310
- ]);
311
- return {
312
- ...operationMeta,
313
- id,
314
- type,
315
- range: readTextRange(value, path),
316
- formatting: readInlineFormatting(value, path)
317
- };
318
- }
378
+ if (type === "formatRange") return {
379
+ ...operationMeta,
380
+ id,
381
+ type,
382
+ range: readTextRange(value, path),
383
+ formatting: readInlineFormatting(value, path)
384
+ };
319
385
  const blockId = readString(value, "blockId", path);
320
- if (type === "replaceInBlock") {
321
- assertAllowedKeys(value, path, [
322
- ...COMMON_OPERATION_KEYS,
323
- "find",
324
- "replace",
325
- "comment"
326
- ]);
327
- return {
328
- ...operationMeta,
329
- id,
330
- type,
331
- blockId,
332
- find: readString(value, "find", path),
333
- replace: readString(value, "replace", path),
334
- ...comment !== void 0 && { comment }
335
- };
336
- }
386
+ if (type === "replaceInBlock") return {
387
+ ...operationMeta,
388
+ id,
389
+ type,
390
+ blockId,
391
+ find: readString(value, "find", path),
392
+ replace: readString(value, "replace", path),
393
+ ...comment !== void 0 && { comment }
394
+ };
337
395
  if (type === "insertAfterBlock" || type === "insertBeforeBlock") {
338
- assertAllowedKeys(value, path, [
339
- ...COMMON_OPERATION_KEYS,
340
- "text",
341
- "inheritFormatting",
342
- "pageBreakBefore",
343
- "styleId",
344
- "comment"
345
- ]);
346
396
  const inheritFormatting = readOptionalBoolean(value, "inheritFormatting", path);
347
397
  const pageBreakBefore = readOptionalBoolean(value, "pageBreakBefore", path);
348
398
  const styleId = readOptionalString(value, "styleId", path);
@@ -359,13 +409,6 @@ const parseDocumentOperation = (value, index) => {
359
409
  };
360
410
  }
361
411
  if (type === "replaceBlock") {
362
- assertAllowedKeys(value, path, [
363
- ...COMMON_OPERATION_KEYS,
364
- "text",
365
- "preserveFormatting",
366
- "styleId",
367
- "comment"
368
- ]);
369
412
  const preserveFormatting = readOptionalBoolean(value, "preserveFormatting", path);
370
413
  const styleId = readOptionalString(value, "styleId", path);
371
414
  return {
@@ -379,22 +422,14 @@ const parseDocumentOperation = (value, index) => {
379
422
  ...comment !== void 0 && { comment }
380
423
  };
381
424
  }
382
- if (type === "deleteBlock") {
383
- assertAllowedKeys(value, path, [...COMMON_OPERATION_KEYS, "comment"]);
384
- return {
385
- ...operationMeta,
386
- id,
387
- type,
388
- blockId,
389
- ...comment !== void 0 && { comment }
390
- };
391
- }
425
+ if (type === "deleteBlock") return {
426
+ ...operationMeta,
427
+ id,
428
+ type,
429
+ blockId,
430
+ ...comment !== void 0 && { comment }
431
+ };
392
432
  if (type === "commentOnBlock") {
393
- assertAllowedKeys(value, path, [
394
- ...COMMON_OPERATION_KEYS,
395
- "quote",
396
- "comment"
397
- ]);
398
433
  if (comment === void 0) return invalidBatch(`${path}.comment`, "expected an object");
399
434
  const quote = readOptionalString(value, "quote", path);
400
435
  return {
@@ -407,12 +442,6 @@ const parseDocumentOperation = (value, index) => {
407
442
  };
408
443
  }
409
444
  if (type === "insertSignatureTable") {
410
- assertAllowedKeys(value, path, [
411
- ...COMMON_OPERATION_KEYS,
412
- "position",
413
- "parties",
414
- "comment"
415
- ]);
416
445
  const position = value["position"];
417
446
  if (position !== void 0 && position !== "after" && position !== "before") return invalidBatch(`${path}.position`, "expected \"after\" or \"before\" when provided");
418
447
  return {
@@ -426,11 +455,6 @@ const parseDocumentOperation = (value, index) => {
426
455
  };
427
456
  }
428
457
  if (type === "insertTableRow") {
429
- assertAllowedKeys(value, path, [
430
- ...COMMON_OPERATION_KEYS,
431
- "position",
432
- "cellTexts"
433
- ]);
434
458
  const position = value["position"];
435
459
  if (position !== void 0 && position !== "after" && position !== "before") return invalidBatch(`${path}.position`, "expected \"after\" or \"before\" when provided");
436
460
  const cellTexts = readOptionalStringArray(value, "cellTexts", path);
@@ -443,21 +467,13 @@ const parseDocumentOperation = (value, index) => {
443
467
  ...cellTexts !== void 0 && { cellTexts }
444
468
  };
445
469
  }
446
- if (type === "deleteTableRow") {
447
- assertAllowedKeys(value, path, COMMON_OPERATION_KEYS);
448
- return {
449
- ...operationMeta,
450
- id,
451
- type,
452
- blockId
453
- };
454
- }
470
+ if (type === "deleteTableRow") return {
471
+ ...operationMeta,
472
+ id,
473
+ type,
474
+ blockId
475
+ };
455
476
  if (type === "insertTableColumn") {
456
- assertAllowedKeys(value, path, [
457
- ...COMMON_OPERATION_KEYS,
458
- "position",
459
- "cellTexts"
460
- ]);
461
477
  const position = value["position"];
462
478
  if (position !== void 0 && position !== "after" && position !== "before") return invalidBatch(`${path}.position`, "expected \"after\" or \"before\" when provided");
463
479
  const cellTexts = readOptionalStringArray(value, "cellTexts", path);
@@ -470,21 +486,13 @@ const parseDocumentOperation = (value, index) => {
470
486
  ...cellTexts !== void 0 && { cellTexts }
471
487
  };
472
488
  }
473
- if (type === "deleteTableColumn") {
474
- assertAllowedKeys(value, path, COMMON_OPERATION_KEYS);
475
- return {
476
- ...operationMeta,
477
- id,
478
- type,
479
- blockId
480
- };
481
- }
489
+ if (type === "deleteTableColumn") return {
490
+ ...operationMeta,
491
+ id,
492
+ type,
493
+ blockId
494
+ };
482
495
  if (type === "mergeTableCells") {
483
- assertAllowedKeys(value, path, [
484
- ...COMMON_OPERATION_KEYS,
485
- "endBlockId",
486
- "rowCount"
487
- ]);
488
496
  const endBlockId = readOptionalString(value, "endBlockId", path);
489
497
  const rawRowCount = value["rowCount"];
490
498
  if (endBlockId === void 0 === (rawRowCount === void 0)) return invalidBatch(path, "expected exactly one of endBlockId or rowCount");
@@ -508,16 +516,21 @@ const parseDocumentOperation = (value, index) => {
508
516
  rowCount
509
517
  };
510
518
  }
511
- if (type === "splitTableCell") {
512
- assertAllowedKeys(value, path, COMMON_OPERATION_KEYS);
513
- return {
514
- ...operationMeta,
515
- id,
516
- type,
517
- blockId
518
- };
519
- }
520
- return invalidBatch(`${path}.type`, `unsupported operation type "${type}"`);
519
+ return {
520
+ ...operationMeta,
521
+ id,
522
+ type,
523
+ blockId
524
+ };
525
+ };
526
+ const readOptionalBatchPrecondition = (value) => {
527
+ const candidate = value["precondition"];
528
+ if (candidate === void 0) return;
529
+ if (!isPlainObject(candidate)) return invalidBatch("$.precondition", "expected an object when provided");
530
+ assertAllowedKeys(candidate, "$.precondition", FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS);
531
+ const documentVersion = readString(candidate, "documentVersion", "$.precondition");
532
+ if (documentVersion.length === 0) return invalidBatch("$.precondition.documentVersion", "expected a non-empty string");
533
+ return { documentVersion };
521
534
  };
522
535
  const parseFolioDocumentOperationBatch = (value) => {
523
536
  if (isParsedFolioDocumentOperationBatch(value)) return value;
@@ -527,7 +540,8 @@ const parseFolioDocumentOperationBatch = (value) => {
527
540
  "operations",
528
541
  "mode",
529
542
  "atomic",
530
- "dryRun"
543
+ "dryRun",
544
+ "precondition"
531
545
  ]);
532
546
  const version = assertSupportedFolioDocumentOperationVersion(value["version"]);
533
547
  const operations = value["operations"];
@@ -536,6 +550,7 @@ const parseFolioDocumentOperationBatch = (value) => {
536
550
  if (mode !== void 0 && mode !== "direct" && mode !== "tracked-changes" && mode !== "suggested") return invalidBatch("$.mode", "expected \"direct\", \"tracked-changes\", or \"suggested\" when provided");
537
551
  const atomic = readOptionalBoolean(value, "atomic", "$");
538
552
  const dryRun = readOptionalBoolean(value, "dryRun", "$");
553
+ const precondition = readOptionalBatchPrecondition(value);
539
554
  const parsedOperations = operations.map(parseDocumentOperation);
540
555
  const operationIds = /* @__PURE__ */ new Set();
541
556
  for (const [index, operation] of parsedOperations.entries()) {
@@ -547,7 +562,8 @@ const parseFolioDocumentOperationBatch = (value) => {
547
562
  operations: parsedOperations,
548
563
  ...mode !== void 0 && { mode },
549
564
  ...atomic !== void 0 && { atomic },
550
- ...dryRun !== void 0 && { dryRun }
565
+ ...dryRun !== void 0 && { dryRun },
566
+ ...precondition !== void 0 && { precondition }
551
567
  };
552
568
  freezeParsedValue(parsedBatch);
553
569
  parsedFolioDocumentOperationBatches.add(parsedBatch);
@@ -564,7 +580,9 @@ const recoveryByReason = {
564
580
  preconditionFailed: "refreshDocument",
565
581
  staleRange: "refreshDocument",
566
582
  emptyOperation: "removeOperation",
567
- noopOperation: "removeOperation"
583
+ noopOperation: "removeOperation",
584
+ documentVersionMismatch: "refreshDocument",
585
+ documentNotEditable: "retryLater"
568
586
  };
569
587
  const getFolioDocumentOperationIssues = (operations, skipped) => {
570
588
  const indexById = /* @__PURE__ */ new Map();
@@ -780,4 +798,4 @@ const applyFolioDocumentOperations = ({ view, snapshot, batch, story = "main", a
780
798
  };
781
799
  };
782
800
  //#endregion
783
- export { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch };
801
+ export { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch };
@@ -1,4 +1,16 @@
1
+ import JSZip from "jszip";
1
2
  //#region src/docx/server/boundedArchive.d.ts
3
+ declare module "jszip" {
4
+ interface JSZipObject {
5
+ /**
6
+ * Chunked read of the entry content, missing from the published typings.
7
+ * `nodeStream` is this stream wrapped in a Node.js `Readable`, which
8
+ * browsers and web workers cannot provide; the stream itself is
9
+ * platform-neutral.
10
+ */
11
+ internalStream(type: "uint8array"): JSZip.JSZipStreamHelper<Uint8Array>;
12
+ }
13
+ }
2
14
  declare const DOCX_MAX_ENTRY_BYTES: number;
3
15
  declare const DOCX_MAX_TOTAL_BYTES: number;
4
16
  declare const DOCX_MAX_ENTRIES = 4096;
@@ -7,20 +7,34 @@ const DOCX_MAX_ENTRIES = 4096;
7
7
  const DOCX_MAX_INPUT_BYTES = 50 * 1024 * 1024;
8
8
  /** Error raised when a DOCX archive cannot be loaded within configured limits. */
9
9
  var DocxArchiveError = class extends TaggedError("DocxArchiveError") {};
10
+ const concatChunks = (chunks) => {
11
+ let totalBytes = 0;
12
+ for (const chunk of chunks) totalBytes += chunk.length;
13
+ const merged = new Uint8Array(totalBytes);
14
+ let offset = 0;
15
+ for (const chunk of chunks) {
16
+ merged.set(chunk, offset);
17
+ offset += chunk.length;
18
+ }
19
+ return merged;
20
+ };
21
+ /**
22
+ * Accumulate an entry chunk by chunk, checking both caps before each chunk is
23
+ * retained. Pausing the stream abandons a decompression bomb mid-inflate, so
24
+ * the caps bound memory instead of merely reporting the overrun afterwards.
25
+ */
10
26
  const collectStream = async ({ stream, maxEntryBytes, remainingBytes, maxTotalBytes, path }) => await new Promise((resolve, reject) => {
11
27
  const chunks = [];
12
28
  let entryBytes = 0;
13
29
  const fail = (reason, message) => {
14
- const destroy = Reflect.get(stream, "destroy");
15
- if (typeof destroy === "function") Reflect.apply(destroy, stream, []);
30
+ stream.pause();
16
31
  reject(new DocxArchiveError({
17
32
  message,
18
33
  reason
19
34
  }));
20
35
  };
21
36
  stream.on("data", (chunk) => {
22
- const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
23
- entryBytes += bytes.length;
37
+ entryBytes += chunk.length;
24
38
  if (entryBytes > maxEntryBytes) {
25
39
  fail("entry-too-large", `DOCX entry "${path}" exceeded the ${maxEntryBytes}-byte limit`);
26
40
  return;
@@ -29,10 +43,8 @@ const collectStream = async ({ stream, maxEntryBytes, remainingBytes, maxTotalBy
29
43
  fail("total-too-large", `DOCX archive exceeded the ${maxTotalBytes}-byte cumulative limit while reading "${path}"`);
30
44
  return;
31
45
  }
32
- chunks.push(bytes);
33
- });
34
- stream.on("end", () => resolve(Buffer.concat(chunks)));
35
- stream.on("error", reject);
46
+ chunks.push(chunk);
47
+ }).on("end", () => resolve(concatChunks(chunks))).on("error", reject).resume();
36
48
  });
37
49
  const getDeclaredUncompressedBytes = (entry) => {
38
50
  const data = "_data" in entry ? entry._data : void 0;
@@ -116,15 +128,15 @@ const loadDocxArchive = async (bytes, options = {}) => {
116
128
  const work = async () => {
117
129
  const entry = zip.file(path);
118
130
  if (!entry) return null;
119
- const buffer = await collectStream({
120
- stream: entry.nodeStream("nodebuffer"),
131
+ const content = await collectStream({
132
+ stream: entry.internalStream("uint8array"),
121
133
  maxEntryBytes: Math.min(requestedMaxBytes, maxEntryBytes),
122
134
  remainingBytes: maxTotalBytes - totalBytesRead,
123
135
  maxTotalBytes,
124
136
  path
125
137
  });
126
- totalBytesRead += buffer.length;
127
- return buffer;
138
+ totalBytesRead += content.length;
139
+ return content;
128
140
  };
129
141
  const next = readChain.then(work, work);
130
142
  readChain = next.then(() => void 0, () => void 0);
@@ -138,13 +150,10 @@ const loadDocxArchive = async (bytes, options = {}) => {
138
150
  declaredUncompressedBytes: getDeclaredUncompressedBytes(entry)
139
151
  }))),
140
152
  async readEntryString(path) {
141
- const buffer = await readEntry(path);
142
- return buffer === null ? null : buffer.toString("utf-8");
153
+ const content = await readEntry(path);
154
+ return content === null ? null : new TextDecoder("utf-8", { ignoreBOM: true }).decode(content);
143
155
  },
144
- async readEntryUint8(path, readOptions) {
145
- const buffer = await readEntry(path, readOptions);
146
- return buffer === null ? null : new Uint8Array(buffer);
147
- }
156
+ readEntryUint8: readEntry
148
157
  };
149
158
  };
150
159
  //#endregion
@@ -1,227 +1,24 @@
1
1
  import { createEmptyDocument } from "../utils/createDocument.js";
2
- import { sanitizeExternalUrl } from "../utils/urlSecurity.js";
3
- import { marked } from "marked";
2
+ import { compileMarkdownToContent } from "@stll/docx-core";
4
3
  //#region src/markdown/fromMarkdown.ts
5
4
  /**
6
5
  * Markdown → DOCX-document import — the inverse of {@link toMarkdown} and the
7
- * second half of the skills bridge. Parses the GFM subset skill bodies use
8
- * (headings, paragraphs, bold/italic/strike, inline code, bullet + ordered
9
- * lists incl. nesting, pipe tables, blockquotes, links) into the docx
10
- * `Document` model so a skill's markdown can be edited in the Folio editor and
11
- * re-exported with {@link toMarkdown} without drift.
6
+ * second half of the skills bridge. The parsing lives in `@stll/docx-core`
7
+ * (`compileMarkdownToContent`), the same GFM reader the legal-source compiler
8
+ * uses; this wrapper only places the blocks into an editor-ready `Document`.
12
9
  *
13
10
  * Round-trip notes:
14
- * - Lists are emitted as real list paragraphs (`listRendering`), so the editor
15
- * shows a marker and {@link toMarkdown} re-derives `- ` / `1. ` rather than
16
- * leaking a literal bullet glyph into the text.
17
- * - Inline code uses `Courier New` a whitelisted monospace family that
18
- * {@link toMarkdown} infers back to a backtick span (Folio renders it via its
19
- * bundled Cousine substitute).
11
+ * - Lists arrive as real list paragraphs (`listRendering` plus `numPr`) with a
12
+ * matching `document.package.numbering`, so the editor shows a marker and
13
+ * {@link toMarkdown} re-derives `- ` / `1. ` rather than leaking a literal
14
+ * bullet glyph. Merging this content onto another document that has its own
15
+ * numbering (e.g. a styled preset) needs `mergeDocumentContent` to renumber
16
+ * the two numbering namespaces apart.
20
17
  * - Markdown carries no page geometry, so the section is flattened to a
21
18
  * continuous, header/footer-free band (a skill body is a document, not a
22
19
  * Word page). Headers/footers live outside `document.content` and are never
23
20
  * produced here.
24
- * - Every markdown list also gets a matching `w:abstractNum`/`w:num` pair in
25
- * `document.package.numbering` (see {@link buildNumbering}), so the result
26
- * is self-consistent and `createDocx` never has to fail with a missing
27
- * numbering definition. Merging this content onto another document that
28
- * has its own numbering (e.g. a styled preset) needs `mergeDocumentContent`
29
- * to renumber the two numbering namespaces apart — appending
30
- * `document.package.document.content` directly can collide.
31
21
  */
32
- const MONO_FONT = {
33
- ascii: "Courier New",
34
- hAnsi: "Courier New"
35
- };
36
- const isTokenType = (token, type) => token.type === type;
37
- const textRun = (text, fmt = {}) => {
38
- const formatting = {
39
- ...fmt.bold ? { bold: true } : {},
40
- ...fmt.italic ? { italic: true } : {},
41
- ...fmt.strike ? { strike: true } : {},
42
- ...fmt.mono ? { fontFamily: MONO_FONT } : {}
43
- };
44
- const segments = text.split("\n");
45
- const content = [];
46
- for (const [index, segment] of segments.entries()) {
47
- if (index > 0) content.push({ type: "break" });
48
- if (segment.length > 0) content.push({
49
- type: "text",
50
- text: segment
51
- });
52
- }
53
- if (content.length === 0) content.push({
54
- type: "text",
55
- text: ""
56
- });
57
- return {
58
- type: "run",
59
- formatting,
60
- content
61
- };
62
- };
63
- const sanitizeMarkdownHref = (rawHref) => {
64
- const trimmed = rawHref.trim();
65
- if (!trimmed) return;
66
- if (trimmed.startsWith("#")) {
67
- const anchor = trimmed.slice(1);
68
- if (!anchor || hasUnsafeAnchorCharacter(anchor)) return;
69
- return `#${anchor}`;
70
- }
71
- return sanitizeExternalUrl(trimmed);
72
- };
73
- const hasUnsafeAnchorCharacter = (anchor) => {
74
- for (const char of anchor) {
75
- const codePoint = char.codePointAt(0) ?? 0;
76
- if (codePoint <= 32 || codePoint === 127 || char.trim() === "") return true;
77
- }
78
- return false;
79
- };
80
- const inlineToRuns = (tokens, fallback, base) => {
81
- if (!tokens || tokens.length === 0) return [textRun(fallback, base)];
82
- const runs = [];
83
- for (const token of tokens) if (isTokenType(token, "strong")) runs.push(...inlineToRuns(token.tokens, token.text, {
84
- ...base,
85
- bold: true
86
- }));
87
- else if (isTokenType(token, "em")) runs.push(...inlineToRuns(token.tokens, token.text, {
88
- ...base,
89
- italic: true
90
- }));
91
- else if (isTokenType(token, "del")) runs.push(...inlineToRuns(token.tokens, token.text, {
92
- ...base,
93
- strike: true
94
- }));
95
- else if (isTokenType(token, "codespan")) runs.push(textRun(token.text, {
96
- ...base,
97
- mono: true
98
- }));
99
- else if (isTokenType(token, "link")) {
100
- const children = inlineToRuns(token.tokens, token.text, base).filter((child) => child.type === "run");
101
- const href = sanitizeMarkdownHref(token.href);
102
- const linkChildren = children.length > 0 ? children : [textRun(token.text, base)];
103
- if (!href) {
104
- runs.push(...linkChildren);
105
- continue;
106
- }
107
- const anchor = href.startsWith("#") ? href.slice(1) : void 0;
108
- runs.push({
109
- type: "hyperlink",
110
- href,
111
- ...anchor ? { anchor } : {},
112
- children: linkChildren
113
- });
114
- } else if (isTokenType(token, "paragraph")) runs.push(...inlineToRuns(token.tokens, token.text, base));
115
- else if (token.type === "br") runs.push({
116
- type: "run",
117
- content: [{ type: "break" }]
118
- });
119
- else if (token.type === "space") {
120
- if (runs.length > 0 && token.raw.includes("\n")) runs.push(textRun("\n", base));
121
- } else if (isTokenType(token, "text")) {
122
- const nested = token.tokens;
123
- if (nested && nested.length > 0) runs.push(...inlineToRuns(nested, token.text, base));
124
- else runs.push(textRun(token.text, base));
125
- } else if ("text" in token && typeof token.text === "string") runs.push(textRun(token.text, base));
126
- return runs.length > 0 ? runs : [textRun(fallback, base)];
127
- };
128
- const para = (runs, styleId) => ({
129
- type: "paragraph",
130
- formatting: styleId ? { styleId } : {},
131
- content: runs.length > 0 ? runs : [textRun("")]
132
- });
133
- const listPara = (runs, rendering) => ({
134
- type: "paragraph",
135
- formatting: { numPr: {
136
- numId: rendering.numId,
137
- ilvl: rendering.level
138
- } },
139
- listRendering: rendering,
140
- content: runs.length > 0 ? runs : [textRun("")]
141
- });
142
- const cellOf = (cell) => ({
143
- type: "tableCell",
144
- content: [para(inlineToRuns(cell.tokens, cell.text, {}))]
145
- });
146
- const tableFromToken = (token) => ({
147
- type: "table",
148
- rows: [{
149
- type: "tableRow",
150
- cells: token.header.map((c) => cellOf(c))
151
- }, ...token.rows.map((row) => ({
152
- type: "tableRow",
153
- cells: row.map((c) => cellOf(c))
154
- }))]
155
- });
156
- const LIST_INDENT_STEP_TWIPS = 720;
157
- const buildListLevel = (ilvl, isBullet, start) => ({
158
- ilvl,
159
- ...!isBullet && { start },
160
- numFmt: isBullet ? "bullet" : "decimal",
161
- lvlText: isBullet ? "•" : `%${ilvl + 1}.`,
162
- suffix: "tab",
163
- pPr: {
164
- indentLeft: LIST_INDENT_STEP_TWIPS * (ilvl + 1),
165
- indentFirstLine: -360,
166
- hangingIndent: true
167
- }
168
- });
169
- const listBlocks = (list, level, numId, levels) => {
170
- const out = [];
171
- const start = Number(list.start) || 1;
172
- const decimalLevels = Array.from({ length: level + 1 }, () => "decimal");
173
- if (!levels.has(level)) levels.set(level, buildListLevel(level, !list.ordered, start));
174
- for (const item of list.items) {
175
- const rendering = list.ordered ? {
176
- marker: `%${level + 1}.`,
177
- level,
178
- numId,
179
- isBullet: false,
180
- numFmt: "decimal",
181
- levelNumFmts: decimalLevels,
182
- ...start !== 1 && { startOverride: start }
183
- } : {
184
- marker: "•",
185
- level,
186
- numId,
187
- isBullet: true
188
- };
189
- const inlineTokens = [];
190
- const nestedLists = [];
191
- for (const child of item.tokens) if (isTokenType(child, "list")) nestedLists.push(child);
192
- else inlineTokens.push(child);
193
- out.push(listPara(inlineToRuns(inlineTokens, item.text, {}), rendering));
194
- for (const nested of nestedLists) out.push(...listBlocks(nested, level + 1, numId, levels));
195
- }
196
- return out;
197
- };
198
- const blocksFromTokens = (tokens, numIds) => {
199
- const blocks = [];
200
- for (const token of tokens ?? []) if (isTokenType(token, "heading")) {
201
- const level = Math.min(Math.max(token.depth, 1), 4);
202
- blocks.push(para(inlineToRuns(token.tokens, token.text, {}), `Heading${level}`));
203
- } else if (isTokenType(token, "paragraph")) blocks.push(para(inlineToRuns(token.tokens, token.text, {})));
204
- else if (isTokenType(token, "list")) {
205
- const numId = numIds.next++;
206
- const levels = /* @__PURE__ */ new Map();
207
- numIds.levels.set(numId, levels);
208
- blocks.push(...listBlocks(token, 0, numId, levels));
209
- } else if (isTokenType(token, "table")) blocks.push(tableFromToken(token));
210
- else if (isTokenType(token, "code")) for (const line of token.text.split("\n")) blocks.push(para([textRun(line.length > 0 ? line : " ", { mono: true })]));
211
- else if (isTokenType(token, "blockquote")) for (const inner of blocksFromTokens(token.tokens, numIds)) {
212
- const styled = inner.type === "paragraph" ? {
213
- ...inner,
214
- formatting: {
215
- ...inner.formatting,
216
- styleId: "Quote"
217
- }
218
- } : inner;
219
- blocks.push(styled);
220
- }
221
- else if (token.type === "hr") blocks.push(para([textRun("———")]));
222
- else if (token.type !== "space" && "text" in token && typeof token.text === "string" && token.text.trim().length > 0) blocks.push(para([textRun(token.text)]));
223
- return blocks;
224
- };
225
22
  const applyMarkdownPageGeometry = (document) => {
226
23
  const section = document.package.document.finalSectionProperties;
227
24
  if (!section) return;
@@ -232,26 +29,6 @@ const applyMarkdownPageGeometry = (document) => {
232
29
  section.headerDistance = 0;
233
30
  section.footerDistance = 0;
234
31
  };
235
- const buildNumbering = (numIdLevels) => {
236
- const abstractNums = [];
237
- const nums = [];
238
- for (const [numId, levels] of numIdLevels) {
239
- const sortedLevels = [...levels.entries()].sort(([a], [b]) => a - b).map(([, lvl]) => lvl);
240
- abstractNums.push({
241
- abstractNumId: numId,
242
- multiLevelType: sortedLevels.length > 1 ? "multilevel" : "singleLevel",
243
- levels: sortedLevels
244
- });
245
- nums.push({
246
- numId,
247
- abstractNumId: numId
248
- });
249
- }
250
- return {
251
- abstractNums,
252
- nums
253
- };
254
- };
255
32
  /**
256
33
  * Convert a markdown string to a parsed `Document`. Synchronous. The result is
257
34
  * ready to hand to the editor (`<DocxEditor document={…} />`) and to re-export
@@ -259,13 +36,9 @@ const buildNumbering = (numIdLevels) => {
259
36
  */
260
37
  function fromMarkdown(markdown) {
261
38
  const document = createEmptyDocument();
262
- const numIds = {
263
- next: 1,
264
- levels: /* @__PURE__ */ new Map()
265
- };
266
- const blocks = blocksFromTokens(marked.lexer(markdown), numIds);
267
- if (blocks.length > 0) document.package.document.content = blocks;
268
- if (numIds.levels.size > 0) document.package.numbering = buildNumbering(numIds.levels);
39
+ const { content, numbering } = compileMarkdownToContent(markdown);
40
+ if (content.length > 0) document.package.document.content = content;
41
+ if (numbering) document.package.numbering = numbering;
269
42
  applyMarkdownPageGeometry(document);
270
43
  return document;
271
44
  }
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIComment, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditSnapshot, FolioAIInlineFormatting, FolioAITextRangeHandle, FolioDocumentNavigationTarget, FolioDocumentOutline, FolioDocumentOutlineEntry, FolioDocumentSection, FolioDocumentSectionHandle, FolioDocumentSectionReadResult } from "./ai-edits/types.js";
2
- import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
2
+ import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationBatchPrecondition, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationQueuedOperation, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
3
3
  import { FolioReviewChange, FolioReviewChangeKind } from "./ai-edits/read.js";
4
4
  import { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsOptions, FolioApplyDocumentOperationsToStoryOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioDocxReviewer, FolioDocxReviewerOptions, FolioEditableDocumentStoryHandle, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, FolioReviewChangeFilter, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, FolioReviewedStory, FolioReviewedView, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, isFolioResolvedReviewedView, isFolioReviewedView } from "./ai-edits/headless.js";
5
5
  import { createFolioAITextRangeHandle, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./ai-edits/snapshot.js";
@@ -27,4 +27,4 @@ import { FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_E
27
27
  import { FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, FolioYjsDocxMaterializationError, FolioYjsDocxMaterializationErrorCode, MaterializeYjsDocxOptions, materializeYjsDocx } from "./docx/server/materializeYjsDocx.js";
28
28
  import { GenerateRedlineDocxOptions, GenerateRedlineDocxResult, GenerateRedlineUnprocessedStory, InvalidGenerateRedlineDocxOptionsError, generateRedlineDocx } from "./redline.js";
29
29
  import { FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FolioBlockDiff, FolioCompareDocxVersionsOptions, FolioDocumentMetadataValue, FolioFormatProperty, FolioMetadataDiff, FolioStoryDiff, FolioVersionBlockHandle, FolioVersionComparisonPrivacyTransform, FolioVersionComparisonScope, FolioVersionDiff, FolioVersionDiffPrivacyOptions, FolioVersionDiffPrivacyReport, FolioVersionDiffSegment, FolioVersionDiffSummaryCounts, InvalidFolioVersionComparisonOptionsError, applyFolioVersionDiffPrivacy, compareDocxVersions, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope } from "./version-comparison.js";
30
- export { type ApplyDocxXmlPatchProposalArgs, type ApplyFolioAIEditsToBufferOptions, type ApplyFolioAIEditsToBufferResult, BILINGUAL_TABLE_LAYOUTS, type BilingualBorders, type BilingualParagraphRef, type BilingualRow, type BilingualRowKind, type BilingualTableLayout, type BilingualTableParagraphRef, type CreateBilingualDocumentOptions, type CreateBilingualDocumentResult, type CreateBilingualDocxOptions, type CreateBilingualDocxResult, type CreateCommentReplyInput, type CreateEmptyDocumentOptions, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, type DeriveBlockIdInput, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, DocxArchiveError, type DocxArchiveOptions, type DocxParagraphSource, type DocxTableRowKind, type DocxTableRowPosition, EnsureParaIdsError, type EnsureParaIdsOptions, type EnsureParaIdsResult, type EvaluateDocxXmlPatchProposalArgs, type ExtractDocumentStyleSetOptions, type ExtractedDocxParagraph, type ExtractedDocxTableCell, type ExtractedDocxTableCellParagraph, type ExtractedDocxText, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditSnapshot, type FolioAIInlineFormatting, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsOptions, type FolioApplyDocumentOperationsToStoryOptions, type FolioApplyOperationsOptions, type FolioBlockDiff, type FolioBlockId, type FolioCompareDocxVersionsOptions, type FolioDocumentMetadataProperty, type FolioDocumentMetadataValue, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, FolioDocumentPrivacyArchiveError, type FolioDocumentPrivacyOptions, type FolioDocumentPrivacyReport, type FolioDocumentPrivacyTransform, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioDocxConformanceCheck, type FolioDocxConformanceCheckId, type FolioDocxConformanceCheckStatus, type FolioDocxConformanceIssue, type FolioDocxConformanceIssueCode, type FolioDocxConformanceReport, type FolioDocxConformanceStatus, type FolioDocxInspectedXmlPart, type FolioDocxPackageInspection, FolioDocxPackageInspectionError, type FolioDocxPackageInspectionErrorCode, type FolioDocxPackageInspectionLimits, type FolioDocxPackagePart, type FolioDocxPackagePartKind, type FolioDocxPreparedXmlReplacement, FolioDocxReviewer, type FolioDocxReviewerOptions, type FolioDocxXmlPatchApplication, FolioDocxXmlPatchApplicationError, type FolioDocxXmlPatchApplicationReceipt, type FolioDocxXmlPatchProposal, type FolioDocxXmlPatchProposalEvaluation, type FolioDocxXmlPatchProposalIssue, type FolioDocxXmlPatchProposalIssueCode, type FolioDocxXmlPatchProposalLimits, type FolioDocxXmlReplacement, type FolioEditableDocumentStoryHandle, type FolioFormatProperty, type FolioMetadataDiff, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeFilter, type FolioReviewChangeKind, type FolioReviewComment, type FolioReviewCommentFilter, type FolioReviewCommentReply, type FolioReviewReplyInput, type FolioReviewedStory, type FolioReviewedView, type FolioStoryDiff, type FolioVersionBlockHandle, type FolioVersionComparisonPrivacyTransform, type FolioVersionComparisonScope, type FolioVersionDiff, type FolioVersionDiffPrivacyOptions, type FolioVersionDiffPrivacyReport, type FolioVersionDiffSegment, type FolioVersionDiffSummaryCounts, FolioYjsDocxMaterializationError, type FolioYjsDocxMaterializationErrorCode, type GenerateRedlineDocxOptions, type GenerateRedlineDocxResult, type GenerateRedlineUnprocessedStory, HEADING_LEVELS, type HeadingLevel, type InspectDocxPackageOptions, InvalidBilingualDocumentOptionsError, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, type MaterializeYjsDocxOptions, type ParseOptions, type RewriteDocxMetadataPrivacyResult, STELLA_STYLE_SET_NAME, type TableCellSpec, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, type ValidateDocxConformanceOptions, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createBilingualDocument, createBilingualDocx, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, materializeYjsDocx, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readBilingualDocument, readBilingualDocx, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
30
+ export { type ApplyDocxXmlPatchProposalArgs, type ApplyFolioAIEditsToBufferOptions, type ApplyFolioAIEditsToBufferResult, BILINGUAL_TABLE_LAYOUTS, type BilingualBorders, type BilingualParagraphRef, type BilingualRow, type BilingualRowKind, type BilingualTableLayout, type BilingualTableParagraphRef, type CreateBilingualDocumentOptions, type CreateBilingualDocumentResult, type CreateBilingualDocxOptions, type CreateBilingualDocxResult, type CreateCommentReplyInput, type CreateEmptyDocumentOptions, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, type DeriveBlockIdInput, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, DocxArchiveError, type DocxArchiveOptions, type DocxParagraphSource, type DocxTableRowKind, type DocxTableRowPosition, EnsureParaIdsError, type EnsureParaIdsOptions, type EnsureParaIdsResult, type EvaluateDocxXmlPatchProposalArgs, type ExtractDocumentStyleSetOptions, type ExtractedDocxParagraph, type ExtractedDocxTableCell, type ExtractedDocxTableCellParagraph, type ExtractedDocxText, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditSnapshot, type FolioAIInlineFormatting, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsOptions, type FolioApplyDocumentOperationsToStoryOptions, type FolioApplyOperationsOptions, type FolioBlockDiff, type FolioBlockId, type FolioCompareDocxVersionsOptions, type FolioDocumentMetadataProperty, type FolioDocumentMetadataValue, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationBatchPrecondition, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationQueuedOperation, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, FolioDocumentPrivacyArchiveError, type FolioDocumentPrivacyOptions, type FolioDocumentPrivacyReport, type FolioDocumentPrivacyTransform, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioDocxConformanceCheck, type FolioDocxConformanceCheckId, type FolioDocxConformanceCheckStatus, type FolioDocxConformanceIssue, type FolioDocxConformanceIssueCode, type FolioDocxConformanceReport, type FolioDocxConformanceStatus, type FolioDocxInspectedXmlPart, type FolioDocxPackageInspection, FolioDocxPackageInspectionError, type FolioDocxPackageInspectionErrorCode, type FolioDocxPackageInspectionLimits, type FolioDocxPackagePart, type FolioDocxPackagePartKind, type FolioDocxPreparedXmlReplacement, FolioDocxReviewer, type FolioDocxReviewerOptions, type FolioDocxXmlPatchApplication, FolioDocxXmlPatchApplicationError, type FolioDocxXmlPatchApplicationReceipt, type FolioDocxXmlPatchProposal, type FolioDocxXmlPatchProposalEvaluation, type FolioDocxXmlPatchProposalIssue, type FolioDocxXmlPatchProposalIssueCode, type FolioDocxXmlPatchProposalLimits, type FolioDocxXmlReplacement, type FolioEditableDocumentStoryHandle, type FolioFormatProperty, type FolioMetadataDiff, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeFilter, type FolioReviewChangeKind, type FolioReviewComment, type FolioReviewCommentFilter, type FolioReviewCommentReply, type FolioReviewReplyInput, type FolioReviewedStory, type FolioReviewedView, type FolioStoryDiff, type FolioVersionBlockHandle, type FolioVersionComparisonPrivacyTransform, type FolioVersionComparisonScope, type FolioVersionDiff, type FolioVersionDiffPrivacyOptions, type FolioVersionDiffPrivacyReport, type FolioVersionDiffSegment, type FolioVersionDiffSummaryCounts, FolioYjsDocxMaterializationError, type FolioYjsDocxMaterializationErrorCode, type GenerateRedlineDocxOptions, type GenerateRedlineDocxResult, type GenerateRedlineUnprocessedStory, HEADING_LEVELS, type HeadingLevel, type InspectDocxPackageOptions, InvalidBilingualDocumentOptionsError, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, type MaterializeYjsDocxOptions, type ParseOptions, type RewriteDocxMetadataPrivacyResult, STELLA_STYLE_SET_NAME, type TableCellSpec, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, type ValidateDocxConformanceOptions, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createBilingualDocument, createBilingualDocx, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, materializeYjsDocx, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readBilingualDocument, readBilingualDocx, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
package/dist/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, FolioDocxReviewer, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, isFolioResolvedReviewedView, isFolioReviewedView } from "./ai-edits/headless.js";
2
2
  import { getFolioDocumentOutline, readFolioDocumentSection } from "./ai-edits/scoped-reading.js";
3
3
  import { createFolioAITextRangeHandle, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./ai-edits/snapshot.js";
4
- import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
4
+ import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
5
5
  import { EnsureParaIdsError, ensureParaIds } from "./docx/ensureParaIds.js";
6
6
  import { FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FolioDocumentPrivacyArchiveError, InvalidFolioDocumentPrivacyOptionsError, isFolioDocumentPrivacyTransform, rewriteDocxMetadataPrivacy } from "./docx/metadataPrivacy.js";
7
7
  import { parseDocx } from "./docx/parser.js";
@@ -25,4 +25,4 @@ import { DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION } from "./style-set
25
25
  import { deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
26
26
  import { createEmptyDocument } from "./utils/createDocument.js";
27
27
  import { FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, InvalidFolioVersionComparisonOptionsError, applyFolioVersionDiffPrivacy, compareDocxVersions, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope } from "./version-comparison.js";
28
- export { BILINGUAL_TABLE_LAYOUTS, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DocxArchiveError, EnsureParaIdsError, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, FolioDocumentPrivacyArchiveError, FolioDocumentStoryNotFoundError, FolioDocxPackageInspectionError, FolioDocxReviewer, FolioDocxXmlPatchApplicationError, FolioYjsDocxMaterializationError, HEADING_LEVELS, InvalidBilingualDocumentOptionsError, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createBilingualDocument, createBilingualDocx, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, materializeYjsDocx, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readBilingualDocument, readBilingualDocx, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
28
+ export { BILINGUAL_TABLE_LAYOUTS, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DocxArchiveError, EnsureParaIdsError, FOLIO_DOCUMENT_METADATA_PROPERTIES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_BATCH_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCUMENT_PRIVACY_TRANSFORMS, FOLIO_DOCX_CONFORMANCE_CHECKS, FOLIO_DOCX_CONFORMANCE_ISSUE_CODES, FOLIO_DOCX_CONFORMANCE_PROFILE, FOLIO_DOCX_CONFORMANCE_REPORT_VERSION, FOLIO_DOCX_PACKAGE_INSPECTION_DEFAULTS, FOLIO_DOCX_PACKAGE_INSPECTION_ERROR_CODES, FOLIO_DOCX_PACKAGE_INSPECTION_VERSION, FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FOLIO_VERSION_COMPARISON_PRIVACY_TRANSFORMS, FOLIO_VERSION_COMPARISON_SCOPES, FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, FolioDocumentPrivacyArchiveError, FolioDocumentStoryNotFoundError, FolioDocxPackageInspectionError, FolioDocxReviewer, FolioDocxXmlPatchApplicationError, FolioYjsDocxMaterializationError, HEADING_LEVELS, InvalidBilingualDocumentOptionsError, InvalidFolioDocumentOperationBatchError, InvalidFolioDocumentPrivacyOptionsError, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, InvalidFolioReportBuilderOptionsError, InvalidFolioVersionComparisonOptionsError, InvalidGenerateRedlineDocxOptionsError, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioDocxXmlPatchApplicationProfileError, UnsupportedFolioReviewedViewError, applyDocxXmlPatchProposal, applyFolioAIEditsToBuffer, applyFolioVersionDiffPrivacy, assertSupportedFolioDocumentOperationVersion, bookmark, compareDocxVersions, createBilingualDocument, createBilingualDocx, createDocx, createEmptyDocument, createFolioAITextRangeHandle, createStellaStyleDocumentPreset, createStellaStyleSet, createTableOfContentsField, deriveBlockId, docxToMarkdown, endnote, ensureParaIds, evaluateDocxXmlPatchProposal, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractDocxText, generateRedlineDocx, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, hashFolioAIBlockText, heading, hyperlink, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxPackage, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocumentPrivacyTransform, isFolioResolvedReviewedView, isFolioReviewedView, isFolioVersionComparisonPrivacyTransform, isFolioVersionComparisonScope, isSequentialFolioBlockId, isSupportedFolioDocumentOperationVersion, materializeYjsDocx, normalizeFolioAIBlockText, pageBreak, paragraph, parseDocx, parseFolioDocumentOperationBatch, parseFolioDocxXmlPatchProposal, readBilingualDocument, readBilingualDocx, readFolioDocumentSection, replyToComment, rewriteDocxMetadataPrivacy, run, table, validateDocxConformance };
@@ -1,5 +1,5 @@
1
+ import { sanitizeExternalUrl } from "@stll/docx-core";
1
2
  //#region src/utils/urlSecurity.d.ts
2
- declare function sanitizeExternalUrl(rawUrl: string | undefined): string | undefined;
3
3
  declare function normalizeUserUrl(rawUrl: string): string;
4
4
  declare function isAllowedUserUrl(rawUrl: string): boolean;
5
5
  declare function sanitizeLinkTarget(target: string | undefined): string;
@@ -1,29 +1,11 @@
1
+ import { sanitizeExternalUrl } from "@stll/docx-core";
1
2
  //#region src/utils/urlSecurity.ts
2
- const ALLOWED_URL_PROTOCOLS = /* @__PURE__ */ new Set([
3
- "http:",
4
- "https:",
5
- "mailto:",
6
- "tel:"
7
- ]);
8
3
  const ALLOWED_TARGETS = /* @__PURE__ */ new Set([
9
4
  "_blank",
10
5
  "_self",
11
6
  "_parent",
12
7
  "_top"
13
8
  ]);
14
- function sanitizeExternalUrl(rawUrl) {
15
- if (!rawUrl) return;
16
- const trimmed = rawUrl.trim();
17
- if (!trimmed) return;
18
- try {
19
- const parsed = new URL(trimmed);
20
- if (!ALLOWED_URL_PROTOCOLS.has(parsed.protocol)) return;
21
- if ((parsed.protocol === "mailto:" || parsed.protocol === "tel:") && parsed.pathname.trim() === "") return;
22
- return parsed.href;
23
- } catch {
24
- return;
25
- }
26
- }
27
9
  function normalizeUserUrl(rawUrl) {
28
10
  const trimmed = rawUrl.trim();
29
11
  if (!trimmed) return "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.31.2",
3
+ "version": "0.32.1",
4
4
  "description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
5
5
  "keywords": [
6
6
  "document-model",
@@ -113,7 +113,7 @@
113
113
  "perf": "bun scripts/profile-editor.ts"
114
114
  },
115
115
  "dependencies": {
116
- "@stll/docx-core": "^0.17.3",
116
+ "@stll/docx-core": "^0.18.0",
117
117
  "@stll/docx-utils": "^0.1.0",
118
118
  "@stll/template-conditions": "^0.1.0",
119
119
  "better-result": "3.0.1",
@@ -122,7 +122,6 @@
122
122
  "fast-xml-parser": "^5.10.1",
123
123
  "hyphen": "1.14.1",
124
124
  "jszip": "3.10.1",
125
- "marked": "^18.0.5",
126
125
  "prosemirror-commands": "^1.7.1",
127
126
  "prosemirror-dropcursor": "^1.8.2",
128
127
  "prosemirror-gapcursor": "^1.4.1",