diff --git a/ghost/core/core/server/models/post-revision.js b/ghost/core/core/server/models/post-revision.js index 042a825669..24b0c5562f 100644 --- a/ghost/core/core/server/models/post-revision.js +++ b/ghost/core/core/server/models/post-revision.js @@ -36,8 +36,8 @@ const PostRevision = ghostBookshelf.Model.extend({ toJSON(unfilteredOptions) { const options = PostRevision.filterOptions(unfilteredOptions, 'toJSON'); const attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options); - // CASE: only for internal accuracy - delete attrs.created_at_ts; + + // We embed the full author object, so no need to send the author_id delete attrs.author_id; return attrs; } diff --git a/ghost/core/core/server/models/post.js b/ghost/core/core/server/models/post.js index 7aaee24100..d6129ef731 100644 --- a/ghost/core/core/server/models/post.js +++ b/ghost/core/core/server/models/post.js @@ -37,6 +37,7 @@ const messages = { const MOBILEDOC_REVISIONS_COUNT = 10; const POST_REVISIONS_COUNT = 10; +const POST_REVISIONS_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes const ALL_STATUSES = ['published', 'draft', 'scheduled', 'sent']; let Post; @@ -905,7 +906,8 @@ Post = ghostBookshelf.Model.extend({ if (!model.get('mobiledoc') && !options.importing && !options.migrating) { const postRevisions = new PostRevisions({ config: { - max_revisions: POST_REVISIONS_COUNT + max_revisions: POST_REVISIONS_COUNT, + revision_interval_ms: POST_REVISIONS_INTERVAL_MS } }); const authorId = this.contextUser(options); @@ -913,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'] + columns: ['id', 'lexical', 'created_at', 'author_id', 'title', 'reason', 'post_status', 'created_at_ts'] }, _.pick(options, 'transacting'))); const revisions = revisionModels.toJSON(); diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap index e4e8c5a1db..cae522470b 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/posts.test.js.snap @@ -831,7 +831,7 @@ exports[`Posts API Create Can create a post with lexical 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "5363", + "content-length": "5396", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -1261,7 +1261,7 @@ exports[`Posts API Update Can update a post with lexical 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "5249", + "content-length": "5333", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -1371,7 +1371,7 @@ exports[`Posts API Update Can update a post with lexical 4: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "6619", + "content-length": "6685", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, diff --git a/ghost/post-revisions/lib/post-revisions.js b/ghost/post-revisions/lib/post-revisions.js index 5a48005a61..b0ed841108 100644 --- a/ghost/post-revisions/lib/post-revisions.js +++ b/ghost/post-revisions/lib/post-revisions.js @@ -31,6 +31,7 @@ class PostRevisions { * @param {object} deps * @param {object} deps.config * @param {number} deps.config.max_revisions + * @param {number} deps.config.revision_interval_ms * @param {object} deps.model */ constructor(deps) { @@ -57,11 +58,19 @@ class PostRevisions { } const forceRevision = options && options.forceRevision; - const lexicalHasChangedSinceLatestRevision = latestRevision.lexical !== current.lexical; - const titleHasChanged = latestRevision.title !== current.title; const featuredImagedHasChanged = latestRevision.feature_image !== current.feature_image; - if ((lexicalHasChangedSinceLatestRevision || titleHasChanged || featuredImagedHasChanged) && forceRevision) { - return {value: true, reason: 'explicit_save'}; + const lexicalHasChanged = latestRevision.lexical !== current.lexical; + const titleHasChanged = latestRevision.title !== current.title; + // CASE: we only want to save a revision if something has changed since the previous revision + if (lexicalHasChanged || titleHasChanged || featuredImagedHasChanged) { + // CASE: user has explicitly requested a revision by hitting cmd+s or leaving the editor + if (forceRevision) { + return {value: true, reason: 'explicit_save'}; + } + // CASE: it's been X mins since the last revision, so we should save a new one + if ((Date.now() - latestRevision.created_at_ts) > this.config.revision_interval_ms) { + return {value: true, reason: 'background_save'}; + } } return {value: false}; } diff --git a/ghost/post-revisions/test/hello.test.js b/ghost/post-revisions/test/hello.test.js index 22d14bda43..7d32847572 100644 --- a/ghost/post-revisions/test/hello.test.js +++ b/ghost/post-revisions/test/hello.test.js @@ -3,7 +3,8 @@ const sinon = require('sinon'); const PostRevisions = require('..'); const config = { - max_revisions: 10 + max_revisions: 10, + revision_interval_ms: 1000 }; describe('PostRevisions', function () { @@ -58,7 +59,8 @@ describe('PostRevisions', function () { html: 'blah', title: 'blah2' }, [{ - lexical: 'blah' + lexical: 'blah', + title: 'not blah' }], { forceRevision: true }); @@ -103,6 +105,22 @@ describe('PostRevisions', function () { assert.deepEqual(actual, expected); }); + + it('should return true if the latest revision was more than the interval', function () { + const postRevisions = new PostRevisions({config}); + + const expected = {value: true, reason: 'background_save'}; + const actual = postRevisions.shouldGenerateRevision({ + lexical: 'blah', + html: 'blah', + title: 'blah' + }, [{ + lexical: 'blah', + created_at_ts: Date.now() - 2000 + }], {}); + + assert.deepEqual(actual, expected); + }); }); describe('getRevisions', function () {