From dbd0fd197323f2a83fab384d7b4b116a02165d16 Mon Sep 17 00:00:00 2001 From: xuatz Date: Sun, 18 May 2025 17:21:11 +0900 Subject: fix(search): add new relevance sort order (#1392) * fix(search): add new relevance sort order * address pr comments * some minor fixes --------- Co-authored-by: Mohamed Bassem --- apps/web/app/dashboard/search/page.tsx | 10 +++++- apps/web/components/dashboard/SortOrderToggle.tsx | 36 ++++++++++++++++++---- .../dashboard/bookmarks/UpdatableBookmarksGrid.tsx | 6 +++- apps/web/lib/hooks/bookmark-search.ts | 9 ++++-- apps/web/lib/i18n/locales/en/translation.json | 1 + packages/shared/types/bookmarks.ts | 6 ++-- packages/trpc/routers/bookmarks.ts | 22 +++++++++++-- 7 files changed, 74 insertions(+), 16 deletions(-) diff --git a/apps/web/app/dashboard/search/page.tsx b/apps/web/app/dashboard/search/page.tsx index beae73b8..c3542a88 100644 --- a/apps/web/app/dashboard/search/page.tsx +++ b/apps/web/app/dashboard/search/page.tsx @@ -1,14 +1,22 @@ "use client"; -import { Suspense } from "react"; +import { Suspense, useEffect } from "react"; import BookmarksGrid from "@/components/dashboard/bookmarks/BookmarksGrid"; import { FullPageSpinner } from "@/components/ui/full-page-spinner"; import { useBookmarkSearch } from "@/lib/hooks/bookmark-search"; +import { useSortOrderStore } from "@/lib/store/useSortOrderStore"; function SearchComp() { const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = useBookmarkSearch(); + const { setSortOrder } = useSortOrderStore(); + + useEffect(() => { + // also see related cleanup code in SortOrderToggle.tsx + setSortOrder("relevance"); + }, []); + return (
{data ? ( diff --git a/apps/web/components/dashboard/SortOrderToggle.tsx b/apps/web/components/dashboard/SortOrderToggle.tsx index 8c0f617d..ba3385ac 100644 --- a/apps/web/components/dashboard/SortOrderToggle.tsx +++ b/apps/web/components/dashboard/SortOrderToggle.tsx @@ -1,3 +1,6 @@ +"use client"; + +import { useEffect } from "react"; import { ButtonWithTooltip } from "@/components/ui/button"; import { DropdownMenu, @@ -5,15 +8,26 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { useIsSearchPage } from "@/lib/hooks/bookmark-search"; import { useTranslation } from "@/lib/i18n/client"; import { useSortOrderStore } from "@/lib/store/useSortOrderStore"; -import { Check, SortAsc, SortDesc } from "lucide-react"; +import { Check, ListFilter, SortAsc, SortDesc } from "lucide-react"; export default function SortOrderToggle() { const { t } = useTranslation(); + const isInSearchPage = useIsSearchPage(); const { sortOrder: currentSort, setSortOrder } = useSortOrderStore(); + // also see related on page enter sortOrder.relevance init + // in apps/web/app/dashboard/search/page.tsx + useEffect(() => { + if (!isInSearchPage && currentSort === "relevance") { + // reset to default sort order + setSortOrder("desc"); + } + }, [isInSearchPage, currentSort]); + return ( @@ -22,14 +36,24 @@ export default function SortOrderToggle() { delayDuration={100} variant="ghost" > - {currentSort === "asc" ? ( - - ) : ( - - )} + {currentSort === "relevance" && } + {currentSort === "asc" && } + {currentSort === "desc" && } + {isInSearchPage && ( + setSortOrder("relevance")} + > +
+ + {t("actions.sort.relevant_first")} +
+ {currentSort === "relevance" && } +
+ )} setSortOrder("desc")} diff --git a/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx b/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx index da65b9d9..968d0326 100644 --- a/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx +++ b/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx @@ -23,7 +23,11 @@ export default function UpdatableBookmarksGrid({ showEditorCard?: boolean; itemsPerPage?: number; }) { - const sortOrder = useSortOrderStore((state) => state.sortOrder); + let sortOrder = useSortOrderStore((state) => state.sortOrder); + if (sortOrder === "relevance") { + // Relevance is not supported in the `getBookmarks` endpoint. + sortOrder = "desc"; + } const finalQuery = { ...query, sortOrder, includeContent: false }; diff --git a/apps/web/lib/hooks/bookmark-search.ts b/apps/web/lib/hooks/bookmark-search.ts index 1bccd280..b6af94ee 100644 --- a/apps/web/lib/hooks/bookmark-search.ts +++ b/apps/web/lib/hooks/bookmark-search.ts @@ -6,6 +6,11 @@ import { keepPreviousData } from "@tanstack/react-query"; import { parseSearchQuery } from "@karakeep/shared/searchQueryParser"; +export function useIsSearchPage() { + const pathname = usePathname(); + return pathname.startsWith("/dashboard/search"); +} + function useSearchQuery() { const searchParams = useSearchParams(); const searchQuery = decodeURIComponent(searchParams.get("q") ?? ""); @@ -17,8 +22,8 @@ function useSearchQuery() { export function useDoBookmarkSearch() { const router = useRouter(); const { searchQuery, parsedSearchQuery } = useSearchQuery(); + const isInSearchPage = useIsSearchPage(); const [timeoutId, setTimeoutId] = useState(); - const pathname = usePathname(); useEffect(() => { return () => { @@ -49,7 +54,7 @@ export function useDoBookmarkSearch() { debounceSearch, searchQuery, parsedSearchQuery, - isInSearchPage: pathname.startsWith("/dashboard/search"), + isInSearchPage, }; } diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json index 42a904a4..aef2d2a7 100644 --- a/apps/web/lib/i18n/locales/en/translation.json +++ b/apps/web/lib/i18n/locales/en/translation.json @@ -79,6 +79,7 @@ "ignore": "Ignore", "sort": { "title": "Sort", + "relevant_first": "Most Relevant First", "newest_first": "Newest First", "oldest_first": "Oldest First" } diff --git a/packages/shared/types/bookmarks.ts b/packages/shared/types/bookmarks.ts index 3cac2845..709fd431 100644 --- a/packages/shared/types/bookmarks.ts +++ b/packages/shared/types/bookmarks.ts @@ -12,7 +12,7 @@ export const enum BookmarkTypes { UNKNOWN = "unknown", } -export const zSortOrder = z.enum(["asc", "desc"]); +export const zSortOrder = z.enum(["asc", "desc", "relevance"]); export type ZSortOrder = z.infer; export const zAssetTypesSchema = z.enum([ @@ -178,7 +178,7 @@ export const zGetBookmarksRequestSchema = z.object({ // The value is currently not being used, but keeping it so that client can still set it to true for older // servers. useCursorV2: z.boolean().optional(), - sortOrder: zSortOrder.optional().default("desc"), + sortOrder: zSortOrder.exclude(["relevance"]).optional().default("desc"), includeContent: z.boolean().optional().default(false), }); export type ZGetBookmarksRequest = z.infer; @@ -238,6 +238,6 @@ export const zSearchBookmarksRequestSchema = z.object({ text: z.string(), limit: z.number().max(MAX_NUM_BOOKMARKS_PER_PAGE).optional(), cursor: zSearchBookmarksCursor.nullish(), - sortOrder: zSortOrder.optional().default("desc"), + sortOrder: zSortOrder.optional().default("relevance"), includeContent: z.boolean().optional().default(false), }); diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts index b9a21400..88386657 100644 --- a/packages/trpc/routers/bookmarks.ts +++ b/packages/trpc/routers/bookmarks.ts @@ -712,7 +712,7 @@ export const bookmarksAppRouter = router({ if (!input.limit) { input.limit = DEFAULT_NUM_BOOKMARKS_PER_PAGE; } - const sortOrder = input.sortOrder || "desc"; + const sortOrder = input.sortOrder || "relevance"; const client = await getSearchIdxClient(); if (!client) { throw new TRPCError({ @@ -735,11 +735,16 @@ export const bookmarksAppRouter = router({ filter = [`userId = '${ctx.user.id}'`]; } + /** + * preserve legacy behaviour + */ + const createdAtSortOrder = sortOrder === "relevance" ? "desc" : sortOrder; + const resp = await client.search(parsedQuery.text, { filter, showRankingScore: true, attributesToRetrieve: ["id"], - sort: [`createdAt:${sortOrder}`], + sort: [`createdAt:${createdAtSortOrder}`], limit: input.limit, ...(input.cursor ? { @@ -775,7 +780,18 @@ export const bookmarksAppRouter = router({ assets: true, }, }); - results.sort((a, b) => idToRank[b.id] - idToRank[a.id]); + + switch (true) { + case sortOrder === "relevance": + results.sort((a, b) => idToRank[b.id] - idToRank[a.id]); + break; + case sortOrder === "desc": + results.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); + break; + case sortOrder === "asc": + results.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); + break; + } return { bookmarks: results.map((b) => toZodSchema(b, input.includeContent)), -- cgit v1.2.3-70-g09d2