feat: scaffolds admin revisions

This commit is contained in:
James
2021-11-28 16:37:21 -05:00
parent 974fdd0bfd
commit 72537106a3
16 changed files with 210 additions and 16 deletions

View File

@@ -8,6 +8,9 @@ const RichText: CollectionConfig = {
singular: 'Rich Text',
plural: 'Rich Texts',
},
admin: {
hideAPIURL: true,
},
access: {
read: () => true,
},

View File

@@ -9,6 +9,7 @@ import { requests } from '../api';
import Loading from './elements/Loading';
import StayLoggedIn from './modals/StayLoggedIn';
import Unlicensed from './views/Unlicensed';
import Revisions from './views/collections/Revisions';
const Dashboard = lazy(() => import('./views/Dashboard'));
const ForgotPassword = lazy(() => import('./views/ForgotPassword'));
@@ -177,6 +178,32 @@ const Routes = () => {
/>
))}
{collections.map((collection) => {
if (collection.revisions) {
return (
<Route
key={`${collection.slug}-revisions`}
path={`${match.url}/collections/${collection.slug}/:id/revisions`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.readRevisions?.permission) {
return (
<Revisions
{...routeProps}
collection={collection}
/>
);
}
return <Unauthorized />;
}}
/>
);
}
return null;
})}
{globals && globals.map((global) => (
<Route
key={`${global.slug}`}

View File

@@ -16,6 +16,7 @@ import fieldTypes from '../../../forms/field-types';
import RenderTitle from '../../../elements/RenderTitle';
import LeaveWithoutSaving from '../../../modals/LeaveWithoutSaving';
import Auth from './Auth';
import Revisions from './Revisions';
import Upload from './Upload';
import { Props } from './types';
@@ -38,6 +39,7 @@ const DefaultEditView: React.FC<Props> = (props) => {
apiURL,
action,
hasSavePermission,
submissionCount,
} = props;
const {
@@ -47,7 +49,9 @@ const DefaultEditView: React.FC<Props> = (props) => {
useAsTitle,
disableDuplicate,
preview,
hideAPIURL,
},
revisions,
timestamps,
auth,
upload,
@@ -165,6 +169,7 @@ const DefaultEditView: React.FC<Props> = (props) => {
</div>
{isEditing && (
<ul className={`${baseClass}__meta`}>
{!hideAPIURL && (
<li className={`${baseClass}__api-url`}>
<span className={`${baseClass}__label`}>
API URL
@@ -179,10 +184,21 @@ const DefaultEditView: React.FC<Props> = (props) => {
{apiURL}
</a>
</li>
)}
<li>
<div className={`${baseClass}__label`}>ID</div>
<div>{id}</div>
</li>
{revisions && (
<li>
<div className={`${baseClass}__label`}>Revisions</div>
<Revisions
submissionCount={submissionCount}
collection={collection}
id={id}
/>
</li>
)}
{timestamps && (
<React.Fragment>
{data.updatedAt && (

View File

@@ -0,0 +1,9 @@
@import '../../../../../scss/styles.scss';
.revisions-count__button {
font-weight: 600;
&:hover {
text-decoration: underline;
}
}

View File

@@ -0,0 +1,73 @@
import { useConfig } from '@payloadcms/config-provider';
import React, { useEffect } from 'react';
import Button from '../../../../elements/Button';
import usePayloadAPI from '../../../../../hooks/usePayloadAPI';
import { Props } from './types';
import './index.scss';
const baseClass = 'revisions-count';
const Revisions: React.FC<Props> = ({ collection, id, submissionCount }) => {
const { serverURL, routes: { admin } } = useConfig();
const [{ data, isLoading }, { setParams }] = usePayloadAPI(`${serverURL}/api/${collection.slug}/revisions`, {
initialParams: {
where: {
parent: {
equals: id,
},
},
},
});
useEffect(() => {
if (submissionCount) {
setParams({
where: {
parent: {
equals: id,
},
},
c: submissionCount,
});
}
}, [setParams, submissionCount, id]);
return (
<div className={baseClass}>
{(!isLoading && data?.docs) && (
<React.Fragment>
{data.docs.length === 0 && (
<React.Fragment>
No revisions found
</React.Fragment>
)}
{data?.docs?.length > 0 && (
<React.Fragment>
<Button
className={`${baseClass}__button`}
buttonStyle="none"
el="link"
to={`${admin}/collections/${collection.slug}/${id}/revisions`}
>
{data.docs.length}
{' '}
revision
{data.docs.length > 1 && 's'}
{' '}
found
</Button>
</React.Fragment>
)}
</React.Fragment>
)}
{isLoading && (
<React.Fragment>
Loading revisions...
</React.Fragment>
)}
</div>
);
};
export default Revisions;

View File

@@ -0,0 +1,7 @@
import { SanitizedCollectionConfig } from '../../../../../../collections/config/types';
export type Props = {
collection: SanitizedCollectionConfig,
id: string | number
submissionCount: number
}

View File

@@ -32,6 +32,7 @@ const EditView: React.FC<IndexProps> = (props) => {
} = {},
} = collection;
const [fields] = useState(() => formatFields(collection, isEditing));
const [submissionCount, setSubmissionCount] = useState(0);
const locale = useLocale();
const { serverURL, routes: { admin, api } } = useConfig();
@@ -48,6 +49,7 @@ const EditView: React.FC<IndexProps> = (props) => {
} else {
const state = await buildStateFromSchema(fields, json.doc);
setInitialState(state);
setSubmissionCount((count) => count + 1);
}
};
@@ -109,6 +111,7 @@ const EditView: React.FC<IndexProps> = (props) => {
DefaultComponent={DefaultEdit}
CustomComponent={CustomEdit}
componentProps={{
submissionCount,
isLoading,
data: dataToRender,
collection: { ...collection, fields },

View File

@@ -17,4 +17,5 @@ export type Props = IndexProps & {
apiURL: string
action: string
hasSavePermission: boolean
submissionCount: number
}

View File

@@ -0,0 +1,39 @@
import { useConfig } from '@payloadcms/config-provider';
import React from 'react';
import { useRouteMatch } from 'react-router-dom';
import usePayloadAPI from '../../../../hooks/usePayloadAPI';
import Loading from '../../../elements/Loading';
import { Props } from './types';
const baseClass = 'revisions';
const Revisions: React.FC<Props> = ({ collection }) => {
const { serverURL } = useConfig();
const { params: { id } } = useRouteMatch<{ id: string }>();
const [{ data, isLoading }] = usePayloadAPI(`${serverURL}/api/${collection.slug}/revisions`, {
initialParams: {
where: {
parent: {
equals: id,
},
},
},
});
return (
<div className={baseClass}>
{isLoading && (
<Loading />
)}
{data?.docs && (
<React.Fragment>
<h1>Revisions</h1>
{data?.docs?.length}
</React.Fragment>
)}
</div>
);
};
export default Revisions;

View File

@@ -0,0 +1,5 @@
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
export type Props = {
collection: SanitizedCollectionConfig
}

View File

@@ -36,6 +36,7 @@ const collectionSchema = joi.object().keys({
}),
preview: joi.func(),
disableDuplicate: joi.bool(),
hideAPIURL: joi.bool(),
}),
fields: joi.array(),
hooks: joi.object({

View File

@@ -128,6 +128,10 @@ export type CollectionAdminOptions = {
*/
description?: string | (() => string) | React.FC;
disableDuplicate?: boolean;
/**
* Hide the API URL within the Edit view
*/
hideAPIURL?: boolean
/**
* Custom admin components
*/

View File

@@ -8,6 +8,7 @@ type Args = {
maxPerDoc: number
entityLabel: string
entityType: 'global' | 'collection'
id: string | number
}
export const enforceMaxRevisions = async ({
@@ -16,9 +17,14 @@ export const enforceMaxRevisions = async ({
maxPerDoc,
entityLabel,
entityType,
id,
}: Args): Promise<void> => {
try {
const oldestAllowedDoc = await Model.find().limit(1).skip(maxPerDoc).sort({ createdAt: -1 });
const query: { parent?: string | number } = {};
if (id) query.parent = id;
const oldestAllowedDoc = await Model.find(query).limit(1).skip(maxPerDoc).sort({ createdAt: -1 });
if (oldestAllowedDoc?.[0]?.createdAt) {
const deleteLessThan = oldestAllowedDoc[0].createdAt;

View File

@@ -46,6 +46,7 @@ export const saveCollectionRevision = async ({
if (config.revisions.maxPerDoc) {
enforceMaxRevisions({
id,
payload: this,
Model: RevisionsModel,
entityLabel: config.labels.plural,

View File

@@ -13,7 +13,6 @@
"noEmit": false, /* Do not emit outputs. */
"strict": false, /* Enable all strict type-checking options. */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "",
"paths": {
"payload/config": [
"src/config/types.ts"