Skip to content

Commit 895b439

Browse files
committed
Merge with feature/PNW-2564_duplicate--wip
1 parent d4dadde commit 895b439

22 files changed

Lines changed: 414 additions & 137 deletions

File tree

packages/decap-cms-core/index.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,12 +516,20 @@ declare module 'decap-cms-core' {
516516
handler: ({
517517
entry,
518518
author,
519+
context,
519520
}: {
520521
entry: Map<string, any>;
521522
author: { login: string; name: string };
523+
context?: HookContext;
522524
}) => any;
523525
}
524526

527+
export interface HookContext {
528+
publishStack?: boolean;
529+
actions?: Record<string, Function>;
530+
[key: string]: any;
531+
}
532+
525533
export type CmsEventListenerOptions = any; // TODO: type properly
526534

527535
export type CmsLocalePhrases = any; // TODO: type properly

packages/decap-cms-core/src/actions/editorialWorkflow.ts

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import type { AnyAction } from 'redux';
3939
import type { EntryValue } from '../valueObjects/Entry';
4040
import type { Status } from '../constants/publishModes';
4141
import type { ThunkDispatch } from 'redux-thunk';
42+
import type { HookContext } from '../backend';
4243

4344
/*
4445
* Constant Declarations
@@ -67,6 +68,8 @@ export const UNPUBLISHED_ENTRY_DELETE_REQUEST = 'UNPUBLISHED_ENTRY_DELETE_REQUES
6768
export const UNPUBLISHED_ENTRY_DELETE_SUCCESS = 'UNPUBLISHED_ENTRY_DELETE_SUCCESS';
6869
export const UNPUBLISHED_ENTRY_DELETE_FAILURE = 'UNPUBLISHED_ENTRY_DELETE_FAILURE';
6970

71+
export const EDITORIAL_WORKFLOW_DISMISS_ERROR = 'EDITORIAL_WORKFLOW_DISMISS_ERROR';
72+
7073
/*
7174
* Simple Action Creators (Internal)
7275
*/
@@ -271,6 +274,7 @@ export function loadUnpublishedEntry(collection: Collection, slug: string) {
271274
dispatch(unpublishedEntryLoaded(collection, entry));
272275
dispatch(createDraftFromEntry(entry));
273276
} catch (error) {
277+
if (error.name === EDITORIAL_WORKFLOW_DISMISS_ERROR) return
274278
if (error.name === EDITORIAL_WORKFLOW_ERROR && error.notUnderEditorialWorkflow) {
275279
dispatch(unpublishedEntryRedirected(collection, slug));
276280
dispatch(loadEntry(collection, slug));
@@ -321,39 +325,21 @@ export function loadUnpublishedEntries(collections: Collections) {
321325
};
322326
}
323327

324-
export function persistUnpublishedEntry(collection: Collection, existingUnpublishedEntry: boolean) {
328+
export function persistCustomUnpublishedEntry(
329+
collection: Collection,
330+
existingUnpublishedEntry: boolean,
331+
entryDraft: EntryDraft,
332+
context: HookContext,
333+
) {
325334
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
326335
const state = getState();
327-
const entryDraft = state.entryDraft;
328-
const fieldsErrors = entryDraft.get('fieldsErrors');
329336
const unpublishedSlugs = selectUnpublishedSlugs(state, collection.get('name'));
330337
const publishedSlugs = selectPublishedSlugs(state, collection.get('name'));
331338
const usedSlugs = publishedSlugs.concat(unpublishedSlugs) as List<string>;
332339
const entriesLoaded = get(state.editorialWorkflow.toJS(), 'pages.ids', false);
333340

334-
//load unpublishedEntries
335341
!entriesLoaded && dispatch(loadUnpublishedEntries(state.collections));
336342

337-
// Early return if draft contains validation errors
338-
if (!fieldsErrors.isEmpty()) {
339-
const hasPresenceErrors = fieldsErrors.some(errors =>
340-
errors.some(error => error.type && error.type === ValidationErrorTypes.PRESENCE),
341-
);
342-
343-
if (hasPresenceErrors) {
344-
dispatch(
345-
addNotification({
346-
message: {
347-
key: 'ui.toast.missingRequiredField',
348-
},
349-
type: 'error',
350-
dismissAfter: 8000,
351-
}),
352-
);
353-
}
354-
return Promise.reject();
355-
}
356-
357343
const backend = currentBackend(state.config);
358344
const entry = entryDraft.get('entry');
359345
const assetProxies = getMediaAssets({
@@ -375,6 +361,7 @@ export function persistUnpublishedEntry(collection: Collection, existingUnpublis
375361
entryDraft: serializedEntryDraft,
376362
assetProxies,
377363
usedSlugs,
364+
context,
378365
});
379366
dispatch(
380367
addNotification({
@@ -392,6 +379,7 @@ export function persistUnpublishedEntry(collection: Collection, existingUnpublis
392379
navigateToEntry(collection.get('name'), newSlug);
393380
}
394381
} catch (error) {
382+
if (error.name === EDITORIAL_WORKFLOW_DISMISS_ERROR) return;
395383
dispatch(
396384
addNotification({
397385
message: {
@@ -409,6 +397,37 @@ export function persistUnpublishedEntry(collection: Collection, existingUnpublis
409397
};
410398
}
411399

400+
export function persistUnpublishedEntry(collection: Collection, existingUnpublishedEntry: boolean, context: HookContext) {
401+
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
402+
const state = getState();
403+
const entryDraft = state.entryDraft;
404+
const fieldsErrors = entryDraft.get('fieldsErrors');
405+
406+
// Early return if draft contains validation errors
407+
if (!fieldsErrors.isEmpty()) {
408+
const hasPresenceErrors = fieldsErrors.some(errors =>
409+
errors.some(error => error.type && error.type === ValidationErrorTypes.PRESENCE),
410+
);
411+
412+
if (hasPresenceErrors) {
413+
dispatch(
414+
addNotification({
415+
message: {
416+
key: 'ui.toast.missingRequiredField',
417+
},
418+
type: 'error',
419+
dismissAfter: 8000,
420+
}),
421+
);
422+
}
423+
return Promise.reject();
424+
}
425+
426+
const persistFunc = persistCustomUnpublishedEntry(collection, existingUnpublishedEntry, entryDraft, context);
427+
return persistFunc(dispatch, getState);
428+
};
429+
}
430+
412431
export function updateUnpublishedEntryStatus(
413432
collection: string,
414433
slug: string,
@@ -483,7 +502,7 @@ export function deleteUnpublishedEntry(collection: string, slug: string) {
483502
export function publishUnpublishedEntry(
484503
collectionName: string,
485504
slug: string,
486-
publishStack: boolean,
505+
context: HookContext,
487506
) {
488507
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
489508
const state = getState();
@@ -493,7 +512,7 @@ export function publishUnpublishedEntry(
493512
const isDeleteWorkflow = entry.get('isDeleteWorkflow');
494513
dispatch(unpublishedEntryPublishRequest(collectionName, slug));
495514
try {
496-
if (!publishStack && state.stack.status.status) {
515+
if (!context.publishStack && state.stack.status.status) {
497516
dispatch(
498517
addNotification({
499518
message: {
@@ -507,7 +526,7 @@ export function publishUnpublishedEntry(
507526
return dispatch(unpublishedEntryPublishError(collectionName, slug));
508527
}
509528

510-
await backend.publishUnpublishedEntry(entry, publishStack);
529+
await backend.publishUnpublishedEntry(entry, context);
511530

512531
await dispatch(checkStackStatus());
513532

@@ -537,6 +556,7 @@ export function publishUnpublishedEntry(
537556
return dispatch(loadEntry(collection, slug));
538557
}
539558
} catch (error) {
559+
if (error.name === EDITORIAL_WORKFLOW_DISMISS_ERROR) return;
540560
dispatch(
541561
addNotification({
542562
message: { key: 'ui.toast.onFailToPublishEntry', details: error },
@@ -549,15 +569,19 @@ export function publishUnpublishedEntry(
549569
};
550570
}
551571

552-
export function unpublishPublishedEntry(collection: Collection, slug: string) {
572+
export function unpublishCustomPublishedEntry(
573+
collection: Collection,
574+
slug: string,
575+
entry: EntryMap,
576+
context: HookContext,
577+
) {
553578
return (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
554579
const state = getState();
555580
const backend = currentBackend(state.config);
556-
const entry = selectEntry(state, collection.get('name'), slug);
557581
const entryDraft = Map().set('entry', entry) as unknown as EntryDraft;
558582
dispatch(unpublishedEntryPersisting(collection, slug));
559583
return backend
560-
.deleteEntry(state, collection, slug)
584+
.deleteEntry(state, collection, slug, context)
561585
.then(() => {
562586
if (!backend.implementation.deleteCollectionFiles) {
563587
backend.persistEntry({
@@ -567,6 +591,7 @@ export function unpublishPublishedEntry(collection: Collection, slug: string) {
567591
assetProxies: [],
568592
usedSlugs: List(),
569593
status: status.get('PENDING_PUBLISH'),
594+
context,
570595
});
571596
}
572597
})
@@ -598,3 +623,12 @@ export function unpublishPublishedEntry(collection: Collection, slug: string) {
598623
});
599624
};
600625
}
626+
627+
export function unpublishPublishedEntry(collection: Collection, slug: string, context: HookContext) {
628+
return (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
629+
const state = getState();
630+
const entry = selectEntry(state, collection.get('name'), slug);
631+
const unpublishFunc = unpublishCustomPublishedEntry(collection, slug, entry, context);
632+
return unpublishFunc(dispatch, getState);
633+
};
634+
}

packages/decap-cms-core/src/actions/entries.ts

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ import type {
3535
ViewFilter,
3636
ViewGroup,
3737
Entry,
38+
EntryDraft,
39+
Entries,
3840
} from '../types/redux';
3941
import type { EntryValue } from '../valueObjects/Entry';
40-
import type { Backend } from '../backend';
42+
import type { Backend, HookContext } from '../backend';
4143
import type AssetProxy from '../valueObjects/AssetProxy';
4244
import type { Set } from 'immutable';
4345

@@ -886,33 +888,19 @@ export function getSerializedEntry(collection: Collection, entry: Entry) {
886888
return serializedEntry;
887889
}
888890

889-
export function persistEntry(collection: Collection, publishStack?: boolean) {
891+
export function persistCustomEntry(
892+
collection: Collection,
893+
entryDraft: EntryDraft,
894+
context: HookContext,
895+
entries?: Entries,
896+
) {
890897
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
891898
const state = getState();
892-
const entryDraft = state.entryDraft;
893-
const fieldsErrors = entryDraft.get('fieldsErrors');
894-
const usedSlugs = selectPublishedSlugs(state, collection.get('name'));
895-
896-
// Early return if draft contains validation errors
897-
if (!fieldsErrors.isEmpty()) {
898-
const hasPresenceErrors = fieldsErrors.some(errors =>
899-
errors.some(error => error.type && error.type === ValidationErrorTypes.PRESENCE),
900-
);
901899

902-
if (hasPresenceErrors) {
903-
dispatch(
904-
addNotification({
905-
message: {
906-
key: 'ui.toast.missingRequiredField',
907-
},
908-
type: 'error',
909-
dismissAfter: 8000,
910-
}),
911-
);
912-
}
913-
914-
return Promise.reject();
915-
}
900+
const usedSlugs = selectPublishedSlugs(
901+
entries ? { ...state, entries: fromJS(entries) } : state,
902+
collection.get('name'),
903+
);
916904

917905
const backend = currentBackend(state.config);
918906
const entry = entryDraft.get('entry');
@@ -930,7 +918,7 @@ export function persistEntry(collection: Collection, publishStack?: boolean) {
930918
entryDraft: serializedEntryDraft,
931919
assetProxies,
932920
usedSlugs,
933-
publishStack,
921+
context,
934922
})
935923
.then(async (newSlug: string) => {
936924
dispatch(
@@ -973,14 +961,46 @@ export function persistEntry(collection: Collection, publishStack?: boolean) {
973961
};
974962
}
975963

976-
export function deleteEntry(collection: Collection, slug: string) {
964+
export function persistEntry(collection: Collection, context: HookContext) {
965+
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
966+
const state = getState();
967+
const entryDraft = state.entryDraft;
968+
969+
const fieldsErrors = entryDraft.get('fieldsErrors');
970+
971+
// Early return if draft contains validation errors
972+
if (!fieldsErrors.isEmpty()) {
973+
const hasPresenceErrors = fieldsErrors.some(errors =>
974+
errors.some(error => error.type && error.type === ValidationErrorTypes.PRESENCE),
975+
);
976+
977+
if (hasPresenceErrors) {
978+
dispatch(
979+
addNotification({
980+
message: {
981+
key: 'ui.toast.missingRequiredField',
982+
},
983+
type: 'error',
984+
dismissAfter: 8000,
985+
}),
986+
);
987+
}
988+
989+
return Promise.reject();
990+
}
991+
const persistFunc = persistCustomEntry(collection, entryDraft, context);
992+
return persistFunc(dispatch, getState);
993+
};
994+
}
995+
996+
export function deleteEntry(collection: Collection, slug: string, context: HookContext) {
977997
return (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
978998
const state = getState();
979999
const backend = currentBackend(state.config);
9801000

9811001
dispatch(entryDeleting(collection, slug));
9821002
return backend
983-
.deleteEntry(state, collection, slug)
1003+
.deleteEntry(state, collection, slug, context)
9841004
.then(async () => {
9851005
dispatch(entryDeleted(collection, slug));
9861006
dispatch(

0 commit comments

Comments
 (0)