From b5c23a1246d135a35dbc3cb1bb85e74f2ae033fd Mon Sep 17 00:00:00 2001 From: thomasabishop Date: Fri, 19 Dec 2025 17:57:28 +0000 Subject: [PATCH] feat: add history menu --- src/App.tsx | 44 +++++++------- src/components/AppHeader.tsx | 22 ++++++- src/components/History.tsx | 100 ++++++++++++++++++++++++++++++++ src/lib/utils.ts | 11 ++++ src/templates/EntryTemplate.tsx | 1 + src/templates/MainTemplate.tsx | 9 ++- 6 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 src/components/History.tsx diff --git a/src/App.tsx b/src/App.tsx index aa3548e..d079f7d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,28 +9,30 @@ import Settings from "./pages/settings" import About from "./pages/about" const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 15 * 60 * 1000, // 15 minutes - retry: 3, - refetchOnWindowFocus: false, - }, - }, + defaultOptions: { + queries: { + staleTime: Infinity, + gcTime: Infinity, + retry: 1, + refetchOnWindowFocus: false, + refetchOnMount: false, + }, + }, }) export default function App() { - return ( - - - - } /> - } /> - } /> - } /> - } /> - - - - - ) + return ( + + + + } /> + } /> + } /> + } /> + } /> + + + + + ) } diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx index 98ab531..2b24014 100644 --- a/src/components/AppHeader.tsx +++ b/src/components/AppHeader.tsx @@ -1,14 +1,34 @@ import { SidebarTrigger } from "./ui/sidebar" import { Separator } from "./ui/separator" import Search from "@/containers/Search" +import { Button } from "./ui/button" +import { useState } from "react" +import History from "./History" +import { HistoryIcon } from "lucide-react" -export default function AppHeader({ pageTitle }: { pageTitle: string }) { +export default function AppHeader({ + pageTitle, + historyOpen, + setHistoryOpen, +}: { + pageTitle: string +}) { return (
+ + +
) diff --git a/src/components/History.tsx b/src/components/History.tsx new file mode 100644 index 0000000..bab87df --- /dev/null +++ b/src/components/History.tsx @@ -0,0 +1,100 @@ +import { Link } from "react-router" +import { Badge } from "./ui/badge" +import { SheetContent, Sheet, SheetHeader, SheetTitle } from "./ui/sheet" +import { useLocation } from "react-router" +import { useQueryClient } from "@tanstack/react-query" +import { formatRelativeTime } from "@/lib/utils" +import { useEffect } from "react" + +export default function History({ sheetOpen, setSheetOpen, error }) { + const location = useLocation() + const queryClient = useQueryClient() + const searchHistory = queryClient.getQueriesData({ queryKey: ["search_results"] }) + const entriesHistory = queryClient.getQueryCache().findAll({ + predicate: (query) => { + const firstKey = query.queryKey[0] + return ( + typeof firstKey === "string" && + firstKey.startsWith("entry_") && + query.meta?.visited === true + ) + }, + }) + + const entries = entriesHistory + .map((query) => ({ + title: query.state.data?.title, + visitedAt: query.options?.meta?.visitedAt, + queryKey: query.queryKey[0], + })) + .filter((x) => x.title !== undefined) + .sort((a, b) => b.visitedAt - a.visitedAt) + + // Force Sheet close on renavigation (i.e search result selection) + useEffect(() => { + if (sheetOpen) { + setSheetOpen(false) + } + }, [location.pathname]) + + return ( + { + setSheetOpen(open) + }} + > + + + Session history + + + {error ? ( +
+
+ Error fetching history. +
{" "} +
+ ) : ( + <> +
+

Entries

+ + {entries.length || 0} + +
+ +
+ {entries?.map((entry, i) => ( +
+ + {i === 0 ? ( + + {entry?.title.replace(/_/g, " ")} + + ) : ( + {entry?.title.replace(/_/g, " ")} + )} + + + {formatRelativeTime(entry.visitedAt)} + +
+ ))} +
+
+

Searches

+ + 0 + +
+ + )} +
+
+ ) +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 5709946..b8da3eb 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -27,3 +27,14 @@ export const convertDateFriendly = (isoStamp) => { const year = unixSeconds.getFullYear() return `${day} ${month} ${year}` } + +export const formatRelativeTime = (timestamp) => { + const seconds = Math.floor((Date.now() - timestamp) / 1000) + if (seconds < 60) return `${seconds} secs` + const minutes = Math.floor(seconds / 60) + if (minutes < 60) return `${minutes} mins` + const hours = Math.floor(minutes / 60) + if (hours < 24) return `${hours} hours` + const days = Math.floor(hours / 24) + return `${days} days` +} diff --git a/src/templates/EntryTemplate.tsx b/src/templates/EntryTemplate.tsx index d8bbc9a..45c0415 100644 --- a/src/templates/EntryTemplate.tsx +++ b/src/templates/EntryTemplate.tsx @@ -11,6 +11,7 @@ export default function EntryTemplate() { const { data, isLoading } = useQuery({ queryKey: [`entry_${entry}`], queryFn: () => api.get(`/entries/${entry}`).then((res) => res.data), + meta: { visited: true, visitedAt: Date.now() }, }) return ( diff --git a/src/templates/MainTemplate.tsx b/src/templates/MainTemplate.tsx index 47bb9d5..93f83cb 100644 --- a/src/templates/MainTemplate.tsx +++ b/src/templates/MainTemplate.tsx @@ -2,14 +2,21 @@ import AppHeader from "@/components/AppHeader" import { AppSidebar } from "@/containers/AppSidebar" import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar" import { ThemeProvider } from "@/context/ThemeProvider" +import { useState } from "react" export default function MainTemplate({ children, pageTitle }) { + const [historyOpen, setHistoryOpen] = useState(false) return ( - + +
{children}