implement bookmark tagging

This commit is contained in:
2025-05-21 13:18:16 +01:00
parent f048dee6e2
commit b0d458e5ca
20 changed files with 826 additions and 362 deletions

View File

@@ -1,13 +1,15 @@
import type { LinkBookmark } from "@markone/core/bookmark"
import type { Bookmark } from "@markone/core/bookmark"
import { Link } from "@tanstack/react-router"
import { createStore, useStore } from "zustand"
import { useEffect, useCallback, createContext, useRef, memo, useContext } from "react"
import { useMnemonics } from "~/hooks/use-mnemonics"
import { useBookmarkPageStore, ActiveDialog } from "./-store"
import { Button } from "~/components/button"
import clsx from "clsx"
import { createContext, memo, useCallback, useContext, useEffect, useRef } from "react"
import { twMerge } from "tailwind-merge"
import { createStore, useStore } from "zustand"
import { subscribeWithSelector } from "zustand/middleware"
import { Button } from "~/components/button"
import { useMnemonics } from "~/hooks/use-mnemonics"
import { ActiveDialog, useBookmarkPageStore } from "./-store"
import { useBookmarkTags } from "~/bookmark/api"
import { LoadingSpinner } from "~/components/loading-spinner"
enum BookmarkListItemAction {
Open = "Open",
@@ -15,34 +17,34 @@ enum BookmarkListItemAction {
Delete = "Delete",
}
type SelectionChangeCallback = (bookmark: LinkBookmark) => void
type ItemActionCallback = (bookmark: LinkBookmark, action: BookmarkListItemAction) => void
type SelectionChangeCallback = (bookmark: Bookmark) => void
type ItemActionCallback = (bookmark: Bookmark, action: BookmarkListItemAction) => void
interface BookmarkListProps {
bookmarks: LinkBookmark[]
bookmarks: Bookmark[]
selectedBookmarkId?: string
alwaysExpandItem: boolean
onSelectionChange?: SelectionChangeCallback
onItemAction: (bookmark: LinkBookmark, action: BookmarkListItemAction) => void
onItemAction: (bookmark: Bookmark, action: BookmarkListItemAction) => void
className?: string
}
interface CreateStoreOptions {
bookmarks: LinkBookmark[]
bookmarks: Bookmark[]
selectedBookmarkId?: string
alwaysExpandItem: boolean
onItemAction: ItemActionCallback
}
interface BookmarkListState {
bookmarks: LinkBookmark[]
bookmarks: Bookmark[]
selectedIndex: number
selectedBookmarkId: string
alwaysExpandItem: boolean
isItemExpanded: boolean
onItemAction: ItemActionCallback
setBookmarks: (bookmarks: LinkBookmark[]) => void
setBookmarks: (bookmarks: Bookmark[]) => void
setSelectedIndex: (index: number) => void
setSelectedBookmarkId: (id: string) => void
setIsItemExpanded: (expanded: boolean) => void
@@ -58,17 +60,22 @@ function createBookmarkListStore({
alwaysExpandItem,
onItemAction,
}: CreateStoreOptions) {
let _selectedBookmarkId = selectedBookmarkId
if (!_selectedBookmarkId && bookmarks.length > 0) {
_selectedBookmarkId = bookmarks[0].id
}
return createStore<BookmarkListState>()(
subscribeWithSelector((set) => ({
bookmarks,
alwaysExpandItem,
selectedIndex: selectedBookmarkId ? bookmarks.findIndex((bookmark) => bookmark.id === selectedBookmarkId) : 0,
selectedBookmarkId: selectedBookmarkId ?? bookmarks[0].id,
selectedBookmarkId: _selectedBookmarkId ?? "",
isItemExpanded: false,
onItemAction,
setBookmarks(bookmarks: LinkBookmark[]) {
setBookmarks(bookmarks: Bookmark[]) {
set({ bookmarks })
},
@@ -113,6 +120,12 @@ function BookmarkList({
const setSelectedBookmarkId = useStore(storeRef.current, (state) => state.setSelectedBookmarkId)
useEffect(() => {
// biome-ignore lint/style/noNonNullAssertion: storeRef.current is already set above, so cant be null
const store = storeRef.current!
store.getState().setBookmarks(bookmarks)
}, [bookmarks])
useEffect(() => {
// biome-ignore lint/style/noNonNullAssertion: storeRef.current is already set above, so cant be null
const store = storeRef.current!
@@ -230,7 +243,7 @@ function ListContainer() {
}
const BookmarkListItem = memo(
({ bookmark, index, selected }: { bookmark: LinkBookmark; index: number; selected: boolean }) => {
({ bookmark, index, selected }: { bookmark: Bookmark; index: number; selected: boolean }) => {
const url = new URL(bookmark.url)
const store = useBookmarkListStoreContext()
const alwaysExpandItem = useBookmarkListStore((state) => state.alwaysExpandItem)
@@ -279,21 +292,23 @@ const BookmarkListItem = memo(
</Link>
<p className="opacity-80 text-sm">{url.host}</p>
{isBookmarkItemExpanded && selected ? (
<div className="flex flex-col space-y-1 md:flex-row md:space-y-0 md:space-x-2 items-end justify-between pt-2">
<p className="text-sm">#dev</p>
<div className="flex space-x-2">
<Button variant="light" className="text-sm">
<span>COPY LINK</span>
</Button>
<Button variant="light" className="text-sm">
<span className="underline">E</span>dit
</Button>
<Button variant="light" className="text-sm" onClick={deleteItem}>
<span className="underline">D</span>elete
</Button>
<span className="-ml-2">&nbsp;</span>
<>
<BookmarkTagList bookmark={bookmark} />
<div className="flex flex-col space-y-1 md:flex-row md:space-y-0 md:space-x-2 items-end justify-between pt-2">
<div className="flex space-x-2">
<Button variant="light" className="text-sm">
<span>COPY LINK</span>
</Button>
<Button variant="light" className="text-sm">
<span className="underline">E</span>dit
</Button>
<Button variant="light" className="text-sm" onClick={deleteItem}>
<span className="underline">D</span>elete
</Button>
<span className="-ml-2">&nbsp;</span>
</div>
</div>
</div>
</>
) : null}
</div>
</li>
@@ -301,4 +316,16 @@ const BookmarkListItem = memo(
},
)
function BookmarkTagList({ bookmark }: { bookmark: Bookmark }) {
const { data: tags, status } = useBookmarkTags(bookmark)
switch (status) {
case "pending":
return <LoadingSpinner />
case "success":
return <p className="my-2 text-sm">{tags.map((tag) => `#${tag.name}`).join(" ")}</p>
case "error":
return null
}
}
export { BookmarkList, BookmarkListItemAction }