Skip to content

Commit

Permalink
feat: Add Bookmark Sorting Feature (#812)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
dakshpareek and MohamedBassem authored Jan 12, 2025
1 parent b8bd7d7 commit b6293d1
Show file tree
Hide file tree
Showing 24 changed files with 231 additions and 32 deletions.
2 changes: 1 addition & 1 deletion apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function UpdatingBookmarkList({
query,
header,
}: {
query: ZGetBookmarksRequest;
query: Omit<ZGetBookmarksRequest, "sortOrder">; // Sort order is not supported in mobile yet
header?: React.ReactElement;
}) {
const apiUtils = api.useUtils();
Expand Down
2 changes: 2 additions & 0 deletions apps/web/components/dashboard/GlobalActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
);
Expand Down
56 changes: 56 additions & 0 deletions apps/web/components/dashboard/SortOrderToggle.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
}
2 changes: 1 addition & 1 deletion apps/web/components/dashboard/bookmarks/Bookmarks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 15 additions & 4 deletions apps/web/components/dashboard/bookmarks/UpdatableBookmarksGrid.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -16,24 +18,33 @@ 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],
pageParams: [query.cursor],
}),
initialCursor: null,
getNextPageParam: (lastPage) => lastPage.nextCursor,
refetchOnMount: true,
},
);

useEffect(() => {
refetch();
}, [sortOrder, refetch]);

const grid = (
<BookmarksGrid
bookmarks={data!.pages.flatMap((b) => b.bookmarks)}
Expand All @@ -45,7 +56,7 @@ export default function UpdatableBookmarksGrid({
);

return (
<BookmarkGridContextProvider query={query}>
<BookmarkGridContextProvider query={finalQuery}>
{showEditorCard ? <UploadDropzone>{grid}</UploadDropzone> : grid}
</BookmarkGridContextProvider>
);
Expand Down
9 changes: 9 additions & 0 deletions apps/web/lib/hooks/bookmark-search.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useSortOrderStore } from "@/lib/store/useSortOrderStore";
import { api } from "@/lib/trpc";
import { keepPreviousData } from "@tanstack/react-query";

Expand All @@ -8,6 +9,7 @@ import { parseSearchQuery } from "@hoarder/shared/searchQueryParser";
function useSearchQuery() {
const searchParams = useSearchParams();
const searchQuery = decodeURIComponent(searchParams.get("q") ?? "");

const parsed = useMemo(() => parseSearchQuery(searchQuery), [searchQuery]);
return { searchQuery, parsedSearchQuery: parsed };
}
Expand Down Expand Up @@ -53,6 +55,7 @@ export function useDoBookmarkSearch() {

export function useBookmarkSearch() {
const { searchQuery } = useSearchQuery();
const sortOrder = useSortOrderStore((state) => state.sortOrder);

const {
data,
Expand All @@ -62,9 +65,11 @@ export function useBookmarkSearch() {
hasNextPage,
fetchNextPage,
isFetchingNextPage,
refetch,
} = api.bookmarks.searchBookmarks.useInfiniteQuery(
{
text: searchQuery,
sortOrder,
},
{
placeholderData: keepPreviousData,
Expand All @@ -74,6 +79,10 @@ export function useBookmarkSearch() {
},
);

useEffect(() => {
refetch();
}, [refetch, sortOrder]);

if (error) {
throw error;
}
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/da/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
"close": "Luk",
"edit_tags": "Rediger tags",
"save": "Gem",
"merge": "Sammenflet"
"merge": "Sammenflet",
"sort": {
"title": "Sortér",
"newest_first": "Nyeste først",
"oldest_first": "Ældste først"
}
},
"settings": {
"import": {
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@
"cancel": "Abbrechen",
"apply_all": "Alle anwenden",
"ignore": "Ignorieren",
"recrawl": "Erneutes Crawlen"
"recrawl": "Erneutes Crawlen",
"sort": {
"title": "Sortieren",
"newest_first": "Neueste zuerst",
"oldest_first": "Älteste zuerst"
}
},
"settings": {
"back_to_app": "Zurück zur App",
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@
"merge": "Merge",
"cancel": "Cancel",
"apply_all": "Apply All",
"ignore": "Ignore"
"ignore": "Ignore",
"sort": {
"title": "Sort",
"newest_first": "Newest First",
"oldest_first": "Oldest First"
}
},
"highlights": {
"no_highlights": "You don't have any highlights yet."
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@
"recrawl": "Volver a crawlear",
"close_bulk_edit": "Cerrar editor en masa",
"bulk_edit": "Editar en masa",
"manage_lists": "Administrar listas"
"manage_lists": "Administrar listas",
"sort": {
"title": "Ordenar",
"newest_first": "Más recientes primero",
"oldest_first": "Más antiguos primero"
}
},
"layouts": {
"compact": "Compacto",
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@
"merge": "Fusionner",
"cancel": "Annuler",
"apply_all": "Tout appliquer",
"ignore": "Ignorer"
"ignore": "Ignorer",
"sort": {
"title": "Trier",
"newest_first": "Plus récents d'abord",
"oldest_first": "Plus anciens d'abord"
}
},
"settings": {
"back_to_app": "Retour à l'application",
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/hr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,12 @@
"close": "Zatvori",
"merge": "Spoji",
"cancel": "Otkaži",
"apply_all": "Primijeni sve"
"apply_all": "Primijeni sve",
"sort": {
"title": "Sortiraj",
"newest_first": "Najnovije prvo",
"oldest_first": "Najstarije prvo"
}
},
"highlights": {
"no_highlights": "Još nemate nijednu istaknutu stavku."
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/it/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
"merge": "Unisci",
"cancel": "Cancella",
"apply_all": "Applica a tutto",
"copy_link": "Copia link"
"copy_link": "Copia link",
"sort": {
"title": "Ordina",
"newest_first": "Prima i più recenti",
"oldest_first": "Prima i più vecchi"
}
},
"common": {
"attachments": "Allegati",
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/ja/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
"recrawl": "再クロール",
"ignore": "無視する",
"cancel": "キャンセル",
"download_full_page_archive": "全てのページアーカイブをダウンロード"
"download_full_page_archive": "全てのページアーカイブをダウンロード",
"sort": {
"title": "並び替え",
"newest_first": "新しい順",
"oldest_first": "古い順"
}
},
"admin": {
"actions": {
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/nl/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@
"close": "Sluiten",
"merge": "Samenvoegen",
"cancel": "Annuleer",
"ignore": "Negeren"
"ignore": "Negeren",
"sort": {
"title": "Sorteren",
"newest_first": "Nieuwste eerst",
"oldest_first": "Oudste eerst"
}
},
"settings": {
"ai": {
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/pl/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@
"sign_out": "Wyloguj się",
"merge": "Scal",
"cancel": "Anuluj",
"apply_all": "Zastosuj wszystko"
"apply_all": "Zastosuj wszystko",
"sort": {
"title": "Sortowanie",
"newest_first": "Najnowsze pierwsze",
"oldest_first": "Najstarsze pierwsze"
}
},
"settings": {
"info": {
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/ru/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,12 @@
"copy_link": "Скопировать ссылку",
"unselect_all": "Отменить выбор",
"select_all": "Выбрать все",
"apply_all": "Применить всё"
"apply_all": "Применить всё",
"sort": {
"title": "Сортировка",
"newest_first": "Сначала новые",
"oldest_first": "Сначала старые"
}
},
"editor": {
"text_toolbar": {
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/sv/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@
"edit_tags": "Ändra taggar",
"summarize_with_ai": "Sammanfatta med AI",
"save": "Spara",
"merge": "Sammanfoga"
"merge": "Sammanfoga",
"sort": {
"title": "Sortera",
"newest_first": "Nyast först",
"oldest_first": "Äldst först"
}
},
"settings": {
"back_to_app": "Tillbaka till app",
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/tr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@
"merge": "Birleştir",
"cancel": "İptal",
"apply_all": "Hepsine Uygula",
"ignore": "Yoksay"
"ignore": "Yoksay",
"sort": {
"title": "Sırala",
"newest_first": "En yeni önce",
"oldest_first": "En eski önce"
}
},
"highlights": {
"no_highlights": "Henüz hiçbir öne çıkarılmış içeriğiniz yok."
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/zh/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@
"cancel": "取消",
"apply_all": "全部应用",
"ignore": "忽略",
"recrawl": "重新抓取"
"recrawl": "重新抓取",
"sort": {
"title": "排序",
"newest_first": "最新优先",
"oldest_first": "最早优先"
}
},
"settings": {
"back_to_app": "返回应用",
Expand Down
7 changes: 6 additions & 1 deletion apps/web/lib/i18n/locales/zhtw/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@
"merge": "合併",
"cancel": "取消",
"apply_all": "全部套用",
"ignore": "忽略"
"ignore": "忽略",
"sort": {
"title": "排序",
"newest_first": "最新優先",
"oldest_first": "最舊優先"
}
},
"settings": {
"back_to_app": "返回應用程式",
Expand Down
Loading

0 comments on commit b6293d1

Please sign in to comment.