feat: initial drafts and versions merge
This commit is contained in:
5
src/admin/components/elements/Autosave/index.scss
Normal file
5
src/admin/components/elements/Autosave/index.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
.autosave {
|
||||
min-height: $baseline;
|
||||
}
|
||||
136
src/admin/components/elements/Autosave/index.tsx
Normal file
136
src/admin/components/elements/Autosave/index.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { useConfig } from '@payloadcms/config-provider';
|
||||
import { formatDistance } from 'date-fns';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useWatchForm, useFormModified } from '../../forms/Form/context';
|
||||
import { useLocale } from '../../utilities/Locale';
|
||||
import { Props } from './types';
|
||||
import reduceFieldsToValues from '../../forms/Form/reduceFieldsToValues';
|
||||
import { useDocumentInfo } from '../../utilities/DocumentInfo';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'autosave';
|
||||
|
||||
const Autosave: React.FC<Props> = ({ collection, global, id, publishedDocUpdatedAt }) => {
|
||||
const { serverURL, routes: { api, admin } } = useConfig();
|
||||
const { versions, getVersions } = useDocumentInfo();
|
||||
const { fields, dispatchFields } = useWatchForm();
|
||||
const modified = useFormModified();
|
||||
const locale = useLocale();
|
||||
const { replace } = useHistory();
|
||||
|
||||
const fieldRef = useRef(fields);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [lastSaved, setLastSaved] = useState<number>();
|
||||
|
||||
// Store fields in ref so the autosave func
|
||||
// can always retrieve the most to date copies
|
||||
// after the timeout has executed
|
||||
fieldRef.current = fields;
|
||||
|
||||
const interval = collection.versions.drafts && collection.versions.drafts.autosave ? collection.versions.drafts.autosave.interval : 5;
|
||||
|
||||
const createDoc = useCallback(async () => {
|
||||
const res = await fetch(`${serverURL}${api}/${collection.slug}?locale=${locale}&fallback-locale=null&depth=0&draft=true`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
const json = await res.json();
|
||||
replace(`${admin}/collections/${collection.slug}/${json.doc.id}`);
|
||||
} else {
|
||||
toast.error('There was a problem while autosaving this document.');
|
||||
}
|
||||
}, [collection, serverURL, api, admin, locale, replace]);
|
||||
|
||||
useEffect(() => {
|
||||
// If no ID, but this is used for a collection doc,
|
||||
// Immediately save it and set lastSaved
|
||||
if (!id && collection) {
|
||||
createDoc();
|
||||
}
|
||||
}, [id, collection, global, createDoc]);
|
||||
|
||||
// When fields change, autosave
|
||||
useEffect(() => {
|
||||
const autosave = async () => {
|
||||
if (lastSaved && modified && !saving) {
|
||||
const lastSavedDate = new Date(lastSaved);
|
||||
lastSavedDate.setSeconds(lastSavedDate.getSeconds() + interval);
|
||||
const timeToSaveAgain = lastSavedDate.getTime();
|
||||
|
||||
if (Date.now() >= timeToSaveAgain) {
|
||||
setSaving(true);
|
||||
|
||||
setTimeout(async () => {
|
||||
let url: string;
|
||||
let method: string;
|
||||
|
||||
if (collection && id) {
|
||||
url = `${serverURL}${api}/${collection.slug}/${id}?draft=true&autosave=true`;
|
||||
method = 'PUT';
|
||||
}
|
||||
|
||||
if (global) {
|
||||
url = `${serverURL}${api}/globals/${global.slug}?draft=true&autosave=true`;
|
||||
method = 'POST';
|
||||
}
|
||||
|
||||
if (url) {
|
||||
const body = {
|
||||
...reduceFieldsToValues(fieldRef.current),
|
||||
_status: 'draft',
|
||||
};
|
||||
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
setLastSaved(new Date().getTime());
|
||||
getVersions();
|
||||
}
|
||||
|
||||
setSaving(false);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
autosave();
|
||||
}, [fields, modified, interval, lastSaved, serverURL, api, collection, global, id, saving, dispatchFields, getVersions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (versions?.docs?.[0]) {
|
||||
setLastSaved(new Date(versions.docs[0].updatedAt).getTime());
|
||||
} else if (publishedDocUpdatedAt) {
|
||||
setLastSaved(new Date(publishedDocUpdatedAt).getTime());
|
||||
}
|
||||
}, [publishedDocUpdatedAt, versions]);
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
{saving && 'Saving...'}
|
||||
{(!saving && lastSaved) && (
|
||||
<React.Fragment>
|
||||
Last saved
|
||||
{formatDistance(new Date(), new Date(lastSaved))}
|
||||
ago
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Autosave;
|
||||
9
src/admin/components/elements/Autosave/types.ts
Normal file
9
src/admin/components/elements/Autosave/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
|
||||
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
|
||||
|
||||
export type Props = {
|
||||
collection?: SanitizedCollectionConfig,
|
||||
global?: SanitizedGlobalConfig,
|
||||
id?: string | number
|
||||
publishedDocUpdatedAt: string
|
||||
}
|
||||
@@ -72,8 +72,18 @@
|
||||
background-color: $color-dark-gray;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background: lighten($color-dark-gray, 5%);
|
||||
&.btn--disabled {
|
||||
background-color: rgba($color-dark-gray, .6);
|
||||
}
|
||||
|
||||
&:not(.btn--disabled) {
|
||||
&:hover {
|
||||
background: lighten($color-dark-gray, 5%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: lighten($color-dark-gray, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@@ -81,9 +91,6 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: lighten($color-dark-gray, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&--style-secondary {
|
||||
@@ -99,14 +106,20 @@
|
||||
box-shadow: $hover-box-shadow;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: lighten($color-light-gray, 7%);
|
||||
}
|
||||
|
||||
&.btn--disabled {
|
||||
color: rgba($color-dark-gray, .6);
|
||||
background: none;
|
||||
box-shadow: inset 0 0 0 $style-stroke-width rgba($color-dark-gray, .4);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: $hover-box-shadow, $focus-box-shadow;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: lighten($color-light-gray, 7%);
|
||||
}
|
||||
}
|
||||
|
||||
&--style-none {
|
||||
@@ -172,6 +185,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.btn__icon {
|
||||
@include color-svg(white);
|
||||
|
||||
@@ -88,7 +88,8 @@ const Button: React.FC<Props> = (props) => {
|
||||
const buttonProps = {
|
||||
type,
|
||||
className: classes,
|
||||
onClick: handleClick,
|
||||
disabled,
|
||||
onClick: !disabled ? handleClick : undefined,
|
||||
rel: newTab ? 'noopener noreferrer' : undefined,
|
||||
target: newTab ? '_blank' : undefined,
|
||||
};
|
||||
|
||||
@@ -55,14 +55,8 @@ const DeleteDocument: React.FC<Props> = (props) => {
|
||||
const json = await res.json();
|
||||
if (res.status < 400) {
|
||||
closeAll();
|
||||
return history.push({
|
||||
pathname: `${admin}/collections/${slug}`,
|
||||
state: {
|
||||
status: {
|
||||
message: `${singular} "${title}" successfully deleted.`,
|
||||
},
|
||||
},
|
||||
});
|
||||
toast.success(`${singular} "${title}" successfully deleted.`);
|
||||
return history.push(`${admin}/collections/${slug}`);
|
||||
}
|
||||
|
||||
closeAll();
|
||||
|
||||
11
src/admin/components/elements/IDLabel/index.scss
Normal file
11
src/admin/components/elements/IDLabel/index.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
@import '../../../scss/styles';
|
||||
|
||||
.id-label {
|
||||
font-size: base(.75);
|
||||
font-weight: normal;
|
||||
color: $color-gray;
|
||||
background: $color-background-gray;
|
||||
padding: base(.25) base(.5);
|
||||
border-radius: $style-radius-m;
|
||||
display: inline-flex;
|
||||
}
|
||||
15
src/admin/components/elements/IDLabel/index.tsx
Normal file
15
src/admin/components/elements/IDLabel/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'id-label';
|
||||
|
||||
const IDLabel: React.FC<{ id: string, prefix?: string }> = ({ id, prefix = 'ID:' }) => (
|
||||
<div className={baseClass}>
|
||||
{prefix}
|
||||
|
||||
{id}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default IDLabel;
|
||||
@@ -4,27 +4,22 @@ import { useHistory } from 'react-router-dom';
|
||||
import { useSearchParams } from '../../utilities/SearchParams';
|
||||
import Popup from '../Popup';
|
||||
import Chevron from '../../icons/Chevron';
|
||||
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
|
||||
import { defaults } from '../../../../collections/config/defaults';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'per-page';
|
||||
|
||||
const defaultLimits = defaults.admin.pagination.limits;
|
||||
|
||||
type Props = {
|
||||
collection: SanitizedCollectionConfig
|
||||
limits: number[]
|
||||
limit: number
|
||||
handleChange?: (limit: number) => void
|
||||
modifySearchParams?: boolean
|
||||
}
|
||||
|
||||
const PerPage: React.FC<Props> = ({ collection, limit, handleChange, modifySearchParams = true }) => {
|
||||
const {
|
||||
admin: {
|
||||
pagination: {
|
||||
limits,
|
||||
},
|
||||
},
|
||||
} = collection;
|
||||
|
||||
const PerPage: React.FC<Props> = ({ limits = defaultLimits, limit, handleChange, modifySearchParams = true }) => {
|
||||
const params = useSearchParams();
|
||||
const history = useHistory();
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
border-radius: $style-radius-s;
|
||||
padding: 0 base(.25);
|
||||
padding-left: base(.0875 + .25);
|
||||
cursor: default;
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
@@ -37,12 +38,14 @@
|
||||
}
|
||||
|
||||
&--style-light {
|
||||
&:hover {
|
||||
background: lighten($color-light-gray, 3%);
|
||||
}
|
||||
&.pill--has-action {
|
||||
&:hover {
|
||||
background: lighten($color-light-gray, 3%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: lighten($color-light-gray, 5%);
|
||||
&:active {
|
||||
background: lighten($color-light-gray, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +54,14 @@
|
||||
color: $color-dark-gray;
|
||||
}
|
||||
|
||||
&--style-warning {
|
||||
background: $color-yellow;
|
||||
}
|
||||
|
||||
&--style-success {
|
||||
background: $color-green;
|
||||
}
|
||||
|
||||
&--style-dark {
|
||||
background: $color-dark-gray;
|
||||
color: white;
|
||||
@@ -59,12 +70,14 @@
|
||||
@include color-svg(white);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: lighten($color-dark-gray, 3%);
|
||||
}
|
||||
&.pill--has-action {
|
||||
&:hover {
|
||||
background: lighten($color-dark-gray, 3%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: lighten($color-dark-gray, 5%);
|
||||
&:active {
|
||||
background: lighten($color-dark-gray, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ export type Props = {
|
||||
icon?: React.ReactNode,
|
||||
alignIcon?: 'left' | 'right',
|
||||
onClick?: () => void,
|
||||
pillStyle?: 'light' | 'dark' | 'light-gray',
|
||||
pillStyle?: 'light' | 'dark' | 'light-gray' | 'warning' | 'success',
|
||||
}
|
||||
|
||||
export type RenderedTypeProps = {
|
||||
|
||||
1
src/admin/components/elements/PreviewButton/index.scss
Normal file
1
src/admin/components/elements/PreviewButton/index.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
@@ -4,12 +4,14 @@ import Button from '../Button';
|
||||
import { Props } from './types';
|
||||
import { useLocale } from '../../utilities/Locale';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'preview-btn';
|
||||
|
||||
const PreviewButton: React.FC<Props> = (props) => {
|
||||
const {
|
||||
generatePreviewURL,
|
||||
data
|
||||
data,
|
||||
} = props;
|
||||
|
||||
const [url, setUrl] = useState<string | undefined>(undefined);
|
||||
@@ -22,7 +24,7 @@ const PreviewButton: React.FC<Props> = (props) => {
|
||||
const makeRequest = async () => {
|
||||
const previewURL = await generatePreviewURL(data, { locale, token });
|
||||
setUrl(previewURL);
|
||||
}
|
||||
};
|
||||
|
||||
makeRequest();
|
||||
}
|
||||
@@ -30,7 +32,7 @@ const PreviewButton: React.FC<Props> = (props) => {
|
||||
generatePreviewURL,
|
||||
locale,
|
||||
token,
|
||||
data
|
||||
data,
|
||||
]);
|
||||
|
||||
if (url) {
|
||||
|
||||
34
src/admin/components/elements/Publish/index.tsx
Normal file
34
src/admin/components/elements/Publish/index.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import FormSubmit from '../../forms/Submit';
|
||||
import { Props } from './types';
|
||||
import { useDocumentInfo } from '../../utilities/DocumentInfo';
|
||||
import { useForm, useFormModified } from '../../forms/Form/context';
|
||||
|
||||
const Publish: React.FC<Props> = () => {
|
||||
const { unpublishedVersions, publishedDoc } = useDocumentInfo();
|
||||
const { submit } = useForm();
|
||||
const modified = useFormModified();
|
||||
|
||||
const hasNewerVersions = unpublishedVersions?.totalDocs > 0;
|
||||
const canPublish = modified || hasNewerVersions || !publishedDoc;
|
||||
|
||||
const publish = useCallback(() => {
|
||||
submit({
|
||||
overrides: {
|
||||
_status: 'published',
|
||||
},
|
||||
});
|
||||
}, [submit]);
|
||||
|
||||
return (
|
||||
<FormSubmit
|
||||
type="button"
|
||||
onClick={publish}
|
||||
disabled={!canPublish}
|
||||
>
|
||||
Publish changes
|
||||
</FormSubmit>
|
||||
);
|
||||
};
|
||||
|
||||
export default Publish;
|
||||
1
src/admin/components/elements/Publish/types.ts
Normal file
1
src/admin/components/elements/Publish/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type Props = {}
|
||||
@@ -9,7 +9,8 @@ div.react-select {
|
||||
}
|
||||
|
||||
.rs__value-container {
|
||||
padding: 0;
|
||||
padding: base(.25) 0;
|
||||
min-height: base(1.5);
|
||||
|
||||
> * {
|
||||
margin-top: 0;
|
||||
@@ -20,6 +21,8 @@ div.react-select {
|
||||
|
||||
&--is-multi {
|
||||
margin-left: - base(.25);
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +43,6 @@ div.react-select {
|
||||
}
|
||||
|
||||
.rs__input {
|
||||
margin-top: base(.25);
|
||||
margin-bottom: base(.25);
|
||||
|
||||
input {
|
||||
font-family: $font-body;
|
||||
width: 100% !important;
|
||||
|
||||
@@ -14,6 +14,7 @@ const ReactSelect: React.FC<Props> = (props) => {
|
||||
value,
|
||||
disabled = false,
|
||||
placeholder,
|
||||
isSearchable = true,
|
||||
} = props;
|
||||
|
||||
const classes = [
|
||||
@@ -33,6 +34,7 @@ const ReactSelect: React.FC<Props> = (props) => {
|
||||
className={classes}
|
||||
classNamePrefix="rs"
|
||||
options={options}
|
||||
isSearchable={isSearchable}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,4 +20,5 @@ export type Props = {
|
||||
onInputChange?: (val: string) => void
|
||||
onMenuScrollToBottom?: () => void
|
||||
placeholder?: string
|
||||
isSearchable?: boolean
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
.render-title {
|
||||
&--id-as-title {
|
||||
font-size: base(.75);
|
||||
font-weight: normal;
|
||||
color: $color-gray;
|
||||
background: $color-background-gray;
|
||||
padding: base(.25) base(.5);
|
||||
border-radius: $style-radius-m;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import { Props } from './types';
|
||||
import useTitle from '../../../hooks/useTitle';
|
||||
|
||||
import './index.scss';
|
||||
import IDLabel from '../IDLabel';
|
||||
|
||||
const baseClass = 'render-title';
|
||||
|
||||
@@ -25,18 +24,14 @@ const RenderTitle : React.FC<Props> = (props) => {
|
||||
|
||||
const idAsTitle = title === data?.id;
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
idAsTitle && `${baseClass}--id-as-title`,
|
||||
].filter(Boolean).join(' ');
|
||||
if (idAsTitle) {
|
||||
return (
|
||||
<IDLabel id={data?.id} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={classes}>
|
||||
{idAsTitle && (
|
||||
<Fragment>
|
||||
ID:
|
||||
</Fragment>
|
||||
)}
|
||||
<span className={baseClass}>
|
||||
{title}
|
||||
</span>
|
||||
);
|
||||
|
||||
0
src/admin/components/elements/SaveDraft/index.scss
Normal file
0
src/admin/components/elements/SaveDraft/index.scss
Normal file
57
src/admin/components/elements/SaveDraft/index.tsx
Normal file
57
src/admin/components/elements/SaveDraft/index.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useConfig } from '@payloadcms/config-provider';
|
||||
import FormSubmit from '../../forms/Submit';
|
||||
import { useForm, useFormModified } from '../../forms/Form/context';
|
||||
import { useDocumentInfo } from '../../utilities/DocumentInfo';
|
||||
import { useLocale } from '../../utilities/Locale';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'save-draft';
|
||||
|
||||
const SaveDraft: React.FC = () => {
|
||||
const { serverURL, routes: { api } } = useConfig();
|
||||
const { submit } = useForm();
|
||||
const { collection, global, id } = useDocumentInfo();
|
||||
const modified = useFormModified();
|
||||
const locale = useLocale();
|
||||
|
||||
const canSaveDraft = modified;
|
||||
|
||||
const saveDraft = useCallback(() => {
|
||||
const search = `?locale=${locale}&depth=0&fallback-locale=null&draft=true`;
|
||||
let action;
|
||||
let method = 'POST';
|
||||
|
||||
if (collection) {
|
||||
action = `${serverURL}${api}/${collection.slug}${id ? `/${id}` : ''}${search}`;
|
||||
if (id) method = 'PUT';
|
||||
}
|
||||
|
||||
if (global) {
|
||||
action = `${serverURL}${api}/globals/${global.slug}${search}`;
|
||||
}
|
||||
|
||||
submit({
|
||||
action,
|
||||
method,
|
||||
overrides: {
|
||||
_status: 'draft',
|
||||
},
|
||||
});
|
||||
}, [submit, collection, global, serverURL, api, locale, id]);
|
||||
|
||||
return (
|
||||
<FormSubmit
|
||||
className={baseClass}
|
||||
type="button"
|
||||
buttonStyle="secondary"
|
||||
onClick={saveDraft}
|
||||
disabled={!canSaveDraft}
|
||||
>
|
||||
Save draft
|
||||
</FormSubmit>
|
||||
);
|
||||
};
|
||||
|
||||
export default SaveDraft;
|
||||
11
src/admin/components/elements/Status/index.scss
Normal file
11
src/admin/components/elements/Status/index.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
.status {
|
||||
&__label {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
35
src/admin/components/elements/Status/index.tsx
Normal file
35
src/admin/components/elements/Status/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Props } from './types';
|
||||
import { useDocumentInfo } from '../../utilities/DocumentInfo';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'status';
|
||||
|
||||
const Status: React.FC<Props> = () => {
|
||||
const { publishedDoc, unpublishedVersions } = useDocumentInfo();
|
||||
|
||||
let statusToRender;
|
||||
|
||||
if (unpublishedVersions?.docs?.length > 0 && publishedDoc) {
|
||||
statusToRender = 'Changed';
|
||||
} else if (!publishedDoc) {
|
||||
statusToRender = 'Draft';
|
||||
} else if (publishedDoc && unpublishedVersions?.docs?.length <= 1) {
|
||||
statusToRender = 'Published';
|
||||
}
|
||||
|
||||
if (statusToRender) {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__value`}>
|
||||
{statusToRender}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Status;
|
||||
3
src/admin/components/elements/Status/types.ts
Normal file
3
src/admin/components/elements/Status/types.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export type Props = {
|
||||
|
||||
}
|
||||
9
src/admin/components/elements/VersionsCount/index.scss
Normal file
9
src/admin/components/elements/VersionsCount/index.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
.versions-count__button {
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
56
src/admin/components/elements/VersionsCount/index.tsx
Normal file
56
src/admin/components/elements/VersionsCount/index.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useConfig } from '@payloadcms/config-provider';
|
||||
import React from 'react';
|
||||
import Button from '../Button';
|
||||
import { Props } from './types';
|
||||
|
||||
import './index.scss';
|
||||
import { useDocumentInfo } from '../../utilities/DocumentInfo';
|
||||
|
||||
const baseClass = 'versions-count';
|
||||
|
||||
const Versions: React.FC<Props> = ({ collection, global, id }) => {
|
||||
const { routes: { admin } } = useConfig();
|
||||
const { versions } = useDocumentInfo();
|
||||
|
||||
let versionsURL: string;
|
||||
|
||||
if (collection) {
|
||||
versionsURL = `${admin}/collections/${collection.slug}/${id}/versions`;
|
||||
}
|
||||
|
||||
if (global) {
|
||||
versionsURL = `${admin}/globals/${global.slug}/versions`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
{versions?.docs && (
|
||||
<React.Fragment>
|
||||
{versions.docs.length === 0 && (
|
||||
<React.Fragment>
|
||||
No versions found
|
||||
</React.Fragment>
|
||||
)}
|
||||
{versions?.docs?.length > 0 && (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
className={`${baseClass}__button`}
|
||||
buttonStyle="none"
|
||||
el="link"
|
||||
to={versionsURL}
|
||||
>
|
||||
{versions.totalDocs}
|
||||
{' '}
|
||||
version
|
||||
{versions.totalDocs > 1 && 's'}
|
||||
{' '}
|
||||
found
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Versions;
|
||||
8
src/admin/components/elements/VersionsCount/types.ts
Normal file
8
src/admin/components/elements/VersionsCount/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
|
||||
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
|
||||
|
||||
export type Props = {
|
||||
collection?: SanitizedCollectionConfig,
|
||||
global?: SanitizedGlobalConfig
|
||||
id?: string | number
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import optionsReducer from './optionsReducer';
|
||||
import useDebounce from '../../../../../hooks/useDebounce';
|
||||
import ReactSelect from '../../../ReactSelect';
|
||||
import { Value } from '../../../ReactSelect/types';
|
||||
import { PaginatedDocs } from '../../../../../../collections/config/types';
|
||||
import { PaginatedDocs } from '../../../../../../mongoose/types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { RelationshipField } from '../../../../../../fields/config/types';
|
||||
import { PaginatedDocs, SanitizedCollectionConfig } from '../../../../../../collections/config/types';
|
||||
import { SanitizedCollectionConfig } from '../../../../../../collections/config/types';
|
||||
import { PaginatedDocs } from '../../../../../../mongoose/types';
|
||||
|
||||
export type Props = {
|
||||
onChange: (val: unknown) => void,
|
||||
|
||||
Reference in New Issue
Block a user