Fix/bugfixes (#80)

* Fix widgetsFor for lists and objects
* Fix input for datetime widget
* Fix search
This commit is contained in:
Daniel Lautzenheiser 2022-11-09 09:52:05 -05:00 committed by GitHub
parent b13653b26d
commit 28a1f7a78a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 270 additions and 178 deletions

View File

@ -628,7 +628,7 @@ collections:
widget: select widget: select
options: options:
- label: One - label: One
value: "One" value: 'One'
- label: Two - label: Two
value: 2 value: 2
- label: Three - label: Three

View File

@ -1,7 +1,7 @@
// Register all the things // Register all the things
CMS.init(); CMS.init();
const PostPreview = ({ entry, widgetFor }) => { const PostPreview = ({ entry, widgetFor, widgetsFor }) => {
return h( return h(
'div', 'div',
{}, {},

View File

@ -140,7 +140,7 @@ export function searchEntries(searchTerm: string, searchCollections: string[], p
return dispatch(searchFailure(new Error(`No integration found for name "${integration}"`))); return dispatch(searchFailure(new Error(`No integration found for name "${integration}"`)));
} }
return dispatch(searchSuccess(response.entries, response.pagination)); return dispatch(searchSuccess(response.entries, page));
} catch (error: unknown) { } catch (error: unknown) {
console.error(error); console.error(error);
if (error instanceof Error) { if (error instanceof Error) {

View File

@ -261,7 +261,8 @@ interface PersistArgs {
function collectionDepth(collection: Collection) { function collectionDepth(collection: Collection) {
let depth; let depth;
depth = 'nested' in collection && collection.nested?.depth || getPathDepth(collection.path ?? ''); depth =
('nested' in collection && collection.nested?.depth) || getPathDepth(collection.path ?? '');
if (hasI18n(collection)) { if (hasI18n(collection)) {
depth = getI18nFilesDepth(collection, depth); depth = getI18nFilesDepth(collection, depth);
@ -549,7 +550,7 @@ export class Backend {
} }
const hits = entries const hits = entries
.filter(({ score }: fuzzy.FilterResult<Entry>) => score > 5) .filter(({ score }: fuzzy.FilterResult<Entry>) => score > 3)
.sort(sortByScore) .sort(sortByScore)
.map((f: fuzzy.FilterResult<Entry>) => f.original); .map((f: fuzzy.FilterResult<Entry>) => f.original);
return { entries: hits, pagination: 1 }; return { entries: hits, pagination: 1 };

View File

@ -110,7 +110,14 @@ const CollectionView = ({
} }
} }
return <EntriesSearch collections={searchCollections} searchTerm={searchTerm} />; return (
<EntriesSearch
key="search"
collections={searchCollections}
searchTerm={searchTerm}
viewStyle={viewStyle}
/>
);
} }
return ( return (
@ -210,11 +217,14 @@ const CollectionView = ({
<CollectionMain> <CollectionMain>
<> <>
{isSearchResults ? ( {isSearchResults ? (
<SearchResultContainer> <>
<SearchResultHeading> <SearchResultContainer>
{t(searchResultKey, { searchTerm, collection: collection.label })} <SearchResultHeading>
</SearchResultHeading> {t(searchResultKey, { searchTerm, collection: collection.label })}
</SearchResultContainer> </SearchResultHeading>
</SearchResultContainer>
<CollectionControls viewStyle={viewStyle} onChangeViewStyle={changeViewStyle} t={t} />
</>
) : ( ) : (
<> <>
<CollectionTop collection={collection} newEntryUrl={newEntryUrl} /> <CollectionTop collection={collection} newEntryUrl={newEntryUrl} />

View File

@ -33,15 +33,15 @@ const CollectionControlsContainer = styled('div')`
interface CollectionControlsProps { interface CollectionControlsProps {
viewStyle: CollectionViewStyle; viewStyle: CollectionViewStyle;
onChangeViewStyle: (viewStyle: CollectionViewStyle) => void; onChangeViewStyle: (viewStyle: CollectionViewStyle) => void;
sortableFields: SortableField[]; sortableFields?: SortableField[];
onSortClick: (key: string, direction?: SortDirection) => Promise<void>; onSortClick?: (key: string, direction?: SortDirection) => Promise<void>;
sort: SortMap | undefined; sort?: SortMap | undefined;
filter: Record<string, FilterMap>; filter?: Record<string, FilterMap>;
viewFilters: ViewFilter[]; viewFilters?: ViewFilter[];
onFilterClick: (filter: ViewFilter) => void; onFilterClick?: (filter: ViewFilter) => void;
group: Record<string, GroupMap>; group?: Record<string, GroupMap>;
viewGroups: ViewGroup[]; viewGroups?: ViewGroup[];
onGroupClick: (filter: ViewGroup) => void; onGroupClick?: (filter: ViewGroup) => void;
} }
const CollectionControls = ({ const CollectionControls = ({
@ -61,20 +61,26 @@ const CollectionControls = ({
return ( return (
<CollectionControlsContainer> <CollectionControlsContainer>
<ViewStyleControl viewStyle={viewStyle} onChangeViewStyle={onChangeViewStyle} /> <ViewStyleControl viewStyle={viewStyle} onChangeViewStyle={onChangeViewStyle} />
{viewGroups.length > 0 && ( {viewGroups && onGroupClick && group
<GroupControl viewGroups={viewGroups} onGroupClick={onGroupClick} t={t} group={group} /> ? viewGroups.length > 0 && (
)} <GroupControl viewGroups={viewGroups} onGroupClick={onGroupClick} t={t} group={group} />
{viewFilters.length > 0 && ( )
<FilterControl : null}
viewFilters={viewFilters} {viewFilters && onFilterClick && filter
onFilterClick={onFilterClick} ? viewFilters.length > 0 && (
t={t} <FilterControl
filter={filter} viewFilters={viewFilters}
/> onFilterClick={onFilterClick}
)} t={t}
{sortableFields.length > 0 && ( filter={filter}
<SortControl fields={sortableFields} sort={sort} onSortClick={onSortClick} /> />
)} )
: null}
{sortableFields && onSortClick && sort
? sortableFields.length > 0 && (
<SortControl fields={sortableFields} sort={sort} onSortClick={onSortClick} />
)
: null}
</CollectionControlsContainer> </CollectionControlsContainer>
); );
}; };

View File

@ -1,37 +1,27 @@
import { styled } from '@mui/material/styles';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import InputAdornment from '@mui/material/InputAdornment'; import InputAdornment from '@mui/material/InputAdornment';
import Popover from '@mui/material/Popover';
import { styled } from '@mui/material/styles';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { colors, colorsRaw, lengths } from '../../components/UI/styles';
import { transientOptions } from '../../lib'; import { transientOptions } from '../../lib';
import { colors, colorsRaw, lengths, zIndex } from '../../components/UI/styles';
import type { KeyboardEvent, MouseEvent } from 'react'; import type { ChangeEvent, FocusEvent, KeyboardEvent, MouseEvent } from 'react';
import type { Collection, Collections, TranslatedProps } from '../../interface'; import type { Collection, Collections, TranslatedProps } from '../../interface';
const SearchContainer = styled('div')` const SearchContainer = styled('div')`
position: relative; position: relative;
`; `;
const SuggestionsContainer = styled('div')`
position: relative;
width: 100%;
`;
const Suggestions = styled('ul')` const Suggestions = styled('ul')`
position: absolute;
top: 0px;
left: 0;
right: 0;
padding: 10px 0; padding: 10px 0;
margin: 0; margin: 0;
list-style: none; list-style: none;
background-color: #fff;
border-radius: ${lengths.borderRadius}; border-radius: ${lengths.borderRadius};
border: 1px solid ${colors.textFieldBorder}; width: 240px;
z-index: ${zIndex.zIndex1};
`; `;
const SuggestionHeader = styled('li')` const SuggestionHeader = styled('li')`
@ -66,6 +56,10 @@ const SuggestionDivider = styled('div')`
width: 100%; width: 100%;
`; `;
const StyledPopover = styled(Popover)`
margin-left: -44px;
`;
interface CollectionSearchProps { interface CollectionSearchProps {
collections: Collections; collections: Collections;
collection?: Collection; collection?: Collection;
@ -74,17 +68,34 @@ interface CollectionSearchProps {
} }
const CollectionSearch = ({ const CollectionSearch = ({
collections, collections: collectionsMap,
collection, collection,
searchTerm, searchTerm,
onSubmit, onSubmit,
t, t,
}: TranslatedProps<CollectionSearchProps>) => { }: TranslatedProps<CollectionSearchProps>) => {
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>();
const [query, setQuery] = useState(searchTerm); const [query, setQuery] = useState(searchTerm);
const [suggestionsVisible, setSuggestionsVisible] = useState(false); const [anchorEl, setAnchorEl] = useState<HTMLInputElement | HTMLTextAreaElement | null>(null);
const open = Boolean(anchorEl);
const collections = useMemo(() => Object.values(collectionsMap), [collectionsMap]);
const handleClose = useCallback(() => {
setAnchorEl(null);
inputRef.current?.blur();
}, []);
const handleFocus = useCallback((event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setAnchorEl(event.currentTarget);
}, []);
const handleBlur = useCallback(() => {
setAnchorEl(null);
}, []);
const getSelectedSelectionBasedOnProps = useCallback(() => { const getSelectedSelectionBasedOnProps = useCallback(() => {
return collection ? Object.keys(collections).indexOf(collection.name) : -1; return collection ? collections.findIndex(c => c.name === collection.name) : -1;
}, [collection, collections]); }, [collection, collections]);
const [selectedCollectionIdx, setSelectedCollectionIdx] = useState( const [selectedCollectionIdx, setSelectedCollectionIdx] = useState(
@ -92,50 +103,62 @@ const CollectionSearch = ({
); );
const [prevCollection, setPrevCollection] = useState(collection); const [prevCollection, setPrevCollection] = useState(collection);
console.log('selectedCollectionIdx', selectedCollectionIdx);
useEffect(() => { useEffect(() => {
if (prevCollection !== collection) { if (prevCollection !== collection) {
console.log(
'resetting to ',
getSelectedSelectionBasedOnProps(),
'collection',
collection,
'prev',
prevCollection,
);
setSelectedCollectionIdx(getSelectedSelectionBasedOnProps()); setSelectedCollectionIdx(getSelectedSelectionBasedOnProps());
} }
setPrevCollection(collection); setPrevCollection(collection);
}, [collection, getSelectedSelectionBasedOnProps, prevCollection]); }, [collection, getSelectedSelectionBasedOnProps, prevCollection]);
const toggleSuggestions = useCallback((visible: boolean) => {
setSuggestionsVisible(visible);
}, []);
const selectNextSuggestion = useCallback(() => { const selectNextSuggestion = useCallback(() => {
setSelectedCollectionIdx( console.log('selectNextSuggestion');
Math.min(selectedCollectionIdx + 1, Object.keys(collections).length - 1), setSelectedCollectionIdx(Math.min(selectedCollectionIdx + 1, collections.length - 1));
);
}, [collections, selectedCollectionIdx]); }, [collections, selectedCollectionIdx]);
const selectPreviousSuggestion = useCallback(() => { const selectPreviousSuggestion = useCallback(() => {
console.log('selectPreviousSuggestion');
setSelectedCollectionIdx(Math.max(selectedCollectionIdx - 1, -1)); setSelectedCollectionIdx(Math.max(selectedCollectionIdx - 1, -1));
}, [selectedCollectionIdx]); }, [selectedCollectionIdx]);
const resetSelectedSuggestion = useCallback(() => { const resetSelectedSuggestion = useCallback(() => {
console.log('resetSelectedSuggestion');
setSelectedCollectionIdx(-1); setSelectedCollectionIdx(-1);
}, []); }, []);
const submitSearch = useCallback(() => { const submitSearch = useCallback(
toggleSuggestions(false); (index: number) => {
if (selectedCollectionIdx !== -1) { console.log('searching selectedCollectionIdx', index);
onSubmit(query, Object.values(collections)[selectedCollectionIdx]?.name); if (index !== -1) {
} else { console.log(collections, index);
onSubmit(query); onSubmit(query, collections[index]?.name);
} } else {
}, [collections, onSubmit, query, selectedCollectionIdx, toggleSuggestions]); onSubmit(query);
}
handleClose();
},
[collections, handleClose, onSubmit, query],
);
const handleKeyDown = useCallback( const handleKeyDown = useCallback(
(event: KeyboardEvent) => { (event: KeyboardEvent) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
submitSearch(); submitSearch(selectedCollectionIdx);
} }
if (suggestionsVisible) { if (open) {
// allow closing of suggestions with escape key // allow closing of suggestions with escape key
if (event.key === 'Escape') { if (event.key === 'Escape') {
toggleSuggestions(false); handleClose();
} }
if (event.key === 'ArrowDown') { if (event.key === 'ArrowDown') {
@ -148,30 +171,37 @@ const CollectionSearch = ({
} }
}, },
[ [
handleClose,
open,
selectNextSuggestion, selectNextSuggestion,
selectPreviousSuggestion, selectPreviousSuggestion,
selectedCollectionIdx,
submitSearch, submitSearch,
suggestionsVisible,
toggleSuggestions,
], ],
); );
const handleQueryChange = useCallback( const handleQueryChange = useCallback(
(newQuery: string) => { (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const newQuery = event.target.value;
setQuery(newQuery); setQuery(newQuery);
toggleSuggestions(newQuery !== '');
if (newQuery === '') { if (newQuery !== '') {
resetSelectedSuggestion(); setAnchorEl(event.currentTarget);
return;
} }
resetSelectedSuggestion();
handleClose();
}, },
[resetSelectedSuggestion, toggleSuggestions], [handleClose, resetSelectedSuggestion],
); );
const handleSuggestionClick = useCallback( const handleSuggestionClick = useCallback(
(event: MouseEvent, idx: number) => { (event: MouseEvent, idx: number) => {
setSelectedCollectionIdx(idx);
submitSearch();
event.preventDefault(); event.preventDefault();
console.log('clicked index', idx);
setSelectedCollectionIdx(idx);
submitSearch(idx);
}, },
[submitSearch], [submitSearch],
); );
@ -180,16 +210,16 @@ const CollectionSearch = ({
<SearchContainer> <SearchContainer>
<TextField <TextField
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onClick={() => toggleSuggestions(true)}
placeholder={t('collection.sidebar.searchAll')} placeholder={t('collection.sidebar.searchAll')}
onBlur={() => toggleSuggestions(false)} onBlur={handleBlur}
onFocus={() => toggleSuggestions(query !== '')} onFocus={handleFocus}
value={query} value={query}
onChange={e => handleQueryChange(e.target.value)} onChange={handleQueryChange}
variant="outlined" variant="outlined"
size="small" size="small"
fullWidth fullWidth
InputProps={{ InputProps={{
inputRef,
startAdornment: ( startAdornment: (
<InputAdornment position="start"> <InputAdornment position="start">
<SearchIcon /> <SearchIcon />
@ -197,31 +227,42 @@ const CollectionSearch = ({
), ),
}} }}
/> />
{suggestionsVisible && ( <StyledPopover
<SuggestionsContainer> id="search-popover"
<Suggestions> open={open}
<SuggestionHeader>{t('collection.sidebar.searchIn')}</SuggestionHeader> anchorEl={anchorEl}
onClose={handleClose}
disableAutoFocus
disableEnforceFocus
disableScrollLock
hideBackdrop
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
<Suggestions>
<SuggestionHeader>{t('collection.sidebar.searchIn')}</SuggestionHeader>
<SuggestionItem
$isActive={selectedCollectionIdx === -1}
onClick={e => handleSuggestionClick(e, -1)}
onMouseDown={e => e.preventDefault()}
>
{t('collection.sidebar.allCollections')}
</SuggestionItem>
<SuggestionDivider />
{collections.map((collection, idx) => (
<SuggestionItem <SuggestionItem
$isActive={selectedCollectionIdx === -1} key={idx}
onClick={e => handleSuggestionClick(e, -1)} $isActive={idx === selectedCollectionIdx}
onClick={e => handleSuggestionClick(e, idx)}
onMouseDown={e => e.preventDefault()} onMouseDown={e => e.preventDefault()}
> >
{t('collection.sidebar.allCollections')} {collection.label}
</SuggestionItem> </SuggestionItem>
<SuggestionDivider /> ))}
{Object.values(collections).map((collection, idx) => ( </Suggestions>
<SuggestionItem </StyledPopover>
key={idx}
$isActive={idx === selectedCollectionIdx}
onClick={e => handleSuggestionClick(e, idx)}
onMouseDown={e => e.preventDefault()}
>
{collection.label}
</SuggestionItem>
))}
</Suggestions>
</SuggestionsContainer>
)}
</SearchContainer> </SearchContainer>
); );
}; };

View File

@ -24,7 +24,7 @@ export interface BaseEntriesProps {
isFetching: boolean; isFetching: boolean;
viewStyle: CollectionViewStyle; viewStyle: CollectionViewStyle;
cursor: Cursor; cursor: Cursor;
handleCursorActions: (action: string) => void; handleCursorActions?: (action: string) => void;
} }
export interface SingleCollectionEntriesProps extends BaseEntriesProps { export interface SingleCollectionEntriesProps extends BaseEntriesProps {

View File

@ -1,5 +1,5 @@
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
@ -11,6 +11,7 @@ import { selectSearchedEntries } from '../../../reducers';
import Entries from './Entries'; import Entries from './Entries';
import type { ConnectedProps } from 'react-redux'; import type { ConnectedProps } from 'react-redux';
import type { CollectionViewStyle } from '../../../constants/collectionViews';
import type { Collections } from '../../../interface'; import type { Collections } from '../../../interface';
import type { RootState } from '../../../store'; import type { RootState } from '../../../store';
@ -20,35 +21,24 @@ const EntriesSearch = ({
isFetching, isFetching,
page, page,
searchTerm, searchTerm,
viewStyle,
searchEntries, searchEntries,
collectionNames,
clearSearch, clearSearch,
}: EntriesSearchProps) => { }: EntriesSearchProps) => {
const collectionNames = useMemo(() => Object.keys(collections), [collections]);
const getCursor = useCallback(() => { const getCursor = useCallback(() => {
return Cursor.create({ return Cursor.create({
actions: Number.isNaN(page) ? [] : ['append_next'], actions: Number.isNaN(page) ? [] : ['append_next'],
}); });
}, [page]); }, [page]);
const handleCursorActions = useCallback(
(action: string) => {
if (action === 'append_next') {
const nextPage = page + 1;
searchEntries(searchTerm, collectionNames, nextPage);
}
},
[collectionNames, page, searchEntries, searchTerm],
);
useEffect(() => {
searchEntries(searchTerm, collectionNames);
}, [collectionNames, searchEntries, searchTerm]);
useEffect(() => { useEffect(() => {
return () => { return () => {
clearSearch(); clearSearch();
}; };
}, [clearSearch]); // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [prevSearch, setPrevSearch] = useState(''); const [prevSearch, setPrevSearch] = useState('');
const [prevCollectionNames, setPrevCollectionNames] = useState<string[]>([]); const [prevCollectionNames, setPrevCollectionNames] = useState<string[]>([]);
@ -61,16 +51,19 @@ const EntriesSearch = ({
setPrevSearch(searchTerm); setPrevSearch(searchTerm);
setPrevCollectionNames(collectionNames); setPrevCollectionNames(collectionNames);
searchEntries(searchTerm, collectionNames); setTimeout(() => {
}, [collectionNames, prevCollectionNames, prevSearch, searchEntries, searchTerm]); searchEntries(searchTerm, collectionNames);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [collectionNames, prevCollectionNames, prevSearch, searchTerm]);
return ( return (
<Entries <Entries
cursor={getCursor()} cursor={getCursor()}
handleCursorActions={handleCursorActions}
collections={collections} collections={collections}
entries={entries} entries={entries}
isFetching={isFetching} isFetching={isFetching}
viewStyle={viewStyle}
/> />
); );
}; };
@ -78,16 +71,16 @@ const EntriesSearch = ({
interface EntriesSearchOwnProps { interface EntriesSearchOwnProps {
searchTerm: string; searchTerm: string;
collections: Collections; collections: Collections;
viewStyle: CollectionViewStyle;
} }
function mapStateToProps(state: RootState, ownProps: EntriesSearchOwnProps) { function mapStateToProps(state: RootState, ownProps: EntriesSearchOwnProps) {
const { searchTerm } = ownProps; const { searchTerm, collections, viewStyle } = ownProps;
const collections = Object.values(ownProps.collections); const collectionNames = Object.keys(collections);
const collectionNames = Object.keys(ownProps.collections);
const isFetching = state.search.isFetching; const isFetching = state.search.isFetching;
const page = state.search.page; const page = state.search.page;
const entries = selectSearchedEntries(state, collectionNames); const entries = selectSearchedEntries(state, collectionNames);
return { isFetching, page, collections, collectionNames, entries, searchTerm }; return { isFetching, page, collections, viewStyle, entries, searchTerm };
} }
const mapDispatchToProps = { const mapDispatchToProps = {

View File

@ -41,7 +41,7 @@ export interface BaseEntryListingProps {
entries: Entry[]; entries: Entry[];
viewStyle: CollectionViewStyle; viewStyle: CollectionViewStyle;
cursor?: Cursor; cursor?: Cursor;
handleCursorActions: (action: string) => void; handleCursorActions?: (action: string) => void;
page?: number; page?: number;
} }
@ -65,14 +65,11 @@ const EntryListing = ({
handleCursorActions, handleCursorActions,
...otherProps ...otherProps
}: EntryListingProps) => { }: EntryListingProps) => {
const hasMore = useMemo(() => { const hasMore = useMemo(() => cursor?.actions?.has('append_next'), [cursor?.actions]);
const hasMore = cursor?.actions?.has('append_next');
return hasMore;
}, [cursor?.actions]);
const handleLoadMore = useCallback(() => { const handleLoadMore = useCallback(() => {
if (hasMore) { if (hasMore) {
handleCursorActions('append_next'); handleCursorActions?.('append_next');
} }
}, [handleCursorActions, hasMore]); }, [handleCursorActions, hasMore]);
@ -138,7 +135,7 @@ const EntryListing = ({
<div> <div>
<CardsGrid $layout={viewStyle}> <CardsGrid $layout={viewStyle}>
{renderedCards} {renderedCards}
{hasMore && <Waypoint key={page} onEnter={handleLoadMore} />} {hasMore && handleLoadMore && <Waypoint key={page} onEnter={handleLoadMore} />}
</CardsGrid> </CardsGrid>
</div> </div>
); );

View File

@ -1,6 +1,5 @@
import React, { useMemo } from 'react'; import React, { memo } from 'react';
import type { ReactNode } from 'react';
import type { TemplatePreviewComponent, TemplatePreviewProps } from '../../../interface'; import type { TemplatePreviewComponent, TemplatePreviewProps } from '../../../interface';
interface EditorPreviewContentProps { interface EditorPreviewContentProps {
@ -8,17 +7,16 @@ interface EditorPreviewContentProps {
previewProps: TemplatePreviewProps; previewProps: TemplatePreviewProps;
} }
const EditorPreviewContent = ({ previewComponent, previewProps }: EditorPreviewContentProps) => { const EditorPreviewContent = memo(
return useMemo(() => { ({ previewComponent, previewProps }: EditorPreviewContentProps) => {
let children: ReactNode;
if (!previewComponent) { if (!previewComponent) {
children = null; return null;
} else {
children = React.createElement(previewComponent, previewProps);
} }
return children; return React.createElement(previewComponent, previewProps);
}, [previewComponent, previewProps]); },
}; );
EditorPreviewContent.displayName = 'EditorPreviewContent';
export default EditorPreviewContent; export default EditorPreviewContent;

View File

@ -29,6 +29,7 @@ import type {
Field, Field,
GetAssetFunction, GetAssetFunction,
ListField, ListField,
ObjectValue,
RenderedField, RenderedField,
TemplatePreviewProps, TemplatePreviewProps,
TranslatedProps, TranslatedProps,
@ -367,36 +368,92 @@ const PreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
*/ */
const widgetsFor = useCallback( const widgetsFor = useCallback(
(name: string) => { (name: string) => {
const field = fields.find(f => f.name === name); const cmsConfig = config.config;
if (!field) { if (!cmsConfig) {
return { return {
data: null, data: null,
widgets: {}, widgets: {},
}; };
} }
const nestedFields = field && 'fields' in field ? field.fields ?? [] : []; const field = fields.find(f => f.name === name);
const value = entry.data?.[field.name] as EntryData | EntryData[]; if (!field || !('fields' in field)) {
return {
data: null,
widgets: {},
};
}
if (Array.isArray(value)) { const value = entry.data?.[field.name];
return value.map(val => { const nestedFields = field && 'fields' in field ? field.fields ?? [] : [];
const widgets = nestedFields.reduce((acc, field, index) => {
acc[field.name] = <div key={index}>{widgetFor(field.name)}</div>; if (field.widget === 'list' || Array.isArray(value)) {
return acc; let finalValue: ObjectValue[];
}, {} as Record<string, ReactNode>); if (!value || typeof value !== 'object') {
return { data: val, widgets }; finalValue = [];
}); } else if (!Array.isArray(value)) {
finalValue = [value];
} else {
finalValue = value as ObjectValue[];
}
return finalValue
.filter((val: unknown) => typeof val === 'object')
.map((val: ObjectValue) => {
const widgets = nestedFields.reduce((acc, field, index) => {
acc[field.name] = (
<div key={index}>
{getWidgetFor(
cmsConfig,
collection,
field.name,
fields,
entry,
inferedFields,
handleGetAsset,
nestedFields,
val,
index,
)}
</div>
);
return acc;
}, {} as Record<string, ReactNode>);
return { data: val, widgets };
});
}
if (typeof value !== 'object') {
return {
data: {},
widgets: {},
};
} }
return { return {
data: value, data: value,
widgets: nestedFields.reduce((acc, field, index) => { widgets: nestedFields.reduce((acc, field, index) => {
acc[field.name] = <div key={index}>{widgetFor(field.name)}</div>; acc[field.name] = (
<div key={index}>
{getWidgetFor(
cmsConfig,
collection,
field.name,
fields,
entry,
inferedFields,
handleGetAsset,
nestedFields,
value,
index,
)}
</div>
);
return acc; return acc;
}, {} as Record<string, ReactNode>), }, {} as Record<string, ReactNode>),
}; };
}, },
[entry.data, fields, widgetFor], [collection, config.config, entry, fields, handleGetAsset, inferedFields],
); );
const previewStyles = useMemo( const previewStyles = useMemo(

View File

@ -19,7 +19,7 @@ const knownMetaKeys = [
]; ];
function filterUnknownMetaKeys(meta: Record<string, unknown>) { function filterUnknownMetaKeys(meta: Record<string, unknown>) {
return Object.keys(meta).reduce((acc, k) => { return Object.keys(meta ?? {}).reduce((acc, k) => {
if (knownMetaKeys.includes(k)) { if (knownMetaKeys.includes(k)) {
acc[k] = meta[k]; acc[k] = meta[k];
} }

View File

@ -13,7 +13,6 @@ import parse from 'date-fns/parse';
import parseISO from 'date-fns/parseISO'; import parseISO from 'date-fns/parseISO';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import alert from '../../components/UI/Alert';
import { isNotEmpty } from '../../lib/util/string.util'; import { isNotEmpty } from '../../lib/util/string.util';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
@ -169,16 +168,7 @@ const DateTimeControl = ({
return null; return null;
} }
let formattedValue = defaultValue; const inputDate = field.picker_utc ? utcDate : dateValue;
try {
formattedValue = formatDate(field.picker_utc ? utcDate : dateValue, inputFormat);
} catch (e) {
alert({
title: 'editor.editorWidgets.datetime.invalidDateTitle',
body: 'editor.editorWidgets.datetime.invalidDateBody',
});
console.error(e);
}
if (dateFormat && !timeFormat) { if (dateFormat && !timeFormat) {
return ( return (
@ -186,7 +176,7 @@ const DateTimeControl = ({
key="mobile-date-picker" key="mobile-date-picker"
inputFormat={inputFormat} inputFormat={inputFormat}
label={label} label={label}
value={formattedValue} value={inputDate}
onChange={handleChange} onChange={handleChange}
renderInput={params => ( renderInput={params => (
<TextField <TextField
@ -216,7 +206,7 @@ const DateTimeControl = ({
key="time-picker" key="time-picker"
label={label} label={label}
inputFormat={inputFormat} inputFormat={inputFormat}
value={formattedValue} value={inputDate}
onChange={handleChange} onChange={handleChange}
renderInput={params => ( renderInput={params => (
<TextField <TextField
@ -245,7 +235,7 @@ const DateTimeControl = ({
key="mobile-date-time-picker" key="mobile-date-time-picker"
inputFormat={inputFormat} inputFormat={inputFormat}
label={label} label={label}
value={formattedValue} value={inputDate}
onChange={handleChange} onChange={handleChange}
renderInput={params => ( renderInput={params => (
<TextField <TextField
@ -270,7 +260,6 @@ const DateTimeControl = ({
}, [ }, [
dateFormat, dateFormat,
dateValue, dateValue,
defaultValue,
field.picker_utc, field.picker_utc,
handleChange, handleChange,
hasErrors, hasErrors,