From 9430aab97935d588eacc5d4ffe68f28d26e9abd8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:48:36 +0800 Subject: [PATCH] Stage Release (#3242) --- .../src/app/components/Editor/Editor.js | 30 +- .../FieldSelectorDialog/dateFilter.ts | 23 ++ .../FieldSelectorDialog/index.tsx | 340 ++++++++++-------- .../keywordSearchFilter.ts | 51 +++ .../FieldSelectorDialog/statusFilter.ts | 31 ++ .../FieldSelectorDialog/userFilter.ts | 16 + 6 files changed, 327 insertions(+), 164 deletions(-) create mode 100644 src/shell/components/RelationalFieldBase/FieldSelectorDialog/dateFilter.ts create mode 100644 src/shell/components/RelationalFieldBase/FieldSelectorDialog/keywordSearchFilter.ts create mode 100644 src/shell/components/RelationalFieldBase/FieldSelectorDialog/statusFilter.ts create mode 100644 src/shell/components/RelationalFieldBase/FieldSelectorDialog/userFilter.ts diff --git a/src/apps/content-editor/src/app/components/Editor/Editor.js b/src/apps/content-editor/src/app/components/Editor/Editor.js index 02059a80b..6f9c30438 100644 --- a/src/apps/content-editor/src/app/components/Editor/Editor.js +++ b/src/apps/content-editor/src/app/components/Editor/Editor.js @@ -47,6 +47,8 @@ export default memo(function Editor({ const isNewItem = itemZUID.slice(0, 3) === "new"; const { data: fields } = useGetContentModelFieldsQuery(modelZUID); const [isLoaded, setIsLoaded] = useState(false); + const [prevFirstContentFieldValue, setPrevFirstContentFieldValue] = + useState(null); const metaFields = useMemo(() => { if (fields?.length) { @@ -309,12 +311,20 @@ export default memo(function Editor({ ?.slice(0, 160) || "" ); - dispatch({ - type: "SET_ITEM_WEB", - itemZUID, - key: "metaDescription", - value: cleanedValue, - }); + if ( + item?.web?.["metaDescription"] === prevFirstContentFieldValue || + !item?.web?.["metaDescription"] || + !prevFirstContentFieldValue + ) { + dispatch({ + type: "SET_ITEM_WEB", + itemZUID, + key: "metaDescription", + value: cleanedValue, + }); + + setPrevFirstContentFieldValue(cleanedValue); + } if ("og_description" in metaFields) { dispatch({ @@ -336,7 +346,13 @@ export default memo(function Editor({ } } }, - [fieldErrors, metaFields] + [ + fieldErrors, + metaFields, + item, + prevFirstContentFieldValue, + setPrevFirstContentFieldValue, + ] ); const applyDefaultValuesToItemData = useCallback(() => { diff --git a/src/shell/components/RelationalFieldBase/FieldSelectorDialog/dateFilter.ts b/src/shell/components/RelationalFieldBase/FieldSelectorDialog/dateFilter.ts new file mode 100644 index 000000000..8d1d914ca --- /dev/null +++ b/src/shell/components/RelationalFieldBase/FieldSelectorDialog/dateFilter.ts @@ -0,0 +1,23 @@ +import { GridFilterOperator } from "@mui/x-data-grid-pro"; +import { getDateFilterFnByValues } from "../../Filters/DateFilter/getDateFilter"; + +export const dateFilterOperator: GridFilterOperator = { + label: "dateFilter", + value: "dateFilter", + getApplyFilterFn: (filterItem) => { + if (!filterItem.value) { + return null; + } + + return (params): boolean => { + const version = params.value; + const dateFilterFn = getDateFilterFnByValues(filterItem.value); + + if (!dateFilterFn || !version?.itemData?.meta?.updatedAt) { + return false; + } + + return dateFilterFn(version.itemData.meta.updatedAt); + }; + }, +}; diff --git a/src/shell/components/RelationalFieldBase/FieldSelectorDialog/index.tsx b/src/shell/components/RelationalFieldBase/FieldSelectorDialog/index.tsx index 4515d2b91..c5358e7f0 100644 --- a/src/shell/components/RelationalFieldBase/FieldSelectorDialog/index.tsx +++ b/src/shell/components/RelationalFieldBase/FieldSelectorDialog/index.tsx @@ -19,6 +19,7 @@ import { GridColumns, GridInputSelectionModel, GridRenderCellParams, + GridLinkOperator, } from "@mui/x-data-grid-pro"; import { debounce } from "lodash"; import { useDispatch, useSelector } from "react-redux"; @@ -40,6 +41,10 @@ import { AppState } from "../../../store/types"; import { ContentItem } from "../../../services/types"; import moment from "moment"; import { getDateFilterFnByValues } from "../../Filters/DateFilter/getDateFilter"; +import { statusFilterOperator } from "./statusFilter"; +import { userFilterOperator } from "./userFilter"; +import { keywordSearchFilterOperator } from "./keywordSearchFilter"; +import { dateFilterOperator } from "./dateFilter"; const selectFilteredItems = ( state: AppState, @@ -156,6 +161,12 @@ export const FieldSelectorDialog = ({ const columns = useMemo(() => { let defaultCols: GridColumns = [ + // Column is only used for keyword search purposes + { + field: "keywordSearch", + hide: true, + filterOperators: [keywordSearchFilterOperator], + }, { field: "title", flex: 1, @@ -169,6 +180,11 @@ export const FieldSelectorDialog = ({ { field: "version", width: 60, + filterOperators: [ + userFilterOperator, + statusFilterOperator, + dateFilterOperator, + ], renderCell: (params: GridRenderCellParams) => ( ({ id: item.meta?.ZUID, + // Column is only used for keyword search purposes + keywordSearch: { + title: { + primary: + item.data?.[relatedFieldName] || + item.web?.metaTitle || + item.web?.metaLinkText, + secondary: item.web?.metaDescription, + }, + version: { + itemData: { + ...item, + createdByName: resolveUserZUID(item.meta?.createdByUserZUID), + }, + publishData: item?.publishing?.version + ? { + ...item.publishing, + publishedByName: resolveUserZUID( + item.publishing?.publishedByUserZUID + ), + } + : null, + scheduleData: item?.scheduling?.version + ? { + ...item.scheduling, + scheduledByName: resolveUserZUID( + item.scheduling?.publishedByUserZUID + ), + } + : null, + }, + }, image: { imageFieldName, itemZUID: item.meta?.ZUID, @@ -423,74 +471,6 @@ export const FieldSelectorDialog = ({ } }); - // Keyword search - if (!!filterKeyword) { - const search = filterKeyword.toLowerCase(); - - _rows = _rows?.filter((row) => { - const matchedUser = users.find( - (user) => user.ZUID === row?.item?.meta?.createdByUserZUID - ); - const creator = matchedUser - ? `${matchedUser.firstName} ${matchedUser.lastName}` - : null; - - return ( - Object.values(row?.item.data).some((value: any) => { - if (!value) return false; - - if (value?.filename || value?.title) { - return ( - value?.filename?.toLowerCase()?.includes(search) || - value?.title?.toLowerCase()?.includes(search) - ); - } - - return value.toString().toLowerCase().includes(search); - }) || - row?.item?.meta?.createdAt?.toLowerCase().includes(search) || - row?.item?.web?.updatedAt?.toLowerCase().includes(search) || - row?.item?.meta?.ZUID?.toLowerCase().includes(search) || - creator?.toLowerCase()?.includes(search) - ); - }); - } - - // Filtering - if (filters.status) { - _rows = _rows?.filter((item) => { - if (filters.status === "published") { - return ( - item.item?.publishing?.publishAt && - !item.item?.scheduling?.publishAt - ); - } else if (filters.status === "scheduled") { - return item.item?.scheduling?.publishAt; - } else if (filters.status === "notPublished") { - return ( - !item.item?.publishing?.publishAt && - !item.item?.scheduling?.publishAt - ); - } - }); - } - - if (filters.user) { - _rows = _rows.filter( - (item) => item.item?.meta?.createdByUserZUID === filters.user - ); - } - - const dateFilterFn = getDateFilterFnByValues(filters.date); - if (dateFilterFn) { - _rows = _rows.filter((item) => { - if (!!item.item?.meta?.updatedAt) { - return dateFilterFn(item.item?.meta?.updatedAt); - } - - return false; - }); - } return _rows; }, [mappedRows, filterKeyword, relatedModelFields]); @@ -600,101 +580,147 @@ export const FieldSelectorDialog = ({ }, }} > - {!rows?.length && isFilteringResults ? ( - { - if (!!filterKeyword) { - setFilterKeyword(""); - if (!!searchField.current) { - searchField.current.querySelector("input").value = ""; - searchField.current.querySelector("input").focus(); - } - } - - updateFilters({ - sortOrder: "lastSaved", - user: null, - date: { - preset: null, - from: null, - to: null, - }, - lang: langs.find((lang) => lang.default)?.ID, - status: null, - }); - }} - ignoreFilters - hideBackButton - /> - ) : ( - { - let _newSelectionModel = newSelectionModel as string[]; - - if (!multiselect && _newSelectionModel?.length > 1) { - _newSelectionModel = [_newSelectionModel[0]]; - } - - setSelectionModel([ - ...deletedItemZUIDs, - ..._newSelectionModel, - ]); - }} - sx={{ - bgcolor: "background.paper", - - "& .MuiDataGrid-columnHeaders": { - borderBottom: 0, - }, - - "& .MuiDataGrid-cellCheckbox": { - mx: "3px", - }, - - "& .MuiDataGrid-row.Mui-selected": { - borderBottom: (theme) => - `1px solid ${theme.palette.primary.main}`, - - "& .MuiDataGrid-cell": { - borderBottom: 0, - }, + { + let _newSelectionModel = newSelectionModel as string[]; + + if (!multiselect && _newSelectionModel?.length > 1) { + _newSelectionModel = [_newSelectionModel[0]]; + } + + setSelectionModel([...deletedItemZUIDs, ..._newSelectionModel]); + }} + components={{ + NoResultsOverlay: () => ( + { + if (!!filterKeyword) { + setFilterKeyword(""); + if (!!searchField.current) { + searchField.current.querySelector("input").value = ""; + searchField.current.querySelector("input").focus(); + } + } + + updateFilters({ + sortOrder: "lastSaved", + user: null, + date: { + preset: null, + from: null, + to: null, + }, + lang: langs.find((lang) => lang.default)?.ID, + status: null, + }); + }} + ignoreFilters + hideBackButton + /> + ), + }} + sx={{ + bgcolor: "background.paper", - "& .MuiDataGrid-cell:focus-within": { - outline: "none", - }, + "& .MuiDataGrid-columnHeaders": { + borderBottom: 0, + }, - ".MuiDataGrid-row": { - cursor: "pointer", - }, + "& .MuiDataGrid-cellCheckbox": { + mx: "3px", + }, - "& [data-field='image']": { - p: 0, - }, + "& .MuiDataGrid-row.Mui-selected": { + borderBottom: (theme) => + `1px solid ${theme.palette.primary.main}`, - "& [data-field='title']": { - pl: !!imageFieldName ? 2 : 0, - pr: 2, - }, - - "& [data-field='version']": { - pl: 0, - pr: 2, - justifyContent: "center", + "& .MuiDataGrid-cell": { + borderBottom: 0, }, - }} - /> - )} + }, + + "& .MuiDataGrid-cell:focus-within": { + outline: "none", + }, + + ".MuiDataGrid-row": { + cursor: "pointer", + }, + + "& [data-field='image']": { + p: 0, + }, + + "& [data-field='title']": { + pl: !!imageFieldName ? 2 : 0, + pr: 2, + }, + + "& [data-field='version']": { + pl: 0, + pr: 2, + justifyContent: "center", + }, + + // Makes sure that the custom overlay is interactive + "& [data-cy='NoSearchResults']": { + pointerEvents: "all", + }, + }} + /> )} diff --git a/src/shell/components/RelationalFieldBase/FieldSelectorDialog/keywordSearchFilter.ts b/src/shell/components/RelationalFieldBase/FieldSelectorDialog/keywordSearchFilter.ts new file mode 100644 index 000000000..5135510ea --- /dev/null +++ b/src/shell/components/RelationalFieldBase/FieldSelectorDialog/keywordSearchFilter.ts @@ -0,0 +1,51 @@ +import { GridFilterOperator } from "@mui/x-data-grid-pro"; +import moment from "moment"; + +export const keywordSearchFilterOperator: GridFilterOperator = { + label: "contains", + value: "keywordContains", + getApplyFilterFn: (filterItem) => { + if (!filterItem.value) { + return null; + } + + return (params): boolean => { + const row = params.row; + const searchValue = filterItem.value.toLowerCase(); + + // Check title + const title = row.title; + const titleMatch = + title?.primary?.toLowerCase().includes(searchValue) || + title?.secondary?.toLowerCase().includes(searchValue) || + false; + + // Check version + const version = row.version; + const createdBy = version?.itemData?.createdByName?.toLowerCase() || ""; + const publishedBy = + version?.publishData?.publishedByName?.toLowerCase() || ""; + const scheduledBy = + version?.scheduleData?.scheduledByName?.toLowerCase() || ""; + const createdAt = version?.itemData?.meta?.createdAt + ? moment(version.itemData.meta.createdAt) + .format("MMM D, YYYY h:mm A") + .toLowerCase() + : ""; + const updatedAt = version?.itemData?.meta?.updatedAt + ? moment(version.itemData.meta.updatedAt) + .format("MMM D, YYYY h:mm A") + .toLowerCase() + : ""; + + const versionMatch = + createdBy.includes(searchValue) || + publishedBy.includes(searchValue) || + scheduledBy.includes(searchValue) || + createdAt.includes(searchValue) || + updatedAt.includes(searchValue); + + return titleMatch || versionMatch; + }; + }, +}; diff --git a/src/shell/components/RelationalFieldBase/FieldSelectorDialog/statusFilter.ts b/src/shell/components/RelationalFieldBase/FieldSelectorDialog/statusFilter.ts new file mode 100644 index 000000000..158aca3b7 --- /dev/null +++ b/src/shell/components/RelationalFieldBase/FieldSelectorDialog/statusFilter.ts @@ -0,0 +1,31 @@ +import { GridFilterOperator } from "@mui/x-data-grid-pro"; + +export const statusFilterOperator: GridFilterOperator = { + label: "equals", + value: "statusEquals", + getApplyFilterFn: (filterItem) => { + if (!filterItem.value) { + return null; + } + + return (params): boolean => { + const version = params.value; + const status = filterItem.value; + + if (status === "published") { + return ( + version?.itemData?.publishing?.publishAt && + !version?.itemData?.scheduling?.publishAt + ); + } else if (status === "scheduled") { + return version?.itemData?.scheduling?.publishAt; + } else if (status === "notPublished") { + return ( + !version?.itemData?.publishing?.publishAt && + !version?.itemData?.scheduling?.publishAt + ); + } + return true; + }; + }, +}; diff --git a/src/shell/components/RelationalFieldBase/FieldSelectorDialog/userFilter.ts b/src/shell/components/RelationalFieldBase/FieldSelectorDialog/userFilter.ts new file mode 100644 index 000000000..731a483ca --- /dev/null +++ b/src/shell/components/RelationalFieldBase/FieldSelectorDialog/userFilter.ts @@ -0,0 +1,16 @@ +import { GridFilterOperator } from "@mui/x-data-grid-pro"; + +export const userFilterOperator: GridFilterOperator = { + label: "equals", + value: "userEquals", + getApplyFilterFn: (filterItem) => { + if (!filterItem.value) { + return null; + } + + return (params): boolean => { + const version = params.value; + return version?.itemData?.meta?.createdByUserZUID === filterItem.value; + }; + }, +};