@harbour-enterprises/superdoc 2.0.0-next.22 → 2.0.0-next.23
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/chunks/{PdfViewer-F9XQJkng.cjs → PdfViewer-BPh-D_Hz.cjs} +2 -2
- package/dist/chunks/{PdfViewer-CGUK5UvE.es.js → PdfViewer-CBOz7W4_.es.js} +2 -2
- package/dist/chunks/{SuperConverter-BgQ_7WLE.cjs → SuperConverter-H5PfOKjB.cjs} +1 -1
- package/dist/chunks/{SuperConverter-94ypJzq2.es.js → SuperConverter-tJ_8LYGZ.es.js} +1 -1
- package/dist/chunks/{index-BgvzOicm.cjs → index-BZQlqpzE.cjs} +5 -5
- package/dist/chunks/{index-BYTMVIbP.cjs → index-BihcZqMS.cjs} +672 -20
- package/dist/chunks/{index-YkVH-fFY.es.js → index-CJc-jUP_.es.js} +672 -20
- package/dist/chunks/{index-BpXSabgi.es.js → index-DKYzdefu.es.js} +5 -5
- package/dist/super-editor/converter.cjs +1 -1
- package/dist/super-editor/converter.es.js +1 -1
- package/dist/super-editor.cjs +2 -2
- package/dist/super-editor.es.js +2 -2
- package/dist/superdoc.cjs +3 -3
- package/dist/superdoc.es.js +3 -3
- package/dist/superdoc.umd.js +690 -37
- package/dist/superdoc.umd.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const jszip = require("./jszip-C8_CqJxM.cjs");
|
|
3
3
|
const helpers$1 = require("./helpers-nOdwpmwb.cjs");
|
|
4
|
-
const superEditor_converter = require("./SuperConverter-
|
|
5
|
-
require("./jszip.min-BPh2MMAa.cjs");
|
|
4
|
+
const superEditor_converter = require("./SuperConverter-H5PfOKjB.cjs");
|
|
6
5
|
const vue = require("./vue-De9wkgLl.cjs");
|
|
6
|
+
require("./jszip.min-BPh2MMAa.cjs");
|
|
7
7
|
const eventemitter3 = require("./eventemitter3-BQuRcMPI.cjs");
|
|
8
8
|
const uuid = require("./uuid-R7L08bOx.cjs");
|
|
9
9
|
const blankDocx = require("./blank-docx-DfW3Eeh2.cjs");
|
|
@@ -9053,6 +9053,37 @@ class OxmlNode extends Node$1 {
|
|
|
9053
9053
|
return new OxmlNode(config);
|
|
9054
9054
|
}
|
|
9055
9055
|
}
|
|
9056
|
+
class EditorError extends Error {
|
|
9057
|
+
constructor(message) {
|
|
9058
|
+
super(message);
|
|
9059
|
+
this.name = "EditorError";
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
class InvalidStateError extends EditorError {
|
|
9063
|
+
constructor(message) {
|
|
9064
|
+
super(message);
|
|
9065
|
+
this.name = "InvalidStateError";
|
|
9066
|
+
}
|
|
9067
|
+
}
|
|
9068
|
+
class NoSourcePathError extends EditorError {
|
|
9069
|
+
constructor(message) {
|
|
9070
|
+
super(message);
|
|
9071
|
+
this.name = "NoSourcePathError";
|
|
9072
|
+
}
|
|
9073
|
+
}
|
|
9074
|
+
class FileSystemNotAvailableError extends EditorError {
|
|
9075
|
+
constructor(message) {
|
|
9076
|
+
super(message);
|
|
9077
|
+
this.name = "FileSystemNotAvailableError";
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
class DocumentLoadError extends EditorError {
|
|
9081
|
+
constructor(message, cause) {
|
|
9082
|
+
super(message);
|
|
9083
|
+
this.name = "DocumentLoadError";
|
|
9084
|
+
this.cause = cause;
|
|
9085
|
+
}
|
|
9086
|
+
}
|
|
9056
9087
|
const first = (commands2) => (props) => {
|
|
9057
9088
|
const items = typeof commands2 === "function" ? commands2(props) : commands2;
|
|
9058
9089
|
for (let i = 0; i < items.length; i += 1) {
|
|
@@ -14884,7 +14915,7 @@ const canUseDOM = () => {
|
|
|
14884
14915
|
return false;
|
|
14885
14916
|
}
|
|
14886
14917
|
};
|
|
14887
|
-
const summaryVersion = "2.0.0-next.
|
|
14918
|
+
const summaryVersion = "2.0.0-next.23";
|
|
14888
14919
|
const nodeKeys = ["group", "content", "marks", "inline", "atom", "defining", "code", "tableRole", "summary"];
|
|
14889
14920
|
const markKeys = ["group", "inclusive", "excludes", "spanning", "code"];
|
|
14890
14921
|
function mapAttributes(attrs) {
|
|
@@ -15368,14 +15399,37 @@ class ProseMirrorRenderer {
|
|
|
15368
15399
|
}
|
|
15369
15400
|
class Editor extends EventEmitter {
|
|
15370
15401
|
/**
|
|
15371
|
-
* Create a new Editor instance
|
|
15402
|
+
* Create a new Editor instance.
|
|
15403
|
+
*
|
|
15404
|
+
* **Legacy mode (backward compatible):**
|
|
15405
|
+
* When `content` or `fileSource` is provided, the editor initializes synchronously
|
|
15406
|
+
* with the document loaded immediately. This preserves existing behavior where
|
|
15407
|
+
* `editor.view` is available right after construction.
|
|
15408
|
+
*
|
|
15409
|
+
* **New mode (document lifecycle API):**
|
|
15410
|
+
* When no `content` or `fileSource` is provided, only core services (extensions,
|
|
15411
|
+
* commands, schema) are initialized. Call `editor.open()` to load a document.
|
|
15412
|
+
*
|
|
15372
15413
|
* @param options - Editor configuration options
|
|
15414
|
+
*
|
|
15415
|
+
* @example
|
|
15416
|
+
* ```typescript
|
|
15417
|
+
* // Legacy mode (still works)
|
|
15418
|
+
* const editor = new Editor({ content: docx, element: el });
|
|
15419
|
+
* console.log(editor.view.state.doc); // Works immediately
|
|
15420
|
+
*
|
|
15421
|
+
* // New mode
|
|
15422
|
+
* const editor = new Editor({ element: el });
|
|
15423
|
+
* await editor.open('/path/to/doc.docx');
|
|
15424
|
+
* ```
|
|
15373
15425
|
*/
|
|
15374
15426
|
constructor(options) {
|
|
15375
15427
|
super();
|
|
15376
15428
|
this.extensionStorage = {};
|
|
15377
15429
|
this.#renderer = null;
|
|
15378
15430
|
this.#isDestroyed = false;
|
|
15431
|
+
this.#editorLifecycleState = "initialized";
|
|
15432
|
+
this.#sourcePath = null;
|
|
15379
15433
|
this.presentationEditor = null;
|
|
15380
15434
|
this.isFocused = false;
|
|
15381
15435
|
this.fontsImported = [];
|
|
@@ -15488,18 +15542,25 @@ class Editor extends EventEmitter {
|
|
|
15488
15542
|
this.#checkHeadless(resolvedOptions);
|
|
15489
15543
|
this.setOptions(resolvedOptions);
|
|
15490
15544
|
this.#renderer = resolvedOptions.renderer ?? (domAvailable ? new ProseMirrorRenderer() : null);
|
|
15491
|
-
const modes = {
|
|
15492
|
-
docx: () => this.#init(),
|
|
15493
|
-
text: () => this.#initRichText(),
|
|
15494
|
-
html: () => this.#initRichText(),
|
|
15495
|
-
default: () => {
|
|
15496
|
-
console.log("Not implemented.");
|
|
15497
|
-
}
|
|
15498
|
-
};
|
|
15499
|
-
const initMode = modes[this.options.mode] ?? modes.default;
|
|
15500
15545
|
const { setHighContrastMode } = useHighContrastMode();
|
|
15501
15546
|
this.setHighContrastMode = setHighContrastMode;
|
|
15502
|
-
|
|
15547
|
+
const useNewApiMode = resolvedOptions.deferDocumentLoad === true;
|
|
15548
|
+
if (useNewApiMode) {
|
|
15549
|
+
this.#initCore();
|
|
15550
|
+
this.#editorLifecycleState = "initialized";
|
|
15551
|
+
} else {
|
|
15552
|
+
const modes = {
|
|
15553
|
+
docx: () => this.#init(),
|
|
15554
|
+
text: () => this.#initRichText(),
|
|
15555
|
+
html: () => this.#initRichText(),
|
|
15556
|
+
default: () => {
|
|
15557
|
+
console.log("Not implemented.");
|
|
15558
|
+
}
|
|
15559
|
+
};
|
|
15560
|
+
const initMode = modes[this.options.mode] ?? modes.default;
|
|
15561
|
+
initMode();
|
|
15562
|
+
this.#editorLifecycleState = "ready";
|
|
15563
|
+
}
|
|
15503
15564
|
}
|
|
15504
15565
|
/**
|
|
15505
15566
|
* Command service for handling editor commands
|
|
@@ -15507,6 +15568,8 @@ class Editor extends EventEmitter {
|
|
|
15507
15568
|
#commandService;
|
|
15508
15569
|
#renderer;
|
|
15509
15570
|
#isDestroyed;
|
|
15571
|
+
#editorLifecycleState;
|
|
15572
|
+
#sourcePath;
|
|
15510
15573
|
/**
|
|
15511
15574
|
* Getter which indicates if any changes happen in Editor
|
|
15512
15575
|
*/
|
|
@@ -15531,6 +15594,303 @@ class Editor extends EventEmitter {
|
|
|
15531
15594
|
this.emit("create", { editor: this });
|
|
15532
15595
|
}, 0);
|
|
15533
15596
|
}
|
|
15597
|
+
/**
|
|
15598
|
+
* Assert that the editor is in one of the allowed states.
|
|
15599
|
+
* Throws InvalidStateError if not.
|
|
15600
|
+
*/
|
|
15601
|
+
#assertState(...allowed) {
|
|
15602
|
+
if (!allowed.includes(this.#editorLifecycleState)) {
|
|
15603
|
+
throw new InvalidStateError(
|
|
15604
|
+
`Invalid operation: editor is in '${this.#editorLifecycleState}' state, expected one of: ${allowed.join(", ")}`
|
|
15605
|
+
);
|
|
15606
|
+
}
|
|
15607
|
+
}
|
|
15608
|
+
/**
|
|
15609
|
+
* Wraps an async operation with state transitions for safe lifecycle management.
|
|
15610
|
+
*
|
|
15611
|
+
* This method ensures atomic state transitions during async operations:
|
|
15612
|
+
* 1. Sets state to `during` before executing the operation
|
|
15613
|
+
* 2. On success: sets state to `success` and returns the operation result
|
|
15614
|
+
* 3. On error: sets state to `failure` and re-throws the error
|
|
15615
|
+
*
|
|
15616
|
+
* This prevents race conditions and ensures the editor is always in a valid state,
|
|
15617
|
+
* even when operations fail.
|
|
15618
|
+
*
|
|
15619
|
+
* @template T - The return type of the operation
|
|
15620
|
+
* @param during - State to set while the operation is running
|
|
15621
|
+
* @param success - State to set if the operation succeeds
|
|
15622
|
+
* @param failure - State to set if the operation fails
|
|
15623
|
+
* @param operation - Async operation to execute
|
|
15624
|
+
* @returns Promise resolving to the operation's return value
|
|
15625
|
+
* @throws Re-throws any error from the operation after setting failure state
|
|
15626
|
+
*
|
|
15627
|
+
* @example
|
|
15628
|
+
* ```typescript
|
|
15629
|
+
* // Used internally for save operations:
|
|
15630
|
+
* await this.#withState('saving', 'ready', 'ready', async () => {
|
|
15631
|
+
* const data = await this.exportDocument();
|
|
15632
|
+
* await this.#writeToPath(path, data);
|
|
15633
|
+
* });
|
|
15634
|
+
* ```
|
|
15635
|
+
*/
|
|
15636
|
+
async #withState(during, success, failure, operation) {
|
|
15637
|
+
this.#editorLifecycleState = during;
|
|
15638
|
+
try {
|
|
15639
|
+
const result = await operation();
|
|
15640
|
+
this.#editorLifecycleState = success;
|
|
15641
|
+
return result;
|
|
15642
|
+
} catch (error) {
|
|
15643
|
+
this.#editorLifecycleState = failure;
|
|
15644
|
+
throw error;
|
|
15645
|
+
}
|
|
15646
|
+
}
|
|
15647
|
+
/**
|
|
15648
|
+
* Initialize core editor services for new lifecycle API mode.
|
|
15649
|
+
*
|
|
15650
|
+
* When `deferDocumentLoad: true` is set, this method initializes only the
|
|
15651
|
+
* document-independent components:
|
|
15652
|
+
* - Extension service (loads and configures all extensions)
|
|
15653
|
+
* - Command service (registers all editor commands)
|
|
15654
|
+
* - ProseMirror schema (derived from extensions, reusable across documents)
|
|
15655
|
+
*
|
|
15656
|
+
* These services are created once during construction and reused when opening
|
|
15657
|
+
* different documents via the `open()` method. This enables efficient document
|
|
15658
|
+
* switching without recreating the entire editor infrastructure.
|
|
15659
|
+
*
|
|
15660
|
+
* Called exclusively from the constructor when `deferDocumentLoad` is true.
|
|
15661
|
+
*
|
|
15662
|
+
* @remarks
|
|
15663
|
+
* This is part of the new lifecycle API that separates editor initialization
|
|
15664
|
+
* from document loading. The schema and extensions remain constant while
|
|
15665
|
+
* documents can be opened, closed, and reopened.
|
|
15666
|
+
*
|
|
15667
|
+
* @see #loadDocument - Loads document-specific state after core initialization
|
|
15668
|
+
*/
|
|
15669
|
+
#initCore() {
|
|
15670
|
+
if (!this.options.extensions?.length) {
|
|
15671
|
+
this.options.extensions = this.options.mode === "docx" ? getStarterExtensions() : getRichTextExtensions();
|
|
15672
|
+
}
|
|
15673
|
+
this.#createExtensionService();
|
|
15674
|
+
this.#createCommandService();
|
|
15675
|
+
this.#createSchema();
|
|
15676
|
+
this.#registerEventListeners();
|
|
15677
|
+
}
|
|
15678
|
+
/**
|
|
15679
|
+
* Register all event listeners from options.
|
|
15680
|
+
*
|
|
15681
|
+
* Called once during core initialization. These listeners persist across
|
|
15682
|
+
* document open/close cycles since the callbacks are set at construction time.
|
|
15683
|
+
*/
|
|
15684
|
+
#registerEventListeners() {
|
|
15685
|
+
this.on("create", this.options.onCreate);
|
|
15686
|
+
this.on("update", this.options.onUpdate);
|
|
15687
|
+
this.on("selectionUpdate", this.options.onSelectionUpdate);
|
|
15688
|
+
this.on("transaction", this.options.onTransaction);
|
|
15689
|
+
this.on("focus", this.#onFocus.bind(this));
|
|
15690
|
+
this.on("blur", this.options.onBlur);
|
|
15691
|
+
this.on("destroy", this.options.onDestroy);
|
|
15692
|
+
this.on("trackedChangesUpdate", this.options.onTrackedChangesUpdate);
|
|
15693
|
+
this.on("commentsLoaded", this.options.onCommentsLoaded);
|
|
15694
|
+
this.on("commentClick", this.options.onCommentClicked);
|
|
15695
|
+
this.on("commentsUpdate", this.options.onCommentsUpdate);
|
|
15696
|
+
this.on("locked", this.options.onDocumentLocked);
|
|
15697
|
+
this.on("collaborationReady", this.#onCollaborationReady.bind(this));
|
|
15698
|
+
this.on("comment-positions", this.options.onCommentLocationsUpdate);
|
|
15699
|
+
this.on("list-definitions-change", this.options.onListDefinitionsChange);
|
|
15700
|
+
this.on("fonts-resolved", this.options.onFontsResolved);
|
|
15701
|
+
this.on("exception", this.options.onException);
|
|
15702
|
+
}
|
|
15703
|
+
/**
|
|
15704
|
+
* Load a document into the editor from various source types.
|
|
15705
|
+
*
|
|
15706
|
+
* This method handles the complete document loading pipeline:
|
|
15707
|
+
* 1. **Source resolution**: Determines source type (path/File/Blob/Buffer/blank)
|
|
15708
|
+
* 2. **Content loading**:
|
|
15709
|
+
* - String path: Reads file from disk (Node.js) or fetches URL (browser)
|
|
15710
|
+
* - File/Blob: Extracts docx archive data
|
|
15711
|
+
* - Buffer: Processes binary data (Node.js)
|
|
15712
|
+
* - undefined/null: Creates blank document
|
|
15713
|
+
* 3. **Document initialization**: Creates converter, media, fonts, initial state
|
|
15714
|
+
* 4. **View mounting**: Attaches ProseMirror view (unless headless)
|
|
15715
|
+
* 5. **Event wiring**: Connects all lifecycle event handlers
|
|
15716
|
+
*
|
|
15717
|
+
* Called by `open()` after state validation, wrapped in `#withState()` for
|
|
15718
|
+
* atomic state transitions.
|
|
15719
|
+
*
|
|
15720
|
+
* @param source - Document source:
|
|
15721
|
+
* - `string`: File path (Node.js reads from disk, browser fetches as URL)
|
|
15722
|
+
* - `File | Blob`: Browser file object or blob
|
|
15723
|
+
* - `Buffer`: Node.js buffer containing docx data
|
|
15724
|
+
* - `undefined | null`: Creates a blank document
|
|
15725
|
+
* @param options - Document-level options (mode, comments, styles, etc.)
|
|
15726
|
+
* @returns Promise that resolves when document is fully loaded and ready
|
|
15727
|
+
* @throws {DocumentLoadError} If any step of document loading fails. The error
|
|
15728
|
+
* wraps the underlying cause for debugging.
|
|
15729
|
+
*
|
|
15730
|
+
* @remarks
|
|
15731
|
+
* - Sets `#sourcePath` for path-based sources (enables `save()`)
|
|
15732
|
+
* - Sets `#sourcePath = null` for Blob/Buffer sources (requires `saveTo()`)
|
|
15733
|
+
* - In browser, string paths are treated as URLs to fetch
|
|
15734
|
+
* - In Node.js, string paths are read from the filesystem
|
|
15735
|
+
*
|
|
15736
|
+
* @see open - Public API that calls this method
|
|
15737
|
+
* @see #unloadDocument - Cleanup counterpart that reverses this process
|
|
15738
|
+
*/
|
|
15739
|
+
async #loadDocument(source, options) {
|
|
15740
|
+
try {
|
|
15741
|
+
const resolvedMode = options?.mode ?? this.options.mode ?? "docx";
|
|
15742
|
+
const resolvedOptions = {
|
|
15743
|
+
...this.options,
|
|
15744
|
+
mode: resolvedMode,
|
|
15745
|
+
isCommentsEnabled: options?.isCommentsEnabled ?? this.options.isCommentsEnabled,
|
|
15746
|
+
suppressDefaultDocxStyles: options?.suppressDefaultDocxStyles ?? this.options.suppressDefaultDocxStyles,
|
|
15747
|
+
documentMode: options?.documentMode ?? this.options.documentMode ?? "editing",
|
|
15748
|
+
html: options?.html,
|
|
15749
|
+
markdown: options?.markdown,
|
|
15750
|
+
jsonOverride: options?.json ?? null
|
|
15751
|
+
};
|
|
15752
|
+
if (typeof source === "string") {
|
|
15753
|
+
if (typeof vue.process$1 !== "undefined" && vue.process$1.versions?.node) {
|
|
15754
|
+
const fs = require("fs");
|
|
15755
|
+
const buffer = fs.readFileSync(source);
|
|
15756
|
+
const [docx, _media, mediaFiles, fonts] = await Editor.loadXmlData(buffer, true);
|
|
15757
|
+
resolvedOptions.content = docx;
|
|
15758
|
+
resolvedOptions.mediaFiles = mediaFiles;
|
|
15759
|
+
resolvedOptions.fonts = fonts;
|
|
15760
|
+
resolvedOptions.fileSource = buffer;
|
|
15761
|
+
this.#sourcePath = source;
|
|
15762
|
+
} else {
|
|
15763
|
+
const response = await fetch(source);
|
|
15764
|
+
const blob = await response.blob();
|
|
15765
|
+
const [docx, _media, mediaFiles, fonts] = await Editor.loadXmlData(blob);
|
|
15766
|
+
resolvedOptions.content = docx;
|
|
15767
|
+
resolvedOptions.mediaFiles = mediaFiles;
|
|
15768
|
+
resolvedOptions.fonts = fonts;
|
|
15769
|
+
resolvedOptions.fileSource = blob;
|
|
15770
|
+
this.#sourcePath = source.split("/").pop() || null;
|
|
15771
|
+
}
|
|
15772
|
+
} else if (source != null && typeof source === "object") {
|
|
15773
|
+
const isNodeBuffer = typeof jszip.Buffer !== "undefined" && (jszip.Buffer.isBuffer(source) || source instanceof jszip.Buffer);
|
|
15774
|
+
const isBlob = typeof Blob !== "undefined" && source instanceof Blob;
|
|
15775
|
+
const isArrayBuffer = source instanceof ArrayBuffer;
|
|
15776
|
+
const hasArrayBuffer = typeof source === "object" && "buffer" in source && source.buffer instanceof ArrayBuffer;
|
|
15777
|
+
if (isNodeBuffer || isBlob || isArrayBuffer || hasArrayBuffer) {
|
|
15778
|
+
const [docx, _media, mediaFiles, fonts] = await Editor.loadXmlData(
|
|
15779
|
+
source,
|
|
15780
|
+
isNodeBuffer
|
|
15781
|
+
);
|
|
15782
|
+
resolvedOptions.content = docx;
|
|
15783
|
+
resolvedOptions.mediaFiles = mediaFiles;
|
|
15784
|
+
resolvedOptions.fonts = fonts;
|
|
15785
|
+
resolvedOptions.fileSource = source;
|
|
15786
|
+
this.#sourcePath = null;
|
|
15787
|
+
} else {
|
|
15788
|
+
const [docx, _media, mediaFiles, fonts] = await Editor.loadXmlData(source, false);
|
|
15789
|
+
resolvedOptions.content = docx;
|
|
15790
|
+
resolvedOptions.mediaFiles = mediaFiles;
|
|
15791
|
+
resolvedOptions.fonts = fonts;
|
|
15792
|
+
resolvedOptions.fileSource = source;
|
|
15793
|
+
this.#sourcePath = null;
|
|
15794
|
+
}
|
|
15795
|
+
} else {
|
|
15796
|
+
resolvedOptions.content = options?.content ?? [];
|
|
15797
|
+
resolvedOptions.mediaFiles = options?.mediaFiles ?? {};
|
|
15798
|
+
resolvedOptions.fonts = options?.fonts ?? {};
|
|
15799
|
+
resolvedOptions.fileSource = null;
|
|
15800
|
+
resolvedOptions.isNewFile = !options?.content;
|
|
15801
|
+
this.#sourcePath = null;
|
|
15802
|
+
}
|
|
15803
|
+
this.setOptions(resolvedOptions);
|
|
15804
|
+
this.#createConverter();
|
|
15805
|
+
this.#initMedia();
|
|
15806
|
+
const shouldMountRenderer = this.#shouldMountRenderer();
|
|
15807
|
+
if (shouldMountRenderer) {
|
|
15808
|
+
this.#initContainerElement(this.options);
|
|
15809
|
+
this.#initFonts();
|
|
15810
|
+
}
|
|
15811
|
+
this.#createInitialState({ includePlugins: !shouldMountRenderer });
|
|
15812
|
+
if (!shouldMountRenderer) {
|
|
15813
|
+
const tr = this.state.tr.setMeta("forcePluginPass", true).setMeta("addToHistory", false);
|
|
15814
|
+
this.#dispatchTransaction(tr);
|
|
15815
|
+
}
|
|
15816
|
+
if (shouldMountRenderer) {
|
|
15817
|
+
this.mount(this.options.element);
|
|
15818
|
+
this.#configureStateWithExtensionPlugins();
|
|
15819
|
+
}
|
|
15820
|
+
if (!shouldMountRenderer) {
|
|
15821
|
+
this.#emitCreateAsync();
|
|
15822
|
+
}
|
|
15823
|
+
if (shouldMountRenderer) {
|
|
15824
|
+
this.initDefaultStyles();
|
|
15825
|
+
this.#checkFonts();
|
|
15826
|
+
}
|
|
15827
|
+
const shouldMigrateListsOnInit = Boolean(
|
|
15828
|
+
this.options.markdown || this.options.html || this.options.loadFromSchema || this.options.jsonOverride || this.options.mode === "html" || this.options.mode === "text"
|
|
15829
|
+
);
|
|
15830
|
+
if (shouldMigrateListsOnInit) {
|
|
15831
|
+
this.migrateListsToV2();
|
|
15832
|
+
}
|
|
15833
|
+
this.setDocumentMode(this.options.documentMode, "init");
|
|
15834
|
+
this.initializeCollaborationData();
|
|
15835
|
+
if (!this.options.ydoc && !this.options.isChildEditor) {
|
|
15836
|
+
this.#initComments();
|
|
15837
|
+
}
|
|
15838
|
+
if (shouldMountRenderer) {
|
|
15839
|
+
this.#initDevTools();
|
|
15840
|
+
this.#registerCopyHandler();
|
|
15841
|
+
}
|
|
15842
|
+
} catch (error) {
|
|
15843
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
15844
|
+
throw new DocumentLoadError(`Failed to load document: ${err.message}`, err);
|
|
15845
|
+
}
|
|
15846
|
+
}
|
|
15847
|
+
/**
|
|
15848
|
+
* Unload the current document and clean up all document-specific resources.
|
|
15849
|
+
*
|
|
15850
|
+
* This method performs a complete cleanup of document state while preserving
|
|
15851
|
+
* the core editor services (schema, extensions, commands) for reuse:
|
|
15852
|
+
*
|
|
15853
|
+
* **Resources cleaned up:**
|
|
15854
|
+
* - ProseMirror view (unmounted from DOM)
|
|
15855
|
+
* - Header/footer editors (destroyed)
|
|
15856
|
+
* - Document converter instance
|
|
15857
|
+
* - Media references and image storage
|
|
15858
|
+
* - Source path reference
|
|
15859
|
+
* - Document-specific options (content, fileSource, initialState)
|
|
15860
|
+
* - ProseMirror editor state
|
|
15861
|
+
*
|
|
15862
|
+
* **Resources preserved:**
|
|
15863
|
+
* - ProseMirror schema
|
|
15864
|
+
* - Extension service and registered extensions
|
|
15865
|
+
* - Command service and registered commands
|
|
15866
|
+
* - Event listeners (registered once during core init, reused across documents)
|
|
15867
|
+
*
|
|
15868
|
+
* After cleanup, the editor transitions to 'closed' state and can be reopened
|
|
15869
|
+
* with a new document via `open()`.
|
|
15870
|
+
*
|
|
15871
|
+
* Called by `close()` after emitting the `documentClose` event.
|
|
15872
|
+
*
|
|
15873
|
+
* @remarks
|
|
15874
|
+
* This is a critical part of the document lifecycle API that enables efficient
|
|
15875
|
+
* document switching. By preserving schema and extensions, we avoid expensive
|
|
15876
|
+
* reinitialization when opening multiple documents sequentially.
|
|
15877
|
+
*
|
|
15878
|
+
* @see close - Public API that calls this method
|
|
15879
|
+
* @see #loadDocument - Counterpart method that loads document resources
|
|
15880
|
+
*/
|
|
15881
|
+
#unloadDocument() {
|
|
15882
|
+
this.unmount();
|
|
15883
|
+
this.destroyHeaderFooterEditors();
|
|
15884
|
+
this.converter = void 0;
|
|
15885
|
+
if (this.storage.image) {
|
|
15886
|
+
this.storage.image.media = {};
|
|
15887
|
+
}
|
|
15888
|
+
this.#sourcePath = null;
|
|
15889
|
+
this.options.initialState = null;
|
|
15890
|
+
this.options.content = "";
|
|
15891
|
+
this.options.fileSource = null;
|
|
15892
|
+
this._state = void 0;
|
|
15893
|
+
}
|
|
15534
15894
|
/**
|
|
15535
15895
|
* Initialize the editor with the given options
|
|
15536
15896
|
*/
|
|
@@ -15695,6 +16055,25 @@ class Editor extends EventEmitter {
|
|
|
15695
16055
|
get state() {
|
|
15696
16056
|
return this._state;
|
|
15697
16057
|
}
|
|
16058
|
+
/**
|
|
16059
|
+
* Get the current editor lifecycle state.
|
|
16060
|
+
*
|
|
16061
|
+
* @returns The current lifecycle state ('initialized', 'documentLoading', 'ready', 'saving', 'closed', 'destroyed')
|
|
16062
|
+
*/
|
|
16063
|
+
get lifecycleState() {
|
|
16064
|
+
return this.#editorLifecycleState;
|
|
16065
|
+
}
|
|
16066
|
+
/**
|
|
16067
|
+
* Get the source path of the currently opened document.
|
|
16068
|
+
*
|
|
16069
|
+
* Returns the file path if the document was opened from a path (Node.js),
|
|
16070
|
+
* or null if opened from a Blob/Buffer or created as a blank document.
|
|
16071
|
+
*
|
|
16072
|
+
* In browsers, this is only a suggested filename, not an actual filesystem path.
|
|
16073
|
+
*/
|
|
16074
|
+
get sourcePath() {
|
|
16075
|
+
return this.#sourcePath;
|
|
16076
|
+
}
|
|
15698
16077
|
/**
|
|
15699
16078
|
* Replace the editor state entirely.
|
|
15700
16079
|
*
|
|
@@ -15792,19 +16171,19 @@ class Editor extends EventEmitter {
|
|
|
15792
16171
|
if (this.options.role === "viewer") cleanedMode = "viewing";
|
|
15793
16172
|
if (this.options.role === "suggester" && cleanedMode === "editing") cleanedMode = "suggesting";
|
|
15794
16173
|
if (cleanedMode === "viewing") {
|
|
15795
|
-
this.commands.toggleTrackChangesShowOriginal();
|
|
16174
|
+
this.commands.toggleTrackChangesShowOriginal?.();
|
|
15796
16175
|
this.setEditable(false, false);
|
|
15797
16176
|
this.setOptions({ documentMode: "viewing" });
|
|
15798
16177
|
if (pm) pm.classList.add("view-mode");
|
|
15799
16178
|
} else if (cleanedMode === "suggesting") {
|
|
15800
|
-
this.commands.disableTrackChangesShowOriginal();
|
|
15801
|
-
this.commands.enableTrackChanges();
|
|
16179
|
+
this.commands.disableTrackChangesShowOriginal?.();
|
|
16180
|
+
this.commands.enableTrackChanges?.();
|
|
15802
16181
|
this.setOptions({ documentMode: "suggesting" });
|
|
15803
16182
|
this.setEditable(true, false);
|
|
15804
16183
|
if (pm) pm.classList.remove("view-mode");
|
|
15805
16184
|
} else if (cleanedMode === "editing") {
|
|
15806
|
-
this.commands.disableTrackChangesShowOriginal();
|
|
15807
|
-
this.commands.disableTrackChanges();
|
|
16185
|
+
this.commands.disableTrackChangesShowOriginal?.();
|
|
16186
|
+
this.commands.disableTrackChanges?.();
|
|
15808
16187
|
this.setEditable(true, false);
|
|
15809
16188
|
this.setOptions({ documentMode: "editing" });
|
|
15810
16189
|
if (pm) pm.classList.remove("view-mode");
|
|
@@ -16852,16 +17231,289 @@ class Editor extends EventEmitter {
|
|
|
16852
17231
|
console.error(err);
|
|
16853
17232
|
}
|
|
16854
17233
|
}
|
|
17234
|
+
// ============================================================================
|
|
17235
|
+
// Document Lifecycle API
|
|
17236
|
+
// ============================================================================
|
|
17237
|
+
/**
|
|
17238
|
+
* Open a document in the editor.
|
|
17239
|
+
*
|
|
17240
|
+
* @param source - Document source:
|
|
17241
|
+
* - `string` - File path (Node.js reads from disk, browser fetches URL)
|
|
17242
|
+
* - `File | Blob` - Browser file object
|
|
17243
|
+
* - `Buffer` - Node.js buffer
|
|
17244
|
+
* - `undefined` - Creates a blank document
|
|
17245
|
+
* @param options - Document options (mode, comments, etc.)
|
|
17246
|
+
* @returns Promise that resolves when document is loaded
|
|
17247
|
+
*
|
|
17248
|
+
* @throws {InvalidStateError} If editor is not in 'initialized' or 'closed' state
|
|
17249
|
+
* @throws {DocumentLoadError} If document loading fails
|
|
17250
|
+
*
|
|
17251
|
+
* @example
|
|
17252
|
+
* ```typescript
|
|
17253
|
+
* const editor = new Editor({ element: myDiv });
|
|
17254
|
+
*
|
|
17255
|
+
* // Open from file path (Node.js)
|
|
17256
|
+
* await editor.open('/path/to/document.docx');
|
|
17257
|
+
*
|
|
17258
|
+
* // Open from File object (browser)
|
|
17259
|
+
* await editor.open(fileInput.files[0]);
|
|
17260
|
+
*
|
|
17261
|
+
* // Open blank document
|
|
17262
|
+
* await editor.open();
|
|
17263
|
+
*
|
|
17264
|
+
* // Open with options
|
|
17265
|
+
* await editor.open('/path/to/doc.docx', { isCommentsEnabled: true });
|
|
17266
|
+
* ```
|
|
17267
|
+
*/
|
|
17268
|
+
async open(source, options) {
|
|
17269
|
+
this.#assertState("initialized", "closed");
|
|
17270
|
+
await this.#withState("documentLoading", "ready", "closed", async () => {
|
|
17271
|
+
await this.#loadDocument(source, options);
|
|
17272
|
+
});
|
|
17273
|
+
this.emit("documentOpen", { editor: this, sourcePath: this.#sourcePath });
|
|
17274
|
+
}
|
|
17275
|
+
/**
|
|
17276
|
+
* Static factory method for one-liner document opening.
|
|
17277
|
+
* Creates an Editor instance and opens the document in one call.
|
|
17278
|
+
*
|
|
17279
|
+
* Smart defaults enable minimal configuration:
|
|
17280
|
+
* - No element/selector → headless mode
|
|
17281
|
+
* - No extensions → uses getStarterExtensions() for docx, getRichTextExtensions() for text/html
|
|
17282
|
+
* - No mode → defaults to 'docx'
|
|
17283
|
+
*
|
|
17284
|
+
* @param source - Document source (path, File, Blob, Buffer, or undefined for blank)
|
|
17285
|
+
* @param config - Combined editor and document options (all optional)
|
|
17286
|
+
* @returns Promise resolving to the ready Editor instance
|
|
17287
|
+
*
|
|
17288
|
+
* @example
|
|
17289
|
+
* ```typescript
|
|
17290
|
+
* // Minimal headless usage - just works!
|
|
17291
|
+
* const editor = await Editor.open('/path/to/doc.docx');
|
|
17292
|
+
*
|
|
17293
|
+
* // With options
|
|
17294
|
+
* const editor = await Editor.open('/path/to/doc.docx', {
|
|
17295
|
+
* isCommentsEnabled: true,
|
|
17296
|
+
* });
|
|
17297
|
+
*
|
|
17298
|
+
* // With UI element (automatically not headless)
|
|
17299
|
+
* const editor = await Editor.open('/path/to/doc.docx', {
|
|
17300
|
+
* element: document.getElementById('editor'),
|
|
17301
|
+
* });
|
|
17302
|
+
*
|
|
17303
|
+
* // Blank document
|
|
17304
|
+
* const editor = await Editor.open();
|
|
17305
|
+
* ```
|
|
17306
|
+
*/
|
|
17307
|
+
static async open(source, config) {
|
|
17308
|
+
const hasElement = config?.element != null || config?.selector != null;
|
|
17309
|
+
const resolvedConfig = {
|
|
17310
|
+
mode: "docx",
|
|
17311
|
+
isHeadless: !hasElement,
|
|
17312
|
+
...config
|
|
17313
|
+
};
|
|
17314
|
+
const {
|
|
17315
|
+
// OpenOptions (document-level)
|
|
17316
|
+
html,
|
|
17317
|
+
markdown,
|
|
17318
|
+
isCommentsEnabled,
|
|
17319
|
+
suppressDefaultDocxStyles,
|
|
17320
|
+
documentMode,
|
|
17321
|
+
content,
|
|
17322
|
+
mediaFiles,
|
|
17323
|
+
fonts,
|
|
17324
|
+
// Everything else is EditorOptions
|
|
17325
|
+
...editorConfig
|
|
17326
|
+
} = resolvedConfig;
|
|
17327
|
+
const openOptions = {
|
|
17328
|
+
mode: resolvedConfig.mode,
|
|
17329
|
+
html,
|
|
17330
|
+
markdown,
|
|
17331
|
+
isCommentsEnabled,
|
|
17332
|
+
suppressDefaultDocxStyles,
|
|
17333
|
+
documentMode,
|
|
17334
|
+
content,
|
|
17335
|
+
mediaFiles,
|
|
17336
|
+
fonts
|
|
17337
|
+
};
|
|
17338
|
+
const editor = new Editor({ ...editorConfig, deferDocumentLoad: true });
|
|
17339
|
+
await editor.open(source, openOptions);
|
|
17340
|
+
return editor;
|
|
17341
|
+
}
|
|
17342
|
+
/**
|
|
17343
|
+
* Close the current document.
|
|
17344
|
+
*
|
|
17345
|
+
* This unloads the document but keeps the editor instance alive.
|
|
17346
|
+
* The editor can be reused by calling `open()` again.
|
|
17347
|
+
*
|
|
17348
|
+
* This method is idempotent - calling it when already closed is a no-op.
|
|
17349
|
+
*
|
|
17350
|
+
* @example
|
|
17351
|
+
* ```typescript
|
|
17352
|
+
* await editor.open('/doc1.docx');
|
|
17353
|
+
* // ... work with document ...
|
|
17354
|
+
* editor.close();
|
|
17355
|
+
*
|
|
17356
|
+
* await editor.open('/doc2.docx'); // Reuse the same editor
|
|
17357
|
+
* ```
|
|
17358
|
+
*/
|
|
17359
|
+
close() {
|
|
17360
|
+
if (this.#editorLifecycleState === "closed" || this.#editorLifecycleState === "initialized") {
|
|
17361
|
+
return;
|
|
17362
|
+
}
|
|
17363
|
+
if (this.#editorLifecycleState === "destroyed") {
|
|
17364
|
+
return;
|
|
17365
|
+
}
|
|
17366
|
+
this.#assertState("ready");
|
|
17367
|
+
this.emit("documentClose", { editor: this });
|
|
17368
|
+
this.#unloadDocument();
|
|
17369
|
+
this.#editorLifecycleState = "closed";
|
|
17370
|
+
}
|
|
17371
|
+
/**
|
|
17372
|
+
* Save the document to the original source path.
|
|
17373
|
+
*
|
|
17374
|
+
* Only works if the document was opened from a file path.
|
|
17375
|
+
* If opened from Blob/Buffer or created blank, use `saveTo()` or `exportDocument()`.
|
|
17376
|
+
*
|
|
17377
|
+
* @param options - Save options (comments, final doc, etc.)
|
|
17378
|
+
* @throws {InvalidStateError} If editor is not in 'ready' state
|
|
17379
|
+
* @throws {NoSourcePathError} If no source path is available
|
|
17380
|
+
* @throws {FileSystemNotAvailableError} If file system access is not available
|
|
17381
|
+
*
|
|
17382
|
+
* @example
|
|
17383
|
+
* ```typescript
|
|
17384
|
+
* const editor = await Editor.open('/path/to/doc.docx');
|
|
17385
|
+
* // ... make changes ...
|
|
17386
|
+
* await editor.save(); // Saves back to /path/to/doc.docx
|
|
17387
|
+
* ```
|
|
17388
|
+
*/
|
|
17389
|
+
async save(options) {
|
|
17390
|
+
this.#assertState("ready");
|
|
17391
|
+
if (!this.#sourcePath) {
|
|
17392
|
+
throw new NoSourcePathError("No source path. Use saveTo(path) or exportDocument() instead.");
|
|
17393
|
+
}
|
|
17394
|
+
await this.#withState("saving", "ready", "ready", async () => {
|
|
17395
|
+
const data = await this.exportDocument(options);
|
|
17396
|
+
await this.#writeToPath(this.#sourcePath, data);
|
|
17397
|
+
});
|
|
17398
|
+
}
|
|
17399
|
+
/**
|
|
17400
|
+
* Save the document to a specific path.
|
|
17401
|
+
*
|
|
17402
|
+
* Updates the source path to the new location after saving.
|
|
17403
|
+
*
|
|
17404
|
+
* @param path - File path to save to
|
|
17405
|
+
* @param options - Save options
|
|
17406
|
+
* @throws {InvalidStateError} If editor is not in 'ready' state
|
|
17407
|
+
* @throws {FileSystemNotAvailableError} If file system access is not available
|
|
17408
|
+
*
|
|
17409
|
+
* @example
|
|
17410
|
+
* ```typescript
|
|
17411
|
+
* const editor = await Editor.open(blobData); // No source path
|
|
17412
|
+
* await editor.saveTo('/path/to/new-doc.docx');
|
|
17413
|
+
* await editor.save(); // Now saves to /path/to/new-doc.docx
|
|
17414
|
+
* ```
|
|
17415
|
+
*/
|
|
17416
|
+
async saveTo(path, options) {
|
|
17417
|
+
this.#assertState("ready");
|
|
17418
|
+
await this.#withState("saving", "ready", "ready", async () => {
|
|
17419
|
+
const data = await this.exportDocument(options);
|
|
17420
|
+
await this.#writeToPath(path, data);
|
|
17421
|
+
this.#sourcePath = path;
|
|
17422
|
+
});
|
|
17423
|
+
}
|
|
17424
|
+
/**
|
|
17425
|
+
* Export the document as a Blob or Buffer.
|
|
17426
|
+
*
|
|
17427
|
+
* This is a convenience wrapper around `exportDocx()` that returns
|
|
17428
|
+
* the document data without writing to a file.
|
|
17429
|
+
*
|
|
17430
|
+
* @param options - Export options
|
|
17431
|
+
* @returns Promise resolving to Blob (browser) or Buffer (Node.js)
|
|
17432
|
+
* @throws {InvalidStateError} If editor is not in 'ready' state
|
|
17433
|
+
*
|
|
17434
|
+
* @example
|
|
17435
|
+
* ```typescript
|
|
17436
|
+
* const blob = await editor.exportDocument();
|
|
17437
|
+
*
|
|
17438
|
+
* // Create download link in browser
|
|
17439
|
+
* const url = URL.createObjectURL(blob);
|
|
17440
|
+
* const a = document.createElement('a');
|
|
17441
|
+
* a.href = url;
|
|
17442
|
+
* a.download = 'document.docx';
|
|
17443
|
+
* a.click();
|
|
17444
|
+
* ```
|
|
17445
|
+
*/
|
|
17446
|
+
async exportDocument(options) {
|
|
17447
|
+
this.#assertState("ready", "saving");
|
|
17448
|
+
const result = await this.exportDocx({
|
|
17449
|
+
isFinalDoc: options?.isFinalDoc,
|
|
17450
|
+
commentsType: options?.commentsType,
|
|
17451
|
+
comments: options?.comments,
|
|
17452
|
+
fieldsHighlightColor: options?.fieldsHighlightColor
|
|
17453
|
+
});
|
|
17454
|
+
return result;
|
|
17455
|
+
}
|
|
17456
|
+
/**
|
|
17457
|
+
* Writes document data to a file path.
|
|
17458
|
+
*
|
|
17459
|
+
* **Browser behavior:**
|
|
17460
|
+
* In browsers, the `path` parameter is only used as a suggested filename.
|
|
17461
|
+
* The File System Access API shows a save dialog and the user chooses the actual location.
|
|
17462
|
+
*
|
|
17463
|
+
* **Node.js behavior:**
|
|
17464
|
+
* The path is an actual filesystem path, written directly.
|
|
17465
|
+
*/
|
|
17466
|
+
async #writeToPath(path, data) {
|
|
17467
|
+
const isNode2 = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" && globalThis.process.versions?.node != null;
|
|
17468
|
+
const hasNodeBuffer = typeof jszip.Buffer !== "undefined" && typeof jszip.Buffer.isBuffer === "function";
|
|
17469
|
+
if (isNode2 || hasNodeBuffer) {
|
|
17470
|
+
try {
|
|
17471
|
+
const fs = require("fs");
|
|
17472
|
+
const buffer = jszip.Buffer.isBuffer(data) ? data : jszip.Buffer.from(await data.arrayBuffer());
|
|
17473
|
+
fs.writeFileSync(path, buffer);
|
|
17474
|
+
return;
|
|
17475
|
+
} catch {
|
|
17476
|
+
}
|
|
17477
|
+
}
|
|
17478
|
+
if (typeof window !== "undefined" && "showSaveFilePicker" in window) {
|
|
17479
|
+
const handle = await window.showSaveFilePicker({
|
|
17480
|
+
suggestedName: path.split("/").pop() || "document.docx",
|
|
17481
|
+
types: [
|
|
17482
|
+
{
|
|
17483
|
+
description: "Word Document",
|
|
17484
|
+
accept: { "application/vnd.openxmlformats-officedocument.wordprocessingml.document": [".docx"] }
|
|
17485
|
+
}
|
|
17486
|
+
]
|
|
17487
|
+
});
|
|
17488
|
+
const writable = await handle.createWritable();
|
|
17489
|
+
await writable.write(data);
|
|
17490
|
+
await writable.close();
|
|
17491
|
+
return;
|
|
17492
|
+
}
|
|
17493
|
+
throw new FileSystemNotAvailableError(
|
|
17494
|
+
"File System Access API not available. Use exportDocument() to get the document data and handle the download manually."
|
|
17495
|
+
);
|
|
17496
|
+
}
|
|
16855
17497
|
/**
|
|
16856
17498
|
* Destroy the editor and clean up resources
|
|
16857
17499
|
*/
|
|
16858
17500
|
destroy() {
|
|
17501
|
+
if (this.#editorLifecycleState === "ready") {
|
|
17502
|
+
this.close();
|
|
17503
|
+
}
|
|
17504
|
+
if (this.#editorLifecycleState === "destroyed") {
|
|
17505
|
+
return;
|
|
17506
|
+
}
|
|
16859
17507
|
this.#isDestroyed = true;
|
|
16860
17508
|
this.emit("destroy");
|
|
16861
17509
|
this.unmount();
|
|
16862
17510
|
this.destroyHeaderFooterEditors();
|
|
16863
17511
|
this.#endCollaboration();
|
|
16864
17512
|
this.removeAllListeners();
|
|
17513
|
+
this.extensionService = void 0;
|
|
17514
|
+
this.schema = void 0;
|
|
17515
|
+
this.#commandService = void 0;
|
|
17516
|
+
this.#editorLifecycleState = "destroyed";
|
|
16865
17517
|
}
|
|
16866
17518
|
destroyHeaderFooterEditors() {
|
|
16867
17519
|
try {
|
|
@@ -16893,7 +17545,7 @@ class Editor extends EventEmitter {
|
|
|
16893
17545
|
* Process collaboration migrations
|
|
16894
17546
|
*/
|
|
16895
17547
|
processCollaborationMigrations() {
|
|
16896
|
-
console.debug("[checkVersionMigrations] Current editor version", "2.0.0-next.
|
|
17548
|
+
console.debug("[checkVersionMigrations] Current editor version", "2.0.0-next.23");
|
|
16897
17549
|
if (!this.options.ydoc) return;
|
|
16898
17550
|
const metaMap = this.options.ydoc.getMap("meta");
|
|
16899
17551
|
let docVersion = metaMap.get("version");
|