diff options
Diffstat (limited to 'apps')
22 files changed, 187 insertions, 21 deletions
diff --git a/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx b/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx index 8644dcbf..99bb5ab8 100644 --- a/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx +++ b/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx @@ -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(); 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> ); diff --git a/apps/web/lib/hooks/bookmark-search.ts b/apps/web/lib/hooks/bookmark-search.ts index 386355f7..5ffec1b0 100644 --- a/apps/web/lib/hooks/bookmark-search.ts +++ b/apps/web/lib/hooks/bookmark-search.ts @@ -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"; @@ -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 }; } @@ -53,6 +55,7 @@ export function useDoBookmarkSearch() { export function useBookmarkSearch() { const { searchQuery } = useSearchQuery(); + const sortOrder = useSortOrderStore((state) => state.sortOrder); const { data, @@ -62,9 +65,11 @@ export function useBookmarkSearch() { hasNextPage, fetchNextPage, isFetchingNextPage, + refetch, } = api.bookmarks.searchBookmarks.useInfiniteQuery( { text: searchQuery, + sortOrder, }, { placeholderData: keepPreviousData, @@ -74,6 +79,10 @@ export function useBookmarkSearch() { }, ); + useEffect(() => { + refetch(); + }, [refetch, sortOrder]); + if (error) { throw error; } diff --git a/apps/web/lib/i18n/locales/da/translation.json b/apps/web/lib/i18n/locales/da/translation.json index 2ef97e76..d4744bac 100644 --- a/apps/web/lib/i18n/locales/da/translation.json +++ b/apps/web/lib/i18n/locales/da/translation.json @@ -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": { diff --git a/apps/web/lib/i18n/locales/de/translation.json b/apps/web/lib/i18n/locales/de/translation.json index d868b7f9..cb376be6 100644 --- a/apps/web/lib/i18n/locales/de/translation.json +++ b/apps/web/lib/i18n/locales/de/translation.json @@ -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", diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json index d23d5a96..ac08fa3f 100644 --- a/apps/web/lib/i18n/locales/en/translation.json +++ b/apps/web/lib/i18n/locales/en/translation.json @@ -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." diff --git a/apps/web/lib/i18n/locales/es/translation.json b/apps/web/lib/i18n/locales/es/translation.json index 55e60ab5..40c6cb01 100644 --- a/apps/web/lib/i18n/locales/es/translation.json +++ b/apps/web/lib/i18n/locales/es/translation.json @@ -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", diff --git a/apps/web/lib/i18n/locales/fr/translation.json b/apps/web/lib/i18n/locales/fr/translation.json index 142e9148..dade8d21 100644 --- a/apps/web/lib/i18n/locales/fr/translation.json +++ b/apps/web/lib/i18n/locales/fr/translation.json @@ -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", diff --git a/apps/web/lib/i18n/locales/hr/translation.json b/apps/web/lib/i18n/locales/hr/translation.json index 83ae3851..6e250924 100644 --- a/apps/web/lib/i18n/locales/hr/translation.json +++ b/apps/web/lib/i18n/locales/hr/translation.json @@ -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." diff --git a/apps/web/lib/i18n/locales/it/translation.json b/apps/web/lib/i18n/locales/it/translation.json index 6479f6c8..e24b6b7f 100644 --- a/apps/web/lib/i18n/locales/it/translation.json +++ b/apps/web/lib/i18n/locales/it/translation.json @@ -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", diff --git a/apps/web/lib/i18n/locales/ja/translation.json b/apps/web/lib/i18n/locales/ja/translation.json index e44603bb..76767fcc 100644 --- a/apps/web/lib/i18n/locales/ja/translation.json +++ b/apps/web/lib/i18n/locales/ja/translation.json @@ -30,7 +30,12 @@ "recrawl": "再クロール", "ignore": "無視する", "cancel": "キャンセル", - "download_full_page_archive": "全てのページアーカイブをダウンロード" + "download_full_page_archive": "全てのページアーカイブをダウンロード", + "sort": { + "title": "並び替え", + "newest_first": "新しい順", + "oldest_first": "古い順" + } }, "admin": { "actions": { diff --git a/apps/web/lib/i18n/locales/nl/translation.json b/apps/web/lib/i18n/locales/nl/translation.json index 08953704..569e38bc 100644 --- a/apps/web/lib/i18n/locales/nl/translation.json +++ b/apps/web/lib/i18n/locales/nl/translation.json @@ -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": { diff --git a/apps/web/lib/i18n/locales/pl/translation.json b/apps/web/lib/i18n/locales/pl/translation.json index b072b56d..0d026542 100644 --- a/apps/web/lib/i18n/locales/pl/translation.json +++ b/apps/web/lib/i18n/locales/pl/translation.json @@ -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": { diff --git a/apps/web/lib/i18n/locales/ru/translation.json b/apps/web/lib/i18n/locales/ru/translation.json index eabe2e3f..4a8cdd52 100644 --- a/apps/web/lib/i18n/locales/ru/translation.json +++ b/apps/web/lib/i18n/locales/ru/translation.json @@ -110,7 +110,12 @@ "copy_link": "Скопировать ссылку", "unselect_all": "Отменить выбор", "select_all": "Выбрать все", - "apply_all": "Применить всё" + "apply_all": "Применить всё", + "sort": { + "title": "Сортировка", + "newest_first": "Сначала новые", + "oldest_first": "Сначала старые" + } }, "editor": { "text_toolbar": { diff --git a/apps/web/lib/i18n/locales/sv/translation.json b/apps/web/lib/i18n/locales/sv/translation.json index 9adf3bec..26db4c88 100644 --- a/apps/web/lib/i18n/locales/sv/translation.json +++ b/apps/web/lib/i18n/locales/sv/translation.json @@ -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", diff --git a/apps/web/lib/i18n/locales/tr/translation.json b/apps/web/lib/i18n/locales/tr/translation.json index b14df6d7..9840c6f0 100644 --- a/apps/web/lib/i18n/locales/tr/translation.json +++ b/apps/web/lib/i18n/locales/tr/translation.json @@ -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." diff --git a/apps/web/lib/i18n/locales/zh/translation.json b/apps/web/lib/i18n/locales/zh/translation.json index be09b221..87d098a6 100644 --- a/apps/web/lib/i18n/locales/zh/translation.json +++ b/apps/web/lib/i18n/locales/zh/translation.json @@ -61,7 +61,12 @@ "cancel": "取消", "apply_all": "全部应用", "ignore": "忽略", - "recrawl": "重新抓取" + "recrawl": "重新抓取", + "sort": { + "title": "排序", + "newest_first": "最新优先", + "oldest_first": "最早优先" + } }, "settings": { "back_to_app": "返回应用", diff --git a/apps/web/lib/i18n/locales/zhtw/translation.json b/apps/web/lib/i18n/locales/zhtw/translation.json index bcdb83fb..693b0c0a 100644 --- a/apps/web/lib/i18n/locales/zhtw/translation.json +++ b/apps/web/lib/i18n/locales/zhtw/translation.json @@ -60,7 +60,12 @@ "merge": "合併", "cancel": "取消", "apply_all": "全部套用", - "ignore": "忽略" + "ignore": "忽略", + "sort": { + "title": "排序", + "newest_first": "最新優先", + "oldest_first": "最舊優先" + } }, "settings": { "back_to_app": "返回應用程式", diff --git a/apps/web/lib/store/useSortOrderStore.ts b/apps/web/lib/store/useSortOrderStore.ts new file mode 100644 index 00000000..217e142e --- /dev/null +++ b/apps/web/lib/store/useSortOrderStore.ts @@ -0,0 +1,13 @@ +import { create } from "zustand"; + +import { ZSortOrder } from "@hoarder/shared/types/bookmarks"; + +interface SortOrderState { + sortOrder: ZSortOrder; + setSortOrder: (sortOrder: ZSortOrder) => void; +} + +export const useSortOrderStore = create<SortOrderState>((set) => ({ + sortOrder: "desc", // default sort order + setSortOrder: (sortOrder) => set({ sortOrder }), +})); |
