mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
added watchdog for CKEditor to recover from crashes, fixes #3227
This commit is contained in:
parent
648dd73fa1
commit
b202b43bf5
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -9,6 +9,7 @@ import noteCreateService from "../../services/note_create.js";
|
|||||||
import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
|
import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
|
||||||
import link from "../../services/link.js";
|
import link from "../../services/link.js";
|
||||||
import appContext from "../../components/app_context.js";
|
import appContext from "../../components/app_context.js";
|
||||||
|
import dialogService from "../../services/dialog.js";
|
||||||
|
|
||||||
const ENABLE_INSPECTOR = false;
|
const ENABLE_INSPECTOR = false;
|
||||||
|
|
||||||
@ -118,7 +119,42 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
// display of $widget in both branches.
|
// display of $widget in both branches.
|
||||||
this.$widget.show();
|
this.$widget.show();
|
||||||
|
|
||||||
this.textEditor = await BalloonEditor.create(this.$editor[0], {
|
this.watchdog = new EditorWatchdog(BalloonEditor, {
|
||||||
|
// An average number of milliseconds between the last editor errors (defaults to 5000).
|
||||||
|
// When the period of time between errors is lower than that and the crashNumberLimit
|
||||||
|
// is also reached, the watchdog changes its state to crashedPermanently and it stops
|
||||||
|
// restarting the editor. This prevents an infinite restart loop.
|
||||||
|
minimumNonErrorTimePeriod: 5000,
|
||||||
|
// A threshold specifying the number of errors (defaults to 3).
|
||||||
|
// After this limit is reached and the time between last errors
|
||||||
|
// is shorter than minimumNonErrorTimePeriod, the watchdog changes
|
||||||
|
// its state to crashedPermanently and it stops restarting the editor.
|
||||||
|
// This prevents an infinite restart loop.
|
||||||
|
crashNumberLimit: 3,
|
||||||
|
// A minimum number of milliseconds between saving the editor data internally (defaults to 5000).
|
||||||
|
// Note that for large documents this might impact the editor performance.
|
||||||
|
saveInterval: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
this.watchdog.on('stateChange', () => {
|
||||||
|
const currentState = this.watchdog.state;
|
||||||
|
|
||||||
|
if (!['crashed', 'crashedPermanently'].includes(currentState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`CKEditor changed to ${currentState}`);
|
||||||
|
|
||||||
|
this.watchdog.crashes.forEach(crashInfo => console.log(crashInfo));
|
||||||
|
|
||||||
|
if (currentState === 'crashedPermanently') {
|
||||||
|
dialogService.info(`Editing component keeps crashing. Please try restarting Trilium. If problem persists, consider creating a bug report.`);
|
||||||
|
|
||||||
|
this.watchdog.editor.enableReadOnlyMode('crashed-editor');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.watchdog.create(this.$editor[0], {
|
||||||
placeholder: "Type the content of your note here ...",
|
placeholder: "Type the content of your note here ...",
|
||||||
mention: mentionSetup,
|
mention: mentionSetup,
|
||||||
codeBlock: {
|
codeBlock: {
|
||||||
@ -133,11 +169,11 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());
|
this.watchdog.editor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());
|
||||||
|
|
||||||
if (glob.isDev && ENABLE_INSPECTOR) {
|
if (glob.isDev && ENABLE_INSPECTOR) {
|
||||||
await import(/* webpackIgnore: true */'../../../libraries/ckeditor/inspector');
|
await import(/* webpackIgnore: true */'../../../libraries/ckeditor/inspector');
|
||||||
CKEditorInspector.attach(this.textEditor);
|
CKEditorInspector.attach(this.watchdog.editor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,12 +181,12 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
const noteComplement = await froca.getNoteComplement(note.noteId);
|
const noteComplement = await froca.getNoteComplement(note.noteId);
|
||||||
|
|
||||||
await this.spacedUpdate.allowUpdateWithoutChange(() => {
|
await this.spacedUpdate.allowUpdateWithoutChange(() => {
|
||||||
this.textEditor.setData(noteComplement.content || "");
|
this.watchdog.editor.setData(noteComplement.content || "");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getContent() {
|
getContent() {
|
||||||
const content = this.textEditor.getData();
|
const content = this.watchdog.editor.getData();
|
||||||
|
|
||||||
// if content is only tags/whitespace (typically <p> </p>), then just make it empty
|
// if content is only tags/whitespace (typically <p> </p>), then just make it empty
|
||||||
// this is important when setting new note to code
|
// this is important when setting new note to code
|
||||||
@ -164,13 +200,13 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
show() {}
|
show() {}
|
||||||
|
|
||||||
getEditor() {
|
getEditor() {
|
||||||
return this.textEditor;
|
return this.watchdog?.editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
if (this.textEditor) {
|
if (this.watchdog?.editor) {
|
||||||
this.spacedUpdate.allowUpdateWithoutChange(() => {
|
this.spacedUpdate.allowUpdateWithoutChange(() => {
|
||||||
this.textEditor.setData('');
|
this.watchdog.editor.setData('');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,8 +221,8 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
async addLinkToEditor(linkHref, linkTitle) {
|
async addLinkToEditor(linkHref, linkTitle) {
|
||||||
await this.initialized;
|
await this.initialized;
|
||||||
|
|
||||||
this.textEditor.model.change(writer => {
|
this.watchdog.editor.model.change(writer => {
|
||||||
const insertPosition = this.textEditor.model.document.selection.getFirstPosition();
|
const insertPosition = this.watchdog.editor.model.document.selection.getFirstPosition();
|
||||||
writer.insertText(linkTitle, {linkHref: linkHref}, insertPosition);
|
writer.insertText(linkTitle, {linkHref: linkHref}, insertPosition);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -194,8 +230,8 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
async addTextToEditor(text) {
|
async addTextToEditor(text) {
|
||||||
await this.initialized;
|
await this.initialized;
|
||||||
|
|
||||||
this.textEditor.model.change(writer => {
|
this.watchdog.editor.model.change(writer => {
|
||||||
const insertPosition = this.textEditor.model.document.selection.getLastPosition();
|
const insertPosition = this.watchdog.editor.model.document.selection.getLastPosition();
|
||||||
writer.insertText(text, insertPosition);
|
writer.insertText(text, insertPosition);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -213,21 +249,21 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
|
|
||||||
if (linkTitle) {
|
if (linkTitle) {
|
||||||
if (this.hasSelection()) {
|
if (this.hasSelection()) {
|
||||||
this.textEditor.execute('link', '#' + notePath);
|
this.watchdog.editor.execute('link', '#' + notePath);
|
||||||
} else {
|
} else {
|
||||||
await this.addLinkToEditor('#' + notePath, linkTitle);
|
await this.addLinkToEditor('#' + notePath, linkTitle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.textEditor.execute('referenceLink', { notePath: notePath });
|
this.watchdog.editor.execute('referenceLink', { notePath: notePath });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.textEditor.editing.view.focus();
|
this.watchdog.editor.editing.view.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns true if user selected some text, false if there's no selection
|
// returns true if user selected some text, false if there's no selection
|
||||||
hasSelection() {
|
hasSelection() {
|
||||||
const model = this.textEditor.model;
|
const model = this.watchdog.editor.model;
|
||||||
const selection = model.document.selection;
|
const selection = model.document.selection;
|
||||||
|
|
||||||
return !selection.isCollapsed;
|
return !selection.isCollapsed;
|
||||||
@ -241,10 +277,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
await this.initialized;
|
await this.initialized;
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(this.textEditor);
|
callback(this.watchdog.editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(this.textEditor);
|
resolve(this.watchdog.editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
addLinkToTextCommand() {
|
addLinkToTextCommand() {
|
||||||
@ -254,7 +290,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSelectedText() {
|
getSelectedText() {
|
||||||
const range = this.textEditor.model.document.selection.getFirstRange();
|
const range = this.watchdog.editor.model.document.selection.getFirstRange();
|
||||||
let text = '';
|
let text = '';
|
||||||
|
|
||||||
for (const item of range.getItems()) {
|
for (const item of range.getItems()) {
|
||||||
@ -269,7 +305,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
async followLinkUnderCursorCommand() {
|
async followLinkUnderCursorCommand() {
|
||||||
await this.initialized;
|
await this.initialized;
|
||||||
|
|
||||||
const selection = this.textEditor.model.document.selection;
|
const selection = this.watchdog.editor.model.document.selection;
|
||||||
const selectedElement = selection.getSelectedElement();
|
const selectedElement = selection.getSelectedElement();
|
||||||
|
|
||||||
if (selectedElement?.name === 'reference') {
|
if (selectedElement?.name === 'reference') {
|
||||||
@ -301,10 +337,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addIncludeNote(noteId, boxSize) {
|
addIncludeNote(noteId, boxSize) {
|
||||||
this.textEditor.model.change( writer => {
|
this.watchdog.editor.model.change( writer => {
|
||||||
// Insert <includeNote>*</includeNote> at the current selection position
|
// Insert <includeNote>*</includeNote> at the current selection position
|
||||||
// in a way that will result in creating a valid model structure
|
// in a way that will result in creating a valid model structure
|
||||||
this.textEditor.model.insertContent(writer.createElement('includeNote', {
|
this.watchdog.editor.model.insertContent(writer.createElement('includeNote', {
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
boxSize: boxSize
|
boxSize: boxSize
|
||||||
}));
|
}));
|
||||||
@ -314,13 +350,13 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
|||||||
async addImage(noteId) {
|
async addImage(noteId) {
|
||||||
const note = await froca.getNote(noteId);
|
const note = await froca.getNote(noteId);
|
||||||
|
|
||||||
this.textEditor.model.change( writer => {
|
this.watchdog.editor.model.change( writer => {
|
||||||
const sanitizedTitle = note.title.replace(/[^a-z0-9-.]/gi, "");
|
const sanitizedTitle = note.title.replace(/[^a-z0-9-.]/gi, "");
|
||||||
const src = `api/images/${note.noteId}/${sanitizedTitle}`;
|
const src = `api/images/${note.noteId}/${sanitizedTitle}`;
|
||||||
|
|
||||||
const imageElement = writer.createElement( 'image', { 'src': src } );
|
const imageElement = writer.createElement( 'image', { 'src': src } );
|
||||||
|
|
||||||
this.textEditor.model.insertContent(imageElement, this.textEditor.model.document.selection);
|
this.watchdog.editor.model.insertContent(imageElement, this.watchdog.editor.model.document.selection);
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user