From 812716e18b09a716547f128b783c8e6f3d54cc5b Mon Sep 17 00:00:00 2001 From: Bartholomew Date: Wed, 27 May 2020 14:14:35 +0100 Subject: [PATCH] feat: add pre save/ post save hooks (#3812) --- .../src/implementation.ts | 4 --- packages/netlify-cms-core/src/backend.ts | 23 ++++++++++------ .../src/lib/__tests__/registry.spec.js | 9 ++++++- packages/netlify-cms-core/src/lib/registry.js | 11 ++++++-- .../netlify-cms-core/src/types/immutable.ts | 4 +++ .../src/implementation.ts | 1 - website/content/docs/beta-features.md | 26 +++++++------------ 7 files changed, 46 insertions(+), 32 deletions(-) diff --git a/packages/netlify-cms-backend-test/src/implementation.ts b/packages/netlify-cms-backend-test/src/implementation.ts index 7479b532..77534cce 100644 --- a/packages/netlify-cms-backend-test/src/implementation.ts +++ b/packages/netlify-cms-backend-test/src/implementation.ts @@ -226,8 +226,6 @@ export default class TestBackend implements Implementation { const unpubEntry = { ...unpubStore[existingEntryIndex], data: raw, - title: options.parsedData && options.parsedData.title, - description: options.parsedData && options.parsedData.description, mediaFiles: assetProxies.map(this.normalizeAsset), }; @@ -242,8 +240,6 @@ export default class TestBackend implements Implementation { metaData: { collection: options.collectionName as string, status: (options.status || this.options.initialWorkflowStatus) as string, - title: options.parsedData && options.parsedData.title, - description: options.parsedData && options.parsedData.description, }, slug, mediaFiles: assetProxies.map(this.normalizeAsset), diff --git a/packages/netlify-cms-core/src/backend.ts b/packages/netlify-cms-core/src/backend.ts index 41ffbcf2..7327dea1 100644 --- a/packages/netlify-cms-core/src/backend.ts +++ b/packages/netlify-cms-core/src/backend.ts @@ -702,18 +702,16 @@ export class Backend { async persistEntry({ config, collection, - entryDraft, + entryDraft: draft, assetProxies, usedSlugs, unpublished = false, status, }: PersistArgs) { - const newEntry = entryDraft.getIn(['entry', 'newRecord']) || false; + const modifiedData = await this.invokePreSaveEvent(draft.get('entry')); + const entryDraft = (modifiedData && draft.setIn(['entry', 'data'], modifiedData)) || draft; - const parsedData = { - title: entryDraft.getIn(['entry', 'data', 'title'], 'No Title') as string, - description: entryDraft.getIn(['entry', 'data', 'description'], 'No Description!') as string, - }; + const newEntry = entryDraft.getIn(['entry', 'newRecord']) || false; let entryObj: { path: string; @@ -782,7 +780,6 @@ export class Backend { const updatedOptions = { unpublished, status }; const opts = { newEntry, - parsedData, commitMessage, collectionName, useWorkflow, @@ -795,6 +792,8 @@ export class Backend { await this.implementation.persistEntry(entryObj, assetProxies, opts); + await this.invokePostSaveEvent(entryDraft.get('entry')); + if (!useWorkflow) { await this.invokePostPublishEvent(entryDraft.get('entry')); } @@ -804,7 +803,7 @@ export class Backend { async invokeEventWithEntry(event: string, entry: EntryMap) { const { login, name } = (await this.currentUser()) as User; - await invokeEvent({ name: event, data: { entry, author: { login, name } } }); + return await invokeEvent({ name: event, data: { entry, author: { login, name } } }); } async invokePrePublishEvent(entry: EntryMap) { @@ -823,6 +822,14 @@ export class Backend { await this.invokeEventWithEntry('postUnpublish', entry); } + async invokePreSaveEvent(entry: EntryMap) { + return await this.invokeEventWithEntry('preSave', entry); + } + + async invokePostSaveEvent(entry: EntryMap) { + await this.invokeEventWithEntry('postSave', entry); + } + async persistMedia(config: Config, file: AssetProxy) { const user = (await this.currentUser()) as User; const options = { diff --git a/packages/netlify-cms-core/src/lib/__tests__/registry.spec.js b/packages/netlify-cms-core/src/lib/__tests__/registry.spec.js index 92d8a83c..20473e50 100644 --- a/packages/netlify-cms-core/src/lib/__tests__/registry.spec.js +++ b/packages/netlify-cms-core/src/lib/__tests__/registry.spec.js @@ -45,7 +45,14 @@ describe('registry', () => { }); describe('eventHandlers', () => { - const events = ['prePublish', 'postPublish', 'preUnpublish', 'postUnpublish']; + const events = [ + 'prePublish', + 'postPublish', + 'preUnpublish', + 'postUnpublish', + 'preSave', + 'postSave', + ]; describe('registerEventListener', () => { it('should throw error on invalid event', () => { diff --git a/packages/netlify-cms-core/src/lib/registry.js b/packages/netlify-cms-core/src/lib/registry.js index 53ee4bb8..57df0adc 100644 --- a/packages/netlify-cms-core/src/lib/registry.js +++ b/packages/netlify-cms-core/src/lib/registry.js @@ -3,7 +3,14 @@ import produce from 'immer'; import { oneLine } from 'common-tags'; import EditorComponent from 'ValueObjects/EditorComponent'; -const allowedEvents = ['prePublish', 'postPublish', 'preUnpublish', 'postUnpublish']; +const allowedEvents = [ + 'prePublish', + 'postPublish', + 'preUnpublish', + 'postUnpublish', + 'preSave', + 'postSave', +]; const eventHandlers = {}; allowedEvents.forEach(e => { eventHandlers[e] = []; @@ -213,7 +220,7 @@ export async function invokeEvent({ name, data }) { const handlers = registry.eventHandlers[name]; for (const { handler, options } of handlers) { try { - await handler(data, options); + return await handler(data, options); } catch (e) { console.warn(`Failed running handler for event ${name} with message: ${e.message}`); } diff --git a/packages/netlify-cms-core/src/types/immutable.ts b/packages/netlify-cms-core/src/types/immutable.ts index 41bd2d2f..3b6ffdce 100644 --- a/packages/netlify-cms-core/src/types/immutable.ts +++ b/packages/netlify-cms-core/src/types/immutable.ts @@ -16,6 +16,10 @@ export interface StaticallyTypedRecord { keys: [K1, K2, K3], defaultValue?: V, ): T[K1][K2][K3]; + setIn( + keys: [K1, K2], + value: V, + ): StaticallyTypedRecord; toJS(): T; isEmpty(): boolean; some(predicate: (value: T[K], key: K, iter: this) => boolean): boolean; diff --git a/packages/netlify-cms-lib-util/src/implementation.ts b/packages/netlify-cms-lib-util/src/implementation.ts index 69102968..c272440e 100644 --- a/packages/netlify-cms-lib-util/src/implementation.ts +++ b/packages/netlify-cms-lib-util/src/implementation.ts @@ -52,7 +52,6 @@ export type Entry = { path: string; slug: string; raw: string }; export type PersistOptions = { newEntry?: boolean; - parsedData?: { title: string; description: string }; commitMessage: string; collectionName?: string; useWorkflow?: boolean; diff --git a/website/content/docs/beta-features.md b/website/content/docs/beta-features.md index 9e260d44..081f31a0 100644 --- a/website/content/docs/beta-features.md +++ b/website/content/docs/beta-features.md @@ -399,24 +399,18 @@ CMS.registerEventListener({ name: 'prePublish', handler: ({ author, entry }) => console.log(JSON.stringify({ author, data: entry.get('data') })), }); - -CMS.registerEventListener({ - name: 'postPublish', - handler: ({ author, entry }) => console.log(JSON.stringify({ author, data: entry.get('data') })), -}); - -CMS.registerEventListener({ - name: 'preUnpublish', - handler: ({ author, entry }) => console.log(JSON.stringify({ author, data: entry.get('data') })), -}); - -CMS.registerEventListener({ - name: 'postUnpublish', - handler: ({ author, entry }) => console.log(JSON.stringify({ author, data: entry.get('data') })), -}); ``` -**Note:** Supported events are `prePublish`, `postPublish`, `preUnpublish` and `postUnpublish`. +Supported events are `prePublish`, `postPublish`, `preUnpublish`, `postUnpublish`, `preSave` and `postSave`. The `preSave` hook can be used to modify the entry data like so: + +```javascript +CMS.registerEventListener({ + name: 'preSave', + handler: ({ entry }) => { + return entry.get('data').set('title', 'new title'); + }, +}); +``` ## Dynamic Default Values