diff options
| author | xuatz <xzlow10@gmail.com> | 2025-06-02 04:01:26 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-06-01 20:01:26 +0100 |
| commit | 3afe1e21df6dcc0483e74e0db02d9d82af32ecea (patch) | |
| tree | 32208bf2de6fedae00aaeb4f7ca0d74a816b4764 /apps/web | |
| parent | 8784c73c112b5b87bcf48670401e3956464ea436 (diff) | |
| download | karakeep-3afe1e21df6dcc0483e74e0db02d9d82af32ecea.tar.zst | |
feat: add user customisable default archive display behaviour (#1505)
* fix typo
* implementation
* bug fix and refactoring
* Use nuqs for searchParam management
* remove the todo about the tests
* fix tests
---------
Co-authored-by: Mohamed Bassem <me@mbassem.com>
Diffstat (limited to 'apps/web')
| -rw-r--r-- | apps/web/app/dashboard/lists/[listId]/page.tsx | 15 | ||||
| -rw-r--r-- | apps/web/app/dashboard/tags/[tagId]/page.tsx | 15 | ||||
| -rw-r--r-- | apps/web/app/layout.tsx | 21 | ||||
| -rw-r--r-- | apps/web/components/dashboard/lists/ListOptions.tsx | 20 | ||||
| -rw-r--r-- | apps/web/components/dashboard/tags/TagOptions.tsx | 14 | ||||
| -rw-r--r-- | apps/web/components/settings/UserOptions.tsx | 52 | ||||
| -rw-r--r-- | apps/web/components/utils/useShowArchived.tsx | 24 | ||||
| -rw-r--r-- | apps/web/lib/i18n/locales/en/translation.json | 14 | ||||
| -rw-r--r-- | apps/web/lib/userSettings.tsx | 1 | ||||
| -rw-r--r-- | apps/web/package.json | 1 |
10 files changed, 157 insertions, 20 deletions
diff --git a/apps/web/app/dashboard/lists/[listId]/page.tsx b/apps/web/app/dashboard/lists/[listId]/page.tsx index 6c0bc36c..de0f5054 100644 --- a/apps/web/app/dashboard/lists/[listId]/page.tsx +++ b/apps/web/app/dashboard/lists/[listId]/page.tsx @@ -8,9 +8,14 @@ import { BookmarkListContextProvider } from "@karakeep/shared-react/hooks/bookma export default async function ListPage({ params, + searchParams, }: { params: { listId: string }; + searchParams?: { + includeArchived?: string; + }; }) { + const userSettings = await api.users.settings(); let list; try { list = await api.lists.get({ listId: params.listId }); @@ -23,10 +28,18 @@ export default async function ListPage({ throw e; } + const includeArchived = + searchParams?.includeArchived !== undefined + ? searchParams.includeArchived === "true" + : userSettings.archiveDisplayBehaviour === "show"; + return ( <BookmarkListContextProvider list={list}> <Bookmarks - query={{ listId: list.id }} + query={{ + listId: list.id, + archived: !includeArchived ? false : undefined, + }} showDivider={true} showEditorCard={list.type === "manual"} header={<ListHeader initialData={list} />} diff --git a/apps/web/app/dashboard/tags/[tagId]/page.tsx b/apps/web/app/dashboard/tags/[tagId]/page.tsx index f6e02a65..b33a351a 100644 --- a/apps/web/app/dashboard/tags/[tagId]/page.tsx +++ b/apps/web/app/dashboard/tags/[tagId]/page.tsx @@ -9,8 +9,12 @@ import { MoreHorizontal } from "lucide-react"; export default async function TagPage({ params, + searchParams, }: { params: { tagId: string }; + searchParams?: { + includeArchived?: string; + }; }) { let tag; try { @@ -23,6 +27,12 @@ export default async function TagPage({ } throw e; } + const userSettings = await api.users.settings(); + + const includeArchived = + searchParams?.includeArchived !== undefined + ? searchParams.includeArchived === "true" + : userSettings.archiveDisplayBehaviour === "show"; return ( <Bookmarks @@ -40,7 +50,10 @@ export default async function TagPage({ </TagOptions> </div> } - query={{ tagId: tag.id }} + query={{ + tagId: tag.id, + archived: !includeArchived ? false : undefined, + }} showEditorCard={true} /> ); diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index beeecc2b..d5af9e35 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; +import { NuqsAdapter } from "nuqs/adapters/next/app"; import "@karakeep/tailwind-config/globals.css"; @@ -55,15 +56,17 @@ export default async function RootLayout({ dir={isRTL ? "rtl" : "ltr"} > <body className={inter.className}> - <Providers - session={session} - clientConfig={clientConfig} - userLocalSettings={await getUserLocalSettings()} - > - {children} - <ReactQueryDevtools initialIsOpen={false} /> - </Providers> - <Toaster /> + <NuqsAdapter> + <Providers + session={session} + clientConfig={clientConfig} + userLocalSettings={await getUserLocalSettings()} + > + {children} + <ReactQueryDevtools initialIsOpen={false} /> + </Providers> + <Toaster /> + </NuqsAdapter> </body> </html> ); diff --git a/apps/web/components/dashboard/lists/ListOptions.tsx b/apps/web/components/dashboard/lists/ListOptions.tsx index d0dedfd5..7e020374 100644 --- a/apps/web/components/dashboard/lists/ListOptions.tsx +++ b/apps/web/components/dashboard/lists/ListOptions.tsx @@ -5,8 +5,17 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { useShowArchived } from "@/components/utils/useShowArchived"; import { useTranslation } from "@/lib/i18n/client"; -import { FolderInput, Pencil, Plus, Share, Trash2 } from "lucide-react"; +import { + FolderInput, + Pencil, + Plus, + Share, + Square, + SquareCheck, + Trash2, +} from "lucide-react"; import { ZBookmarkList } from "@karakeep/shared/types/lists"; @@ -27,6 +36,7 @@ export function ListOptions({ children?: React.ReactNode; }) { const { t } = useTranslation(); + const { showArchived, onClickShowArchived } = useShowArchived(); const [deleteListDialogOpen, setDeleteListDialogOpen] = useState(false); const [newNestedListModalOpen, setNewNestedListModalOpen] = useState(false); @@ -93,6 +103,14 @@ export function ListOptions({ <FolderInput className="size-4" /> <span>{t("lists.merge_list")}</span> </DropdownMenuItem> + <DropdownMenuItem className="flex gap-2" onClick={onClickShowArchived}> + {showArchived ? ( + <SquareCheck className="size-4" /> + ) : ( + <Square className="size-4" /> + )} + <span>{t("actions.toggle_show_archived")}</span> + </DropdownMenuItem> <DropdownMenuItem className="flex gap-2" onClick={() => setDeleteListDialogOpen(true)} diff --git a/apps/web/components/dashboard/tags/TagOptions.tsx b/apps/web/components/dashboard/tags/TagOptions.tsx index 8d8cc9db..1419e6c3 100644 --- a/apps/web/components/dashboard/tags/TagOptions.tsx +++ b/apps/web/components/dashboard/tags/TagOptions.tsx @@ -7,8 +7,9 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { useShowArchived } from "@/components/utils/useShowArchived"; import { useTranslation } from "@/lib/i18n/client"; -import { Combine, Trash2 } from "lucide-react"; +import { Combine, Square, SquareCheck, Trash2 } from "lucide-react"; import DeleteTagConfirmationDialog from "./DeleteTagConfirmationDialog"; import { MergeTagModal } from "./MergeTagModal"; @@ -21,6 +22,8 @@ export function TagOptions({ children?: React.ReactNode; }) { const { t } = useTranslation(); + const { showArchived, onClickShowArchived } = useShowArchived(); + const [deleteTagDialogOpen, setDeleteTagDialogOpen] = useState(false); const [mergeTagDialogOpen, setMergeTagDialogOpen] = useState(false); @@ -45,7 +48,14 @@ export function TagOptions({ <Combine className="size-4" /> <span>{t("actions.merge")}</span> </DropdownMenuItem> - + <DropdownMenuItem className="flex gap-2" onClick={onClickShowArchived}> + {showArchived ? ( + <SquareCheck className="size-4" /> + ) : ( + <Square className="size-4" /> + )} + <span>{t("actions.toggle_show_archived")}</span> + </DropdownMenuItem> <DropdownMenuItem className="flex gap-2" onClick={() => setDeleteTagDialogOpen(true)} diff --git a/apps/web/components/settings/UserOptions.tsx b/apps/web/components/settings/UserOptions.tsx index c8aa5e86..3918ceed 100644 --- a/apps/web/components/settings/UserOptions.tsx +++ b/apps/web/components/settings/UserOptions.tsx @@ -73,12 +73,22 @@ export default function UserSettings() { ZUserSettings["bookmarkClickAction"], string > = { - open_original_link: t("settings.info.user_settings.open_external_url"), + open_original_link: t( + "settings.info.user_settings.bookmark_click_action.open_external_url", + ), expand_bookmark_preview: t( - "settings.info.user_settings.open_bookmark_details", + "settings.info.user_settings.bookmark_click_action.open_bookmark_details", ), }; + const archiveDisplayBehaviourTranslation: Record< + ZUserSettings["archiveDisplayBehaviour"], + string + > = { + show: t("settings.info.user_settings.archive_display_behaviour.show"), + hide: t("settings.info.user_settings.archive_display_behaviour.hide"), + }; + const form = useForm<z.infer<typeof zUserSettingsSchema>>({ resolver: zodResolver(zUserSettingsSchema), defaultValues: data, @@ -97,7 +107,7 @@ export default function UserSettings() { render={({ field }) => ( <div className="flex w-full flex-col gap-2"> <Label> - {t("settings.info.user_settings.boomark_click_action")} + {t("settings.info.user_settings.bookmark_click_action.title")} </Label> <Select disabled={!!clientConfig.demoMode} @@ -127,6 +137,42 @@ export default function UserSettings() { </div> )} /> + <FormField + control={form.control} + name="archiveDisplayBehaviour" + render={({ field }) => ( + <div className="flex w-full flex-col gap-2"> + <Label> + {t("settings.info.user_settings.archive_display_behaviour.title")} + </Label> + <Select + disabled={!!clientConfig.demoMode} + value={field.value} + onValueChange={(value) => { + mutate({ + archiveDisplayBehaviour: + value as ZUserSettings["archiveDisplayBehaviour"], + }); + }} + > + <SelectTrigger> + <SelectValue> + {archiveDisplayBehaviourTranslation[field.value]} + </SelectValue> + </SelectTrigger> + <SelectContent> + {Object.entries(archiveDisplayBehaviourTranslation).map( + ([value, label]) => ( + <SelectItem key={value} value={value}> + {label} + </SelectItem> + ), + )} + </SelectContent> + </Select> + </div> + )} + /> </Form> ); } diff --git a/apps/web/components/utils/useShowArchived.tsx b/apps/web/components/utils/useShowArchived.tsx new file mode 100644 index 00000000..3fc66e91 --- /dev/null +++ b/apps/web/components/utils/useShowArchived.tsx @@ -0,0 +1,24 @@ +import { useCallback } from "react"; +import { useUserSettings } from "@/lib/userSettings"; +import { parseAsBoolean, useQueryState } from "nuqs"; + +export function useShowArchived() { + const userSettings = useUserSettings(); + const [showArchived, setShowArchived] = useQueryState( + "includeArchived", + parseAsBoolean + .withOptions({ + shallow: false, + }) + .withDefault(userSettings.archiveDisplayBehaviour === "show"), + ); + + const onClickShowArchived = useCallback(() => { + setShowArchived((prev) => !prev); + }, [setShowArchived]); + + return { + showArchived, + onClickShowArchived, + }; +} diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json index d7ee54a3..39be43f3 100644 --- a/apps/web/lib/i18n/locales/en/translation.json +++ b/apps/web/lib/i18n/locales/en/translation.json @@ -51,6 +51,7 @@ "favorite": "Favorite", "unfavorite": "Unfavorite", "delete": "Delete", + "toggle_show_archived": "Show Archived", "refresh": "Refresh", "recrawl": "Recrawl", "download_full_page_archive": "Download Full Page Archive", @@ -101,9 +102,16 @@ "interface_lang": "Interface Language", "user_settings": { "user_settings_updated": "User settings have been updated!", - "boomark_click_action": "Bookmark Click Action", - "open_external_url": "Open Original URL", - "open_bookmark_details": "Open Bookmark Details" + "bookmark_click_action": { + "title": "Bookmark Click Action", + "open_external_url": "Open Original URL", + "open_bookmark_details": "Open Bookmark Details" + }, + "archive_display_behaviour": { + "title": "Archived Bookmarks", + "show": "Show archived bookmarks in tags and lists", + "hide": "Hide archived bookmarks in tags and lists" + } } }, "ai": { diff --git a/apps/web/lib/userSettings.tsx b/apps/web/lib/userSettings.tsx index 727c823e..1590f727 100644 --- a/apps/web/lib/userSettings.tsx +++ b/apps/web/lib/userSettings.tsx @@ -8,6 +8,7 @@ import { api } from "./trpc"; export const UserSettingsContext = createContext<ZUserSettings>({ bookmarkClickAction: "open_original_link", + archiveDisplayBehaviour: "show", }); export function UserSettingsContextProvider({ diff --git a/apps/web/package.json b/apps/web/package.json index 062565d8..7efb1830 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -71,6 +71,7 @@ "next-i18next": "^15.3.1", "next-pwa": "^5.6.0", "next-themes": "^0.3.0", + "nuqs": "^2.4.3", "prettier": "^3.4.2", "react": "^18.3.1", "react-day-picker": "8.10.1", |
