@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.
- package/dist/ai-edits/apply.d.ts +1 -1
- package/dist/document-operations.d.ts +4 -4
- package/dist/document-operations.js +17 -1
- package/dist/docx/rezip.d.ts +1 -1
- package/dist/docx/rezip.js +12 -31
- package/dist/layout-bridge/convert/toFlowBlocks.js +35 -61
- package/dist/layout-painter/imageLayout.d.ts +1 -1
- package/dist/managers/AutoSaveManager.js +8 -40
- package/dist/managers/TableSelectionManager.d.ts +1 -1
- package/dist/managers/autoSaveCodec.d.ts +17 -0
- package/dist/managers/autoSaveCodec.js +109 -0
- package/dist/prosemirror/attrs/index.js +273 -25
- package/dist/prosemirror/commands/comments.js +3 -3
- package/dist/prosemirror/commands/formatting.js +19 -19
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/paragraph.js +32 -32
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +4 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +8 -8
- package/dist/prosemirror/commands/table.d.ts +10 -52
- package/dist/prosemirror/commands/table.js +34 -34
- package/dist/prosemirror/conversion/fromProseDoc.js +20 -17
- package/dist/prosemirror/conversion/sdtAttrs.d.ts +2 -1
- package/dist/prosemirror/conversion/sdtAttrs.js +9 -4
- package/dist/prosemirror/extensions/ExtensionManager.d.ts +6 -3
- package/dist/prosemirror/extensions/ExtensionManager.js +5 -3
- package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/features/ListExtension.js +4 -4
- package/dist/prosemirror/extensions/nodes/ShapeExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/types.d.ts +134 -1
- package/dist/prosemirror/index.d.ts +2 -2
- package/dist/prosemirror/plugins/selectionTracker.js +25 -99
- package/dist/prosemirror/revisionCarriers.js +3 -1
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/index.js +87 -0
- package/dist/prosemirror/schema/nodes.d.ts +13 -2
- package/dist/prosemirror/selectionState.d.ts +11 -5
- package/dist/prosemirror/selectionState.js +94 -57
- package/dist/utils/tableOperations.d.ts +7 -6
- package/package.json +3 -3
package/dist/ai-edits/apply.d.ts
CHANGED
|
@@ -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
|
-
|
|
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",
|
package/dist/docx/rezip.d.ts
CHANGED
|
@@ -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
|
|
148
|
+
declare const validateDocx: (buffer: ArrayBuffer) => Promise<{
|
|
149
149
|
valid: boolean;
|
|
150
150
|
errors: string[];
|
|
151
151
|
warnings: string[];
|
package/dist/docx/rezip.js
CHANGED
|
@@ -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
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
670
|
+
const numPr = previousFormatting.numPr;
|
|
696
671
|
if (isListNumPr(numPr)) attrs.numPr = numPr;
|
|
697
|
-
const listIsBullet = previousFormatting
|
|
698
|
-
if (
|
|
699
|
-
const listIsLegal = previousFormatting
|
|
700
|
-
if (
|
|
701
|
-
const listMarker = previousFormatting
|
|
702
|
-
if (
|
|
703
|
-
const listNumFmt = previousFormatting
|
|
704
|
-
if (
|
|
705
|
-
const listLevelNumFmts =
|
|
706
|
-
if (listLevelNumFmts) attrs.listLevelNumFmts = listLevelNumFmts;
|
|
707
|
-
const listLevelStarts = previousFormatting
|
|
708
|
-
if (
|
|
709
|
-
const listAbstractNumId = previousFormatting
|
|
710
|
-
if (
|
|
711
|
-
const listStartOverride = previousFormatting
|
|
712
|
-
if (
|
|
713
|
-
const listMarkerHidden = previousFormatting
|
|
714
|
-
if (
|
|
715
|
-
const listMarkerFontFamily = previousFormatting
|
|
716
|
-
if (
|
|
717
|
-
const listMarkerFontSize = previousFormatting
|
|
718
|
-
if (
|
|
719
|
-
const listMarkerBold = previousFormatting
|
|
720
|
-
if (
|
|
721
|
-
const listMarkerAlignment =
|
|
722
|
-
if (listMarkerAlignment) attrs.listMarkerAlignment = listMarkerAlignment;
|
|
723
|
-
const 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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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 };
|