Merge branch 'main' into next

This commit is contained in:
Daniel Lautzenheiser
2023-02-27 13:19:11 -05:00
21 changed files with 740 additions and 817 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@staticcms/core",
"version": "1.2.10",
"version": "1.2.12",
"license": "MIT",
"description": "Static CMS core application.",
"repository": "https://github.com/StaticJsCMS/static-cms",
@ -54,7 +54,7 @@
"@codemirror/language": "6.6.0",
"@codemirror/language-data": "6.1.0",
"@codemirror/legacy-modes": "6.3.1",
"@codemirror/lint": "6.1.1",
"@codemirror/lint": "6.2.0",
"@codemirror/search": "6.2.3",
"@codemirror/state": "6.2.0",
"@codemirror/theme-one-dark": "6.1.1",
@ -76,9 +76,9 @@
"@reduxjs/toolkit": "1.9.3",
"@styled-icons/fluentui-system-regular": "10.47.0",
"@styled-icons/remix-editor": "10.46.0",
"@udecode/plate": "19.6.0",
"@udecode/plate-juice": "19.5.0",
"@udecode/plate-serializer-md": "19.5.0",
"@udecode/plate": "19.7.0",
"@udecode/plate-juice": "19.7.0",
"@udecode/plate-serializer-md": "19.7.0",
"@uiw/codemirror-extensions-langs": "4.19.9",
"@uiw/react-codemirror": "4.19.9",
"ajv": "8.12.0",
@ -144,7 +144,7 @@
"slate": "0.91.4",
"slate-history": "0.86.0",
"slate-hyperscript": "0.77.0",
"slate-react": "0.91.5",
"slate-react": "0.91.6",
"stream-browserify": "3.0.0",
"styled-components": "5.3.6",
"symbol-observable": "4.0.0",
@ -193,7 +193,7 @@
"@types/jwt-decode": "2.2.1",
"@types/lodash": "4.14.191",
"@types/minimatch": "5.1.2",
"@types/node": "16.18.12",
"@types/node": "16.18.13",
"@types/node-fetch": "2.6.2",
"@types/react": "18.0.28",
"@types/react-color": "3.0.6",
@ -203,8 +203,8 @@
"@types/styled-components": "5.1.26",
"@types/url-join": "4.0.1",
"@types/uuid": "9.0.1",
"@typescript-eslint/eslint-plugin": "5.53.0",
"@typescript-eslint/parser": "5.53.0",
"@typescript-eslint/eslint-plugin": "5.54.0",
"@typescript-eslint/parser": "5.54.0",
"axios": "1.3.4",
"babel-core": "7.0.0-bridge.0",
"babel-loader": "9.1.2",

View File

@ -1,2 +0,0 @@
/* eslint-disable import/prefer-default-export */
export const createHashHistory = jest.fn();

View File

@ -1,18 +0,0 @@
import { history } from '../routing/history';
import { getCollectionUrl, getNewEntryUrl } from '../lib/urlHelper';
export function searchCollections(query: string, collection?: string) {
if (collection) {
history.push(`/collections/${collection}/search/${query}`);
} else {
history.push(`/search/${query}`);
}
}
export function showCollection(collectionName: string) {
history.push(getCollectionUrl(collectionName));
}
export function createNewEntry(collectionName: string) {
history.push(getNewEntryUrl(collectionName));
}

View File

@ -48,7 +48,6 @@ import {
selectIsFetching,
selectPublishedSlugs,
} from '../reducers/selectors/entries';
import { navigateToEntry } from '../routing/history';
import { addSnackbar } from '../store/slices/snackbars';
import { createAssetProxy } from '../valueObjects/AssetProxy';
import createEntry from '../valueObjects/createEntry';
@ -56,6 +55,7 @@ import { addAssets, getAsset } from './media';
import { loadMedia, waitForMediaLibraryToLoad } from './mediaLibrary';
import { waitUntil } from './waitUntil';
import type { NavigateFunction } from 'react-router-dom';
import type { AnyAction } from 'redux';
import type { ThunkDispatch } from 'redux-thunk';
import type { Backend } from '../backend';
@ -950,7 +950,7 @@ export function getSerializedEntry(collection: Collection, entry: Entry): Entry
return serializedEntry;
}
export function persistEntry(collection: Collection) {
export function persistEntry(collection: Collection, navigate: NavigateFunction) {
return async (dispatch: ThunkDispatch<RootState, {}, AnyAction>, getState: () => RootState) => {
const state = getState();
const entryDraft = state.entryDraft;
@ -1039,7 +1039,7 @@ export function persistEntry(collection: Collection) {
}
if (entry.slug !== newSlug) {
await dispatch(loadEntry(collection, newSlug));
navigateToEntry(collection.name, newSlug);
navigate(`/collections/${collection.name}/entries/${newSlug}`);
} else {
await dispatch(loadEntry(collection, newSlug, true));
}

View File

@ -4,15 +4,23 @@ import { styled } from '@mui/material/styles';
import React, { useCallback, useEffect, useMemo } from 'react';
import { translate } from 'react-polyglot';
import { connect } from 'react-redux';
import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom';
import {
Navigate,
Route,
Routes,
useLocation,
useNavigate,
useParams,
useSearchParams,
} from 'react-router-dom';
import { ScrollSync } from 'react-scroll-sync';
import TopBarProgress from 'react-topbar-progress-indicator';
import { loginUser as loginUserAction } from '@staticcms/core/actions/auth';
import { discardDraft as discardDraftAction } from '@staticcms/core/actions/entries';
import { discardDraft } from '@staticcms/core/actions/entries';
import { currentBackend } from '@staticcms/core/backend';
import { colors, GlobalStyles } from '@staticcms/core/components/UI/styles';
import { history } from '@staticcms/core/routing/history';
import { useAppDispatch } from '@staticcms/core/store/hooks';
import { getDefaultPath } from '../../lib/util/collection.util';
import CollectionRoute from '../collection/CollectionRoute';
import EditorRoute from '../editor/EditorRoute';
@ -82,8 +90,10 @@ const App = ({
useMediaLibrary,
t,
scrollSyncEnabled,
discardDraft,
}: TranslatedProps<AppProps>) => {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const configError = useCallback(
(error?: string) => {
return (
@ -140,22 +150,28 @@ const App = ({
base_url={config.config.backend.base_url}
authEndpoint={config.config.backend.auth_endpoint}
config={config.config}
clearHash={() => history.replace('/')}
clearHash={() => navigate('/', { replace: true })}
t={t}
/>
</div>
);
}, [AuthComponent, auth.error, auth.isFetching, config.config, handleLogin, t]);
}, [AuthComponent, auth.error, auth.isFetching, config.config, handleLogin, navigate, t]);
const defaultPath = useMemo(() => getDefaultPath(collections), [collections]);
const { pathname } = useLocation();
const [searchParams] = useSearchParams();
useEffect(() => {
if (!/\/collections\/[a-zA-Z0-9_-]+\/entries\/[a-zA-Z0-9_-]+/g.test(pathname)) {
discardDraft();
if (
/\/collections\/[a-zA-Z0-9_-]+\/entries\/[a-zA-Z0-9_-]+/g.test(pathname) ||
(/\/collections\/[a-zA-Z0-9_-]+\/new/g.test(pathname) &&
searchParams.get('duplicate') === 'true')
) {
return;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pathname]);
dispatch(discardDraft());
}, [dispatch, pathname, searchParams]);
const content = useMemo(() => {
if (!user) {
@ -265,7 +281,6 @@ function mapStateToProps(state: RootState) {
const mapDispatchToProps = {
loginUser: loginUserAction,
discardDraft: discardDraftAction,
};
const connector = connect(mapStateToProps, mapDispatchToProps);

View File

@ -12,13 +12,13 @@ import Toolbar from '@mui/material/Toolbar';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { translate } from 'react-polyglot';
import { connect } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { logoutUser as logoutUserAction } from '@staticcms/core/actions/auth';
import { createNewEntry } from '@staticcms/core/actions/collections';
import { openMediaLibrary as openMediaLibraryAction } from '@staticcms/core/actions/mediaLibrary';
import { checkBackendStatus as checkBackendStatusAction } from '@staticcms/core/actions/status';
import { buttons, colors } from '@staticcms/core/components/UI/styles';
import { stripProtocol } from '@staticcms/core/lib/urlHelper';
import { stripProtocol, getNewEntryUrl } from '@staticcms/core/lib/urlHelper';
import NavLink from '../UI/NavLink';
import SettingsDropdown from '../UI/SettingsDropdown';
@ -82,9 +82,7 @@ const Header = ({
setAnchorEl(null);
}, []);
const handleCreatePostClick = useCallback((collectionName: string) => {
createNewEntry(collectionName);
}, []);
const navigate = useNavigate();
const creatableCollections = useMemo(
() =>
@ -148,7 +146,7 @@ const Header = ({
{creatableCollections.map(collection => (
<MenuItem
key={collection.name}
onClick={() => handleCreatePostClick(collection.name)}
onClick={() => navigate(getNewEntryUrl(collection.name))}
>
{collection.label_singular || collection.label}
</MenuItem>

View File

@ -83,6 +83,10 @@ const CollectionView = ({
}, [collection]);
const newEntryUrl = useMemo(() => {
if (!collectionName || !collection) {
return undefined;
}
let url = 'fields' in collection && collection.create ? getNewEntryUrl(collectionName) : '';
if (url && filterTerm) {
url = getNewEntryUrl(collectionName);
@ -90,6 +94,7 @@ const CollectionView = ({
url = `${newEntryUrl}?path=${filterTerm}`;
}
}
return url;
}, [collection, collectionName, filterTerm]);
@ -120,6 +125,10 @@ const CollectionView = ({
);
}
if (!collection) {
return null;
}
return (
<EntriesCollection
collection={collection}
@ -142,21 +151,21 @@ const CollectionView = ({
const onSortClick = useCallback(
async (key: string, direction?: SortDirection) => {
await sortByField(collection, key, direction);
collection && (await sortByField(collection, key, direction));
},
[collection, sortByField],
);
const onFilterClick = useCallback(
async (filter: ViewFilter) => {
await filterByField(collection, filter);
collection && (await filterByField(collection, filter));
},
[collection, filterByField],
);
const onGroupClick = useCallback(
async (group: ViewGroup) => {
await groupByField(collection, group);
collection && (await groupByField(collection, group));
},
[collection, groupByField],
);
@ -176,7 +185,7 @@ const CollectionView = ({
return;
}
const defaultSort = collection.sortable_fields?.default;
const defaultSort = collection?.sortable_fields?.default;
if (!defaultSort || !defaultSort.field) {
if (!readyToLoad) {
setReadyToLoad(true);
@ -220,7 +229,7 @@ const CollectionView = ({
<>
<SearchResultContainer>
<SearchResultHeading>
{t(searchResultKey, { searchTerm, collection: collection.label })}
{t(searchResultKey, { searchTerm, collection: collection?.label })}
</SearchResultHeading>
</SearchResultContainer>
<CollectionControls viewStyle={viewStyle} onChangeViewStyle={changeViewStyle} t={t} />
@ -254,7 +263,7 @@ const CollectionView = ({
interface CollectionViewOwnProps {
isSearchResults?: boolean;
isSingleSearchResult?: boolean;
name: string;
name?: string;
searchTerm?: string;
filterTerm?: string;
}
@ -270,13 +279,13 @@ function mapStateToProps(state: RootState, ownProps: TranslatedProps<CollectionV
filterTerm = '',
t,
} = ownProps;
const collection: Collection = name ? collections[name] : collections[0];
const sort = selectEntriesSort(state, collection.name);
const collection = (name ? collections[name] : collections[0]) as Collection | undefined;
const sort = selectEntriesSort(state, collection?.name);
const sortableFields = selectSortableFields(collection, t);
const viewFilters = selectViewFilters(collection);
const viewGroups = selectViewGroups(collection);
const filter = selectEntriesFilter(state, collection.name);
const group = selectEntriesGroup(state, collection.name);
const filter = selectEntriesFilter(state, collection?.name);
const group = selectEntriesGroup(state, collection?.name);
const viewStyle = selectViewStyle(state);
return {

View File

@ -28,11 +28,11 @@ const CollectionRoute = ({
const defaultPath = useMemo(() => getDefaultPath(collections), [collections]);
if (!name || !collection) {
if (!searchTerm && (!name || !collection)) {
return <Navigate to={defaultPath} />;
}
if ('files' in collection && collection.files?.length === 1) {
if (collection && 'files' in collection && collection.files?.length === 1) {
return <Navigate to={`/collections/${collection.name}/entries/${collection.files[0].name}`} />;
}

View File

@ -25,6 +25,7 @@ const EntriesSearch = ({
searchEntries,
clearSearch,
}: EntriesSearchProps) => {
console.log('collections', collections);
const collectionNames = useMemo(() => Object.keys(collections), [collections]);
const getCursor = useCallback(() => {
@ -80,6 +81,7 @@ function mapStateToProps(state: RootState, ownProps: EntriesSearchOwnProps) {
const isFetching = state.search.isFetching;
const page = state.search.page;
const entries = selectSearchedEntries(state, collectionNames);
console.log('searched entries', entries);
return { isFetching, page, collections, viewStyle, entries, searchTerm };
}

View File

@ -10,8 +10,8 @@ import ListItemText from '@mui/material/ListItemText';
import Typography from '@mui/material/Typography';
import React, { useMemo } from 'react';
import { translate } from 'react-polyglot';
import { useNavigate } from 'react-router-dom';
import { searchCollections } from '@staticcms/core/actions/collections';
import { colors } from '@staticcms/core/components/UI/styles';
import { getAdditionalLinks, getIcon } from '@staticcms/core/lib/registry';
import NavLink from '../UI/NavLink';
@ -48,6 +48,15 @@ const Sidebar = ({
t,
filterTerm,
}: TranslatedProps<SidebarProps>) => {
const navigate = useNavigate();
function searchCollections(query: string, collection?: string) {
if (collection) {
navigate(`/collections/${collection}/search/${query}`);
} else {
navigate(`/search/${query}`);
}
}
const collectionLinks = useMemo(
() =>
Object.values(collections)

View File

@ -1,31 +1,28 @@
import { createHashHistory } from 'history';
import debounce from 'lodash/debounce';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { translate } from 'react-polyglot';
import { connect } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { logoutUser as logoutUserAction } from '@staticcms/core/actions/auth';
import { logoutUser } from '@staticcms/core/actions/auth';
import {
createDraftDuplicateFromEntry as createDraftDuplicateFromEntryAction,
createEmptyDraft as createEmptyDraftAction,
deleteDraftLocalBackup as deleteDraftLocalBackupAction,
deleteEntry as deleteEntryAction,
deleteLocalBackup as deleteLocalBackupAction,
discardDraft as discardDraftAction,
loadEntries as loadEntriesAction,
loadEntry as loadEntryAction,
loadLocalBackup as loadLocalBackupAction,
persistEntry as persistEntryAction,
persistLocalBackup as persistLocalBackupAction,
retrieveLocalBackup as retrieveLocalBackupAction,
createDraftDuplicateFromEntry,
createEmptyDraft,
deleteDraftLocalBackup,
deleteEntry,
deleteLocalBackup,
loadEntry,
loadLocalBackup,
persistEntry,
persistLocalBackup,
retrieveLocalBackup,
} from '@staticcms/core/actions/entries';
import {
loadScroll as loadScrollAction,
toggleScroll as toggleScrollAction,
} from '@staticcms/core/actions/scroll';
import { loadScroll, toggleScroll } from '@staticcms/core/actions/scroll';
import { selectFields } from '@staticcms/core/lib/util/collection.util';
import { useWindowEvent } from '@staticcms/core/lib/util/window.util';
import { selectEntry } from '@staticcms/core/reducers/selectors/entries';
import { history, navigateToCollection, navigateToNewEntry } from '@staticcms/core/routing/history';
import { useAppDispatch } from '@staticcms/core/store/hooks';
import confirm from '../UI/Confirm';
import Loader from '../UI/Loader';
import EditorInterface from './EditorInterface';
@ -38,10 +35,10 @@ import type {
} from '@staticcms/core/interface';
import type { RootState } from '@staticcms/core/store';
import type { Blocker } from 'history';
import type { ComponentType } from 'react';
import type { ComponentType, FC } from 'react';
import type { ConnectedProps } from 'react-redux';
const Editor = ({
const Editor: FC<TranslatedProps<EditorProps>> = ({
entry,
entryDraft,
fields,
@ -50,34 +47,25 @@ const Editor = ({
hasChanged,
displayUrl,
isModification,
logoutUser,
draftKey,
t,
editorBackLink,
toggleScroll,
scrollSyncEnabled,
loadScroll,
showDelete,
slug,
localBackup,
persistLocalBackup,
loadEntry,
persistEntry,
deleteEntry,
loadLocalBackup,
retrieveLocalBackup,
deleteLocalBackup,
deleteDraftLocalBackup,
createDraftDuplicateFromEntry,
createEmptyDraft,
discardDraft,
}: TranslatedProps<EditorProps>) => {
t,
}) => {
const [version, setVersion] = useState(0);
const history = createHashHistory();
const dispatch = useAppDispatch();
const navigate = useNavigate();
const createBackup = useMemo(
() =>
debounce(function (entry: Entry, collection: Collection) {
persistLocalBackup(entry, collection);
dispatch(persistLocalBackup(entry, collection));
}, 2000),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
@ -86,9 +74,9 @@ const Editor = ({
const deleteBackup = useCallback(() => {
createBackup.cancel();
if (slug) {
deleteLocalBackup(collection, slug);
dispatch(deleteLocalBackup(collection, slug));
}
deleteDraftLocalBackup();
dispatch(deleteDraftLocalBackup());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [collection, createBackup, slug]);
@ -105,29 +93,29 @@ const Editor = ({
setTimeout(async () => {
try {
await persistEntry(collection);
await dispatch(persistEntry(collection, navigate));
setVersion(version + 1);
deleteBackup();
if (createNew) {
navigateToNewEntry(collection.name);
if (duplicate && entryDraft.entry) {
createDraftDuplicateFromEntry(entryDraft.entry);
dispatch(createDraftDuplicateFromEntry(entryDraft.entry));
navigate(`/collections/${collection.name}/new`, { replace: true });
} else {
setSubmitted(false);
setTimeout(() => {
dispatch(createEmptyDraft(collection, location.search));
setVersion(version + 1);
navigate(`/collections/${collection.name}/new`, { replace: true });
}, 100);
}
}
// eslint-disable-next-line no-empty
} catch (e) {}
}, 100);
},
[
collection,
createDraftDuplicateFromEntry,
deleteBackup,
entryDraft.entry,
persistEntry,
version,
],
[collection, deleteBackup, dispatch, entryDraft.entry, navigate, version],
);
const handleDuplicateEntry = useCallback(() => {
@ -135,9 +123,9 @@ const Editor = ({
return;
}
navigateToNewEntry(collection.name);
createDraftDuplicateFromEntry(entryDraft.entry);
}, [collection.name, createDraftDuplicateFromEntry, entryDraft.entry]);
dispatch(createDraftDuplicateFromEntry(entryDraft.entry));
navigate(`/collections/${collection.name}/new?duplicate=true`, { replace: true });
}, [collection.name, dispatch, entryDraft.entry, navigate]);
const handleDeleteEntry = useCallback(async () => {
if (entryDraft.hasChanged) {
@ -161,15 +149,15 @@ const Editor = ({
}
if (!slug) {
return navigateToCollection(collection.name);
return navigate(`/collections/${collection.name}`);
}
setTimeout(async () => {
await deleteEntry(collection, slug);
await dispatch(deleteEntry(collection, slug));
deleteBackup();
return navigateToCollection(collection.name);
return navigate(`/collections/${collection.name}`);
}, 0);
}, [collection, deleteBackup, deleteEntry, entryDraft.hasChanged, slug]);
}, [collection, deleteBackup, dispatch, entryDraft.hasChanged, navigate, slug]);
const [prevLocalBackup, setPrevLocalBackup] = useState<
| {
@ -187,7 +175,7 @@ const Editor = ({
});
if (confirmLoadBackupBody) {
loadLocalBackup();
dispatch(loadLocalBackup());
setVersion(version + 1);
} else {
deleteBackup();
@ -198,7 +186,7 @@ const Editor = ({
}
setPrevLocalBackup(localBackup);
}, [deleteBackup, loadLocalBackup, localBackup, prevLocalBackup, version]);
}, [deleteBackup, dispatch, localBackup, prevLocalBackup, version]);
useEffect(() => {
if (hasChanged && entryDraft.entry) {
@ -211,32 +199,22 @@ const Editor = ({
}, [collection, createBackup, entryDraft.entry, hasChanged]);
const [prevCollection, setPrevCollection] = useState<Collection | null>(null);
const [preSlug, setPrevSlug] = useState<string | undefined | null>(null);
const [prevSlug, setPrevSlug] = useState<string | undefined | null>(null);
useEffect(() => {
if (!slug && preSlug !== slug) {
if (!slug && prevSlug !== slug) {
setTimeout(() => {
createEmptyDraft(collection, location.search);
dispatch(createEmptyDraft(collection, location.search));
});
} else if (slug && (prevCollection !== collection || preSlug !== slug)) {
} else if (slug && (prevCollection !== collection || prevSlug !== slug)) {
setTimeout(() => {
retrieveLocalBackup(collection, slug);
loadEntry(collection, slug);
dispatch(retrieveLocalBackup(collection, slug));
dispatch(loadEntry(collection, slug));
});
}
setPrevCollection(collection);
setPrevSlug(slug);
}, [
collection,
createEmptyDraft,
discardDraft,
entryDraft.entry,
loadEntry,
preSlug,
prevCollection,
retrieveLocalBackup,
slug,
]);
}, [collection, entryDraft.entry, prevSlug, prevCollection, slug, dispatch]);
const leaveMessage = useMemo(() => t('editor.editor.onLeavePage'), [t]);
@ -284,7 +262,19 @@ const Editor = ({
return () => {
unblock();
};
}, [collection.name, deleteBackup, discardDraft, navigationBlocker]);
}, [collection.name, history, navigationBlocker]);
const handleLogout = useCallback(() => {
dispatch(logoutUser());
}, [dispatch]);
const handleToggleScroll = useCallback(async () => {
await dispatch(toggleScroll());
}, [dispatch]);
const handleLoadScroll = useCallback(async () => {
await dispatch(loadScroll());
}, [dispatch]);
if (entry && entry.error) {
return (
@ -313,11 +303,11 @@ const Editor = ({
displayUrl={displayUrl}
isNewEntry={!slug}
isModification={isModification}
onLogoutClick={logoutUser}
onLogoutClick={handleLogout}
editorBackLink={editorBackLink}
toggleScroll={toggleScroll}
toggleScroll={handleToggleScroll}
scrollSyncEnabled={scrollSyncEnabled}
loadScroll={loadScroll}
loadScroll={handleLoadScroll}
submitted={submitted}
t={t}
/>
@ -378,25 +368,7 @@ function mapStateToProps(state: RootState, ownProps: CollectionViewOwnProps) {
};
}
const mapDispatchToProps = {
loadEntry: loadEntryAction,
loadEntries: loadEntriesAction,
loadLocalBackup: loadLocalBackupAction,
deleteDraftLocalBackup: deleteDraftLocalBackupAction,
retrieveLocalBackup: retrieveLocalBackupAction,
persistLocalBackup: persistLocalBackupAction,
deleteLocalBackup: deleteLocalBackupAction,
createDraftDuplicateFromEntry: createDraftDuplicateFromEntryAction,
createEmptyDraft: createEmptyDraftAction,
discardDraft: discardDraftAction,
persistEntry: persistEntryAction,
deleteEntry: deleteEntryAction,
logoutUser: logoutUserAction,
toggleScroll: toggleScrollAction,
loadScroll: loadScrollAction,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
const connector = connect(mapStateToProps);
export type EditorProps = ConnectedProps<typeof connector>;
export default connector(translate()(Editor) as ComponentType<EditorProps>);

View File

@ -137,7 +137,7 @@ interface EditorInterfaceProps {
isModification: boolean;
onLogoutClick: () => void;
editorBackLink: string;
toggleScroll: () => Promise<{ readonly type: 'TOGGLE_SCROLL' }>;
toggleScroll: () => Promise<void>;
scrollSyncEnabled: boolean;
loadScroll: () => void;
submitted: boolean;

View File

@ -1,6 +1,6 @@
import { styled } from '@mui/material/styles';
import React, { useCallback, useMemo } from 'react';
import ReactDOM from 'react-dom';
import { createPortal } from 'react-dom';
import Frame from 'react-frame-component';
import { translate } from 'react-polyglot';
import { connect } from 'react-redux';
@ -132,7 +132,7 @@ const PreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
return null;
}
return ReactDOM.createPortal(
return createPortal(
<StyledPreviewContent className="preview-content">
{!entry || !entry.data ? null : (
<ErrorBoundary config={config}>

View File

@ -156,9 +156,13 @@ export function selectDefaultSortableFields(collection: Collection, backend: Bac
}
export function selectSortableFields(
collection: Collection,
collection: Collection | undefined,
t: (key: string) => string,
): SortableField[] {
if (!collection) {
return [];
}
const fields = (collection.sortable_fields?.fields ?? [])
.map(key => {
if (key === COMMIT_DATE) {
@ -177,12 +181,12 @@ export function selectSortableFields(
return fields;
}
export function selectViewFilters(collection: Collection) {
return collection.view_filters;
export function selectViewFilters(collection?: Collection) {
return collection?.view_filters;
}
export function selectViewGroups(collection: Collection) {
return collection.view_groups;
export function selectViewGroups(collection?: Collection) {
return collection?.view_groups;
}
export function selectFieldsComments(collection: Collection, entryMap: Entry) {

View File

@ -21,7 +21,7 @@ const de: LocalePhrasesRoot = {
content: 'Inhalt',
workflow: 'Arbeitsablauf',
media: 'Medien',
quickAdd: 'Schnell-Erstellung',
quickAdd: 'Schnellerstellung',
},
app: {
errorHeader: 'Fehler beim Laden der CMS-Konfiguration.',
@ -36,8 +36,8 @@ const de: LocalePhrasesRoot = {
},
collection: {
sidebar: {
collections: 'Inhaltstypen',
allCollections: 'Allen Inhaltstypen',
collections: 'Bereiche',
allCollections: 'Allen Bereichen',
searchAll: 'Alles durchsuchen',
searchIn: 'Suchen in',
},
@ -231,6 +231,10 @@ const de: LocalePhrasesRoot = {
},
},
ui: {
common: {
yes: 'Ja',
no: 'Nein',
},
default: {
goBackToSite: 'Zurück zur Seite',
},

View File

@ -6,16 +6,28 @@ import type { CollectionViewStyle } from '@staticcms/core/constants/collectionVi
import type { Entry, Group, GroupMap, Sort } from '@staticcms/core/interface';
import type { RootState } from '@staticcms/core/store';
export function selectEntriesSort(entries: RootState, collection: string) {
export function selectEntriesSort(entries: RootState, collection?: string) {
if (!collection) {
return undefined;
}
const sort = entries.entries.sort as Sort | undefined;
return sort?.[collection];
}
export const selectEntriesFilter = (collectionName: string) => (entries: RootState) => {
return entries.entries.filter?.[collectionName];
export const selectEntriesFilter = (collectionName?: string) => (entries: RootState) => {
if (!collectionName) {
return {};
}
return entries.entries.filter?.[collectionName] ?? {};
};
export function selectEntriesGroup(entries: RootState, collection: string) {
export function selectEntriesGroup(entries: RootState, collection?: string) {
if (!collection) {
return {};
}
const group = entries.entries.group as Group | undefined;
return group?.[collection] || {};
}

View File

@ -1,17 +0,0 @@
import { createHashHistory } from 'history';
const history = createHashHistory();
export function navigateToCollection(collectionName: string) {
return history.push(`/collections/${collectionName}`);
}
export function navigateToNewEntry(collectionName: string) {
return history.replace(`/collections/${collectionName}/new`);
}
export function navigateToEntry(collectionName: string, slug: string) {
return history.replace(`/collections/${collectionName}/entries/${slug}`);
}
export { history };