import { LinkIncludingShortenedCollectionAndTags } from "@linkwarden/types"; import useLocalSettingsStore from "@/store/localSettings"; import { ArchivedFormat, CollectionIncludingMembersAndLinkCount, } from "@linkwarden/types"; import { useEffect, useRef, useState } from "react"; import unescapeString from "@/lib/client/unescapeString"; import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection"; import Image from "next/image"; import { atLeastOneFormatAvailable, formatAvailable, } from "@linkwarden/lib/formatStats"; import useOnScreen from "@/hooks/useOnScreen"; import { useCollections } from "@linkwarden/router/collections"; import { useUser } from "@linkwarden/router/user"; import { useGetLink, useLinks } from "@linkwarden/router/links"; import { useRouter } from "next/router"; import openLink from "@/lib/client/openLink"; import LinkIcon from "./LinkViews/LinkComponents/LinkIcon"; import LinkFormats from "./LinkViews/LinkComponents/LinkFormats"; import LinkTypeBadge from "./LinkViews/LinkComponents/LinkTypeBadge"; import LinkPin from "./LinkViews/LinkComponents/LinkPin"; import { Separator } from "./ui/separator"; import { useDraggable } from "@dnd-kit/core"; import { cn } from "@linkwarden/lib"; import { useTranslation } from "next-i18next"; export function DashboardLinks({ links, isLoading, type, }: { links?: LinkIncludingShortenedCollectionAndTags[]; isLoading?: boolean; type?: "collection" | "recent"; }) { return (
{isLoading ? (
) : ( links?.map((e, i) => ) )}
); } type Props = { link: LinkIncludingShortenedCollectionAndTags; editMode?: boolean; dashboardType?: "collection" | "recent"; }; export function Card({ link, editMode, dashboardType }: Props) { const { t } = useTranslation(); const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id: `${link.id}-${dashboardType}`, data: { linkId: link.id, link, dashboardType, }, }); const { data: collections = [] } = useCollections(); const { data: user } = useUser(); const { settings: { show }, } = useLocalSettingsStore(); const { links } = useLinks(); const router = useRouter(); const isPublicRoute = router.pathname.startsWith("/public") ? true : false; const { refetch } = useGetLink({ id: link.id as number, isPublicRoute }); const [collection, setCollection] = useState( collections.find( (e) => e.id === link.collection.id ) as CollectionIncludingMembersAndLinkCount ); useEffect(() => { setCollection( collections.find( (e) => e.id === link.collection.id ) as CollectionIncludingMembersAndLinkCount ); }, [collections, links]); const ref = useRef(null); const isVisible = useOnScreen(ref); const [linkModal, setLinkModal] = useState(false); useEffect(() => { let interval: NodeJS.Timeout | null = null; if ( isVisible && !link.preview?.startsWith("archives") && link.preview !== "unavailable" ) { interval = setInterval(async () => { refetch().catch((error) => { console.error("Error refetching link:", error); }); }, 5000); } return () => { if (interval) { clearInterval(interval); } }; }, [isVisible, link.preview]); return (
!editMode && openLink(link, user, () => setLinkModal(true)) } {...listeners} {...attributes} > {show.image && (
{formatAvailable(link, "preview") ? ( { const target = e.target as HTMLElement; target.style.display = "none"; }} unoptimized /> ) : link.preview === "unavailable" ? (
) : (
)} {show.icon && (
)} {show.preserved_formats && link.type === "url" && atLeastOneFormatAvailable(link) && (
)}
)}
{show.name && (

{unescapeString(link.name)}

)} {show.link && }
{(show.collection || show.date) && (
{show.collection && !isPublicRoute && (
)} {show.date && }
)}
{/* Overlay on hover */}
setLinkModal(e)} className="absolute top-3 right-3 group-hover:opacity-100 group-focus-within:opacity-100 opacity-0 duration-100 text-neutral z-20" /> {!isPublicRoute && }
); }