feat: local backup enhancements (#714)
This commit is contained in:
parent
39bb9647b2
commit
804c09415b
@ -11,7 +11,7 @@ const PostPreview = ({ entry, widgetFor }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PostPreviewCard = ({ entry, theme }) => {
|
const PostPreviewCard = ({ entry, theme, hasLocalBackup }) => {
|
||||||
const date = new Date(entry.data.date);
|
const date = new Date(entry.data.date);
|
||||||
|
|
||||||
const month = date.getMonth() + 1;
|
const month = date.getMonth() + 1;
|
||||||
@ -70,19 +70,53 @@ const PostPreviewCard = ({ entry, theme }) => {
|
|||||||
'div',
|
'div',
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: entry.data.draft === true ? 'blue' : 'green',
|
display: 'flex',
|
||||||
color: 'white',
|
alignItems: 'center',
|
||||||
border: 'none',
|
whiteSpace: 'no-wrap',
|
||||||
padding: '2px 6px',
|
gap: '8px',
|
||||||
textAlign: 'center',
|
|
||||||
textDecoration: 'none',
|
|
||||||
display: 'inline-block',
|
|
||||||
cursor: 'pointer',
|
|
||||||
borderRadius: '4px',
|
|
||||||
fontSize: '14px',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
entry.data.draft === true ? 'Draft' : 'Published',
|
hasLocalBackup
|
||||||
|
? h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
border: '2px solid rgb(147, 197, 253)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
color: 'rgb(147, 197, 253)',
|
||||||
|
height: '18px',
|
||||||
|
width: '18px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: '11px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
title: 'Has local backup'
|
||||||
|
},
|
||||||
|
'i',
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
backgroundColor:
|
||||||
|
entry.data.draft === true ? 'rgb(37, 99, 235)' : 'rgb(22, 163, 74)',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
padding: '2px 6px',
|
||||||
|
textAlign: 'center',
|
||||||
|
textDecoration: 'none',
|
||||||
|
display: 'inline-block',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '14px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
entry.data.draft === true ? 'Draft' : 'Published',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
getPathDepth,
|
getPathDepth,
|
||||||
localForage,
|
localForage,
|
||||||
} from './lib/util';
|
} from './lib/util';
|
||||||
|
import { getEntryBackupKey } from './lib/util/backup.util';
|
||||||
import {
|
import {
|
||||||
selectAllowDeletion,
|
selectAllowDeletion,
|
||||||
selectAllowNewEntries,
|
selectAllowNewEntries,
|
||||||
@ -47,6 +48,7 @@ import createEntry from './valueObjects/createEntry';
|
|||||||
import type {
|
import type {
|
||||||
BackendClass,
|
BackendClass,
|
||||||
BackendInitializer,
|
BackendInitializer,
|
||||||
|
BackupEntry,
|
||||||
BaseField,
|
BaseField,
|
||||||
Collection,
|
Collection,
|
||||||
CollectionFile,
|
CollectionFile,
|
||||||
@ -104,15 +106,6 @@ export class LocalStorageAuthStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEntryBackupKey(collectionName?: string, slug?: string) {
|
|
||||||
const baseKey = 'backup';
|
|
||||||
if (!collectionName) {
|
|
||||||
return baseKey;
|
|
||||||
}
|
|
||||||
const suffix = slug ? `.${slug}` : '';
|
|
||||||
return `${baseKey}.${collectionName}${suffix}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEntryField(field: string, entry: Entry): string {
|
export function getEntryField(field: string, entry: Entry): string {
|
||||||
const value = get(entry.data, field);
|
const value = get(entry.data, field);
|
||||||
if (value) {
|
if (value) {
|
||||||
@ -254,13 +247,6 @@ export interface MediaFile {
|
|||||||
isDirectory?: boolean;
|
isDirectory?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BackupEntry {
|
|
||||||
raw: string;
|
|
||||||
path: string;
|
|
||||||
mediaFiles: MediaFile[];
|
|
||||||
i18n?: Record<string, { raw: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectionDepth<EF extends BaseField>(collection: Collection<EF>) {
|
function collectionDepth<EF extends BaseField>(collection: Collection<EF>) {
|
||||||
let depth;
|
let depth;
|
||||||
depth =
|
depth =
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
|
import { Info as InfoIcon } from '@styled-icons/material-outlined/Info';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { VIEW_STYLE_LIST } from '@staticcms/core/constants/views';
|
import { VIEW_STYLE_LIST } from '@staticcms/core/constants/views';
|
||||||
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
|
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
|
||||||
import { getFieldPreview, getPreviewCard } from '@staticcms/core/lib/registry';
|
import { getFieldPreview, getPreviewCard } from '@staticcms/core/lib/registry';
|
||||||
|
import { getEntryBackupKey } from '@staticcms/core/lib/util/backup.util';
|
||||||
import {
|
import {
|
||||||
selectEntryCollectionTitle,
|
selectEntryCollectionTitle,
|
||||||
selectFields,
|
selectFields,
|
||||||
selectTemplateName,
|
selectTemplateName,
|
||||||
} from '@staticcms/core/lib/util/collection.util';
|
} from '@staticcms/core/lib/util/collection.util';
|
||||||
|
import localForage from '@staticcms/core/lib/util/localForage';
|
||||||
import { isNullish } from '@staticcms/core/lib/util/null.util';
|
import { isNullish } from '@staticcms/core/lib/util/null.util';
|
||||||
import { selectConfig } from '@staticcms/core/reducers/selectors/config';
|
import { selectConfig } from '@staticcms/core/reducers/selectors/config';
|
||||||
import { selectTheme } from '@staticcms/core/reducers/selectors/globalUI';
|
import { selectTheme } from '@staticcms/core/reducers/selectors/globalUI';
|
||||||
@ -22,7 +25,15 @@ import TableRow from '../../common/table/TableRow';
|
|||||||
import useWidgetsFor from '../../common/widget/useWidgetsFor';
|
import useWidgetsFor from '../../common/widget/useWidgetsFor';
|
||||||
|
|
||||||
import type { ViewStyle } from '@staticcms/core/constants/views';
|
import type { ViewStyle } from '@staticcms/core/constants/views';
|
||||||
import type { Collection, Entry, FileOrImageField, MediaField } from '@staticcms/core/interface';
|
import type {
|
||||||
|
BackupEntry,
|
||||||
|
Collection,
|
||||||
|
Entry,
|
||||||
|
FileOrImageField,
|
||||||
|
MediaField,
|
||||||
|
TranslatedProps,
|
||||||
|
} from '@staticcms/core/interface';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
export interface EntryCardProps {
|
export interface EntryCardProps {
|
||||||
entry: Entry;
|
entry: Entry;
|
||||||
@ -33,14 +44,15 @@ export interface EntryCardProps {
|
|||||||
summaryFields: string[];
|
summaryFields: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const EntryCard = ({
|
const EntryCard: FC<TranslatedProps<EntryCardProps>> = ({
|
||||||
collection,
|
collection,
|
||||||
entry,
|
entry,
|
||||||
collectionLabel,
|
collectionLabel,
|
||||||
viewStyle = VIEW_STYLE_LIST,
|
viewStyle = VIEW_STYLE_LIST,
|
||||||
imageFieldName,
|
imageFieldName,
|
||||||
summaryFields,
|
summaryFields,
|
||||||
}: EntryCardProps) => {
|
t,
|
||||||
|
}) => {
|
||||||
const entryData = entry.data;
|
const entryData = entry.data;
|
||||||
|
|
||||||
const path = useMemo(
|
const path = useMemo(
|
||||||
@ -86,6 +98,31 @@ const EntryCard = ({
|
|||||||
|
|
||||||
const theme = useAppSelector(selectTheme);
|
const theme = useAppSelector(selectTheme);
|
||||||
|
|
||||||
|
const [hasLocalBackup, setHasLocalBackup] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
let alive = true;
|
||||||
|
|
||||||
|
const checkLocalBackup = async () => {
|
||||||
|
const key = getEntryBackupKey(collection.name, entry.slug);
|
||||||
|
const backup = await localForage.getItem<BackupEntry>(key);
|
||||||
|
|
||||||
|
if (alive) {
|
||||||
|
setHasLocalBackup(Boolean(backup));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkLocalBackup();
|
||||||
|
|
||||||
|
// Check again after small delay to ensure we capture the draft just made from the editor
|
||||||
|
setTimeout(() => {
|
||||||
|
checkLocalBackup();
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
alive = false;
|
||||||
|
};
|
||||||
|
}, [collection.name, entry.slug]);
|
||||||
|
|
||||||
if (viewStyle === VIEW_STYLE_LIST) {
|
if (viewStyle === VIEW_STYLE_LIST) {
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
@ -129,6 +166,19 @@ const EntryCard = ({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
<TableCell key="unsavedChanges" to={path} shrink>
|
||||||
|
{hasLocalBackup ? (
|
||||||
|
<InfoIcon
|
||||||
|
className="
|
||||||
|
w-5
|
||||||
|
h-5
|
||||||
|
text-blue-600
|
||||||
|
dark:text-blue-300
|
||||||
|
"
|
||||||
|
title={t('ui.localBackup.hasLocalBackup')}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -144,6 +194,7 @@ const EntryCard = ({
|
|||||||
widgetFor={widgetFor}
|
widgetFor={widgetFor}
|
||||||
widgetsFor={widgetsFor}
|
widgetsFor={widgetsFor}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
hasLocalBackup={hasLocalBackup}
|
||||||
/>
|
/>
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
</Card>
|
</Card>
|
||||||
@ -154,7 +205,22 @@ const EntryCard = ({
|
|||||||
<Card>
|
<Card>
|
||||||
<CardActionArea to={path}>
|
<CardActionArea to={path}>
|
||||||
{image && imageField ? <CardMedia height="140" image={imageUrl} /> : null}
|
{image && imageField ? <CardMedia height="140" image={imageUrl} /> : null}
|
||||||
<CardContent>{summary}</CardContent>
|
<CardContent>
|
||||||
|
<div className="flex w-full items-center justify-between">
|
||||||
|
<div>{summary}</div>
|
||||||
|
{hasLocalBackup ? (
|
||||||
|
<InfoIcon
|
||||||
|
className="
|
||||||
|
w-5
|
||||||
|
h-5
|
||||||
|
text-blue-600
|
||||||
|
dark:text-blue-300
|
||||||
|
"
|
||||||
|
title={t('ui.localBackup.hasLocalBackup')}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import { translate } from 'react-polyglot';
|
||||||
import { Waypoint } from 'react-waypoint';
|
import { Waypoint } from 'react-waypoint';
|
||||||
|
|
||||||
import { selectFields, selectInferredField } from '@staticcms/core/lib/util/collection.util';
|
import { selectFields, selectInferredField } from '@staticcms/core/lib/util/collection.util';
|
||||||
@ -7,8 +8,15 @@ import Table from '../../common/table/Table';
|
|||||||
import EntryCard from './EntryCard';
|
import EntryCard from './EntryCard';
|
||||||
|
|
||||||
import type { ViewStyle } from '@staticcms/core/constants/views';
|
import type { ViewStyle } from '@staticcms/core/constants/views';
|
||||||
import type { Collection, Collections, Entry, Field } from '@staticcms/core/interface';
|
import type {
|
||||||
|
Collection,
|
||||||
|
Collections,
|
||||||
|
Entry,
|
||||||
|
Field,
|
||||||
|
TranslatedProps,
|
||||||
|
} from '@staticcms/core/interface';
|
||||||
import type Cursor from '@staticcms/core/lib/util/Cursor';
|
import type Cursor from '@staticcms/core/lib/util/Cursor';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
export interface BaseEntryListingProps {
|
export interface BaseEntryListingProps {
|
||||||
entries: Entry[];
|
entries: Entry[];
|
||||||
@ -30,14 +38,15 @@ export type EntryListingProps =
|
|||||||
| SingleCollectionEntryListingProps
|
| SingleCollectionEntryListingProps
|
||||||
| MultipleCollectionEntryListingProps;
|
| MultipleCollectionEntryListingProps;
|
||||||
|
|
||||||
const EntryListing = ({
|
const EntryListing: FC<TranslatedProps<EntryListingProps>> = ({
|
||||||
entries,
|
entries,
|
||||||
page,
|
page,
|
||||||
cursor,
|
cursor,
|
||||||
viewStyle,
|
viewStyle,
|
||||||
handleCursorActions,
|
handleCursorActions,
|
||||||
|
t,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: EntryListingProps) => {
|
}) => {
|
||||||
const hasMore = useMemo(() => cursor?.actions?.has('append_next'), [cursor?.actions]);
|
const hasMore = useMemo(() => cursor?.actions?.has('append_next'), [cursor?.actions]);
|
||||||
|
|
||||||
const handleLoadMore = useCallback(() => {
|
const handleLoadMore = useCallback(() => {
|
||||||
@ -95,6 +104,7 @@ const EntryListing = ({
|
|||||||
entry={entry}
|
entry={entry}
|
||||||
key={entry.slug}
|
key={entry.slug}
|
||||||
summaryFields={summaryFields}
|
summaryFields={summaryFields}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -116,10 +126,11 @@ const EntryListing = ({
|
|||||||
collectionLabel={collectionLabel}
|
collectionLabel={collectionLabel}
|
||||||
key={entry.slug}
|
key={entry.slug}
|
||||||
summaryFields={summaryFields}
|
summaryFields={summaryFields}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
});
|
});
|
||||||
}, [entries, inferFields, isSingleCollectionInList, otherProps, summaryFields, viewStyle]);
|
}, [entries, inferFields, isSingleCollectionInList, otherProps, summaryFields, t, viewStyle]);
|
||||||
|
|
||||||
const summaryFieldHeaders = useMemo(() => {
|
const summaryFieldHeaders = useMemo(() => {
|
||||||
if ('collection' in otherProps) {
|
if ('collection' in otherProps) {
|
||||||
@ -143,7 +154,9 @@ const EntryListing = ({
|
|||||||
<>
|
<>
|
||||||
<Table
|
<Table
|
||||||
columns={
|
columns={
|
||||||
!isSingleCollectionInList ? ['Collection', ...summaryFieldHeaders] : summaryFieldHeaders
|
!isSingleCollectionInList
|
||||||
|
? ['Collection', ...summaryFieldHeaders, '']
|
||||||
|
: [...summaryFieldHeaders, '']
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{renderedCards}
|
{renderedCards}
|
||||||
@ -171,4 +184,4 @@ const EntryListing = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EntryListing;
|
export default translate()(EntryListing) as FC<EntryListingProps>;
|
||||||
|
@ -8,7 +8,7 @@ import type { CSSProperties, FC, MouseEventHandler, ReactNode, Ref } from 'react
|
|||||||
|
|
||||||
export interface BaseBaseProps {
|
export interface BaseBaseProps {
|
||||||
variant?: 'contained' | 'outlined' | 'text';
|
variant?: 'contained' | 'outlined' | 'text';
|
||||||
color?: 'primary' | 'secondary' | 'success' | 'error';
|
color?: 'primary' | 'secondary' | 'success' | 'error' | 'warning';
|
||||||
size?: 'medium' | 'small';
|
size?: 'medium' | 'small';
|
||||||
rounded?: boolean | 'no-padding';
|
rounded?: boolean | 'no-padding';
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -11,18 +11,21 @@ const classes: Record<
|
|||||||
secondary: 'btn-contained-secondary',
|
secondary: 'btn-contained-secondary',
|
||||||
success: 'btn-contained-success',
|
success: 'btn-contained-success',
|
||||||
error: 'btn-contained-error',
|
error: 'btn-contained-error',
|
||||||
|
warning: 'btn-contained-warning',
|
||||||
},
|
},
|
||||||
outlined: {
|
outlined: {
|
||||||
primary: 'btn-outlined-primary',
|
primary: 'btn-outlined-primary',
|
||||||
secondary: 'btn-outlined-secondary',
|
secondary: 'btn-outlined-secondary',
|
||||||
success: 'btn-outlined-success',
|
success: 'btn-outlined-success',
|
||||||
error: 'btn-outlined-error',
|
error: 'btn-outlined-error',
|
||||||
|
warning: 'btn-outlined-warning',
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
primary: 'btn-text-primary',
|
primary: 'btn-text-primary',
|
||||||
secondary: 'btn-text-secondary',
|
secondary: 'btn-text-secondary',
|
||||||
success: 'btn-text-success',
|
success: 'btn-text-success',
|
||||||
error: 'btn-text-error',
|
error: 'btn-text-error',
|
||||||
|
warning: 'btn-text-warning',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ interface CardContentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CardContent = ({ children }: CardContentProps) => {
|
const CardContent = ({ children }: CardContentProps) => {
|
||||||
return <p className="p-5 font-normal text-gray-700 dark:text-gray-300">{children}</p>;
|
return <p className="w-full p-5 font-normal text-gray-700 dark:text-gray-300">{children}</p>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CardContent;
|
export default CardContent;
|
||||||
|
@ -13,7 +13,7 @@ interface ConfirmProps {
|
|||||||
body: string | { key: string; options?: Record<string, unknown> };
|
body: string | { key: string; options?: Record<string, unknown> };
|
||||||
cancel?: string | { key: string; options?: Record<string, unknown> };
|
cancel?: string | { key: string; options?: Record<string, unknown> };
|
||||||
confirm?: string | { key: string; options?: Record<string, unknown> };
|
confirm?: string | { key: string; options?: Record<string, unknown> };
|
||||||
color?: 'success' | 'error' | 'primary';
|
color?: 'success' | 'error' | 'warning' | 'primary';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfirmDialogProps extends ConfirmProps {
|
export interface ConfirmDialogProps extends ConfirmProps {
|
||||||
@ -120,7 +120,7 @@ const ConfirmDialog = ({ t }: TranslateProps) => {
|
|||||||
gap-2
|
gap-2
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<Button onClick={handleCancel} variant="text">
|
<Button onClick={handleCancel} variant="text" color="secondary">
|
||||||
{cancel}
|
{cancel}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleConfirm} variant="contained" color={color}>
|
<Button onClick={handleConfirm} variant="contained" color={color}>
|
||||||
|
@ -13,7 +13,7 @@ export interface MenuItemButtonProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
startIcon?: FC<{ className?: string }>;
|
startIcon?: FC<{ className?: string }>;
|
||||||
endIcon?: FC<{ className?: string }>;
|
endIcon?: FC<{ className?: string }>;
|
||||||
color?: 'default' | 'error';
|
color?: 'default' | 'error' | 'warning';
|
||||||
'data-testid'?: string;
|
'data-testid'?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,19 +46,32 @@ const MenuItemButton = ({
|
|||||||
items-center
|
items-center
|
||||||
justify-between
|
justify-between
|
||||||
cursor-pointer
|
cursor-pointer
|
||||||
hover:bg-gray-200
|
|
||||||
dark:hover:bg-slate-600
|
|
||||||
dark:disabled:text-gray-700
|
dark:disabled:text-gray-700
|
||||||
`,
|
`,
|
||||||
color === 'default' &&
|
color === 'default' &&
|
||||||
`
|
`
|
||||||
text-gray-700
|
text-gray-700
|
||||||
dark:text-gray-300
|
dark:text-gray-300
|
||||||
|
hover:bg-gray-200
|
||||||
|
dark:hover:bg-slate-600
|
||||||
|
`,
|
||||||
|
color === 'warning' &&
|
||||||
|
`
|
||||||
|
text-yellow-600
|
||||||
|
dark:text-yellow-500
|
||||||
|
hover:text-white
|
||||||
|
hover:bg-yellow-500
|
||||||
|
dark:hover:text-yellow-100
|
||||||
|
dark:hover:bg-yellow-600
|
||||||
`,
|
`,
|
||||||
color === 'error' &&
|
color === 'error' &&
|
||||||
`
|
`
|
||||||
text-red-500
|
text-red-500
|
||||||
dark:text-red-500
|
dark:text-red-500
|
||||||
|
hover:text-white
|
||||||
|
hover:bg-red-500
|
||||||
|
dark:hover:text-red-100
|
||||||
|
dark:hover:bg-red-600
|
||||||
`,
|
`,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -9,9 +9,10 @@ interface TableCellProps {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
emphasis?: boolean;
|
emphasis?: boolean;
|
||||||
to?: string;
|
to?: string;
|
||||||
|
shrink?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TableCell = ({ children, emphasis = false, to }: TableCellProps) => {
|
const TableCell = ({ children, emphasis = false, to, shrink = false }: TableCellProps) => {
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
if (to) {
|
if (to) {
|
||||||
return (
|
return (
|
||||||
@ -39,6 +40,7 @@ const TableCell = ({ children, emphasis = false, to }: TableCellProps) => {
|
|||||||
!to ? 'px-4 py-3' : 'p-0',
|
!to ? 'px-4 py-3' : 'p-0',
|
||||||
'text-gray-500 dark:text-gray-300',
|
'text-gray-500 dark:text-gray-300',
|
||||||
emphasis && 'font-medium text-gray-900 whitespace-nowrap dark:text-white',
|
emphasis && 'font-medium text-gray-900 whitespace-nowrap dark:text-white',
|
||||||
|
shrink && 'w-0',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { createHashHistory } from 'history';
|
import { createHashHistory } from 'history';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { translate } from 'react-polyglot';
|
import { translate } from 'react-polyglot';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import isEqual from 'lodash/isEqual';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createDraftDuplicateFromEntry,
|
createDraftDuplicateFromEntry,
|
||||||
@ -21,8 +21,9 @@ import {
|
|||||||
import { loadScroll, toggleScroll } from '@staticcms/core/actions/scroll';
|
import { loadScroll, toggleScroll } from '@staticcms/core/actions/scroll';
|
||||||
import { selectFields } from '@staticcms/core/lib/util/collection.util';
|
import { selectFields } from '@staticcms/core/lib/util/collection.util';
|
||||||
import { useWindowEvent } from '@staticcms/core/lib/util/window.util';
|
import { useWindowEvent } from '@staticcms/core/lib/util/window.util';
|
||||||
|
import { selectConfig } from '@staticcms/core/reducers/selectors/config';
|
||||||
import { selectEntry } from '@staticcms/core/reducers/selectors/entries';
|
import { selectEntry } from '@staticcms/core/reducers/selectors/entries';
|
||||||
import { useAppDispatch } from '@staticcms/core/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@staticcms/core/store/hooks';
|
||||||
import confirm from '../common/confirm/Confirm';
|
import confirm from '../common/confirm/Confirm';
|
||||||
import Loader from '../common/progress/Loader';
|
import Loader from '../common/progress/Loader';
|
||||||
import MediaLibraryModal from '../media-library/MediaLibraryModal';
|
import MediaLibraryModal from '../media-library/MediaLibraryModal';
|
||||||
@ -61,23 +62,32 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
|
|||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const config = useAppSelector(selectConfig);
|
||||||
|
|
||||||
const createBackup = useMemo(
|
const createBackup = useMemo(
|
||||||
() =>
|
() =>
|
||||||
debounce(function (entry: Entry, collection: Collection) {
|
debounce(function (entry: Entry, collection: Collection) {
|
||||||
|
if (config?.disable_local_backup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(persistLocalBackup(entry, collection));
|
dispatch(persistLocalBackup(entry, collection));
|
||||||
}, 2000),
|
}, 2000),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[],
|
[config],
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteBackup = useCallback(() => {
|
const deleteBackup = useCallback(() => {
|
||||||
|
if (config?.disable_local_backup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
createBackup.cancel();
|
createBackup.cancel();
|
||||||
if (slug) {
|
if (slug) {
|
||||||
dispatch(deleteLocalBackup(collection, slug));
|
dispatch(deleteLocalBackup(collection, slug));
|
||||||
}
|
}
|
||||||
dispatch(deleteDraftLocalBackup());
|
dispatch(deleteDraftLocalBackup());
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [config?.disable_local_backup, createBackup, slug, dispatch, collection]);
|
||||||
}, [collection, createBackup, slug]);
|
|
||||||
|
|
||||||
const [submitted, setSubmitted] = useState(false);
|
const [submitted, setSubmitted] = useState(false);
|
||||||
const handlePersistEntry = useCallback(
|
const handlePersistEntry = useCallback(
|
||||||
@ -166,6 +176,10 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
|
|||||||
>();
|
>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (config?.disable_local_backup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!prevLocalBackup &&
|
!prevLocalBackup &&
|
||||||
localBackup &&
|
localBackup &&
|
||||||
@ -173,17 +187,8 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
|
|||||||
!isEqual(localBackup.entry.meta, entryDraft.entry?.meta))
|
!isEqual(localBackup.entry.meta, entryDraft.entry?.meta))
|
||||||
) {
|
) {
|
||||||
const updateLocalBackup = async () => {
|
const updateLocalBackup = async () => {
|
||||||
const confirmLoadBackupBody = await confirm({
|
dispatch(loadLocalBackup());
|
||||||
title: 'editor.editor.confirmLoadBackupTitle',
|
setVersion(version + 1);
|
||||||
body: 'editor.editor.confirmLoadBackupBody',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (confirmLoadBackupBody) {
|
|
||||||
dispatch(loadLocalBackup());
|
|
||||||
setVersion(version + 1);
|
|
||||||
} else {
|
|
||||||
deleteBackup();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateLocalBackup();
|
updateLocalBackup();
|
||||||
@ -191,6 +196,7 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
|
|||||||
|
|
||||||
setPrevLocalBackup(localBackup);
|
setPrevLocalBackup(localBackup);
|
||||||
}, [
|
}, [
|
||||||
|
config?.disable_local_backup,
|
||||||
deleteBackup,
|
deleteBackup,
|
||||||
dispatch,
|
dispatch,
|
||||||
entryDraft.entry?.data,
|
entryDraft.entry?.data,
|
||||||
@ -219,14 +225,25 @@ const Editor: FC<TranslatedProps<EditorProps>> = ({
|
|||||||
});
|
});
|
||||||
} else if (!newRecord && slug && (prevCollection !== collection || prevSlug !== slug)) {
|
} else if (!newRecord && slug && (prevCollection !== collection || prevSlug !== slug)) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
dispatch(retrieveLocalBackup(collection, slug));
|
if (!config?.disable_local_backup) {
|
||||||
|
dispatch(retrieveLocalBackup(collection, slug));
|
||||||
|
}
|
||||||
dispatch(loadEntry(collection, slug));
|
dispatch(loadEntry(collection, slug));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setPrevCollection(collection);
|
setPrevCollection(collection);
|
||||||
setPrevSlug(slug);
|
setPrevSlug(slug);
|
||||||
}, [collection, entryDraft.entry, prevSlug, prevCollection, slug, dispatch, newRecord]);
|
}, [
|
||||||
|
collection,
|
||||||
|
entryDraft.entry,
|
||||||
|
prevSlug,
|
||||||
|
prevCollection,
|
||||||
|
slug,
|
||||||
|
dispatch,
|
||||||
|
newRecord,
|
||||||
|
config?.disable_local_backup,
|
||||||
|
]);
|
||||||
|
|
||||||
const leaveMessage = useMemo(() => t('editor.editor.onLeavePage'), [t]);
|
const leaveMessage = useMemo(() => t('editor.editor.onLeavePage'), [t]);
|
||||||
|
|
||||||
|
@ -280,6 +280,7 @@ const EditorInterface = ({
|
|||||||
togglePreview={handleTogglePreview}
|
togglePreview={handleTogglePreview}
|
||||||
toggleScrollSync={handleToggleScrollSync}
|
toggleScrollSync={handleToggleScrollSync}
|
||||||
toggleI18n={handleToggleI18n}
|
toggleI18n={handleToggleI18n}
|
||||||
|
slug={slug}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -7,13 +7,16 @@ import { Height as HeightIcon } from '@styled-icons/material-rounded/Height';
|
|||||||
import { Check as CheckIcon } from '@styled-icons/material/Check';
|
import { Check as CheckIcon } from '@styled-icons/material/Check';
|
||||||
import { MoreVert as MoreVertIcon } from '@styled-icons/material/MoreVert';
|
import { MoreVert as MoreVertIcon } from '@styled-icons/material/MoreVert';
|
||||||
import { Publish as PublishIcon } from '@styled-icons/material/Publish';
|
import { Publish as PublishIcon } from '@styled-icons/material/Publish';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { translate } from 'react-polyglot';
|
import { translate } from 'react-polyglot';
|
||||||
|
|
||||||
import { selectAllowDeletion } from '@staticcms/core/lib/util/collection.util';
|
import { selectAllowDeletion } from '@staticcms/core/lib/util/collection.util';
|
||||||
import Menu from '../common/menu/Menu';
|
import Menu from '../common/menu/Menu';
|
||||||
import MenuGroup from '../common/menu/MenuGroup';
|
import MenuGroup from '../common/menu/MenuGroup';
|
||||||
import MenuItemButton from '../common/menu/MenuItemButton';
|
import MenuItemButton from '../common/menu/MenuItemButton';
|
||||||
|
import confirm from '../common/confirm/Confirm';
|
||||||
|
import { useAppDispatch } from '@staticcms/core/store/hooks';
|
||||||
|
import { deleteLocalBackup, loadEntry } from '@staticcms/core/actions/entries';
|
||||||
|
|
||||||
import type { Collection, EditorPersistOptions, TranslatedProps } from '@staticcms/core/interface';
|
import type { Collection, EditorPersistOptions, TranslatedProps } from '@staticcms/core/interface';
|
||||||
import type { FC, MouseEventHandler } from 'react';
|
import type { FC, MouseEventHandler } from 'react';
|
||||||
@ -39,6 +42,7 @@ export interface EditorToolbarProps {
|
|||||||
togglePreview: MouseEventHandler;
|
togglePreview: MouseEventHandler;
|
||||||
toggleScrollSync: MouseEventHandler;
|
toggleScrollSync: MouseEventHandler;
|
||||||
toggleI18n: MouseEventHandler;
|
toggleI18n: MouseEventHandler;
|
||||||
|
slug?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditorToolbar = ({
|
const EditorToolbar = ({
|
||||||
@ -61,6 +65,7 @@ const EditorToolbar = ({
|
|||||||
togglePreview,
|
togglePreview,
|
||||||
toggleScrollSync,
|
toggleScrollSync,
|
||||||
toggleI18n,
|
toggleI18n,
|
||||||
|
slug,
|
||||||
}: TranslatedProps<EditorToolbarProps>) => {
|
}: TranslatedProps<EditorToolbarProps>) => {
|
||||||
const canCreate = useMemo(
|
const canCreate = useMemo(
|
||||||
() => ('folder' in collection && collection.create) ?? false,
|
() => ('folder' in collection && collection.create) ?? false,
|
||||||
@ -69,7 +74,28 @@ const EditorToolbar = ({
|
|||||||
const canDelete = useMemo(() => selectAllowDeletion(collection), [collection]);
|
const canDelete = useMemo(() => selectAllowDeletion(collection), [collection]);
|
||||||
const isPublished = useMemo(() => !isNewEntry && !hasChanged, [hasChanged, isNewEntry]);
|
const isPublished = useMemo(() => !isNewEntry && !hasChanged, [hasChanged, isNewEntry]);
|
||||||
|
|
||||||
const menuItems = useMemo(() => {
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleDiscardDraft = useCallback(async () => {
|
||||||
|
if (!slug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
await confirm({
|
||||||
|
title: 'editor.editorToolbar.discardChangesTitle',
|
||||||
|
body: {
|
||||||
|
key: 'editor.editorToolbar.discardChangesBody',
|
||||||
|
},
|
||||||
|
color: 'warning',
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
dispatch(deleteLocalBackup(collection, slug));
|
||||||
|
dispatch(loadEntry(collection, slug));
|
||||||
|
}
|
||||||
|
}, [collection, dispatch, slug]);
|
||||||
|
|
||||||
|
const menuItems: JSX.Element[][] = useMemo(() => {
|
||||||
const items: JSX.Element[] = [];
|
const items: JSX.Element[] = [];
|
||||||
|
|
||||||
if (!isPublished) {
|
if (!isPublished) {
|
||||||
@ -105,8 +131,34 @@ const EditorToolbar = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
if (hasChanged) {
|
||||||
}, [canCreate, isPublished, onDuplicate, onPersist, onPersistAndDuplicate, onPersistAndNew, t]);
|
return [
|
||||||
|
items,
|
||||||
|
[
|
||||||
|
<MenuItemButton
|
||||||
|
key="discardChanges"
|
||||||
|
onClick={handleDiscardDraft}
|
||||||
|
startIcon={TrashIcon}
|
||||||
|
color="warning"
|
||||||
|
>
|
||||||
|
{t('editor.editorToolbar.discardChanges')}
|
||||||
|
</MenuItemButton>,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [items];
|
||||||
|
}, [
|
||||||
|
canCreate,
|
||||||
|
handleDiscardDraft,
|
||||||
|
hasChanged,
|
||||||
|
isPublished,
|
||||||
|
onDuplicate,
|
||||||
|
onPersist,
|
||||||
|
onPersistAndDuplicate,
|
||||||
|
onPersistAndNew,
|
||||||
|
t,
|
||||||
|
]);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
@ -166,9 +218,11 @@ const EditorToolbar = ({
|
|||||||
isPublished ? t('editor.editorToolbar.published') : t('editor.editorToolbar.publish')
|
isPublished ? t('editor.editorToolbar.published') : t('editor.editorToolbar.publish')
|
||||||
}
|
}
|
||||||
color={isPublished ? 'success' : 'primary'}
|
color={isPublished ? 'success' : 'primary'}
|
||||||
disabled={menuItems.length == 0}
|
disabled={menuItems.length == 1 && menuItems[0].length === 0}
|
||||||
>
|
>
|
||||||
<MenuGroup>{menuItems}</MenuGroup>
|
{menuItems.map((group, index) => (
|
||||||
|
<MenuGroup key={`menu-group-${index}`}>{group}</MenuGroup>
|
||||||
|
))}
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
@ -363,7 +363,6 @@ function getConfigSchema() {
|
|||||||
folder_support: { type: 'boolean' },
|
folder_support: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
load_config_file: { type: 'boolean' },
|
|
||||||
slug: {
|
slug: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@ -388,6 +387,7 @@ function getConfigSchema() {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
disable_local_backup: { type: 'boolean' },
|
||||||
editor: {
|
editor: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -336,6 +336,7 @@ export interface TemplatePreviewCardProps<T = EntryData, EF extends BaseField =
|
|||||||
widgetFor: WidgetFor<T>;
|
widgetFor: WidgetFor<T>;
|
||||||
widgetsFor: WidgetsFor<T>;
|
widgetsFor: WidgetsFor<T>;
|
||||||
theme: 'dark' | 'light';
|
theme: 'dark' | 'light';
|
||||||
|
hasLocalBackup: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TemplatePreviewCardComponent<
|
export type TemplatePreviewCardComponent<
|
||||||
@ -791,10 +792,10 @@ export interface Config<EF extends BaseField = UnknownField> {
|
|||||||
public_folder?: string;
|
public_folder?: string;
|
||||||
media_folder_relative?: boolean;
|
media_folder_relative?: boolean;
|
||||||
media_library?: MediaLibraryConfig;
|
media_library?: MediaLibraryConfig;
|
||||||
load_config_file?: boolean;
|
|
||||||
slug?: Slug;
|
slug?: Slug;
|
||||||
i18n?: I18nInfo;
|
i18n?: I18nInfo;
|
||||||
local_backend?: boolean | LocalBackend;
|
local_backend?: boolean | LocalBackend;
|
||||||
|
disable_local_backup?: boolean;
|
||||||
editor?: EditorConfig;
|
editor?: EditorConfig;
|
||||||
search?: boolean;
|
search?: boolean;
|
||||||
}
|
}
|
||||||
@ -989,3 +990,10 @@ export interface MediaLibrarInsertOptions {
|
|||||||
showAlt?: boolean;
|
showAlt?: boolean;
|
||||||
chooseUrl?: boolean;
|
chooseUrl?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BackupEntry {
|
||||||
|
raw: string;
|
||||||
|
path: string;
|
||||||
|
mediaFiles: MediaFile[];
|
||||||
|
i18n?: Record<string, { raw: string }>;
|
||||||
|
}
|
||||||
|
9
packages/core/src/lib/util/backup.util.ts
Normal file
9
packages/core/src/lib/util/backup.util.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
export function getEntryBackupKey(collectionName?: string, slug?: string) {
|
||||||
|
const baseKey = 'backup';
|
||||||
|
if (!collectionName) {
|
||||||
|
return baseKey;
|
||||||
|
}
|
||||||
|
const suffix = slug ? `.${slug}` : '';
|
||||||
|
return `${baseKey}.${collectionName}${suffix}`;
|
||||||
|
}
|
@ -108,8 +108,6 @@ const bg: LocalePhrasesRoot = {
|
|||||||
'Наистина ли искате да изтриете този публикуван запис, както и незаписаните промени от текущата сесия?',
|
'Наистина ли искате да изтриете този публикуван запис, както и незаписаните промени от текущата сесия?',
|
||||||
onDeletePublishedEntryBody: 'Наистина ли искате да изтриете този публикуван запис?',
|
onDeletePublishedEntryBody: 'Наистина ли искате да изтриете този публикуван запис?',
|
||||||
loadingEntry: 'Зареждане на запис...',
|
loadingEntry: 'Зареждане на запис...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'За този запис беше възстановен локален архив, бихте ли искали да го използвате?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'Превключване i18n',
|
toggleI18n: 'Превключване i18n',
|
||||||
|
@ -107,8 +107,6 @@ const ca: LocalePhrasesRoot = {
|
|||||||
'Està segur que vol eliminar aquesta entrada publicada, així com els canvis no guardats de la sessió actual?',
|
'Està segur que vol eliminar aquesta entrada publicada, així com els canvis no guardats de la sessió actual?',
|
||||||
onDeletePublishedEntryBody: 'Està segur que vol eliminar aquesta entrada publicada?',
|
onDeletePublishedEntryBody: 'Està segur que vol eliminar aquesta entrada publicada?',
|
||||||
loadingEntry: 'Carregant entrada...',
|
loadingEntry: 'Carregant entrada...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
"S'ha recuperat una copia de seguretat local per aquesta entrada. La vol utilitzar?",
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'Mostrar/Amagar traduccions',
|
toggleI18n: 'Mostrar/Amagar traduccions',
|
||||||
|
@ -108,7 +108,6 @@ const cs: LocalePhrasesRoot = {
|
|||||||
'Chcete opravdu vymazat tento publikovaný záznam a všechny neuložené změny z této relace?',
|
'Chcete opravdu vymazat tento publikovaný záznam a všechny neuložené změny z této relace?',
|
||||||
onDeletePublishedEntryBody: 'Chcete opravdu smazat tento publikovaný záznam?',
|
onDeletePublishedEntryBody: 'Chcete opravdu smazat tento publikovaný záznam?',
|
||||||
loadingEntry: 'Načítání záznamu…',
|
loadingEntry: 'Načítání záznamu…',
|
||||||
confirmLoadBackupBody: 'Lokální kopie tohoto záznamu byla nalezena, chcete ji použít?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'Přepnout lokalizaci',
|
toggleI18n: 'Přepnout lokalizaci',
|
||||||
|
@ -107,8 +107,6 @@ const da: LocalePhrasesRoot = {
|
|||||||
onDeletePublishedEntryBody:
|
onDeletePublishedEntryBody:
|
||||||
'Er du sikker på at du vil slette dette tidliere publiceret dokument?',
|
'Er du sikker på at du vil slette dette tidliere publiceret dokument?',
|
||||||
loadingEntry: 'Indlæser dokument...',
|
loadingEntry: 'Indlæser dokument...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'En lokal sikkerhedskopi blev gendannet for dette dokument, vil du anvende denne?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Publicerer...',
|
publishing: 'Publicerer...',
|
||||||
|
@ -109,9 +109,6 @@ const de: LocalePhrasesRoot = {
|
|||||||
onDeletePublishedEntryTitle: 'Veröffentlichten Beitrag löschen?',
|
onDeletePublishedEntryTitle: 'Veröffentlichten Beitrag löschen?',
|
||||||
onDeletePublishedEntryBody: 'Soll dieser veröffentlichte Beitrag wirklich gelöscht werden?',
|
onDeletePublishedEntryBody: 'Soll dieser veröffentlichte Beitrag wirklich gelöscht werden?',
|
||||||
loadingEntry: 'Beitrag laden...',
|
loadingEntry: 'Beitrag laden...',
|
||||||
confirmLoadBackupTitle: 'Lokales Backup benutzen?',
|
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Für diesen Beitrag ist ein lokales Backup vorhanden. Möchten Sie dieses benutzen?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'Übersetzungen',
|
toggleI18n: 'Übersetzungen',
|
||||||
|
@ -120,9 +120,6 @@ const en: LocalePhrasesRoot = {
|
|||||||
onDeletePublishedEntryTitle: 'Delete this published entry?',
|
onDeletePublishedEntryTitle: 'Delete this published entry?',
|
||||||
onDeletePublishedEntryBody: 'Are you sure you want to delete this published entry?',
|
onDeletePublishedEntryBody: 'Are you sure you want to delete this published entry?',
|
||||||
loadingEntry: 'Loading entry...',
|
loadingEntry: 'Loading entry...',
|
||||||
confirmLoadBackupTitle: 'Use local backup?',
|
|
||||||
confirmLoadBackupBody:
|
|
||||||
'A local backup was recovered for this entry, would you like to use it?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
sideBySideI18n: 'I18n Side by Side',
|
sideBySideI18n: 'I18n Side by Side',
|
||||||
@ -155,6 +152,9 @@ const en: LocalePhrasesRoot = {
|
|||||||
inReview: 'In review',
|
inReview: 'In review',
|
||||||
ready: 'Ready',
|
ready: 'Ready',
|
||||||
publishNow: 'Publish now',
|
publishNow: 'Publish now',
|
||||||
|
discardChanges: 'Discard changes',
|
||||||
|
discardChangesTitle: 'Discard changes',
|
||||||
|
discardChangesBody: 'Are you sure you want to discard the unsaved changed?',
|
||||||
},
|
},
|
||||||
editorWidgets: {
|
editorWidgets: {
|
||||||
markdown: {
|
markdown: {
|
||||||
@ -279,6 +279,9 @@ const en: LocalePhrasesRoot = {
|
|||||||
default: {
|
default: {
|
||||||
goBackToSite: 'Go back to site',
|
goBackToSite: 'Go back to site',
|
||||||
},
|
},
|
||||||
|
localBackup: {
|
||||||
|
hasLocalBackup: 'Has local backup',
|
||||||
|
},
|
||||||
errorBoundary: {
|
errorBoundary: {
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
details: "There's been an error - please ",
|
details: "There's been an error - please ",
|
||||||
|
@ -93,8 +93,6 @@ const es: LocalePhrasesRoot = {
|
|||||||
'¿Está seguro de que desea eliminar esta entrada publicada, así como los cambios no guardados de la sesión actual?',
|
'¿Está seguro de que desea eliminar esta entrada publicada, así como los cambios no guardados de la sesión actual?',
|
||||||
onDeletePublishedEntryBody: '¿Estás seguro de que quieres borrar esta entrada publicada?',
|
onDeletePublishedEntryBody: '¿Estás seguro de que quieres borrar esta entrada publicada?',
|
||||||
loadingEntry: 'Cargando entrada...',
|
loadingEntry: 'Cargando entrada...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Se recuperó una copia de seguridad local para esta entrada, ¿le gustaría utilizarla?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Publicando...',
|
publishing: 'Publicando...',
|
||||||
|
@ -109,8 +109,6 @@ const fr: LocalePhrasesRoot = {
|
|||||||
'Voulez-vous vraiment supprimer cette entrée publiée ainsi que vos modifications non enregistrées de cette session ?',
|
'Voulez-vous vraiment supprimer cette entrée publiée ainsi que vos modifications non enregistrées de cette session ?',
|
||||||
onDeletePublishedEntryBody: 'Voulez-vous vraiment supprimer cette entrée publiée ?',
|
onDeletePublishedEntryBody: 'Voulez-vous vraiment supprimer cette entrée publiée ?',
|
||||||
loadingEntry: "Chargement de l'entrée...",
|
loadingEntry: "Chargement de l'entrée...",
|
||||||
confirmLoadBackupBody:
|
|
||||||
"Une sauvegarde locale a été trouvée pour cette entrée. Voulez-vous l'utiliser ?",
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'Édition multilingue',
|
toggleI18n: 'Édition multilingue',
|
||||||
|
@ -79,8 +79,6 @@ const gr: LocalePhrasesRoot = {
|
|||||||
onDeletePublishedEntryBody:
|
onDeletePublishedEntryBody:
|
||||||
'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν τη δημοσιευμένη καταχώρηση;',
|
'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτήν τη δημοσιευμένη καταχώρηση;',
|
||||||
loadingEntry: 'Φόρτωση εισόδου...',
|
loadingEntry: 'Φόρτωση εισόδου...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Ανακτήθηκε ένα τοπικό αντίγραφο ασφαλείας για αυτήν την καταχώρηση, θέλετε να το χρησιμοποιήσετε;',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Δημοσίευση...',
|
publishing: 'Δημοσίευση...',
|
||||||
|
@ -107,7 +107,6 @@ const he: LocalePhrasesRoot = {
|
|||||||
'האם ברצונך למחוק את האייטם הזה לפני פרסומו, וכן את השינויים שבוצעו כעת וטרם נשמרו?',
|
'האם ברצונך למחוק את האייטם הזה לפני פרסומו, וכן את השינויים שבוצעו כעת וטרם נשמרו?',
|
||||||
onDeletePublishedEntryBody: 'האם ברצונך למחוק את האייטם הזה לאחר פרסומו?',
|
onDeletePublishedEntryBody: 'האם ברצונך למחוק את האייטם הזה לאחר פרסומו?',
|
||||||
loadingEntry: 'טעינת אייטם...',
|
loadingEntry: 'טעינת אייטם...',
|
||||||
confirmLoadBackupBody: 'קיים עותק מקומי שמור של האייטם. האם ברצונך לטעון אותו?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'החלפת שפות',
|
toggleI18n: 'החלפת שפות',
|
||||||
|
@ -108,7 +108,6 @@ const hr: LocalePhrasesRoot = {
|
|||||||
'Jeste li sigurni da želite obrisati objavljeni unos, te nespremljene promjene u trenutnoj sesiji?',
|
'Jeste li sigurni da želite obrisati objavljeni unos, te nespremljene promjene u trenutnoj sesiji?',
|
||||||
onDeletePublishedEntryBody: 'Jeste li sigurni da želite obrisati ovaj objavljeni unos?',
|
onDeletePublishedEntryBody: 'Jeste li sigurni da želite obrisati ovaj objavljeni unos?',
|
||||||
loadingEntry: 'Učitavanje unosa...',
|
loadingEntry: 'Učitavanje unosa...',
|
||||||
confirmLoadBackupBody: 'Lokalna kopija je dohvaćena za ovaj unos, želite li ju koristiti?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Objavljivanje...',
|
publishing: 'Objavljivanje...',
|
||||||
|
@ -62,8 +62,6 @@ const hu: LocalePhrasesRoot = {
|
|||||||
'Töröljük ezt a publikált bejegyzést, a többi mentetlen modositással együtt?',
|
'Töröljük ezt a publikált bejegyzést, a többi mentetlen modositással együtt?',
|
||||||
onDeletePublishedEntryBody: 'Töröljük ezt a publikált bejegyzést?',
|
onDeletePublishedEntryBody: 'Töröljük ezt a publikált bejegyzést?',
|
||||||
loadingEntry: 'Bejegyzés betöltése...',
|
loadingEntry: 'Bejegyzés betöltése...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Helyi biztonsági másolat került helyre ehhez a bejegyzéshez, szeretné használni?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Publikálás...',
|
publishing: 'Publikálás...',
|
||||||
|
@ -77,8 +77,6 @@ const it: LocalePhrasesRoot = {
|
|||||||
'Sei sicuro di voler cancellare questa voce pubblicata e tutte le modifiche non salvate della tua sessione corrente?',
|
'Sei sicuro di voler cancellare questa voce pubblicata e tutte le modifiche non salvate della tua sessione corrente?',
|
||||||
onDeletePublishedEntryBody: 'Sei sicuro di voler cancellare questa voce pubblicata?',
|
onDeletePublishedEntryBody: 'Sei sicuro di voler cancellare questa voce pubblicata?',
|
||||||
loadingEntry: 'Caricando la voce...',
|
loadingEntry: 'Caricando la voce...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Un backup locale è stato recuperato per questa voce, vuoi utilizzarlo?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Pubblicando...',
|
publishing: 'Pubblicando...',
|
||||||
|
@ -107,7 +107,6 @@ const ja: LocalePhrasesRoot = {
|
|||||||
'保存されていない変更も削除されますが、この公開エントリを削除しますか?',
|
'保存されていない変更も削除されますが、この公開エントリを削除しますか?',
|
||||||
onDeletePublishedEntryBody: 'この公開エントリを削除しますか?',
|
onDeletePublishedEntryBody: 'この公開エントリを削除しますか?',
|
||||||
loadingEntry: 'エントリの読込中...',
|
loadingEntry: 'エントリの読込中...',
|
||||||
confirmLoadBackupBody: 'ローカルのバックアップが復旧できました。利用しますか?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: '言語を切り替える',
|
toggleI18n: '言語を切り替える',
|
||||||
|
@ -100,8 +100,6 @@ const ko: LocalePhrasesRoot = {
|
|||||||
'현재 세션에서의 저장되지 않은 변경사항과 이 게시된 항목을 삭제하시겠습니까?',
|
'현재 세션에서의 저장되지 않은 변경사항과 이 게시된 항목을 삭제하시겠습니까?',
|
||||||
onDeletePublishedEntryBody: '이 게시된 항목을 삭제하시겠습니까?',
|
onDeletePublishedEntryBody: '이 게시된 항목을 삭제하시겠습니까?',
|
||||||
loadingEntry: '항목 불러오는 중...',
|
loadingEntry: '항목 불러오는 중...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'이 항목에 대한 로컬 백업이 복구되었습니다, 복구된 것으로 사용하시겠습니까?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: '게시 중...',
|
publishing: '게시 중...',
|
||||||
|
@ -109,8 +109,6 @@ const lt: LocalePhrasesRoot = {
|
|||||||
'Tikrai norite panaikinti publikuotą įrašą ir Jūsų pakeiitmus iš dabartinės sesijos?',
|
'Tikrai norite panaikinti publikuotą įrašą ir Jūsų pakeiitmus iš dabartinės sesijos?',
|
||||||
onDeletePublishedEntryBody: 'Tikrai norite ištrinti šį publikuotą įrašą?',
|
onDeletePublishedEntryBody: 'Tikrai norite ištrinti šį publikuotą įrašą?',
|
||||||
loadingEntry: 'Kraunamas įrašas...',
|
loadingEntry: 'Kraunamas įrašas...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Radome Jūsų įrenginyje išsaugota juodraštį šiam įrašui, ar norite jį atgaivinti ir naudoti?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Publikuojama...',
|
publishing: 'Publikuojama...',
|
||||||
|
@ -91,7 +91,6 @@ const nb_no: LocalePhrasesRoot = {
|
|||||||
'Er du sikker på at du vil slette et publisert innlegg med tilhørende ulagrede endringer?',
|
'Er du sikker på at du vil slette et publisert innlegg med tilhørende ulagrede endringer?',
|
||||||
onDeletePublishedEntryBody: 'Er du sikker på at du vil slette dette publiserte innlegget?',
|
onDeletePublishedEntryBody: 'Er du sikker på at du vil slette dette publiserte innlegget?',
|
||||||
loadingEntry: 'Laster innlegg...',
|
loadingEntry: 'Laster innlegg...',
|
||||||
confirmLoadBackupBody: 'Vil du gjenopprette tidligere endringer som ikke har blitt lagret?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Publiserer...',
|
publishing: 'Publiserer...',
|
||||||
|
@ -106,7 +106,6 @@ const nl: LocalePhrasesRoot = {
|
|||||||
'Weet u zeker dat u dit gepubliceerde item en uw niet-opgeslagen wijzigingen uit de huidige sessie wilt verwijderen?',
|
'Weet u zeker dat u dit gepubliceerde item en uw niet-opgeslagen wijzigingen uit de huidige sessie wilt verwijderen?',
|
||||||
onDeletePublishedEntryBody: 'Weet u zeker dat u dit gepubliceerde item wilt verwijderen?',
|
onDeletePublishedEntryBody: 'Weet u zeker dat u dit gepubliceerde item wilt verwijderen?',
|
||||||
loadingEntry: 'Item laden...',
|
loadingEntry: 'Item laden...',
|
||||||
confirmLoadBackupBody: 'Voor dit item is een lokale back-up hersteld, wilt u deze gebruiken?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'Wissel i18n',
|
toggleI18n: 'Wissel i18n',
|
||||||
|
@ -91,8 +91,6 @@ const nn_no: LocalePhrasesRoot = {
|
|||||||
'Er du sikkert på at du vil slette eit publisert innlegg med tilhøyrande ulagra endringar?',
|
'Er du sikkert på at du vil slette eit publisert innlegg med tilhøyrande ulagra endringar?',
|
||||||
onDeletePublishedEntryBody: 'Er du sikker på at du vil slette dette publiserte innlegget?',
|
onDeletePublishedEntryBody: 'Er du sikker på at du vil slette dette publiserte innlegget?',
|
||||||
loadingEntry: 'Lastar innlegg...',
|
loadingEntry: 'Lastar innlegg...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Ynskjer du å gjennopprette tidlegare endringar som ikkje har verta lagra?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Publiserer...',
|
publishing: 'Publiserer...',
|
||||||
|
@ -108,7 +108,6 @@ const pl: LocalePhrasesRoot = {
|
|||||||
'Czy na pewno chcesz usunąć tę opublikowaną pozycję, a także niezapisane zmiany z bieżącej sesji?',
|
'Czy na pewno chcesz usunąć tę opublikowaną pozycję, a także niezapisane zmiany z bieżącej sesji?',
|
||||||
onDeletePublishedEntryBody: 'Czy na pewno chcesz usunąć tę opublikowaną pozycję?',
|
onDeletePublishedEntryBody: 'Czy na pewno chcesz usunąć tę opublikowaną pozycję?',
|
||||||
loadingEntry: 'Ładowanie pozycji...',
|
loadingEntry: 'Ładowanie pozycji...',
|
||||||
confirmLoadBackupBody: 'Odzyskano lokalną kopię zapasową tej pozycji, czy chcesz jej użyć?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'Przełącz i18n',
|
toggleI18n: 'Przełącz i18n',
|
||||||
|
@ -108,7 +108,6 @@ const pt: LocalePhrasesRoot = {
|
|||||||
'Tem certeza de que deseja excluir esta entrada publicada, bem como as alterações não salvas da sessão atual?',
|
'Tem certeza de que deseja excluir esta entrada publicada, bem como as alterações não salvas da sessão atual?',
|
||||||
onDeletePublishedEntryBody: 'Tem certeza de que deseja excluir esta entrada publicada?',
|
onDeletePublishedEntryBody: 'Tem certeza de que deseja excluir esta entrada publicada?',
|
||||||
loadingEntry: 'Carregando entrada...',
|
loadingEntry: 'Carregando entrada...',
|
||||||
confirmLoadBackupBody: 'Um backup local foi recuperado para esta entrada. Deseja usá-lo?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'Mudar i18n',
|
toggleI18n: 'Mudar i18n',
|
||||||
|
@ -108,8 +108,6 @@ const ro: LocalePhrasesRoot = {
|
|||||||
'Ești sigur/ă că dorești să ștergi această publicare, dar și modificările nesalvate din sesiunea curentă?',
|
'Ești sigur/ă că dorești să ștergi această publicare, dar și modificările nesalvate din sesiunea curentă?',
|
||||||
onDeletePublishedEntryBody: 'Ești sigur/ă că dorești să ștergi această publicare?',
|
onDeletePublishedEntryBody: 'Ești sigur/ă că dorești să ștergi această publicare?',
|
||||||
loadingEntry: 'Se încarcă...',
|
loadingEntry: 'Se încarcă...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Un backup local a fost recuperat pentru această intrare, dorești să îl folosești?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'Comută limba',
|
toggleI18n: 'Comută limba',
|
||||||
|
@ -108,8 +108,6 @@ const ru: LocalePhrasesRoot = {
|
|||||||
'Вы уверены, что хотите удалить эту опубликованную запись, а также несохраненные изменения из текущего сеанса?',
|
'Вы уверены, что хотите удалить эту опубликованную запись, а также несохраненные изменения из текущего сеанса?',
|
||||||
onDeletePublishedEntryBody: 'Вы уверены, что хотите удалить эту опубликованную запись?',
|
onDeletePublishedEntryBody: 'Вы уверены, что хотите удалить эту опубликованную запись?',
|
||||||
loadingEntry: 'Загрузка записи…',
|
loadingEntry: 'Загрузка записи…',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Для этой записи была восстановлена локальная резервная копия, хотите ли вы ее использовать?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Публикация…',
|
publishing: 'Публикация…',
|
||||||
|
@ -108,7 +108,6 @@ const sv: LocalePhrasesRoot = {
|
|||||||
'Är du säker på att du vill radera det här publicerade inlägget, inklusive dina osparade ändringar från nuvarande session?',
|
'Är du säker på att du vill radera det här publicerade inlägget, inklusive dina osparade ändringar från nuvarande session?',
|
||||||
onDeletePublishedEntryBody: 'Är du säker på att du vill radera det här publicerade inlägget?',
|
onDeletePublishedEntryBody: 'Är du säker på att du vill radera det här publicerade inlägget?',
|
||||||
loadingEntry: 'Hämtar inlägg...',
|
loadingEntry: 'Hämtar inlägg...',
|
||||||
confirmLoadBackupBody: 'En lokal kopia hittades för det här inlägget, vill du använda den?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'Slå på/av i18n',
|
toggleI18n: 'Slå på/av i18n',
|
||||||
|
@ -103,7 +103,6 @@ const th: LocalePhrasesRoot = {
|
|||||||
'คุณแน่ใจหรือว่าจะต้องการลบการเผยแพร่เนื้อหานี้ รวมถึงการเปลี่ยนแปลงที่ยังไม่ได้บันทึก?',
|
'คุณแน่ใจหรือว่าจะต้องการลบการเผยแพร่เนื้อหานี้ รวมถึงการเปลี่ยนแปลงที่ยังไม่ได้บันทึก?',
|
||||||
onDeletePublishedEntryBody: 'คุณแน่ใจหรือว่าจะต้องการลบการเผยแพร่เนื้อหานี้?',
|
onDeletePublishedEntryBody: 'คุณแน่ใจหรือว่าจะต้องการลบการเผยแพร่เนื้อหานี้?',
|
||||||
loadingEntry: 'กำลังโหลดเนื้อหา...',
|
loadingEntry: 'กำลังโหลดเนื้อหา...',
|
||||||
confirmLoadBackupBody: 'ข้อมูลสำรองได้ถูกกู้คืนสำหรับเนื้อหานี้ คุณต้องการใช้มันไหม?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'กำลังเผยแพร่...',
|
publishing: 'กำลังเผยแพร่...',
|
||||||
|
@ -109,8 +109,6 @@ const tr: LocalePhrasesRoot = {
|
|||||||
'Bu oturumda kaydedilmiş değişikliklerin yanı sıra geçerli oturumdaki kaydedilmemiş değişikliklerinizi silmek istediğinize emin misiniz?',
|
'Bu oturumda kaydedilmiş değişikliklerin yanı sıra geçerli oturumdaki kaydedilmemiş değişikliklerinizi silmek istediğinize emin misiniz?',
|
||||||
onDeletePublishedEntryBody: 'Bu yayınlanmış girdiyi silmek istediğinize emin misiniz?',
|
onDeletePublishedEntryBody: 'Bu yayınlanmış girdiyi silmek istediğinize emin misiniz?',
|
||||||
loadingEntry: 'Girdiler yükleniyor...',
|
loadingEntry: 'Girdiler yükleniyor...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Bu girdi için yerel bir yedekleme kurtarıldı, kullanmak ister misiniz?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: 'i18n değiştir',
|
toggleI18n: 'i18n değiştir',
|
||||||
|
@ -61,7 +61,6 @@ const uk: LocalePhrasesRoot = {
|
|||||||
'Ви дійсно бажаєте видалити опублікований запис, як і всі незбережені зміни під час поточної сесії?',
|
'Ви дійсно бажаєте видалити опублікований запис, як і всі незбережені зміни під час поточної сесії?',
|
||||||
onDeletePublishedEntryBody: 'Ви дійсно бажаєте видалити опублікований запис?',
|
onDeletePublishedEntryBody: 'Ви дійсно бажаєте видалити опублікований запис?',
|
||||||
loadingEntry: 'Завантаження...',
|
loadingEntry: 'Завантаження...',
|
||||||
confirmLoadBackupBody: 'Відновлено резервну копію, бажаєте її використати?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Публікація...',
|
publishing: 'Публікація...',
|
||||||
|
@ -99,8 +99,6 @@ const vi: LocalePhrasesRoot = {
|
|||||||
'Bạn có chắc rằng bạn muốn xoá mục đã được công bố này, cũng như là những thay đổi chưa lưu của bạn trong phiên làm việc này?',
|
'Bạn có chắc rằng bạn muốn xoá mục đã được công bố này, cũng như là những thay đổi chưa lưu của bạn trong phiên làm việc này?',
|
||||||
onDeletePublishedEntryBody: 'Bạn có chắc rằng bạn muốn xoá mục đã được công bố này?',
|
onDeletePublishedEntryBody: 'Bạn có chắc rằng bạn muốn xoá mục đã được công bố này?',
|
||||||
loadingEntry: 'Đang tải...',
|
loadingEntry: 'Đang tải...',
|
||||||
confirmLoadBackupBody:
|
|
||||||
'Một bản sao lưu trên máy đã được phục hồi cho mục này, bạn có muốn tải lên không?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: 'Đang công bố...',
|
publishing: 'Đang công bố...',
|
||||||
|
@ -105,7 +105,6 @@ const zh_Hans: LocalePhrasesRoot = {
|
|||||||
onDeleteWithUnsavedChangesBody: '你确定要删除这个已经发布的内容,以及当前尚未保存的修改吗?',
|
onDeleteWithUnsavedChangesBody: '你确定要删除这个已经发布的内容,以及当前尚未保存的修改吗?',
|
||||||
onDeletePublishedEntryBody: '你确定要删除这个已经发布的内容吗?',
|
onDeletePublishedEntryBody: '你确定要删除这个已经发布的内容吗?',
|
||||||
loadingEntry: '正在加载内容...',
|
loadingEntry: '正在加载内容...',
|
||||||
confirmLoadBackupBody: '发现了一个对应此内容的本地备份,你要加载它吗?',
|
|
||||||
},
|
},
|
||||||
editorInterface: {
|
editorInterface: {
|
||||||
toggleI18n: '打开/关闭国际化',
|
toggleI18n: '打开/关闭国际化',
|
||||||
|
@ -100,7 +100,6 @@ const zh_Hant: LocalePhrasesRoot = {
|
|||||||
onDeleteWithUnsavedChangesBody: '你確定要刪除這篇已發布的內容以及你尚未儲存的變更?',
|
onDeleteWithUnsavedChangesBody: '你確定要刪除這篇已發布的內容以及你尚未儲存的變更?',
|
||||||
onDeletePublishedEntryBody: '你確定要刪除這篇已發布的內容?',
|
onDeletePublishedEntryBody: '你確定要刪除這篇已發布的內容?',
|
||||||
loadingEntry: '載入內容中...',
|
loadingEntry: '載入內容中...',
|
||||||
confirmLoadBackupBody: '此內容的本地備份已經還原,你想要使用嗎?',
|
|
||||||
},
|
},
|
||||||
editorToolbar: {
|
editorToolbar: {
|
||||||
publishing: '發布中...',
|
publishing: '發布中...',
|
||||||
|
@ -265,6 +265,55 @@
|
|||||||
dark:disabled:hover:bg-transparent;
|
dark:disabled:hover:bg-transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-contained-warning {
|
||||||
|
@apply border
|
||||||
|
border-transparent
|
||||||
|
bg-yellow-500
|
||||||
|
hover:bg-yellow-600
|
||||||
|
text-white
|
||||||
|
disabled:text-gray-50
|
||||||
|
disabled:bg-gray-300/80
|
||||||
|
dark:bg-yellow-600
|
||||||
|
dark:hover:bg-yellow-800
|
||||||
|
dark:disabled:text-slate-400/50
|
||||||
|
dark:disabled:bg-slate-700/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outlined-warning {
|
||||||
|
@apply bg-transparent
|
||||||
|
border
|
||||||
|
text-yellow-500
|
||||||
|
border-yellow-200
|
||||||
|
hover:bg-yellow-50
|
||||||
|
hover:text-yellow-600
|
||||||
|
disabled:text-gray-300
|
||||||
|
disabled:border-gray-200
|
||||||
|
disabled:hover:bg-transparent
|
||||||
|
dark:bg-transparent
|
||||||
|
dark:text-yellow-400
|
||||||
|
dark:border-yellow-600
|
||||||
|
dark:hover:text-yellow-500
|
||||||
|
dark:hover:bg-yellow-700/10
|
||||||
|
dark:disabled:text-slate-600/75
|
||||||
|
dark:disabled:border-slate-600/75
|
||||||
|
dark:disabled:hover:bg-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text-warning {
|
||||||
|
@apply bg-transparent
|
||||||
|
text-yellow-500
|
||||||
|
hover:text-yellow-600
|
||||||
|
hover:bg-yellow-50
|
||||||
|
disabled:text-gray-300
|
||||||
|
disabled:hover:bg-transparent
|
||||||
|
dark:text-yellow-400
|
||||||
|
dark:hover:text-yellow-500
|
||||||
|
dark:hover:bg-yellow-700/10
|
||||||
|
dark:disabled:text-slate-600/75
|
||||||
|
dark:disabled:border-slate-600/75
|
||||||
|
dark:disabled:hover:bg-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checkbox
|
* Checkbox
|
||||||
*/
|
*/
|
||||||
|
@ -300,3 +300,7 @@ _This setting is required._
|
|||||||
The `collections` setting is the heart of your Static CMS configuration, as it determines how content types and editor fields in the UI generate files and content in your repository. Each collection you configure displays in the left sidebar of the Content page of the editor UI, in the order they are entered into your Static CMS `config` file.
|
The `collections` setting is the heart of your Static CMS configuration, as it determines how content types and editor fields in the UI generate files and content in your repository. Each collection you configure displays in the left sidebar of the Content page of the editor UI, in the order they are entered into your Static CMS `config` file.
|
||||||
|
|
||||||
`collections` accepts a list of collection objects. See [Collections](/docs/collection-overview) for details.
|
`collections` accepts a list of collection objects. See [Collections](/docs/collection-overview) for details.
|
||||||
|
|
||||||
|
## Disable Local Backup
|
||||||
|
|
||||||
|
When the `disable_local_backup` setting is set to `true` local backups will no be taken for your entries and you will not be prompted to load local backups.
|
||||||
|
@ -42,10 +42,7 @@ The following parameters will be passed to your `react_component` during render:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
const PostPreview = ({ widgetFor, entry, collection, fields }) => {
|
const PostPreview = ({ widgetFor, entry, collection, fields }) => {
|
||||||
const imageField = useMemo(() =>
|
const imageField = useMemo(() => fields.find(field => field.name === 'image'), [fields]);
|
||||||
fields.find(field => field.name === 'image'),
|
|
||||||
[fields],
|
|
||||||
);
|
|
||||||
const imageUrl = useMediaAsset(entry.data.image, collection, imageField, entry);
|
const imageUrl = useMediaAsset(entry.data.image, collection, imageField, entry);
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
@ -64,10 +61,7 @@ CMS.registerPreviewTemplate('posts', PostPreview);
|
|||||||
import CMS, { useMediaAsset } from '@staticcms/core';
|
import CMS, { useMediaAsset } from '@staticcms/core';
|
||||||
|
|
||||||
const PostPreview = ({ widgetFor, entry, collection, fields }) => {
|
const PostPreview = ({ widgetFor, entry, collection, fields }) => {
|
||||||
const imageField = useMemo(() =>
|
const imageField = useMemo(() => fields.find(field => field.name === 'image'), [fields]);
|
||||||
fields.find(field => field.name === 'image'),
|
|
||||||
[fields],
|
|
||||||
);
|
|
||||||
const imageUrl = useMediaAsset(entry.data.image, collection, imageField, entry);
|
const imageUrl = useMediaAsset(entry.data.image, collection, imageField, entry);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -97,10 +91,7 @@ interface Post {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PostPreview = ({ widgetFor, entry, collection, fields }: TemplatePreviewProps<Post>) => {
|
const PostPreview = ({ widgetFor, entry, collection, fields }: TemplatePreviewProps<Post>) => {
|
||||||
const imageField = useMemo(() =>
|
const imageField = useMemo(() => fields.find(field => field.name === 'image'), [fields]);
|
||||||
fields.find(field => field.name === 'image'),
|
|
||||||
[fields],
|
|
||||||
);
|
|
||||||
const imageUrl = useMediaAsset(entry.data.image, collection, imageField, entry);
|
const imageUrl = useMediaAsset(entry.data.image, collection, imageField, entry);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -355,12 +346,13 @@ CMS.registerPreviewStyle('.main { color: blue; border: 1px solid gree; }', { raw
|
|||||||
|
|
||||||
The following parameters will be passed to your `react_component` during render:
|
The following parameters will be passed to your `react_component` during render:
|
||||||
|
|
||||||
| Param | Type | Description |
|
| Param | Type | Description |
|
||||||
| ---------- | ---------------------- | ------------------------------------------------------------------------------------------------- |
|
| -------------- | ---------------------- | ------------------------------------------------------------------------------------------------- |
|
||||||
| entry | object | Object with a `data` field that contains the current value of all widgets in the editor |
|
| entry | object | Object with a `data` field that contains the current value of all widgets in the editor |
|
||||||
| widgetFor | Function | Given a field name, returns the rendered preview of that field's widget and value |
|
| widgetFor | Function | Given a field name, returns the rendered preview of that field's widget and value |
|
||||||
| widgetsFor | Function | Given a field name, returns the rendered previews of that field's nested child widgets and values |
|
| widgetsFor | Function | Given a field name, returns the rendered previews of that field's nested child widgets and values |
|
||||||
| theme | 'light'<br />\| 'dark' | The current theme being used by the app |
|
| theme | 'light'<br />\| 'dark' | The current theme being used by the app |
|
||||||
|
| hasLocalBackup | boolean | Whether the current entry has a local backup |
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user