import type { LinkBookmark } from "@markone/core/bookmark" import { createFileRoute, useNavigate } from "@tanstack/react-router" import clsx from "clsx" import { useEffect, useId, useState } from "react" import { create } from "zustand" import { fetchApi, useAuthenticatedQuery, BadRequestError, ApiErrorCode } from "~/api" import { useCreateBookmark, useDeleteBookmark } from "~/bookmark/api" import { Button } from "~/components/button" import { Dialog, DialogActionRow, DialogBody, DialogTitle } from "~/components/dialog" import { Message, MessageVariant } from "~/components/message" import { FormField } from "~/components/form-field" import { LoadingSpinner } from "~/components/loading-spinner" import { useMnemonics } from "~/hooks/use-mnemonics" import { useLogOut } from "~/auth" const LAYOUT_MODE = { popup: "popup", sideBySide: "side-by-side", } as const type LayoutMode = (typeof LAYOUT_MODE)[keyof typeof LAYOUT_MODE] enum ActiveDialog { None = "None", AddBookmark = "AddBookmark", DeleteBookmark = "DeleteBookmark", } interface BookmarkPageState { bookmarkCount: number selectedBookmarkId: string selectedBookmarkIndex: number isBookmarkItemExpanded: boolean isBookmarkPreviewOpened: boolean bookmarkToBeDeleted: LinkBookmark | null layoutMode: LayoutMode activeDialog: ActiveDialog setActiveDialog: (dialog: ActiveDialog) => void setBookmarkItemExpanded: (isExpanded: boolean) => void setBookmarkPreviewOpened: (isOpened: boolean) => void setLayoutMode: (mode: LayoutMode) => void selectBookmark: (bookmark: LinkBookmark, index: number) => void reconcileSelection: (bookmarks: LinkBookmark[]) => void markBookmarkForDeletion: (bookmark: LinkBookmark | null) => void } const useBookmarkPageStore = create()((set, get) => ({ bookmarkCount: 0, bookmarks: [], selectedBookmarkId: "", selectedBookmarkIndex: 0, isBookmarkItemExpanded: false, isBookmarkPreviewOpened: false, bookmarkToBeDeleted: null, layoutMode: LAYOUT_MODE.popup, activeDialog: ActiveDialog.None, setActiveDialog(dialog: ActiveDialog) { set({ activeDialog: dialog }) }, setBookmarkItemExpanded(isExpanded: boolean) { set({ isBookmarkItemExpanded: isExpanded }) }, setBookmarkPreviewOpened(isOpened: boolean) { set({ isBookmarkPreviewOpened: isOpened }) }, setLayoutMode(mode: LayoutMode) { set({ layoutMode: mode }) }, selectBookmark(bookmark: LinkBookmark, index: number) { set({ selectedBookmarkId: bookmark.id, selectedBookmarkIndex: index }) }, reconcileSelection(bookmarks: LinkBookmark[]) { const { selectedBookmarkId, selectedBookmarkIndex } = get() const newIndex = bookmarks.findIndex((bookmark) => bookmark.id === selectedBookmarkId) if (newIndex !== selectedBookmarkIndex) { if (newIndex >= 0) { set({ selectedBookmarkIndex: newIndex }) } else if (selectedBookmarkIndex >= bookmarks.length - 1) { set({ selectedBookmarkIndex: bookmarks.length - 1 }) } else if (selectedBookmarkIndex === 0) { set({ selectedBookmarkIndex: 0 }) } else { set({ selectedBookmarkIndex: selectedBookmarkIndex + 1 }) } } }, markBookmarkForDeletion(bookmark: LinkBookmark | null) { set({ bookmarkToBeDeleted: bookmark }) }, })) function Page() { const setLayoutMode = useBookmarkPageStore((state) => state.setLayoutMode) useEffect(() => { function mediaQueryListener(this: MediaQueryList) { if (this.matches) { setLayoutMode(LAYOUT_MODE.sideBySide) } else { setLayoutMode(LAYOUT_MODE.popup) } } const q = window.matchMedia("(width >= 64rem)") q.addEventListener("change", mediaQueryListener) mediaQueryListener.call(q) return () => { q.removeEventListener("change", mediaQueryListener) } }, [setLayoutMode]) return (

YOUR BOOKMARKS

) } function Main({ children }: React.PropsWithChildren) { const isPreviewOpened = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened) const layoutMode = useBookmarkPageStore((state) => state.layoutMode) return (
{children}
) } function PageDialog() { const dialog = useBookmarkPageStore((state) => state.activeDialog) switch (dialog) { case ActiveDialog.None: return null case ActiveDialog.AddBookmark: return case ActiveDialog.DeleteBookmark: return } } function DeleteBookmarkDialog() { // biome-ignore lint/style/noNonNullAssertion: this cannot be null when delete bookmark dialog is visible const bookmark = useBookmarkPageStore((state) => state.bookmarkToBeDeleted!) const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog) const markBookmarkForDeletion = useBookmarkPageStore((state) => state.markBookmarkForDeletion) const deleteBookmarkMutation = useDeleteBookmark() useMnemonics( { y: proceed, n: cancel, }, { active: true }, ) async function proceed() { try { await deleteBookmarkMutation.mutateAsync({ bookmark }) setActiveDialog(ActiveDialog.None) markBookmarkForDeletion(null) } catch (error) { console.error(error) } } function cancel() { setActiveDialog(ActiveDialog.None) markBookmarkForDeletion(null) } function body() { switch (deleteBookmarkMutation.status) { case "pending": return (

Deleting

) case "idle": return (

The bookmark titled{" "} "{bookmark.title}" {" "} will be deleted. Proceed?

) case "error": return

Failed to delete the bookmark!

} } function title() { switch (deleteBookmarkMutation.status) { case "pending": return "PLEASE WAIT" case "idle": return "CONFIRM" case "error": return "ERROR OCCURRED" } } return ( {title()} {body()} ) } function AddBookmarkDialog() { const [isWebsiteUnreachable, setIsWebsiteUnreachable] = useState(false) const createBookmarkMutation = useCreateBookmark() const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog) const formId = useId() useMnemonics( { c: cancel, }, { active: true }, ) async function onSubmit(event: React.FormEvent) { event.preventDefault() const formData = new FormData(event.currentTarget) const url = formData.get("link") if (url && typeof url === "string") { try { await createBookmarkMutation.mutateAsync({ url, force: isWebsiteUnreachable }) } catch (error) { if (error instanceof BadRequestError && error.code === ApiErrorCode.WebsiteUnreachable) { setIsWebsiteUnreachable(true) } } } } function cancel() { setActiveDialog(ActiveDialog.None) } function message() { if (createBookmarkMutation.isPending) { return (

Loading

) } if (isWebsiteUnreachable) { return ( The link does not seem to be reachable. Click "SAVE" to save anyways. ) } if (createBookmarkMutation.status === "error") { return ( An error occurred when saving bookmark ) } return null } return ( NEW BOOKMARK {message()}
) } function BookmarkListSection() { const { data: bookmarks, status } = useAuthenticatedQuery(["bookmarks"], () => fetchApi("/bookmarks").then((res) => res.json()), ) switch (status) { case "pending": return (

Loading 

) case "success": if (bookmarks) { return } return null default: return null } } function BookmarkList({ bookmarks }: { bookmarks: LinkBookmark[] }) { const reconcileSelection = useBookmarkPageStore((state) => state.reconcileSelection) useEffect(() => { reconcileSelection(bookmarks) }, [bookmarks, reconcileSelection]) useEffect(() => { function onKeyDown(event: KeyboardEvent) { const state = useBookmarkPageStore.getState() switch (event.key) { case "ArrowDown": { const nextIndex = state.selectedBookmarkIndex + 1 if (nextIndex < bookmarks.length) { state.selectBookmark(bookmarks[nextIndex], nextIndex) } break } case "ArrowUp": { const prevIndex = state.selectedBookmarkIndex - 1 if (prevIndex >= 0) { state.selectBookmark(bookmarks[prevIndex], prevIndex) } break } case "ArrowLeft": state.setBookmarkItemExpanded(false) break case "ArrowRight": state.setBookmarkItemExpanded(true) break default: break } } window.addEventListener("keydown", onKeyDown) return () => { window.removeEventListener("keydown", onKeyDown) } }, [bookmarks]) return (
{bookmarks.length === 0 ? (

You have not saved any bookmark!

) : ( bookmarks.map((bookmark, i) => ) )}
) } function BookmarkPreview() { const isVisible = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened) const layoutMode = useBookmarkPageStore((state) => state.layoutMode) if (!isVisible) { return null } return (

Content here

) } function BookmarkListItem({ bookmark, index }: { bookmark: LinkBookmark; index: number }) { const url = new URL(bookmark.url) const selectedBookmarkIndex = useBookmarkPageStore((state) => state.selectedBookmarkIndex) const isBookmarkItemExpanded = useBookmarkPageStore((state) => state.isBookmarkItemExpanded) const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog) const setBookmarkItemExpanded = useBookmarkPageStore((state) => state.setBookmarkItemExpanded) const selectBookmark = useBookmarkPageStore((state) => state.selectBookmark) const setBookmarkPreviewOpened = useBookmarkPageStore((state) => state.setBookmarkPreviewOpened) const markBookmarkForDeletion = useBookmarkPageStore((state) => state.markBookmarkForDeletion) const isSelected = selectedBookmarkIndex === index useMnemonics( { d: deleteItem, }, { active: isSelected }, ) function deleteItem() { if (isSelected) { markBookmarkForDeletion(bookmark) setActiveDialog(ActiveDialog.DeleteBookmark) } } function expandOrOpenPreview() { setBookmarkItemExpanded(true) if (useBookmarkPageStore.getState().layoutMode === LAYOUT_MODE.sideBySide) { console.log(useBookmarkPageStore.getState().layoutMode) setBookmarkPreviewOpened(true) } } return (
{ if (!isBookmarkItemExpanded) { selectBookmark(bookmark, index) } }} >

{url.host}

{isBookmarkItemExpanded && isSelected ? (

#dev #devops #devops #devops #devops #devops #devops

 
) : null}
) } function OpenBookmarkPreviewButton() { const isBookmarkPreviewOpened = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened) const setBookmarkPreviewOpened = useBookmarkPageStore((state) => state.setBookmarkPreviewOpened) const setBookmarkItemExpanded = useBookmarkPageStore((state) => state.setBookmarkItemExpanded) useEffect(() => { function onKeyDown(event: KeyboardEvent) { if (isBookmarkPreviewOpened && event.key === "c") { closePreview() } else if (!isBookmarkPreviewOpened && event.key === "o") { openPreview() } } window.addEventListener("keydown", onKeyDown) return () => { window.removeEventListener("keydown", onKeyDown) } }, [isBookmarkPreviewOpened]) function closePreview() { setBookmarkPreviewOpened(false) setBookmarkItemExpanded(false) } function openPreview() { setBookmarkPreviewOpened(true) } return ( ) } function ActionBar() { const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog) useMnemonics( { a: addBookmark, }, { active: true }, ) function addBookmark() { setActiveDialog(ActiveDialog.AddBookmark) } return (
) } function LogOutButton() { const logOutMutation = useLogOut() const navigate = useNavigate() function logOut() { logOutMutation.mutate() navigate({ to: "/", replace: true }) } return ( ) } export const Route = createFileRoute("/bookmarks")({ component: Page, })