From b62a64208440bd478b6e8a60e86f3a26b21a5226 Mon Sep 17 00:00:00 2001 From: Chris Raible Date: Wed, 3 May 2023 14:46:32 -0700 Subject: [PATCH] Added background save every 10 minutes to the lexical editor (#16732) refs TryGhost/Team#3133 - the backend previously had logic to save a revision if more than 10 mins had elapsed since the last revision - however, the frontend would autosave after 3 seconds of inactivity (which doesn't trigger a revision), and never send another save request at 10 minutes, so the backend logic to save a revision was never triggered - this change will save the current contents of the editor every 10 minutes, even if nothing has changed since the last save --- ghost/admin/app/controllers/lexical-editor.js | 28 +++++++++++++++++-- ghost/core/core/server/models/post.js | 2 +- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/ghost/admin/app/controllers/lexical-editor.js b/ghost/admin/app/controllers/lexical-editor.js index fb0e14bfb6..03ccf72602 100644 --- a/ghost/admin/app/controllers/lexical-editor.js +++ b/ghost/admin/app/controllers/lexical-editor.js @@ -30,6 +30,8 @@ const DEFAULT_TITLE = '(Untitled)'; const AUTOSAVE_TIMEOUT = 3000; // time in ms to force a save if the user is continuously typing const TIMEDSAVE_TIMEOUT = 60000; +// time in ms to force a save even if the post is already saved so we trigger a new revision on the server +const REVISIONSAVE_TIMEOUT = 1000 * 60 * 10; // 10 minutes // this array will hold properties we need to watch for this.hasDirtyAttributes let watchedProps = [ @@ -190,12 +192,13 @@ export default class LexicalEditorController extends Controller { return false; } - @computed('_autosaveTask.isRunning', '_timedSaveTask.isRunning') + @computed('_autosaveTask.isRunning', '_timedSaveTask.isRunning', '_revisionSaveTask.isRunning') get _autosaveRunning() { let autosave = this.get('_autosaveTask.isRunning'); let timedsave = this.get('_timedSaveTask.isRunning'); + let revisionsave = this.get('_revisionSaveTask.isRunning'); - return autosave || timedsave; + return autosave || timedsave || revisionsave; } @computed('post.isDraft') @@ -211,6 +214,8 @@ export default class LexicalEditorController extends Controller { this._autosaveTask.perform(); // force save at 60 seconds this._timedSaveTask.perform(); + // force save at 10 minutes to trigger revision + this._revisionSaveTask.perform(); } @action @@ -245,6 +250,7 @@ export default class LexicalEditorController extends Controller { cancelAutosave() { this._autosaveTask.cancelAll(); this._timedSaveTask.cancelAll(); + this._revisionSaveTask.cancelAll(); } // called by the "are you sure?" modal @@ -427,7 +433,7 @@ export default class LexicalEditorController extends Controller { this.cancelAutosave(); - if (options.backgroundSave && !this.hasDirtyAttributes && !options.leavingEditor) { + if (options.backgroundSave && !this.hasDirtyAttributes && !options.leavingEditor && !options.saveRevision) { return; } @@ -481,6 +487,9 @@ export default class LexicalEditorController extends Controller { return true; } + // Even if we've just saved and nothing else has changed, we want to save in 10 minutes to force a revision + this._revisionSaveTask.perform(); + return post; } catch (error) { if (!this.session.isAuthenticated) { @@ -968,6 +977,19 @@ export default class LexicalEditorController extends Controller { }).drop()) _timedSaveTask; + // save at 10 minutes even if the post is already saved + @(task(function* () { + if (!this._canAutosave) { + return; + } + + while (config.environment !== 'test' && true) { + yield timeout(REVISIONSAVE_TIMEOUT); + this.autosaveTask.perform({saveRevision: true}); + } + }).drop()) + _revisionSaveTask; + /* Private methods -------------------------------------------------------*/ _hasDirtyAttributes() { diff --git a/ghost/core/core/server/models/post.js b/ghost/core/core/server/models/post.js index 1e70c0cbc9..7d19b6f719 100644 --- a/ghost/core/core/server/models/post.js +++ b/ghost/core/core/server/models/post.js @@ -915,7 +915,7 @@ Post = ghostBookshelf.Model.extend({ const revisionModels = await ghostBookshelf.model('PostRevision') .findAll(Object.assign({ filter: `post_id:${model.id}`, - columns: ['id', 'lexical', 'created_at', 'author_id', 'title', 'reason', 'post_status', 'created_at_ts'] + columns: ['id', 'lexical', 'created_at', 'author_id', 'title', 'reason', 'post_status', 'created_at_ts', 'feature_image'] }, _.pick(options, 'transacting'))); const revisions = revisionModels.toJSON();