wip: implement bookmark preview

This commit is contained in:
2025-05-08 15:51:17 +01:00
parent 77cb38294c
commit 9b47b806d5
10 changed files with 164 additions and 58 deletions

View File

@@ -1,7 +1,7 @@
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 { useCallback, useEffect, useId, useRef, useState } from "react"
import { create } from "zustand"
import { fetchApi, useAuthenticatedQuery, BadRequestError, ApiErrorCode } from "~/api"
import { useCreateBookmark, useDeleteBookmark } from "~/bookmark/api"
@@ -131,8 +131,8 @@ function Page() {
<BookmarkListSection />
</div>
<BookmarkPreview />
<ActionBar />
</Main>
<ActionBar />
<PageDialog />
</div>
)
@@ -178,7 +178,7 @@ function DeleteBookmarkDialog() {
y: proceed,
n: cancel,
},
{ active: true },
{ ignore: () => false },
)
async function proceed() {
@@ -207,10 +207,14 @@ function DeleteBookmarkDialog() {
case "idle":
return (
<p>
The bookmark titled{" "}
The bookmark titled:
<br />
<br />
<strong>
<em>"{bookmark.title}"</em>
</strong>{" "}
</strong>
<br />
<br />
will be deleted. Proceed?
</p>
)
@@ -251,14 +255,30 @@ function AddBookmarkDialog() {
const createBookmarkMutation = useCreateBookmark()
const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog)
const formId = useId()
const linkInputRef = useRef<HTMLInputElement | null>(null)
useMnemonics(
{
c: cancel,
c: () => {
if (linkInputRef.current !== document.activeElement) {
cancel()
}
},
Escape: () => {
linkInputRef.current?.blur()
},
},
{ active: true },
{ ignore: () => false },
)
useEffect(() => {
setTimeout(() => {
if (linkInputRef.current) {
linkInputRef.current.focus()
}
}, 0)
}, [])
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
@@ -267,9 +287,12 @@ function AddBookmarkDialog() {
if (url && typeof url === "string") {
try {
await createBookmarkMutation.mutateAsync({ url, force: isWebsiteUnreachable })
setActiveDialog(ActiveDialog.None)
} catch (error) {
if (error instanceof BadRequestError && error.code === ApiErrorCode.WebsiteUnreachable) {
setIsWebsiteUnreachable(true)
} else {
setIsWebsiteUnreachable(false)
}
}
}
@@ -311,6 +334,7 @@ function AddBookmarkDialog() {
{message()}
<form id={formId} className="px-8" onSubmit={onSubmit}>
<FormField
ref={linkInputRef}
type="text"
name="link"
label="LINK"
@@ -426,11 +450,36 @@ function BookmarkPreview() {
},
)}
>
<p>Content here</p>
<BookmarkPreviewFrame />
</div>
)
}
function BookmarkPreviewFrame() {
const selectedBookmarkId = useBookmarkPageStore((state) => state.selectedBookmarkId)
const { data, status } = useAuthenticatedQuery(["bookmarks", selectedBookmarkId], () =>
fetchApi(`/bookmark/${selectedBookmarkId}`, {
headers: {
Accept: "text/html",
},
}).then((res) => res.text()),
)
switch (status) {
case "pending":
return (
<p>
Loading <LoadingSpinner />
</p>
)
case "success":
return <iframe className="bg-stone-200 dark:bg-stone-900 w-full h-full pb-20" srcDoc={data} />
default:
return null
}
}
function BookmarkListItem({ bookmark, index }: { bookmark: LinkBookmark; index: number }) {
const url = new URL(bookmark.url)
const selectedBookmarkIndex = useBookmarkPageStore((state) => state.selectedBookmarkIndex)
@@ -446,7 +495,12 @@ function BookmarkListItem({ bookmark, index }: { bookmark: LinkBookmark; index:
{
d: deleteItem,
},
{ active: isSelected },
{
ignore: useCallback(
() => !isSelected || useBookmarkPageStore.getState().activeDialog !== ActiveDialog.None,
[isSelected],
),
},
)
function deleteItem() {
@@ -499,7 +553,7 @@ function BookmarkListItem({ bookmark, index }: { bookmark: LinkBookmark; index:
<p className="opacity-80 text-sm">{url.host}</p>
{isBookmarkItemExpanded && isSelected ? (
<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 #devops #devops #devops #devops #devops #devops</p>
<p className="text-sm">#dev</p>
<div className="flex space-x-2">
<OpenBookmarkPreviewButton />
<Button variant="light" className="text-sm">
@@ -579,7 +633,7 @@ function ActionBar() {
{
a: addBookmark,
},
{ active: true },
{ ignore: useCallback(() => useBookmarkPageStore.getState().activeDialog !== ActiveDialog.None, []) },
)
function addBookmark() {
@@ -587,7 +641,7 @@ function ActionBar() {
}
return (
<div className="fixed z-10 bottom-0 left-0 right-0 border-t-1 flex flex-row justify-center py-4 space-x-4">
<div className="fixed z-10 bottom-0 left-0 right-0 bg-stone-200 dark:bg-stone-900 border-t-1 flex flex-row justify-center py-4 space-x-4">
<Button onClick={addBookmark}>
<span className="underline">A</span>DD
</Button>