Skip to content

Commit

Permalink
[3116]#SEO-Tab-Page-Parent-Dropdown: Enhanced sorting logic with adde… (
Browse files Browse the repository at this point in the history
#3122)

…d support for ZUID-based search.
  • Loading branch information
geodem127 authored Jan 13, 2025
1 parent b50aac4 commit 8e9f430
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FocusEvent, useEffect, useState } from "react";
import { Box, Autocomplete, TextField, ListItem } from "@mui/material";
import { FieldShell } from "../../../../components/Editor/Field/FieldShell";
import { useDispatch, useSelector } from "react-redux";
Expand All @@ -7,11 +8,12 @@ import {
ContentNavItem,
} from "../../../../../../../../shell/services/types";
import { debounce, uniqBy } from "lodash";
import { useEffect, useState } from "react";
import { useLocation, useParams } from "react-router";
import { notify } from "../../../../../../../../shell/store/notifications";
import { searchItems } from "../../../../../../../../shell/store/content";
import { useGetContentNavItemsQuery } from "../../../../../../../../shell/services/instance";
import { sortMostRelevantSearch } from "../../../../../../../../shell/components/GlobalSearch/utils";
import zuid from "zuid";

type ParentOption = {
value: string;
Expand Down Expand Up @@ -103,8 +105,11 @@ export const ItemParent = ({ onChange }: ItemParentProps) => {
getParentOptions(item?.meta?.langID, item?.web?.path, items)
);
const [isLoadingOptions, setIsLoadingOptions] = useState(false);
const [sortedOptions, setSortedOptions] = useState(options);
const [searchText, setSearchText] = useState("");

const handleSearchOptions = debounce((filterTerm) => {
setSearchText(filterTerm);
if (filterTerm) {
dispatch(searchItems(filterTerm))
// @ts-expect-error untyped
Expand Down Expand Up @@ -213,6 +218,27 @@ export const ItemParent = ({ onChange }: ItemParentProps) => {
}
}, [rawNavData]);

useEffect(() => {
const normalizedSearch = searchText.toLowerCase();
const isZuid = zuid.isValid(normalizedSearch);
const filteredOptions = isZuid
? options.filter(
(option) => option?.value.toLowerCase() === searchText.toLowerCase()
)
: options;

setSortedOptions(sortMostRelevantSearch(filteredOptions, searchText));
if (isZuid && filteredOptions?.length === 1) {
setSelectedParent(filteredOptions[0]);
}
}, [
options,
searchText,
setSortedOptions,
sortMostRelevantSearch,
setSelectedParent,
]);

return (
<Box id="parentZUID">
<FieldShell
Expand All @@ -226,12 +252,12 @@ export const ItemParent = ({ onChange }: ItemParentProps) => {
>
<Autocomplete
data-cy="itemRoute"
options={options}
options={sortedOptions}
value={selectedParent}
fullWidth
renderInput={(params) => <TextField {...params} />}
renderOption={(props, value) => (
<ListItem {...props} key={value.value}>
<ListItem {...props} key={value?.value}>
{value.text}
</ListItem>
)}
Expand All @@ -244,10 +270,15 @@ export const ItemParent = ({ onChange }: ItemParentProps) => {
}}
onChange={(_, value) => {
// Always default to homepage when no parent is selected
setSelectedParent(
value !== null ? value : { text: "/", value: "0" }
);
onChange(value !== null ? value.value : "0", "parentZUID");
setSelectedParent(value);
onChange(value?.value, "parentZUID");
}}
onBlur={(event: FocusEvent<HTMLInputElement>) => {
// Always default to homepage when no parent is selected
if (!event?.target?.value) {
setSelectedParent({ text: "/", value: "0" });
onChange("0", "parentZUID");
}
}}
loading={isLoadingOptions}
sx={{
Expand Down
48 changes: 48 additions & 0 deletions src/shell/components/GlobalSearch/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,51 @@ export const splitTextAndAccelerator = (

return { text, resourceType };
};

export interface SearchResult {
text: string;
value: string;
}

export const sortMostRelevantSearch = (
searchResults: SearchResult[],
searchString: string
): SearchResult[] => {
const normalize = (stringVal: string) => stringVal.replace(/^\/|\/$/g, "");

return searchResults.sort((a, b) => {
const normalizedSearch = normalize(searchString);
const normalizedA = normalize(a.text);
const normalizedB = normalize(b.text);

// 1. Exact match (normalized)
const isExactMatchA = normalizedA === normalizedSearch;
const isExactMatchB = normalizedB === normalizedSearch;

if (isExactMatchA && !isExactMatchB) return -1;
if (!isExactMatchA && isExactMatchB) return 1;

// 2. URLs starting with the search string
const startsWithSearchA = normalizedA.startsWith(normalizedSearch);
const startsWithSearchB = normalizedB.startsWith(normalizedSearch);

if (startsWithSearchA && !startsWithSearchB) return -1;
if (!startsWithSearchA && startsWithSearchB) return 1;

// 3. URLs containing the search string
const containsSearchA = normalizedA.includes(normalizedSearch);
const containsSearchB = normalizedB.includes(normalizedSearch);

if (containsSearchA && !containsSearchB) return -1;
if (!containsSearchA && containsSearchB) return 1;

// 4. Compare by nested levels (fewer segments come first)
const nestedLevelA = a.text.split("/").filter(Boolean).length;
const nestedLevelB = b.text.split("/").filter(Boolean).length;

if (nestedLevelA !== nestedLevelB) return nestedLevelA - nestedLevelB;

// 5. Alphabetical order
return normalizedA.localeCompare(normalizedB);
});
};

0 comments on commit 8e9f430

Please sign in to comment.