implement bookmark search
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import { BookmarkList } from "./-bookmark-list"
|
||||
import { useBookmarkPageStore } from "./-store"
|
||||
import { createFileRoute, useNavigate } from "@tanstack/react-router"
|
||||
import { useCallback, useEffect, useId, useRef } from "react"
|
||||
import { fetchApi, useAuthenticatedQuery } from "~/api"
|
||||
import { ActionBar } from "~/app/bookmarks/-action-bar.tsx"
|
||||
import { useLogOut } from "~/auth.ts"
|
||||
import { Button } from "~/components/button.tsx"
|
||||
import { LoadingSpinner } from "~/components/loading-spinner"
|
||||
import { BookmarkListActionBar } from "./-action-bar"
|
||||
import { useMnemonics } from "~/hooks/use-mnemonics.ts"
|
||||
import { BookmarkList } from "./-bookmark-list"
|
||||
import { ActionBarContentKind, DialogKind, useBookmarkPageStore } from "./-store"
|
||||
|
||||
export const Route = createFileRoute("/bookmarks/")({
|
||||
component: RouteComponent,
|
||||
@@ -67,3 +71,177 @@ function BookmarkListContainer() {
|
||||
return <p>error loading bookmarks</p>
|
||||
}
|
||||
}
|
||||
|
||||
function BookmarkListActionBar({ className }: { className?: string }) {
|
||||
const content = useBookmarkPageStore((state) => state.actionBarContent)
|
||||
return (
|
||||
<ActionBar className={className}>
|
||||
{(() => {
|
||||
switch (content.kind) {
|
||||
case ActionBarContentKind.Normal:
|
||||
return <ActionButtons />
|
||||
case ActionBarContentKind.StatusMessage:
|
||||
return (
|
||||
<div className="border-t-1 flex flex-row justify-center py-4 space-x-4">
|
||||
<p>{content.message}</p>
|
||||
</div>
|
||||
)
|
||||
case ActionBarContentKind.SearchBar:
|
||||
return <SearchBar />
|
||||
}
|
||||
})()}
|
||||
</ActionBar>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchBar() {
|
||||
const searchInputId = useId()
|
||||
const inputRef = useRef<HTMLInputElement | null>(null)
|
||||
const setActionBarContent = useBookmarkPageStore((state) => state.setActionBarContent)
|
||||
const searchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const navigate = Route.useNavigate()
|
||||
const { q } = Route.useSearch()
|
||||
|
||||
useMnemonics(
|
||||
{
|
||||
Escape: () => {
|
||||
navigate({
|
||||
search: (prevSearch: Record<string, string | undefined>) => ({
|
||||
...prevSearch,
|
||||
tags: undefined,
|
||||
q: undefined,
|
||||
}),
|
||||
})
|
||||
setActionBarContent({ kind: ActionBarContentKind.Normal })
|
||||
},
|
||||
},
|
||||
{ ignore: () => false },
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setInterval(() => {
|
||||
inputRef.current?.focus()
|
||||
}, 0)
|
||||
}, [])
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (searchTimeoutRef.current) {
|
||||
clearTimeout(searchTimeoutRef.current)
|
||||
}
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
function pushSearchQuery(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
if (searchTimeoutRef.current) {
|
||||
clearTimeout(searchTimeoutRef.current)
|
||||
}
|
||||
const q = event.currentTarget.value
|
||||
searchTimeoutRef.current = setTimeout(() => {
|
||||
navigate({
|
||||
search: (prevSearch: Record<string, string | undefined>) => {
|
||||
if (!q) {
|
||||
return { ...prevSearch, tags: undefined, q: undefined }
|
||||
}
|
||||
|
||||
if (q.startsWith("#")) {
|
||||
const parts = q.split(" ")
|
||||
let searchTermBegin = -1
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
if (!parts[i].startsWith("#")) {
|
||||
searchTermBegin = i
|
||||
break
|
||||
}
|
||||
}
|
||||
const tags = (searchTermBegin >= 0 ? parts.slice(0, searchTermBegin) : parts)
|
||||
.map((tag) => tag.substring(1))
|
||||
.join(",")
|
||||
const query = searchTermBegin >= 0 ? parts.slice(searchTermBegin).join(" ") : ""
|
||||
|
||||
if (query) {
|
||||
return { ...prevSearch, tags, q: query }
|
||||
}
|
||||
|
||||
return { ...prevSearch, tags, q: undefined }
|
||||
}
|
||||
|
||||
return { ...prevSearch, tags: undefined, q }
|
||||
},
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function closeSearchBar() {
|
||||
setActionBarContent({ kind: ActionBarContentKind.Normal })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="-my-4 w-full flex items-center justify-center">
|
||||
<label className="p-4 bg-stone-900 dark:bg-stone-200 text-stone-300 dark:text-stone-800" htmlFor={searchInputId}>
|
||||
SEARCH
|
||||
</label>
|
||||
<input
|
||||
ref={inputRef}
|
||||
id={searchInputId}
|
||||
type="text"
|
||||
className="flex-1 text-center outline-0 ring-0"
|
||||
defaultValue={q ?? ""}
|
||||
onChange={pushSearchQuery}
|
||||
/>
|
||||
<Button className="mx-2" onClick={closeSearchBar}>
|
||||
CLOSE
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ActionButtons() {
|
||||
const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog)
|
||||
const setActionBarContent = useBookmarkPageStore((state) => state.setActionBarContent)
|
||||
|
||||
useMnemonics(
|
||||
{
|
||||
a: addBookmark,
|
||||
s: openSearchBar,
|
||||
},
|
||||
{ ignore: useCallback(() => useBookmarkPageStore.getState().dialog.kind !== DialogKind.None, []) },
|
||||
)
|
||||
|
||||
function addBookmark() {
|
||||
setActiveDialog({ kind: DialogKind.AddBookmark })
|
||||
}
|
||||
|
||||
function openSearchBar() {
|
||||
setActionBarContent({ kind: ActionBarContentKind.SearchBar })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-row justify-center space-x-4">
|
||||
<Button onClick={addBookmark}>
|
||||
<span className="underline">A</span>DD
|
||||
</Button>
|
||||
<Button onClick={openSearchBar}>
|
||||
<span className="underline">S</span>EARCH
|
||||
</Button>
|
||||
<LogOutButton />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
function LogOutButton() {
|
||||
const logOutMutation = useLogOut()
|
||||
const navigate = useNavigate()
|
||||
|
||||
function logOut() {
|
||||
logOutMutation.mutate()
|
||||
navigate({ to: "/", replace: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<Button disabled={logOutMutation.isPending} onClick={logOut}>
|
||||
LOG OUT
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export { BookmarkListActionBar }
|
||||
|
Reference in New Issue
Block a user