import {
ActivityIndicator,
Image,
Platform,
Pressable,
ScrollView,
Text,
View,
} from "react-native";
import Markdown from "react-native-markdown-display";
import * as Haptics from "expo-haptics";
import { Link } from "expo-router";
import * as WebBrowser from "expo-web-browser";
import useAppSettings from "@/lib/settings";
import { api } from "@/lib/trpc";
import { MenuView } from "@react-native-menu/menu";
import { Ellipsis, Star } from "lucide-react-native";
import type { ZBookmark } from "@hoarder/trpc/types/bookmarks";
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 onError = () => {
toast({
message: "Something went wrong",
variant: "destructive",
showProgress: false,
});
};
const { mutate: deleteBookmark, isPending: isDeletionPending } =
api.bookmarks.deleteBookmark.useMutation({
onSuccess: () => {
toast({
message: "The bookmark has been deleted!",
showProgress: false,
});
apiUtils.bookmarks.getBookmarks.invalidate();
apiUtils.bookmarks.searchBookmarks.invalidate();
},
onError,
});
const { mutate: favouriteBookmark, variables } =
api.bookmarks.updateBookmark.useMutation({
onSuccess: () => {
apiUtils.bookmarks.getBookmarks.invalidate();
apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: bookmark.id });
},
onError,
});
const { mutate: archiveBookmark, isPending: isArchivePending } =
api.bookmarks.updateBookmark.useMutation({
onSuccess: (resp) => {
toast({
message: `The bookmark has been ${resp.archived ? "archived" : "un-archived"}!`,
showProgress: false,
});
apiUtils.bookmarks.getBookmarks.invalidate();
apiUtils.bookmarks.getBookmark.invalidate({ bookmarkId: bookmark.id });
apiUtils.bookmarks.searchBookmarks.invalidate();
},
onError,
});
return (
{(isArchivePending || isDeletionPending) && }
{
Haptics.selectionAsync();
favouriteBookmark({
bookmarkId: bookmark.id,
favourited: !bookmark.favourited,
});
}}
>
{(variables ? variables.favourited : bookmark.favourited) ? (
) : (
)}
{
Haptics.selectionAsync();
if (nativeEvent.event === "delete") {
deleteBookmark({
bookmarkId: bookmark.id,
});
} else if (nativeEvent.event === "archive") {
archiveBookmark({
bookmarkId: bookmark.id,
archived: !bookmark.archived,
});
}
}}
actions={[
{
id: "archive",
title: bookmark.archived ? "Un-archive" : "Archive",
image: Platform.select({
ios: "folder",
}),
},
{
id: "delete",
title: "Delete",
attributes: {
destructive: true,
},
image: Platform.select({
ios: "trash",
}),
},
]}
shouldOpenOnLongPress={false}
>
Haptics.selectionAsync()} color="gray" />
);
}
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}
);
}
function AssetCard({ bookmark }: { bookmark: ZBookmark }) {
const { settings } = useAppSettings();
if (bookmark.content.type !== "asset") {
throw new Error("Wrong content type rendered");
}
return (
);
}
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;
case "asset":
comp = ;
break;
}
return {comp};
}