@stll/folio-core 0.22.3 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/ai-edits/apply.d.ts +1 -1
  2. package/dist/document-operations.d.ts +4 -4
  3. package/dist/document-operations.js +17 -1
  4. package/dist/docx/rezip.d.ts +1 -1
  5. package/dist/docx/rezip.js +12 -31
  6. package/dist/layout-bridge/convert/toFlowBlocks.js +35 -61
  7. package/dist/layout-painter/imageLayout.d.ts +1 -1
  8. package/dist/managers/AutoSaveManager.js +8 -40
  9. package/dist/managers/TableSelectionManager.d.ts +1 -1
  10. package/dist/managers/autoSaveCodec.d.ts +17 -0
  11. package/dist/managers/autoSaveCodec.js +109 -0
  12. package/dist/prosemirror/attrs/index.js +273 -25
  13. package/dist/prosemirror/commands/comments.js +3 -3
  14. package/dist/prosemirror/commands/formatting.js +19 -19
  15. package/dist/prosemirror/commands/index.d.ts +2 -2
  16. package/dist/prosemirror/commands/paragraph.js +32 -32
  17. package/dist/prosemirror/commands/propertyChangeScope.d.ts +4 -2
  18. package/dist/prosemirror/commands/propertyChangeScope.js +8 -8
  19. package/dist/prosemirror/commands/table.d.ts +10 -52
  20. package/dist/prosemirror/commands/table.js +34 -34
  21. package/dist/prosemirror/conversion/fromProseDoc.js +20 -17
  22. package/dist/prosemirror/conversion/sdtAttrs.d.ts +2 -1
  23. package/dist/prosemirror/conversion/sdtAttrs.js +9 -4
  24. package/dist/prosemirror/extensions/ExtensionManager.d.ts +6 -3
  25. package/dist/prosemirror/extensions/ExtensionManager.js +5 -3
  26. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +1 -1
  27. package/dist/prosemirror/extensions/features/ListExtension.js +4 -4
  28. package/dist/prosemirror/extensions/nodes/ShapeExtension.d.ts +1 -1
  29. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +1 -1
  30. package/dist/prosemirror/extensions/types.d.ts +134 -1
  31. package/dist/prosemirror/index.d.ts +2 -2
  32. package/dist/prosemirror/plugins/selectionTracker.js +25 -99
  33. package/dist/prosemirror/revisionCarriers.js +3 -1
  34. package/dist/prosemirror/schema/index.d.ts +2 -2
  35. package/dist/prosemirror/schema/index.js +87 -0
  36. package/dist/prosemirror/schema/nodes.d.ts +13 -2
  37. package/dist/prosemirror/selectionState.d.ts +11 -5
  38. package/dist/prosemirror/selectionState.js +94 -57
  39. package/dist/utils/tableOperations.d.ts +7 -6
  40. package/package.json +3 -3
@@ -17,7 +17,7 @@ type FolioAIEditView = {
17
17
  type ApplyFolioAIEditOperationsOptions = {
18
18
  view: FolioAIEditView;
19
19
  snapshot: FolioAIEditSnapshot;
20
- operations: FolioAIEditOperation[];
20
+ operations: readonly FolioAIEditOperation[];
21
21
  mode?: FolioAIEditApplyMode;
22
22
  author?: string;
23
23
  /** Optional author initials (w:initials) stamped alongside the author. */
@@ -56,10 +56,10 @@ declare class InvalidFolioDocumentOperationBatchError extends InvalidFolioDocume
56
56
  declare const assertSupportedFolioDocumentOperationVersion: (value: unknown) => typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
57
57
  type FolioDocumentOperationBatch = {
58
58
  readonly version: typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
59
- operations: FolioDocumentOperation[];
60
- mode?: FolioDocumentOperationMode;
61
- atomic?: boolean;
62
- dryRun?: boolean;
59
+ readonly operations: readonly FolioDocumentOperation[];
60
+ readonly mode?: FolioDocumentOperationMode;
61
+ readonly atomic?: boolean;
62
+ readonly dryRun?: boolean;
63
63
  };
64
64
  declare const parseFolioDocumentOperationBatch: (value: unknown) => FolioDocumentOperationBatch;
65
65
  type FolioDocumentOperationStatus = "committed" | "previewed" | "rejected";
@@ -34,6 +34,7 @@ const FOLIO_DOCUMENT_OPERATION_STORIES = Object.freeze([
34
34
  ]);
35
35
  const FOLIO_DOCUMENT_OPERATION_PRECONDITIONS = Object.freeze(["blockTextHash"]);
36
36
  const FOLIO_DOCUMENT_OPERATION_BATCH_MODES = Object.freeze(["best-effort", "atomic"]);
37
+ const parsedFolioDocumentOperationBatches = /* @__PURE__ */ new WeakSet();
37
38
  const DIRECT_AND_TRACKED_MODES = Object.freeze(["direct", "tracked-changes"]);
38
39
  const DIRECT_TRACKED_AND_SUGGESTED_MODES = FOLIO_DOCUMENT_OPERATION_MODES;
39
40
  const DIRECT_AND_SUGGESTED_MODES = Object.freeze(["direct", "suggested"]);
@@ -83,6 +84,17 @@ const assertSupportedFolioDocumentOperationVersion = (value) => {
83
84
  });
84
85
  };
85
86
  const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
87
+ const isParsedFolioDocumentOperationBatch = (value) => typeof value === "object" && value !== null && parsedFolioDocumentOperationBatches.has(value);
88
+ const freezeParsedValue = (value) => {
89
+ if (Array.isArray(value)) {
90
+ for (const entry of value) freezeParsedValue(entry);
91
+ Object.freeze(value);
92
+ return;
93
+ }
94
+ if (!isPlainObject(value)) return;
95
+ for (const entry of Object.values(value)) freezeParsedValue(entry);
96
+ Object.freeze(value);
97
+ };
86
98
  const invalidBatch = (path, reason) => {
87
99
  throw new InvalidFolioDocumentOperationBatchError({
88
100
  message: `Invalid document operation batch at ${path}: ${reason}.`,
@@ -508,6 +520,7 @@ const parseDocumentOperation = (value, index) => {
508
520
  return invalidBatch(`${path}.type`, `unsupported operation type "${type}"`);
509
521
  };
510
522
  const parseFolioDocumentOperationBatch = (value) => {
523
+ if (isParsedFolioDocumentOperationBatch(value)) return value;
511
524
  if (!isPlainObject(value)) return invalidBatch("$", "expected an object");
512
525
  assertAllowedKeys(value, "$", [
513
526
  "version",
@@ -529,13 +542,16 @@ const parseFolioDocumentOperationBatch = (value) => {
529
542
  if (operationIds.has(operation.id)) return invalidBatch(`$.operations[${index}].id`, "expected a unique operation id");
530
543
  operationIds.add(operation.id);
531
544
  }
532
- return {
545
+ const parsedBatch = {
533
546
  version,
534
547
  operations: parsedOperations,
535
548
  ...mode !== void 0 && { mode },
536
549
  ...atomic !== void 0 && { atomic },
537
550
  ...dryRun !== void 0 && { dryRun }
538
551
  };
552
+ freezeParsedValue(parsedBatch);
553
+ parsedFolioDocumentOperationBatches.add(parsedBatch);
554
+ return parsedBatch;
539
555
  };
540
556
  const recoveryByReason = {
541
557
  missingBlock: "refreshDocument",
@@ -145,7 +145,7 @@ declare function updateCoreProperties(corePropsXml: string, { updateModifiedDate
145
145
  * @param buffer - Buffer to validate
146
146
  * @returns Promise resolving to validation result
147
147
  */
148
- declare function validateDocx(buffer: ArrayBuffer): Promise<{
148
+ declare const validateDocx: (buffer: ArrayBuffer) => Promise<{
149
149
  valid: boolean;
150
150
  errors: string[];
151
151
  warnings: string[];
@@ -21,6 +21,7 @@ import { isPreservableDocxEntry } from "./unzip.js";
21
21
  import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml } from "./xmlParser.js";
22
22
  import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
23
23
  import { panic } from "better-result";
24
+ import { validateDocxPackage } from "@stll/docx-core";
24
25
  import JSZip from "jszip";
25
26
  //#region src/docx/rezip.ts
26
27
  /**
@@ -1223,38 +1224,18 @@ function getContentTypeForExtension(extension, mimeType) {
1223
1224
  * @param buffer - Buffer to validate
1224
1225
  * @returns Promise resolving to validation result
1225
1226
  */
1226
- async function validateDocx(buffer) {
1227
- const errors = [];
1228
- const warnings = [];
1229
- try {
1230
- const zip = await JSZip.loadAsync(buffer);
1231
- for (const file of ["[Content_Types].xml", "word/document.xml"]) if (!zip.file(file)) errors.push(`Missing required file: ${file}`);
1232
- for (const file of [
1233
- "_rels/.rels",
1234
- "word/_rels/document.xml.rels",
1235
- "word/styles.xml"
1236
- ]) if (!zip.file(file)) warnings.push(`Missing recommended file: ${file}`);
1237
- const docFile = zip.file("word/document.xml");
1238
- if (docFile) {
1239
- const docXml = await docFile.async("text");
1240
- if (!docXml.includes("<?xml")) warnings.push("document.xml missing XML declaration");
1241
- if (!docXml.includes("<w:document")) errors.push("document.xml missing w:document element");
1242
- if (!docXml.includes("<w:body>")) errors.push("document.xml missing w:body element");
1243
- }
1244
- const ctFile = zip.file("[Content_Types].xml");
1245
- if (ctFile) {
1246
- const ctXml = await ctFile.async("text");
1247
- if (!ctXml.includes("word/document.xml") && !ctXml.includes("application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml")) warnings.push("Content_Types.xml may be missing document.xml type declaration");
1248
- }
1249
- } catch (error) {
1250
- errors.push(`Failed to read as ZIP: ${error instanceof Error ? error.message : "Unknown error"}`);
1251
- }
1252
- return {
1253
- valid: errors.length === 0,
1254
- errors,
1255
- warnings
1227
+ const validateDocx = async (buffer) => {
1228
+ const result = await validateDocxPackage(buffer);
1229
+ return result.valid ? {
1230
+ valid: true,
1231
+ errors: [],
1232
+ warnings: []
1233
+ } : {
1234
+ valid: false,
1235
+ errors: [result.error],
1236
+ warnings: []
1256
1237
  };
1257
- }
1238
+ };
1258
1239
  /**
1259
1240
  * Check if buffer looks like a DOCX file (quick check)
1260
1241
  *
@@ -9,7 +9,6 @@ import { autospacingMatchesBase } from "../../prosemirror/autospacingBase.js";
9
9
  import { runShadingAttrsToShading } from "../../prosemirror/conversion/runShadingMark.js";
10
10
  import { directionIsRtl } from "../../prosemirror/paragraphDirection.js";
11
11
  import { assertValidProseMirrorDocument } from "../../prosemirror/validation.js";
12
- import { NUMBER_FORMAT_VALUES } from "../../types/documentEnumValues.js";
13
12
  import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.js";
14
13
  import { resolveThemeFont } from "../../utils/fontResolver.js";
15
14
  import { resolveShadingFill } from "../../utils/formatToStyle.js";
@@ -364,7 +363,7 @@ const resolveWesternThemeFont = (fontFamily, theme) => {
364
363
  return (themeRef ? resolveThemeFont(themeRef, theme?.fontScheme) : null) ?? fontFamily.ascii ?? fontFamily.hAnsi ?? void 0;
365
364
  };
366
365
  const resolveComplexScriptThemeFont = (fontFamily, theme) => {
367
- return (fontFamily.cstheme ? resolveThemeFont(fontFamily.cstheme, theme?.fontScheme) : null) ?? fontFamily.cs ?? void 0;
366
+ return (fontFamily.csTheme ? resolveThemeFont(fontFamily.csTheme, theme?.fontScheme) : null) ?? fontFamily.cs ?? void 0;
368
367
  };
369
368
  const resolveEastAsiaThemeFont = (fontFamily, theme) => {
370
369
  return (fontFamily.eastAsiaTheme ? resolveThemeFont(fontFamily.eastAsiaTheme, theme?.fontScheme) : null) ?? fontFamily.eastAsia ?? void 0;
@@ -650,78 +649,54 @@ function toListMarkerRevision(kind, info) {
650
649
  }
651
650
  function isAddedNumberingChange(change) {
652
651
  const previousFormatting = change.previousFormatting;
653
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && previousFormatting["numPr"] == null;
652
+ return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && previousFormatting.numPr == null;
654
653
  }
655
654
  function isRemovedNumberingChange(change) {
656
655
  const previousFormatting = change.previousFormatting;
657
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting["numPr"]);
656
+ return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting.numPr);
658
657
  }
659
658
  function isChangedNumberingChange(currentNumPr, change) {
660
659
  const previousFormatting = change.previousFormatting;
661
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting["numPr"]) && !areListNumPrEqual(previousFormatting["numPr"], currentNumPr);
660
+ return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting.numPr) && !areListNumPrEqual(previousFormatting.numPr, currentNumPr);
662
661
  }
663
662
  function areListNumPrEqual(left, right) {
664
663
  return left.numId === right.numId && left.ilvl === right.ilvl;
665
664
  }
666
- function isRecord(value) {
667
- return typeof value === "object" && value !== null;
668
- }
669
665
  function isListNumPr(value) {
670
- if (!isRecord(value)) return false;
671
- const numId = value["numId"];
672
- const ilvl = value["ilvl"];
673
- return (numId === void 0 || typeof numId === "number") && (ilvl === void 0 || typeof ilvl === "number");
674
- }
675
- function isNumberFormat(value) {
676
- return NUMBER_FORMAT_VALUES.some((format) => format === value);
677
- }
678
- function readNumberFormats(value) {
679
- if (!Array.isArray(value)) return;
680
- const formats = [];
681
- for (const item of value) {
682
- if (!isNumberFormat(item)) return;
683
- formats.push(item);
684
- }
685
- return formats;
686
- }
687
- function readListMarkerSuffix(value) {
688
- if (value === "tab" || value === "space" || value === "nothing") return value;
689
- }
690
- function readListMarkerAlignment(value) {
691
- if (value === "left" || value === "center" || value === "right") return value;
666
+ return value !== void 0 && value !== null;
692
667
  }
693
668
  function toPreviousListAttrs(previousFormatting) {
694
669
  const attrs = {};
695
- const numPr = previousFormatting["numPr"];
670
+ const numPr = previousFormatting.numPr;
696
671
  if (isListNumPr(numPr)) attrs.numPr = numPr;
697
- const listIsBullet = previousFormatting["listIsBullet"];
698
- if (typeof listIsBullet === "boolean") attrs.listIsBullet = listIsBullet;
699
- const listIsLegal = previousFormatting["listIsLegal"];
700
- if (typeof listIsLegal === "boolean") attrs.listIsLegal = listIsLegal;
701
- const listMarker = previousFormatting["listMarker"];
702
- if (typeof listMarker === "string") attrs.listMarker = listMarker;
703
- const listNumFmt = previousFormatting["listNumFmt"];
704
- if (isNumberFormat(listNumFmt)) attrs.listNumFmt = listNumFmt;
705
- const listLevelNumFmts = readNumberFormats(previousFormatting["listLevelNumFmts"]);
706
- if (listLevelNumFmts) attrs.listLevelNumFmts = listLevelNumFmts;
707
- const listLevelStarts = previousFormatting["listLevelStarts"];
708
- if (Array.isArray(listLevelStarts) && listLevelStarts.every((value) => typeof value === "number")) attrs.listLevelStarts = listLevelStarts;
709
- const listAbstractNumId = previousFormatting["listAbstractNumId"];
710
- if (typeof listAbstractNumId === "number") attrs.listAbstractNumId = listAbstractNumId;
711
- const listStartOverride = previousFormatting["listStartOverride"];
712
- if (typeof listStartOverride === "number") attrs.listStartOverride = listStartOverride;
713
- const listMarkerHidden = previousFormatting["listMarkerHidden"];
714
- if (typeof listMarkerHidden === "boolean") attrs.listMarkerHidden = listMarkerHidden;
715
- const listMarkerFontFamily = previousFormatting["listMarkerFontFamily"];
716
- if (typeof listMarkerFontFamily === "string") attrs.listMarkerFontFamily = listMarkerFontFamily;
717
- const listMarkerFontSize = previousFormatting["listMarkerFontSize"];
718
- if (typeof listMarkerFontSize === "number") attrs.listMarkerFontSize = listMarkerFontSize;
719
- const listMarkerBold = previousFormatting["listMarkerBold"];
720
- if (typeof listMarkerBold === "boolean") attrs.listMarkerBold = listMarkerBold;
721
- const listMarkerAlignment = readListMarkerAlignment(previousFormatting["listMarkerAlignment"]);
722
- if (listMarkerAlignment) attrs.listMarkerAlignment = listMarkerAlignment;
723
- const listMarkerSuffix = readListMarkerSuffix(previousFormatting["listMarkerSuffix"]);
724
- if (listMarkerSuffix) attrs.listMarkerSuffix = listMarkerSuffix;
672
+ const listIsBullet = previousFormatting.listIsBullet;
673
+ if (listIsBullet !== void 0) attrs.listIsBullet = listIsBullet;
674
+ const listIsLegal = previousFormatting.listIsLegal;
675
+ if (listIsLegal !== void 0) attrs.listIsLegal = listIsLegal;
676
+ const listMarker = previousFormatting.listMarker;
677
+ if (listMarker !== void 0) attrs.listMarker = listMarker;
678
+ const listNumFmt = previousFormatting.listNumFmt;
679
+ if (listNumFmt !== void 0) attrs.listNumFmt = listNumFmt;
680
+ const listLevelNumFmts = previousFormatting.listLevelNumFmts;
681
+ if (listLevelNumFmts !== void 0) attrs.listLevelNumFmts = listLevelNumFmts;
682
+ const listLevelStarts = previousFormatting.listLevelStarts;
683
+ if (listLevelStarts !== void 0) attrs.listLevelStarts = listLevelStarts;
684
+ const listAbstractNumId = previousFormatting.listAbstractNumId;
685
+ if (listAbstractNumId !== void 0) attrs.listAbstractNumId = listAbstractNumId;
686
+ const listStartOverride = previousFormatting.listStartOverride;
687
+ if (listStartOverride !== void 0) attrs.listStartOverride = listStartOverride;
688
+ const listMarkerHidden = previousFormatting.listMarkerHidden;
689
+ if (listMarkerHidden !== void 0) attrs.listMarkerHidden = listMarkerHidden;
690
+ const listMarkerFontFamily = previousFormatting.listMarkerFontFamily;
691
+ if (listMarkerFontFamily !== void 0) attrs.listMarkerFontFamily = listMarkerFontFamily;
692
+ const listMarkerFontSize = previousFormatting.listMarkerFontSize;
693
+ if (listMarkerFontSize !== void 0) attrs.listMarkerFontSize = listMarkerFontSize;
694
+ const listMarkerBold = previousFormatting.listMarkerBold;
695
+ if (listMarkerBold !== void 0) attrs.listMarkerBold = listMarkerBold;
696
+ const listMarkerAlignment = previousFormatting.listMarkerAlignment;
697
+ if (listMarkerAlignment !== void 0) attrs.listMarkerAlignment = listMarkerAlignment;
698
+ const listMarkerSuffix = previousFormatting.listMarkerSuffix;
699
+ if (listMarkerSuffix !== void 0) attrs.listMarkerSuffix = listMarkerSuffix;
725
700
  return attrs;
726
701
  }
727
702
  function resolveDeletedListMarker(previousListAttrs, listCounters, listAbstractCounters, listSeenNumIds) {
@@ -848,8 +823,7 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
848
823
  if (pmAttrs.runInWithNext) attrs.runInWithNext = true;
849
824
  if (directionIsRtl(pmAttrs.direction)) attrs.bidi = true;
850
825
  if (pmAttrs.styleId) attrs.styleId = pmAttrs.styleId;
851
- const pPrChange = pmAttrs._propertyChanges;
852
- const propertyChanges = Array.isArray(pPrChange) ? pPrChange : [];
826
+ const propertyChanges = pmAttrs._propertyChanges ?? [];
853
827
  let changedNumberingChange;
854
828
  if (pmAttrs.numPr) {
855
829
  const numPr = {};
@@ -1,7 +1,7 @@
1
1
  import { FlowBlock, Measure } from "../layout-engine/types.js";
2
+ import { ImageAttrs } from "../prosemirror/schema/nodes.js";
2
3
  import { BlockLookup } from "./index.js";
3
4
  import { WrapType } from "../docx/wrapTypes.js";
4
- import { ImageAttrs } from "../prosemirror/schema/nodes.js";
5
5
  //#region src/layout-painter/imageLayout.d.ts
6
6
  /**
7
7
  * Anchored wrap-type targets — the OOXML wrap types except `inline`.
@@ -1,10 +1,10 @@
1
1
  import { Subscribable } from "./Subscribable.js";
2
+ import { decodeAutoSaveEnvelope, encodeAutoSaveDocument, encodeAutoSaveEnvelopeFromDocument } from "./autoSaveCodec.js";
2
3
  //#region src/managers/AutoSaveManager.ts
3
4
  const DEFAULT_STORAGE_KEY = "docx-editor-autosave";
4
5
  const DEFAULT_INTERVAL = 3e4;
5
6
  const DEFAULT_MAX_AGE = 1440 * 60 * 1e3;
6
7
  const DEFAULT_DEBOUNCE_DELAY = 2e3;
7
- const SAVE_VERSION = 1;
8
8
  function isLocalStorageAvailable() {
9
9
  try {
10
10
  const testKey = "__docx_editor_test__";
@@ -16,31 +16,7 @@ function isLocalStorageAvailable() {
16
16
  }
17
17
  }
18
18
  function serializeForStorage(document) {
19
- return JSON.stringify({
20
- ...document,
21
- originalBuffer: null
22
- });
23
- }
24
- function isDocumentLike(value) {
25
- return typeof value === "object" && value !== null && "package" in value;
26
- }
27
- function parseSavedData(json) {
28
- let parsed;
29
- try {
30
- parsed = JSON.parse(json);
31
- } catch {
32
- return null;
33
- }
34
- if (typeof parsed !== "object" || parsed === null) return null;
35
- if (!("document" in parsed) || !("savedAt" in parsed) || !("version" in parsed)) return null;
36
- const { document, savedAt, version } = parsed;
37
- if (typeof savedAt !== "string" || typeof version !== "number") return null;
38
- if (!isDocumentLike(document)) return null;
39
- return {
40
- document,
41
- savedAt,
42
- version
43
- };
19
+ return encodeAutoSaveDocument(document);
44
20
  }
45
21
  function isStale(savedAt, maxAge) {
46
22
  const savedTime = new Date(savedAt).getTime();
@@ -99,12 +75,12 @@ var AutoSaveManager = class extends Subscribable {
99
75
  this.updateStatus("saving");
100
76
  try {
101
77
  const serialized = serializeForStorage(doc);
102
- if (serialized === this.lastSavedJson) {
78
+ if (serialized.json === this.lastSavedJson) {
103
79
  this.updateStatus("saved");
104
80
  return true;
105
81
  }
106
- this.persistToStorage(doc);
107
- this.lastSavedJson = serialized;
82
+ this.persistToStorage(serialized);
83
+ this.lastSavedJson = serialized.json;
108
84
  const saveTime = /* @__PURE__ */ new Date();
109
85
  this.lastSaveTime = saveTime;
110
86
  this.updateStatus("saved");
@@ -133,7 +109,7 @@ var AutoSaveManager = class extends Subscribable {
133
109
  if (!this.storageAvailable) return null;
134
110
  const savedJson = localStorage.getItem(this.storageKey);
135
111
  if (!savedJson) return null;
136
- const savedData = parseSavedData(savedJson);
112
+ const savedData = decodeAutoSaveEnvelope(savedJson);
137
113
  if (!savedData) return null;
138
114
  if (isStale(savedData.savedAt, this.maxAge)) {
139
115
  this.clear();
@@ -179,7 +155,7 @@ var AutoSaveManager = class extends Subscribable {
179
155
  destroy() {
180
156
  this.stopTimers();
181
157
  if (this.isEnabled && this.currentDocument && this.storageAvailable) try {
182
- this.persistToStorage(this.currentDocument);
158
+ this.persistToStorage(serializeForStorage(this.currentDocument));
183
159
  } catch (error) {
184
160
  this.onErrorCallback?.(error instanceof Error ? error : new Error(String(error)));
185
161
  }
@@ -194,15 +170,7 @@ var AutoSaveManager = class extends Subscribable {
194
170
  }
195
171
  }
196
172
  persistToStorage(document) {
197
- const dataToSave = {
198
- document: {
199
- ...document,
200
- originalBuffer: null
201
- },
202
- savedAt: (/* @__PURE__ */ new Date()).toISOString(),
203
- version: SAVE_VERSION
204
- };
205
- localStorage.setItem(this.storageKey, JSON.stringify(dataToSave));
173
+ localStorage.setItem(this.storageKey, encodeAutoSaveEnvelopeFromDocument(document, (/* @__PURE__ */ new Date()).toISOString()));
206
174
  }
207
175
  debounceSave() {
208
176
  if (this.debounceTimer) clearTimeout(this.debounceTimer);
@@ -1,7 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { TableAction, TableContext } from "../utils/tableOperations.js";
2
3
  import { Subscribable } from "./Subscribable.js";
3
4
  import { CellCoordinates } from "./types.js";
4
- import { TableAction, TableContext } from "../utils/tableOperations.js";
5
5
  //#region src/managers/TableSelectionManager.d.ts
6
6
  /** Data attributes for table elements in the rendered DOM */
7
7
  declare const TABLE_DATA_ATTRIBUTES: {
@@ -0,0 +1,17 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ //#region src/managers/autoSaveCodec.d.ts
3
+ declare const AUTOSAVE_FORMAT_VERSION = 2;
4
+ type DecodedAutoSave = {
5
+ document: document_d_exports.Document;
6
+ savedAt: string;
7
+ version: typeof AUTOSAVE_FORMAT_VERSION;
8
+ };
9
+ type EncodedAutoSaveDocument = {
10
+ json: string;
11
+ };
12
+ declare const encodeAutoSaveDocument: (document: document_d_exports.Document) => EncodedAutoSaveDocument;
13
+ declare const encodeAutoSaveEnvelopeFromDocument: (document: EncodedAutoSaveDocument, savedAt: string) => string;
14
+ declare const encodeAutoSaveEnvelope: (document: document_d_exports.Document, savedAt: string) => string;
15
+ declare const decodeAutoSaveEnvelope: (json: string) => DecodedAutoSave | null;
16
+ //#endregion
17
+ export { AUTOSAVE_FORMAT_VERSION, DecodedAutoSave, EncodedAutoSaveDocument, decodeAutoSaveEnvelope, encodeAutoSaveDocument, encodeAutoSaveEnvelope, encodeAutoSaveEnvelopeFromDocument };
@@ -0,0 +1,109 @@
1
+ import { validateFolioDocumentModel } from "../docx/modelValidation.js";
2
+ //#region src/managers/autoSaveCodec.ts
3
+ const AUTOSAVE_FORMAT_VERSION = 2;
4
+ const BASE64_CHUNK_BYTES = 32768;
5
+ const encodeBase64 = (buffer) => {
6
+ const bytes = new Uint8Array(buffer);
7
+ const chunks = [];
8
+ for (let offset = 0; offset < bytes.length; offset += BASE64_CHUNK_BYTES) chunks.push(String.fromCharCode(...bytes.subarray(offset, offset + BASE64_CHUNK_BYTES)));
9
+ return btoa(chunks.join(""));
10
+ };
11
+ const decodeBase64 = (value) => {
12
+ const binary = atob(value);
13
+ const bytes = new Uint8Array(binary.length);
14
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
15
+ return bytes.buffer;
16
+ };
17
+ const encodeValue = (value) => {
18
+ if (value === void 0) return { type: "undefined" };
19
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
20
+ if (value instanceof Date) return {
21
+ type: "date",
22
+ value: value.toISOString()
23
+ };
24
+ if (value instanceof ArrayBuffer) return {
25
+ type: "arrayBuffer",
26
+ value: encodeBase64(value)
27
+ };
28
+ if (value instanceof Map) return {
29
+ type: "map",
30
+ value: [...value.entries()].map(([key, entry]) => [encodeValue(key), encodeValue(entry)])
31
+ };
32
+ if (Array.isArray(value)) return {
33
+ type: "array",
34
+ value: value.map(encodeValue)
35
+ };
36
+ if (typeof value === "object") {
37
+ const encoded = {};
38
+ for (const [key, entry] of Object.entries(value)) encoded[key] = encodeValue(entry);
39
+ return {
40
+ type: "object",
41
+ value: encoded
42
+ };
43
+ }
44
+ throw new TypeError(`Unsupported auto-save value: ${typeof value}`);
45
+ };
46
+ const decodeValue = (value) => {
47
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
48
+ if (typeof value !== "object" || !value || !("type" in value)) throw new TypeError("Invalid auto-save encoded value");
49
+ const type = value.type;
50
+ if (type === "undefined") return;
51
+ if (type === "date") {
52
+ if (!("value" in value) || typeof value.value !== "string" || !Number.isFinite(Date.parse(value.value))) throw new TypeError("Invalid auto-save date");
53
+ return new Date(value.value);
54
+ }
55
+ if (type === "arrayBuffer") {
56
+ if (!("value" in value) || typeof value.value !== "string") throw new TypeError("Invalid auto-save binary data");
57
+ return decodeBase64(value.value);
58
+ }
59
+ if (type === "array") {
60
+ if (!("value" in value) || !Array.isArray(value.value)) throw new TypeError("Invalid auto-save array");
61
+ return value.value.map(decodeValue);
62
+ }
63
+ if (type === "map") {
64
+ if (!("value" in value) || !Array.isArray(value.value)) throw new TypeError("Invalid auto-save map");
65
+ return new Map(value.value.map((entry) => {
66
+ if (!Array.isArray(entry) || entry.length !== 2) throw new TypeError("Invalid auto-save map entry");
67
+ return [decodeValue(entry[0]), decodeValue(entry[1])];
68
+ }));
69
+ }
70
+ if (type === "object") {
71
+ if (!("value" in value) || typeof value.value !== "object" || !value.value) throw new TypeError("Invalid auto-save object");
72
+ const decoded = Object.create(null);
73
+ for (const [key, entry] of Object.entries(value.value)) {
74
+ if (key === "__proto__" || key === "constructor" || key === "prototype") throw new TypeError("Invalid auto-save object key");
75
+ const decodedEntry = decodeValue(entry);
76
+ if (decodedEntry !== void 0) decoded[key] = decodedEntry;
77
+ }
78
+ return decoded;
79
+ }
80
+ throw new TypeError("Unknown auto-save encoded value");
81
+ };
82
+ const encodeAutoSaveDocument = (document) => {
83
+ const snapshot = {
84
+ ...document,
85
+ originalBuffer: null
86
+ };
87
+ return { json: JSON.stringify(encodeValue(snapshot)) };
88
+ };
89
+ const encodeAutoSaveEnvelopeFromDocument = (document, savedAt) => `{"document":${document.json},"savedAt":${JSON.stringify(savedAt)},"version":2}`;
90
+ const encodeAutoSaveEnvelope = (document, savedAt) => encodeAutoSaveEnvelopeFromDocument(encodeAutoSaveDocument(document), savedAt);
91
+ const decodeAutoSaveEnvelope = (json) => {
92
+ try {
93
+ const parsed = JSON.parse(json);
94
+ if (typeof parsed !== "object" || !parsed || !("document" in parsed) || !("savedAt" in parsed) || !("version" in parsed)) return null;
95
+ if (parsed.version !== 2 || typeof parsed.savedAt !== "string" || !Number.isFinite(Date.parse(parsed.savedAt))) return null;
96
+ const document = decodeValue(parsed.document);
97
+ if (typeof document !== "object" || !document) return null;
98
+ if (!validateFolioDocumentModel(document).valid) return null;
99
+ return {
100
+ document,
101
+ savedAt: parsed.savedAt,
102
+ version: 2
103
+ };
104
+ } catch {
105
+ return null;
106
+ }
107
+ };
108
+ //#endregion
109
+ export { AUTOSAVE_FORMAT_VERSION, decodeAutoSaveEnvelope, encodeAutoSaveDocument, encodeAutoSaveEnvelope, encodeAutoSaveEnvelopeFromDocument };