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
|
widget: select
|
||||||
options:
|
options:
|
||||||
- label: One
|
- label: One
|
||||||
value: "One"
|
value: 'One'
|
||||||
- label: Two
|
- label: Two
|
||||||
value: 2
|
value: 2
|
||||||
- label: Three
|
- label: Three
|
||||||
|
@ -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',
|
||||||
{},
|
{},
|
||||||
|
@ -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) {
|
||||||
|
@ -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 };
|
||||||
|
@ -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} />
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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 {
|
||||||
|
@ -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 = {
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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;
|
||||||
|
@ -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(
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user