@ckeditor/ckeditor5-watchdog
Advanced tools
Comparing version 0.0.0-nightly-20230726.0 to 0.0.0-nightly-20230727.0
{ | ||
"name": "@ckeditor/ckeditor5-watchdog", | ||
"version": "0.0.0-nightly-20230726.0", | ||
"version": "0.0.0-nightly-20230727.0", | ||
"description": "A watchdog feature for CKEditor 5 editors. It keeps a CKEditor 5 editor instance running.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -13,4 +13,4 @@ /** | ||
*/ | ||
_watchdogInitialData?: EditorData | null; | ||
_watchdogInitialData?: EditorData; | ||
} | ||
} |
@@ -40,2 +40,10 @@ /** | ||
/** | ||
* Specifies whether the editor was initialized using document data (`true`) or HTML elements (`false`). | ||
*/ | ||
private _initUsingData; | ||
/** | ||
* The latest record of the editor editable elements. Used to restart the editor. | ||
*/ | ||
private _editables; | ||
/** | ||
* The editor configuration. | ||
@@ -113,3 +121,3 @@ */ | ||
*/ | ||
create(elementOrData?: HTMLElement | string | Record<string, string>, config?: EditorConfig, context?: Context): Promise<unknown>; | ||
create(elementOrData?: HTMLElement | string | Record<string, string> | Record<string, HTMLElement>, config?: EditorConfig, context?: Context): Promise<unknown>; | ||
/** | ||
@@ -136,2 +144,6 @@ * Destroys the watchdog and the current editor instance. It fires the callback | ||
/** | ||
* For each attached model root, returns its HTML editable element (if available). | ||
*/ | ||
private _getEditables; | ||
/** | ||
* Traverses the error context and the current editor to find out whether these structures are connected | ||
@@ -152,2 +164,3 @@ * to each other via properties. | ||
attributes: string; | ||
isLoaded: boolean; | ||
}>; | ||
@@ -162,2 +175,4 @@ markers: Record<string, { | ||
}>; | ||
commentThreads: string; | ||
suggestions: string; | ||
}; | ||
@@ -174,2 +189,2 @@ /** | ||
}; | ||
export type EditorCreatorFunction<TEditor = Editor> = (elementOrData: HTMLElement | string | Record<string, string>, config: EditorConfig) => Promise<TEditor>; | ||
export type EditorCreatorFunction<TEditor = Editor> = (elementOrData: HTMLElement | string | Record<string, string> | Record<string, HTMLElement>, config: EditorConfig) => Promise<TEditor>; |
@@ -25,2 +25,10 @@ /** | ||
this._editor = null; | ||
/** | ||
* Specifies whether the editor was initialized using document data (`true`) or HTML elements (`false`). | ||
*/ | ||
this._initUsingData = true; | ||
/** | ||
* The latest record of the editor editable elements. Used to restart the editor. | ||
*/ | ||
this._editables = {}; | ||
// this._editorClass = Editor; | ||
@@ -94,18 +102,52 @@ this._throttledSave = throttle(this._save.bind(this), typeof watchdogConfig.saveInterval === 'number' ? watchdogConfig.saveInterval : 5000); | ||
.then(() => { | ||
const existingRoots = Object.keys(this._data.roots).reduce((acc, rootName) => { | ||
acc[rootName] = ''; | ||
return acc; | ||
}, {}); | ||
// Pre-process some data from the original editor config. | ||
// Our goal here is to make sure that the restarted editor will be reinitialized with correct set of roots. | ||
// We are not interested in any data set in config or in `.create()` first parameter. It will be replaced anyway. | ||
// But we need to set them correctly to make sure that proper roots are created. | ||
// | ||
// Since a different set of roots will be created, `lazyRoots` and `rootsAttributes` properties must be managed too. | ||
// Keys are root names, values are ''. Used when the editor was initialized by setting the first parameter to document data. | ||
const existingRoots = {}; | ||
// Keeps lazy roots. They may be different when compared to initial config if some of the roots were loaded. | ||
const lazyRoots = []; | ||
// Roots attributes from the old config. Will be referred when setting new attributes. | ||
const oldRootsAttributes = this._config.rootsAttributes || {}; | ||
// New attributes to be set. Is filled only for roots that still exist in the document. | ||
const rootsAttributes = {}; | ||
// Traverse through the roots saved when the editor crashed and set up the discussed values. | ||
for (const [rootName, rootData] of Object.entries(this._data.roots)) { | ||
if (rootData.isLoaded) { | ||
existingRoots[rootName] = ''; | ||
rootsAttributes[rootName] = oldRootsAttributes[rootName] || {}; | ||
} | ||
else { | ||
lazyRoots.push(rootName); | ||
} | ||
} | ||
const updatedConfig = { | ||
...this._config, | ||
extraPlugins: this._config.extraPlugins || [], | ||
lazyRoots, | ||
rootsAttributes, | ||
_watchdogInitialData: this._data | ||
}; | ||
// Delete `initialData` as it is not needed. Data will be set by the watchdog based on `_watchdogInitialData`. | ||
// First parameter of the editor `.create()` will be used to set up initial roots. | ||
delete updatedConfig.initialData; | ||
updatedConfig.extraPlugins.push(EditorWatchdogInitPlugin); | ||
if (typeof this._elementOrData === 'string') { | ||
if (this._initUsingData) { | ||
return this.create(existingRoots, updatedConfig, updatedConfig.context); | ||
} | ||
else { | ||
updatedConfig.initialData = existingRoots; | ||
return this.create(this._elementOrData, updatedConfig, updatedConfig.context); | ||
// Set correct editables to make sure that proper roots are created and linked with DOM elements. | ||
// No need to set initial data, as it would be discarded anyway. | ||
// | ||
// If one element was initially set in `elementOrData`, then use that original element to restart the editor. | ||
// This is for compatibility purposes with single-root editor types. | ||
if (isElement(this._elementOrData)) { | ||
return this.create(this._elementOrData, updatedConfig, updatedConfig.context); | ||
} | ||
else { | ||
return this.create(this._editables, updatedConfig, updatedConfig.context); | ||
} | ||
} | ||
@@ -129,2 +171,6 @@ }) | ||
this._elementOrData = elementOrData; | ||
// Use document data in the first parameter of the editor `.create()` call only if it was used like this originally. | ||
// Use document data if a string or object with strings was passed. | ||
this._initUsingData = typeof elementOrData == 'string' || | ||
(Object.keys(elementOrData).length > 0 && typeof Object.values(elementOrData)[0] == 'string'); | ||
// Clone configuration because it might be shared within multiple watchdog instances. Otherwise, | ||
@@ -141,2 +187,5 @@ // when an error occurs in one of these editors, the watchdog will restart all of them. | ||
this._data = this._getData(); | ||
if (!this._initUsingData) { | ||
this._editables = this._getEditables(); | ||
} | ||
this.state = 'ready'; | ||
@@ -164,4 +213,3 @@ this._fire('stateChange'); | ||
this._stopErrorHandling(); | ||
// Save data if there is a remaining editor data change. | ||
this._throttledSave.flush(); | ||
this._throttledSave.cancel(); | ||
const editor = this._editor; | ||
@@ -184,2 +232,5 @@ this._editor = null; | ||
this._data = this._getData(); | ||
if (!this._initUsingData) { | ||
this._editables = this._getEditables(); | ||
} | ||
this._lastDocumentVersion = version; | ||
@@ -202,13 +253,19 @@ } | ||
_getData() { | ||
const editor = this.editor; | ||
const rootNames = editor.model.document.getRootNames(); | ||
const editor = this._editor; | ||
const roots = editor.model.document.roots.filter(root => root.isAttached() && root.rootName != '$graveyard'); | ||
const { plugins } = editor; | ||
// `as any` to avoid linking from external private repo. | ||
const commentsRepository = plugins.has('CommentsRepository') && plugins.get('CommentsRepository'); | ||
const trackChanges = plugins.has('TrackChanges') && plugins.get('TrackChanges'); | ||
const data = { | ||
roots: {}, | ||
markers: {} | ||
markers: {}, | ||
commentThreads: JSON.stringify([]), | ||
suggestions: JSON.stringify([]) | ||
}; | ||
rootNames.forEach(rootName => { | ||
const root = editor.model.document.getRoot(rootName); | ||
data.roots[rootName] = { | ||
roots.forEach(root => { | ||
data.roots[root.rootName] = { | ||
content: JSON.stringify(Array.from(root.getChildren())), | ||
attributes: JSON.stringify(Array.from(root.getAttributes())) | ||
attributes: JSON.stringify(Array.from(root.getAttributes())), | ||
isLoaded: root._isLoaded | ||
}; | ||
@@ -226,5 +283,24 @@ }); | ||
} | ||
if (commentsRepository) { | ||
data.commentThreads = JSON.stringify(commentsRepository.getCommentThreads({ toJSON: true, skipNotAttached: true })); | ||
} | ||
if (trackChanges) { | ||
data.suggestions = JSON.stringify(trackChanges.getSuggestions({ toJSON: true, skipNotAttached: true })); | ||
} | ||
return data; | ||
} | ||
/** | ||
* For each attached model root, returns its HTML editable element (if available). | ||
*/ | ||
_getEditables() { | ||
const editables = {}; | ||
for (const rootName of this.editor.model.document.getRootNames()) { | ||
const editable = this.editor.ui.getEditableElement(rootName); | ||
if (editable) { | ||
editables[rootName] = editable; | ||
} | ||
} | ||
return editables; | ||
} | ||
/** | ||
* Traverses the error context and the current editor to find out whether these structures are connected | ||
@@ -271,3 +347,4 @@ * to each other via properties. | ||
evt.stop(); | ||
this.editor.model.enqueueChange({ isUndoable: true }, writer => { | ||
this.editor.model.enqueueChange({ isUndoable: false }, writer => { | ||
this._restoreCollaborationData(); | ||
this._restoreEditorData(writer); | ||
@@ -328,2 +405,29 @@ }); | ||
} | ||
/** | ||
* Restores the editor collaboration data - comment threads and suggestions. | ||
*/ | ||
_restoreCollaborationData() { | ||
// `as any` to avoid linking from external private repo. | ||
const parsedCommentThreads = JSON.parse(this._data.commentThreads); | ||
const parsedSuggestions = JSON.parse(this._data.suggestions); | ||
parsedCommentThreads.forEach(commentThreadData => { | ||
const channelId = this.editor.config.get('collaboration.channelId'); | ||
const commentsRepository = this.editor.plugins.get('CommentsRepository'); | ||
if (commentsRepository.hasCommentThread(commentThreadData.threadId)) { | ||
const commentThread = commentsRepository.getCommentThread(commentThreadData.threadId); | ||
commentThread.remove(); | ||
} | ||
commentsRepository.addCommentThread({ channelId, ...commentThreadData }); | ||
}); | ||
parsedSuggestions.forEach(suggestionData => { | ||
const trackChangesEditing = this.editor.plugins.get('TrackChangesEditing'); | ||
if (trackChangesEditing.hasSuggestion(suggestionData.id)) { | ||
const suggestion = trackChangesEditing.getSuggestion(suggestionData.id); | ||
suggestion.attributes = suggestionData.attributes; | ||
} | ||
else { | ||
trackChangesEditing.addSuggestionData(suggestionData); | ||
} | ||
}); | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
81806
1969