"use client";
import { useEffect, useMemo } from "react";
import Link from "next/link";
import BookmarkFormattedCreatedAt from "@/components/dashboard/bookmarks/BookmarkFormattedCreatedAt";
import { BookmarkMarkdownComponent } from "@/components/dashboard/bookmarks/BookmarkMarkdownComponent";
import FooterLinkURL from "@/components/dashboard/bookmarks/FooterLinkURL";
import { ActionButton } from "@/components/ui/action-button";
import { badgeVariants } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { api } from "@/lib/trpc";
import { cn } from "@/lib/utils";
import tailwindConfig from "@/tailwind.config";
import { Expand, FileIcon, ImageIcon } from "lucide-react";
import { useInView } from "react-intersection-observer";
import Masonry from "react-masonry-css";
import resolveConfig from "tailwindcss/resolveConfig";
import {
BookmarkTypes,
ZPublicBookmark,
} from "@karakeep/shared/types/bookmarks";
import { ZCursor } from "@karakeep/shared/types/pagination";
function TagPill({ tag }: { tag: string }) {
return (
{tag}
);
}
function BookmarkCard({ bookmark }: { bookmark: ZPublicBookmark }) {
const renderContent = () => {
switch (bookmark.content.type) {
case BookmarkTypes.LINK:
return (
{bookmark.bannerImageUrl && (
{/* oxlint-disable-next-line no-img-element */}
)}
{bookmark.title}
);
case BookmarkTypes.TEXT:
return (
{bookmark.title && (
{bookmark.title}
)}
{{
id: bookmark.id,
content: {
text: bookmark.content.text,
},
}}
);
case BookmarkTypes.ASSET:
return (
{bookmark.bannerImageUrl ? (
{/* oxlint-disable-next-line no-img-element */}
) : (
{bookmark.content.assetType === "image" ? (
) : (
)}
)}
{bookmark.title}
);
}
};
return (
{renderContent()}
{/* Tags */}
{bookmark.tags.length > 0 && (
{bookmark.tags.map((tag, index) => (
))}
)}
{/* Footer */}
{bookmark.content.type === BookmarkTypes.LINK && (
<>
•
>
)}
);
}
function getBreakpointConfig() {
const fullConfig = resolveConfig(tailwindConfig);
const breakpointColumnsObj: { [key: number]: number; default: number } = {
default: 3,
};
breakpointColumnsObj[parseInt(fullConfig.theme.screens.lg)] = 2;
breakpointColumnsObj[parseInt(fullConfig.theme.screens.md)] = 1;
breakpointColumnsObj[parseInt(fullConfig.theme.screens.sm)] = 1;
return breakpointColumnsObj;
}
export default function PublicBookmarkGrid({
bookmarks: initialBookmarks,
nextCursor,
list,
}: {
list: {
id: string;
name: string;
description: string | null | undefined;
icon: string;
numItems: number;
ownerName: string;
};
bookmarks: ZPublicBookmark[];
nextCursor: ZCursor | null;
}) {
const { ref: loadMoreRef, inView: loadMoreButtonInView } = useInView();
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
api.publicBookmarks.getPublicBookmarksInList.useInfiniteQuery(
{ listId: list.id },
{
initialData: () => ({
pages: [{ bookmarks: initialBookmarks, nextCursor, list }],
pageParams: [null],
}),
initialCursor: null,
getNextPageParam: (lastPage) => lastPage.nextCursor,
refetchOnMount: true,
},
);
useEffect(() => {
if (loadMoreButtonInView && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}, [loadMoreButtonInView]);
const breakpointConfig = useMemo(() => getBreakpointConfig(), []);
const bookmarks = useMemo(() => {
return data.pages.flatMap((b) => b.bookmarks);
}, [data]);
return (
<>
{bookmarks.map((bookmark) => (
))}
{hasNextPage && (
fetchNextPage()}
variant="ghost"
>
Load More
)}
>
);
}