import { Image, Pressable, ScrollView, Text, View } from "react-native"; import Markdown from "react-native-markdown-display"; import * as WebBrowser from "expo-web-browser"; import { api } from "@/lib/trpc"; import { Archive, ArchiveRestore, Star, Trash } from "lucide-react-native"; import type { ZBookmark } from "@hoarder/trpc/types/bookmarks"; import { ActionButton } from "../ui/ActionButton"; import { Divider } from "../ui/Divider"; import { Skeleton } from "../ui/Skeleton"; import { useToast } from "../ui/Toast"; const MAX_LOADING_MSEC = 30 * 1000; export function isBookmarkStillCrawling(bookmark: ZBookmark) { return ( bookmark.content.type === "link" && !bookmark.content.crawledAt && Date.now().valueOf() - bookmark.createdAt.valueOf() < MAX_LOADING_MSEC ); } export function isBookmarkStillTagging(bookmark: ZBookmark) { return ( bookmark.taggingStatus === "pending" && Date.now().valueOf() - bookmark.createdAt.valueOf() < MAX_LOADING_MSEC ); } export function isBookmarkStillLoading(bookmark: ZBookmark) { return isBookmarkStillTagging(bookmark) || isBookmarkStillCrawling(bookmark); } function ActionBar({ bookmark }: { bookmark: ZBookmark }) { const { toast } = useToast(); const apiUtils = api.useUtils(); const { mutate: deleteBookmark, isPending: isDeletionPending } = api.bookmarks.deleteBookmark.useMutation({ onSuccess: () => { apiUtils.bookmarks.getBookmarks.invalidate(); }, onError: () => { toast({ message: "Something went wrong", variant: "destructive", showProgress: false, }); }, }); const { mutate: updateBookmark, variables, isPending: isUpdatePending, } = api.bookmarks.updateBookmark.useMutation({ onSuccess: () => { apiUtils.bookmarks.getBookmarks.invalidate(); apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: bookmark.id }); }, onError: () => { toast({ message: "Something went wrong", variant: "destructive", showProgress: false, }); }, }); return ( updateBookmark({ bookmarkId: bookmark.id, favourited: !bookmark.favourited, }) } > {(variables ? variables.favourited : bookmark.favourited) ? ( ) : ( )} updateBookmark({ bookmarkId: bookmark.id, archived: !bookmark.archived, }) } > {bookmark.archived ? ( ) : ( )} deleteBookmark({ bookmarkId: bookmark.id, }) } > ); } function TagList({ bookmark }: { bookmark: ZBookmark }) { const tags = bookmark.tags; if (isBookmarkStillTagging(bookmark)) { return ( <> ); } return ( {tags.map((t) => ( {t.name} ))} ); } function LinkCard({ bookmark }: { bookmark: ZBookmark }) { if (bookmark.content.type !== "link") { throw new Error("Wrong content type rendered"); } const url = bookmark.content.url; const parsedUrl = new URL(url); const imageComp = bookmark.content.imageUrl ? ( ) : ( ); return ( {imageComp} WebBrowser.openBrowserAsync(url)} > {bookmark.content.title ?? parsedUrl.host} {parsedUrl.host} ); } function TextCard({ bookmark }: { bookmark: ZBookmark }) { if (bookmark.content.type !== "text") { throw new Error("Wrong content type rendered"); } return ( {bookmark.content.text} ); } export default function BookmarkCard({ bookmark: initialData, }: { bookmark: ZBookmark; }) { const { data: bookmark } = api.bookmarks.getBookmark.useQuery( { bookmarkId: initialData.id, }, { initialData, refetchInterval: (query) => { const data = query.state.data; if (!data) { return false; } // If the link is not crawled or not tagged if (isBookmarkStillLoading(data)) { return 1000; } return false; }, }, ); let comp; switch (bookmark.content.type) { case "link": comp = ; break; case "text": comp = ; break; } return ( {comp} ); }