diff options
| author | Daksh Pareek <dakshpareek7@gmail.com> | 2025-01-12 21:53:34 +0530 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-01-12 16:23:34 +0000 |
| commit | b6293d118e7545b81e216073e66cd54c5b1a0b00 (patch) | |
| tree | 19c2308ea7ea26593d890770945a2c5c71440048 /apps/web/components | |
| parent | b8bd7d7eb27aaaadae728599f64a0874f66196ea (diff) | |
| download | karakeep-b6293d118e7545b81e216073e66cd54c5b1a0b00.tar.zst | |
feat: Add Bookmark Sorting Feature (#812)
* feat: add bookmark sorting by creation date
- Add sort order toggle in GlobalActions component
- Implement ascending/descending sort functionality
- Update translations for sorting feature in all languages
- Add sort order icons and dropdown menu
- Maintain sort preference in URL params
* feat: add bookmark sorting by creation date
- Add sort order toggle in GlobalActions component
- Implement ascending/descending sort functionality
- Update translations for sorting feature in all languages
- Add sort order icons and dropdown menu
- Maintain sort preference in URL params during session
Note: Sort order resets to default on page refresh, server-side persistence can be implemented in future enhancement
* feat: Add global sort by date feature with shared sort order state
- Implement global sort order functionality using a shared Zustand store (`useSortOrder` hook).
- Update `getBookmarks` and `searchBookmarks` endpoints to accept a `sortOrder` parameter.
- Refactor code to import `ZSortOrder` from shared types (`bookmarks.ts`), ensuring consistency across the codebase.
- Update components (`UpdatableBookmarksGrid`, `bookmark-search`) to use the shared `useSortOrder` hook.
- Remove unused `zSortBy` definition from `packages/shared/types/bookmarks.ts` to avoid confusion.
- Ensure consistent naming conventions by prefixing Zod inferred types with `Z`.
- Clean up code and address previous PR feedback comments.
* tiny fixes and fixing TS errors
---------
Co-authored-by: Mohamed Bassem <me@mbassem.com>
Diffstat (limited to 'apps/web/components')
4 files changed, 74 insertions, 5 deletions
diff --git a/apps/web/components/dashboard/GlobalActions.tsx b/apps/web/components/dashboard/GlobalActions.tsx index 9c05dddf..ecbb70bf 100644 --- a/apps/web/components/dashboard/GlobalActions.tsx +++ b/apps/web/components/dashboard/GlobalActions.tsx @@ -2,11 +2,13 @@ import BulkBookmarksAction from "@/components/dashboard/BulkBookmarksAction"; import ChangeLayout from "@/components/dashboard/ChangeLayout"; +import SortOrderToggle from "@/components/dashboard/SortOrderToggle"; export default function GlobalActions() { return ( <div className="flex min-w-max flex-wrap overflow-hidden"> <ChangeLayout /> + <SortOrderToggle /> <BulkBookmarksAction /> </div> ); diff --git a/apps/web/components/dashboard/SortOrderToggle.tsx b/apps/web/components/dashboard/SortOrderToggle.tsx new file mode 100644 index 00000000..8c0f617d --- /dev/null +++ b/apps/web/components/dashboard/SortOrderToggle.tsx @@ -0,0 +1,56 @@ +import { ButtonWithTooltip } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { useTranslation } from "@/lib/i18n/client"; +import { useSortOrderStore } from "@/lib/store/useSortOrderStore"; +import { Check, SortAsc, SortDesc } from "lucide-react"; + +export default function SortOrderToggle() { + const { t } = useTranslation(); + + const { sortOrder: currentSort, setSortOrder } = useSortOrderStore(); + + return ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <ButtonWithTooltip + tooltip={t("actions.sort.title")} + delayDuration={100} + variant="ghost" + > + {currentSort === "asc" ? ( + <SortAsc size={18} /> + ) : ( + <SortDesc size={18} /> + )} + </ButtonWithTooltip> + </DropdownMenuTrigger> + <DropdownMenuContent className="w-fit"> + <DropdownMenuItem + className="cursor-pointer justify-between" + onClick={() => setSortOrder("desc")} + > + <div className="flex items-center"> + <SortDesc size={16} className="mr-2" /> + <span>{t("actions.sort.newest_first")}</span> + </div> + {currentSort === "desc" && <Check className="ml-2 h-4 w-4" />} + </DropdownMenuItem> + <DropdownMenuItem + className="cursor-pointer justify-between" + onClick={() => setSortOrder("asc")} + > + <div className="flex items-center"> + <SortAsc size={16} className="mr-2" /> + <span>{t("actions.sort.oldest_first")}</span> + </div> + {currentSort === "asc" && <Check className="ml-2 h-4 w-4" />} + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + ); +} diff --git a/apps/web/components/dashboard/bookmarks/Bookmarks.tsx b/apps/web/components/dashboard/bookmarks/Bookmarks.tsx index 5729e846..3f606346 100644 --- a/apps/web/components/dashboard/bookmarks/Bookmarks.tsx +++ b/apps/web/components/dashboard/bookmarks/Bookmarks.tsx @@ -13,7 +13,7 @@ export default async function Bookmarks({ showDivider, showEditorCard = false, }: { - query: ZGetBookmarksRequest; + query: Omit<ZGetBookmarksRequest, "sortOrder">; // Sort order is handled by the store header?: React.ReactNode; showDivider?: boolean; showEditorCard?: boolean; diff --git a/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx b/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx index d18eeb1b..e43d061b 100644 --- a/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx +++ b/apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx @@ -1,6 +1,8 @@ "use client"; +import { useEffect } from "react"; import UploadDropzone from "@/components/dashboard/UploadDropzone"; +import { useSortOrderStore } from "@/lib/store/useSortOrderStore"; import { api } from "@/lib/trpc"; import type { @@ -16,14 +18,18 @@ export default function UpdatableBookmarksGrid({ bookmarks: initialBookmarks, showEditorCard = false, }: { - query: ZGetBookmarksRequest; + query: Omit<ZGetBookmarksRequest, "sortOrder">; // Sort order is handled by the store bookmarks: ZGetBookmarksResponse; showEditorCard?: boolean; itemsPerPage?: number; }) { - const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = + const sortOrder = useSortOrderStore((state) => state.sortOrder); + + const finalQuery = { ...query, sortOrder }; + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch } = api.bookmarks.getBookmarks.useInfiniteQuery( - { ...query, useCursorV2: true }, + { ...finalQuery, useCursorV2: true }, { initialData: () => ({ pages: [initialBookmarks], @@ -31,9 +37,14 @@ export default function UpdatableBookmarksGrid({ }), initialCursor: null, getNextPageParam: (lastPage) => lastPage.nextCursor, + refetchOnMount: true, }, ); + useEffect(() => { + refetch(); + }, [sortOrder, refetch]); + const grid = ( <BookmarksGrid bookmarks={data!.pages.flatMap((b) => b.bookmarks)} @@ -45,7 +56,7 @@ export default function UpdatableBookmarksGrid({ ); return ( - <BookmarkGridContextProvider query={query}> + <BookmarkGridContextProvider query={finalQuery}> {showEditorCard ? <UploadDropzone>{grid}</UploadDropzone> : grid} </BookmarkGridContextProvider> ); |
