Fix/bugfixes (#80)
* Fix widgetsFor for lists and objects * Fix input for datetime widget * Fix search
This commit is contained in:
parent
b13653b26d
commit
28a1f7a78a
@ -628,7 +628,7 @@ collections:
|
||||
widget: select
|
||||
options:
|
||||
- label: One
|
||||
value: "One"
|
||||
value: 'One'
|
||||
- label: Two
|
||||
value: 2
|
||||
- label: Three
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Register all the things
|
||||
CMS.init();
|
||||
|
||||
const PostPreview = ({ entry, widgetFor }) => {
|
||||
const PostPreview = ({ entry, widgetFor, widgetsFor }) => {
|
||||
return h(
|
||||
'div',
|
||||
{},
|
||||
|
@ -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(searchSuccess(response.entries, response.pagination));
|
||||
return dispatch(searchSuccess(response.entries, page));
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
if (error instanceof Error) {
|
||||
|
@ -261,7 +261,8 @@ interface PersistArgs {
|
||||
|
||||
function collectionDepth(collection: Collection) {
|
||||
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)) {
|
||||
depth = getI18nFilesDepth(collection, depth);
|
||||
@ -549,7 +550,7 @@ export class Backend {
|
||||
}
|
||||
|
||||
const hits = entries
|
||||
.filter(({ score }: fuzzy.FilterResult<Entry>) => score > 5)
|
||||
.filter(({ score }: fuzzy.FilterResult<Entry>) => score > 3)
|
||||
.sort(sortByScore)
|
||||
.map((f: fuzzy.FilterResult<Entry>) => f.original);
|
||||
return { entries: hits, pagination: 1 };
|
||||
|
@ -110,7 +110,14 @@ const CollectionView = ({
|
||||
}
|
||||
}
|
||||
|
||||
return <EntriesSearch collections={searchCollections} searchTerm={searchTerm} />;
|
||||
return (
|
||||
<EntriesSearch
|
||||
key="search"
|
||||
collections={searchCollections}
|
||||
searchTerm={searchTerm}
|
||||
viewStyle={viewStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -210,11 +217,14 @@ const CollectionView = ({
|
||||
<CollectionMain>
|
||||
<>
|
||||
{isSearchResults ? (
|
||||
<SearchResultContainer>
|
||||
<SearchResultHeading>
|
||||
{t(searchResultKey, { searchTerm, collection: collection.label })}
|
||||
</SearchResultHeading>
|
||||
</SearchResultContainer>
|
||||
<>
|
||||
<SearchResultContainer>
|
||||
<SearchResultHeading>
|
||||
{t(searchResultKey, { searchTerm, collection: collection.label })}
|
||||
</SearchResultHeading>
|
||||
</SearchResultContainer>
|
||||
<CollectionControls viewStyle={viewStyle} onChangeViewStyle={changeViewStyle} t={t} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CollectionTop collection={collection} newEntryUrl={newEntryUrl} />
|
||||
|
@ -33,15 +33,15 @@ const CollectionControlsContainer = styled('div')`
|
||||
interface CollectionControlsProps {
|
||||
viewStyle: CollectionViewStyle;
|
||||
onChangeViewStyle: (viewStyle: CollectionViewStyle) => void;
|
||||
sortableFields: SortableField[];
|
||||
onSortClick: (key: string, direction?: SortDirection) => Promise<void>;
|
||||
sort: SortMap | undefined;
|
||||
filter: Record<string, FilterMap>;
|
||||
viewFilters: ViewFilter[];
|
||||
onFilterClick: (filter: ViewFilter) => void;
|
||||
group: Record<string, GroupMap>;
|
||||
viewGroups: ViewGroup[];
|
||||
onGroupClick: (filter: ViewGroup) => void;
|
||||
sortableFields?: SortableField[];
|
||||
onSortClick?: (key: string, direction?: SortDirection) => Promise<void>;
|
||||
sort?: SortMap | undefined;
|
||||
filter?: Record<string, FilterMap>;
|
||||
viewFilters?: ViewFilter[];
|
||||
onFilterClick?: (filter: ViewFilter) => void;
|
||||
group?: Record<string, GroupMap>;
|
||||
viewGroups?: ViewGroup[];
|
||||
onGroupClick?: (filter: ViewGroup) => void;
|
||||
}
|
||||
|
||||
const CollectionControls = ({
|
||||
@ -61,20 +61,26 @@ const CollectionControls = ({
|
||||
return (
|
||||
<CollectionControlsContainer>
|
||||
<ViewStyleControl viewStyle={viewStyle} onChangeViewStyle={onChangeViewStyle} />
|
||||
{viewGroups.length > 0 && (
|
||||
<GroupControl viewGroups={viewGroups} onGroupClick={onGroupClick} t={t} group={group} />
|
||||
)}
|
||||
{viewFilters.length > 0 && (
|
||||
<FilterControl
|
||||
viewFilters={viewFilters}
|
||||
onFilterClick={onFilterClick}
|
||||
t={t}
|
||||
filter={filter}
|
||||
/>
|
||||
)}
|
||||
{sortableFields.length > 0 && (
|
||||
<SortControl fields={sortableFields} sort={sort} onSortClick={onSortClick} />
|
||||
)}
|
||||
{viewGroups && onGroupClick && group
|
||||
? viewGroups.length > 0 && (
|
||||
<GroupControl viewGroups={viewGroups} onGroupClick={onGroupClick} t={t} group={group} />
|
||||
)
|
||||
: null}
|
||||
{viewFilters && onFilterClick && filter
|
||||
? viewFilters.length > 0 && (
|
||||
<FilterControl
|
||||
viewFilters={viewFilters}
|
||||
onFilterClick={onFilterClick}
|
||||
t={t}
|
||||
filter={filter}
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
{sortableFields && onSortClick && sort
|
||||
? sortableFields.length > 0 && (
|
||||
<SortControl fields={sortableFields} sort={sort} onSortClick={onSortClick} />
|
||||
)
|
||||
: null}
|
||||
</CollectionControlsContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,37 +1,27 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
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 React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { translate } from 'react-polyglot';
|
||||
|
||||
import { colors, colorsRaw, lengths } from '../../components/UI/styles';
|
||||
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';
|
||||
|
||||
const SearchContainer = styled('div')`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const SuggestionsContainer = styled('div')`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const Suggestions = styled('ul')`
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 10px 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
background-color: #fff;
|
||||
border-radius: ${lengths.borderRadius};
|
||||
border: 1px solid ${colors.textFieldBorder};
|
||||
z-index: ${zIndex.zIndex1};
|
||||
width: 240px;
|
||||
`;
|
||||
|
||||
const SuggestionHeader = styled('li')`
|
||||
@ -66,6 +56,10 @@ const SuggestionDivider = styled('div')`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledPopover = styled(Popover)`
|
||||
margin-left: -44px;
|
||||
`;
|
||||
|
||||
interface CollectionSearchProps {
|
||||
collections: Collections;
|
||||
collection?: Collection;
|
||||
@ -74,17 +68,34 @@ interface CollectionSearchProps {
|
||||
}
|
||||
|
||||
const CollectionSearch = ({
|
||||
collections,
|
||||
collections: collectionsMap,
|
||||
collection,
|
||||
searchTerm,
|
||||
onSubmit,
|
||||
t,
|
||||
}: TranslatedProps<CollectionSearchProps>) => {
|
||||
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>();
|
||||
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(() => {
|
||||
return collection ? Object.keys(collections).indexOf(collection.name) : -1;
|
||||
return collection ? collections.findIndex(c => c.name === collection.name) : -1;
|
||||
}, [collection, collections]);
|
||||
|
||||
const [selectedCollectionIdx, setSelectedCollectionIdx] = useState(
|
||||
@ -92,50 +103,62 @@ const CollectionSearch = ({
|
||||
);
|
||||
const [prevCollection, setPrevCollection] = useState(collection);
|
||||
|
||||
console.log('selectedCollectionIdx', selectedCollectionIdx);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevCollection !== collection) {
|
||||
console.log(
|
||||
'resetting to ',
|
||||
getSelectedSelectionBasedOnProps(),
|
||||
'collection',
|
||||
collection,
|
||||
'prev',
|
||||
prevCollection,
|
||||
);
|
||||
setSelectedCollectionIdx(getSelectedSelectionBasedOnProps());
|
||||
}
|
||||
setPrevCollection(collection);
|
||||
}, [collection, getSelectedSelectionBasedOnProps, prevCollection]);
|
||||
|
||||
const toggleSuggestions = useCallback((visible: boolean) => {
|
||||
setSuggestionsVisible(visible);
|
||||
}, []);
|
||||
|
||||
const selectNextSuggestion = useCallback(() => {
|
||||
setSelectedCollectionIdx(
|
||||
Math.min(selectedCollectionIdx + 1, Object.keys(collections).length - 1),
|
||||
);
|
||||
console.log('selectNextSuggestion');
|
||||
setSelectedCollectionIdx(Math.min(selectedCollectionIdx + 1, collections.length - 1));
|
||||
}, [collections, selectedCollectionIdx]);
|
||||
|
||||
const selectPreviousSuggestion = useCallback(() => {
|
||||
console.log('selectPreviousSuggestion');
|
||||
setSelectedCollectionIdx(Math.max(selectedCollectionIdx - 1, -1));
|
||||
}, [selectedCollectionIdx]);
|
||||
|
||||
const resetSelectedSuggestion = useCallback(() => {
|
||||
console.log('resetSelectedSuggestion');
|
||||
setSelectedCollectionIdx(-1);
|
||||
}, []);
|
||||
|
||||
const submitSearch = useCallback(() => {
|
||||
toggleSuggestions(false);
|
||||
if (selectedCollectionIdx !== -1) {
|
||||
onSubmit(query, Object.values(collections)[selectedCollectionIdx]?.name);
|
||||
} else {
|
||||
onSubmit(query);
|
||||
}
|
||||
}, [collections, onSubmit, query, selectedCollectionIdx, toggleSuggestions]);
|
||||
const submitSearch = useCallback(
|
||||
(index: number) => {
|
||||
console.log('searching selectedCollectionIdx', index);
|
||||
if (index !== -1) {
|
||||
console.log(collections, index);
|
||||
onSubmit(query, collections[index]?.name);
|
||||
} else {
|
||||
onSubmit(query);
|
||||
}
|
||||
handleClose();
|
||||
},
|
||||
[collections, handleClose, onSubmit, query],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
submitSearch();
|
||||
submitSearch(selectedCollectionIdx);
|
||||
}
|
||||
|
||||
if (suggestionsVisible) {
|
||||
if (open) {
|
||||
// allow closing of suggestions with escape key
|
||||
if (event.key === 'Escape') {
|
||||
toggleSuggestions(false);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
@ -148,30 +171,37 @@ const CollectionSearch = ({
|
||||
}
|
||||
},
|
||||
[
|
||||
handleClose,
|
||||
open,
|
||||
selectNextSuggestion,
|
||||
selectPreviousSuggestion,
|
||||
selectedCollectionIdx,
|
||||
submitSearch,
|
||||
suggestionsVisible,
|
||||
toggleSuggestions,
|
||||
],
|
||||
);
|
||||
|
||||
const handleQueryChange = useCallback(
|
||||
(newQuery: string) => {
|
||||
(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const newQuery = event.target.value;
|
||||
setQuery(newQuery);
|
||||
toggleSuggestions(newQuery !== '');
|
||||
if (newQuery === '') {
|
||||
resetSelectedSuggestion();
|
||||
|
||||
if (newQuery !== '') {
|
||||
setAnchorEl(event.currentTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
resetSelectedSuggestion();
|
||||
handleClose();
|
||||
},
|
||||
[resetSelectedSuggestion, toggleSuggestions],
|
||||
[handleClose, resetSelectedSuggestion],
|
||||
);
|
||||
|
||||
const handleSuggestionClick = useCallback(
|
||||
(event: MouseEvent, idx: number) => {
|
||||
setSelectedCollectionIdx(idx);
|
||||
submitSearch();
|
||||
event.preventDefault();
|
||||
console.log('clicked index', idx);
|
||||
setSelectedCollectionIdx(idx);
|
||||
submitSearch(idx);
|
||||
},
|
||||
[submitSearch],
|
||||
);
|
||||
@ -180,16 +210,16 @@ const CollectionSearch = ({
|
||||
<SearchContainer>
|
||||
<TextField
|
||||
onKeyDown={handleKeyDown}
|
||||
onClick={() => toggleSuggestions(true)}
|
||||
placeholder={t('collection.sidebar.searchAll')}
|
||||
onBlur={() => toggleSuggestions(false)}
|
||||
onFocus={() => toggleSuggestions(query !== '')}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
value={query}
|
||||
onChange={e => handleQueryChange(e.target.value)}
|
||||
onChange={handleQueryChange}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
inputRef,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
@ -197,31 +227,42 @@ const CollectionSearch = ({
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{suggestionsVisible && (
|
||||
<SuggestionsContainer>
|
||||
<Suggestions>
|
||||
<SuggestionHeader>{t('collection.sidebar.searchIn')}</SuggestionHeader>
|
||||
<StyledPopover
|
||||
id="search-popover"
|
||||
open={open}
|
||||
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
|
||||
$isActive={selectedCollectionIdx === -1}
|
||||
onClick={e => handleSuggestionClick(e, -1)}
|
||||
key={idx}
|
||||
$isActive={idx === selectedCollectionIdx}
|
||||
onClick={e => handleSuggestionClick(e, idx)}
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
>
|
||||
{t('collection.sidebar.allCollections')}
|
||||
{collection.label}
|
||||
</SuggestionItem>
|
||||
<SuggestionDivider />
|
||||
{Object.values(collections).map((collection, idx) => (
|
||||
<SuggestionItem
|
||||
key={idx}
|
||||
$isActive={idx === selectedCollectionIdx}
|
||||
onClick={e => handleSuggestionClick(e, idx)}
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
>
|
||||
{collection.label}
|
||||
</SuggestionItem>
|
||||
))}
|
||||
</Suggestions>
|
||||
</SuggestionsContainer>
|
||||
)}
|
||||
))}
|
||||
</Suggestions>
|
||||
</StyledPopover>
|
||||
</SearchContainer>
|
||||
);
|
||||
};
|
||||
|
@ -24,7 +24,7 @@ export interface BaseEntriesProps {
|
||||
isFetching: boolean;
|
||||
viewStyle: CollectionViewStyle;
|
||||
cursor: Cursor;
|
||||
handleCursorActions: (action: string) => void;
|
||||
handleCursorActions?: (action: string) => void;
|
||||
}
|
||||
|
||||
export interface SingleCollectionEntriesProps extends BaseEntriesProps {
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 {
|
||||
@ -11,6 +11,7 @@ import { selectSearchedEntries } from '../../../reducers';
|
||||
import Entries from './Entries';
|
||||
|
||||
import type { ConnectedProps } from 'react-redux';
|
||||
import type { CollectionViewStyle } from '../../../constants/collectionViews';
|
||||
import type { Collections } from '../../../interface';
|
||||
import type { RootState } from '../../../store';
|
||||
|
||||
@ -20,35 +21,24 @@ const EntriesSearch = ({
|
||||
isFetching,
|
||||
page,
|
||||
searchTerm,
|
||||
viewStyle,
|
||||
searchEntries,
|
||||
collectionNames,
|
||||
clearSearch,
|
||||
}: EntriesSearchProps) => {
|
||||
const collectionNames = useMemo(() => Object.keys(collections), [collections]);
|
||||
|
||||
const getCursor = useCallback(() => {
|
||||
return Cursor.create({
|
||||
actions: Number.isNaN(page) ? [] : ['append_next'],
|
||||
});
|
||||
}, [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(() => {
|
||||
return () => {
|
||||
clearSearch();
|
||||
};
|
||||
}, [clearSearch]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const [prevSearch, setPrevSearch] = useState('');
|
||||
const [prevCollectionNames, setPrevCollectionNames] = useState<string[]>([]);
|
||||
@ -61,16 +51,19 @@ const EntriesSearch = ({
|
||||
setPrevSearch(searchTerm);
|
||||
setPrevCollectionNames(collectionNames);
|
||||
|
||||
searchEntries(searchTerm, collectionNames);
|
||||
}, [collectionNames, prevCollectionNames, prevSearch, searchEntries, searchTerm]);
|
||||
setTimeout(() => {
|
||||
searchEntries(searchTerm, collectionNames);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [collectionNames, prevCollectionNames, prevSearch, searchTerm]);
|
||||
|
||||
return (
|
||||
<Entries
|
||||
cursor={getCursor()}
|
||||
handleCursorActions={handleCursorActions}
|
||||
collections={collections}
|
||||
entries={entries}
|
||||
isFetching={isFetching}
|
||||
viewStyle={viewStyle}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -78,16 +71,16 @@ const EntriesSearch = ({
|
||||
interface EntriesSearchOwnProps {
|
||||
searchTerm: string;
|
||||
collections: Collections;
|
||||
viewStyle: CollectionViewStyle;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: RootState, ownProps: EntriesSearchOwnProps) {
|
||||
const { searchTerm } = ownProps;
|
||||
const collections = Object.values(ownProps.collections);
|
||||
const collectionNames = Object.keys(ownProps.collections);
|
||||
const { searchTerm, collections, viewStyle } = ownProps;
|
||||
const collectionNames = Object.keys(collections);
|
||||
const isFetching = state.search.isFetching;
|
||||
const page = state.search.page;
|
||||
const entries = selectSearchedEntries(state, collectionNames);
|
||||
return { isFetching, page, collections, collectionNames, entries, searchTerm };
|
||||
return { isFetching, page, collections, viewStyle, entries, searchTerm };
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
@ -41,7 +41,7 @@ export interface BaseEntryListingProps {
|
||||
entries: Entry[];
|
||||
viewStyle: CollectionViewStyle;
|
||||
cursor?: Cursor;
|
||||
handleCursorActions: (action: string) => void;
|
||||
handleCursorActions?: (action: string) => void;
|
||||
page?: number;
|
||||
}
|
||||
|
||||
@ -65,14 +65,11 @@ const EntryListing = ({
|
||||
handleCursorActions,
|
||||
...otherProps
|
||||
}: EntryListingProps) => {
|
||||
const hasMore = useMemo(() => {
|
||||
const hasMore = cursor?.actions?.has('append_next');
|
||||
return hasMore;
|
||||
}, [cursor?.actions]);
|
||||
const hasMore = useMemo(() => cursor?.actions?.has('append_next'), [cursor?.actions]);
|
||||
|
||||
const handleLoadMore = useCallback(() => {
|
||||
if (hasMore) {
|
||||
handleCursorActions('append_next');
|
||||
handleCursorActions?.('append_next');
|
||||
}
|
||||
}, [handleCursorActions, hasMore]);
|
||||
|
||||
@ -138,7 +135,7 @@ const EntryListing = ({
|
||||
<div>
|
||||
<CardsGrid $layout={viewStyle}>
|
||||
{renderedCards}
|
||||
{hasMore && <Waypoint key={page} onEnter={handleLoadMore} />}
|
||||
{hasMore && handleLoadMore && <Waypoint key={page} onEnter={handleLoadMore} />}
|
||||
</CardsGrid>
|
||||
</div>
|
||||
);
|
||||
|
@ -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';
|
||||
|
||||
interface EditorPreviewContentProps {
|
||||
@ -8,17 +7,16 @@ interface EditorPreviewContentProps {
|
||||
previewProps: TemplatePreviewProps;
|
||||
}
|
||||
|
||||
const EditorPreviewContent = ({ previewComponent, previewProps }: EditorPreviewContentProps) => {
|
||||
return useMemo(() => {
|
||||
let children: ReactNode;
|
||||
const EditorPreviewContent = memo(
|
||||
({ previewComponent, previewProps }: EditorPreviewContentProps) => {
|
||||
if (!previewComponent) {
|
||||
children = null;
|
||||
} else {
|
||||
children = React.createElement(previewComponent, previewProps);
|
||||
return null;
|
||||
}
|
||||
|
||||
return children;
|
||||
}, [previewComponent, previewProps]);
|
||||
};
|
||||
return React.createElement(previewComponent, previewProps);
|
||||
},
|
||||
);
|
||||
|
||||
EditorPreviewContent.displayName = 'EditorPreviewContent';
|
||||
|
||||
export default EditorPreviewContent;
|
||||
|
@ -29,6 +29,7 @@ import type {
|
||||
Field,
|
||||
GetAssetFunction,
|
||||
ListField,
|
||||
ObjectValue,
|
||||
RenderedField,
|
||||
TemplatePreviewProps,
|
||||
TranslatedProps,
|
||||
@ -367,36 +368,92 @@ const PreviewPane = (props: TranslatedProps<EditorPreviewPaneProps>) => {
|
||||
*/
|
||||
const widgetsFor = useCallback(
|
||||
(name: string) => {
|
||||
const field = fields.find(f => f.name === name);
|
||||
if (!field) {
|
||||
const cmsConfig = config.config;
|
||||
if (!cmsConfig) {
|
||||
return {
|
||||
data: null,
|
||||
widgets: {},
|
||||
};
|
||||
}
|
||||
|
||||
const nestedFields = field && 'fields' in field ? field.fields ?? [] : [];
|
||||
const value = entry.data?.[field.name] as EntryData | EntryData[];
|
||||
const field = fields.find(f => f.name === name);
|
||||
if (!field || !('fields' in field)) {
|
||||
return {
|
||||
data: null,
|
||||
widgets: {},
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(val => {
|
||||
const widgets = nestedFields.reduce((acc, field, index) => {
|
||||
acc[field.name] = <div key={index}>{widgetFor(field.name)}</div>;
|
||||
return acc;
|
||||
}, {} as Record<string, ReactNode>);
|
||||
return { data: val, widgets };
|
||||
});
|
||||
const value = entry.data?.[field.name];
|
||||
const nestedFields = field && 'fields' in field ? field.fields ?? [] : [];
|
||||
|
||||
if (field.widget === 'list' || Array.isArray(value)) {
|
||||
let finalValue: ObjectValue[];
|
||||
if (!value || typeof value !== 'object') {
|
||||
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 {
|
||||
data: value,
|
||||
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;
|
||||
}, {} as Record<string, ReactNode>),
|
||||
};
|
||||
},
|
||||
[entry.data, fields, widgetFor],
|
||||
[collection, config.config, entry, fields, handleGetAsset, inferedFields],
|
||||
);
|
||||
|
||||
const previewStyles = useMemo(
|
||||
|
@ -19,7 +19,7 @@ const knownMetaKeys = [
|
||||
];
|
||||
|
||||
function filterUnknownMetaKeys(meta: Record<string, unknown>) {
|
||||
return Object.keys(meta).reduce((acc, k) => {
|
||||
return Object.keys(meta ?? {}).reduce((acc, k) => {
|
||||
if (knownMetaKeys.includes(k)) {
|
||||
acc[k] = meta[k];
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ import parse from 'date-fns/parse';
|
||||
import parseISO from 'date-fns/parseISO';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import alert from '../../components/UI/Alert';
|
||||
import { isNotEmpty } from '../../lib/util/string.util';
|
||||
|
||||
import type { MouseEvent } from 'react';
|
||||
@ -169,16 +168,7 @@ const DateTimeControl = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
let formattedValue = defaultValue;
|
||||
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);
|
||||
}
|
||||
const inputDate = field.picker_utc ? utcDate : dateValue;
|
||||
|
||||
if (dateFormat && !timeFormat) {
|
||||
return (
|
||||
@ -186,7 +176,7 @@ const DateTimeControl = ({
|
||||
key="mobile-date-picker"
|
||||
inputFormat={inputFormat}
|
||||
label={label}
|
||||
value={formattedValue}
|
||||
value={inputDate}
|
||||
onChange={handleChange}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
@ -216,7 +206,7 @@ const DateTimeControl = ({
|
||||
key="time-picker"
|
||||
label={label}
|
||||
inputFormat={inputFormat}
|
||||
value={formattedValue}
|
||||
value={inputDate}
|
||||
onChange={handleChange}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
@ -245,7 +235,7 @@ const DateTimeControl = ({
|
||||
key="mobile-date-time-picker"
|
||||
inputFormat={inputFormat}
|
||||
label={label}
|
||||
value={formattedValue}
|
||||
value={inputDate}
|
||||
onChange={handleChange}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
@ -270,7 +260,6 @@ const DateTimeControl = ({
|
||||
}, [
|
||||
dateFormat,
|
||||
dateValue,
|
||||
defaultValue,
|
||||
field.picker_utc,
|
||||
handleChange,
|
||||
hasErrors,
|
||||
|
Loading…
x
Reference in New Issue
Block a user