import { useState } from "react"; import { Link } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Quote, MessageSquare, Bookmark, ExternalLink, Link2, Check, } from "lucide-react"; import { type RRNote, type RRPalette, textFragmentUrl, highlightBg, highlightInk, readingRoomNoteUrl, } from "./theme"; function ExternalBadges({ noteId, pal, t, }: { noteId: string; pal: RRPalette; t: (key: string) => string; }) { const isSemble = noteId.includes("network.cosmik") || noteId.includes("semble"); const isLichen = noteId.includes("wiki.lichen.bookmark"); const isExternalBookmark = noteId.includes( "community.lexicon.bookmarks.bookmark", ); const tooltipBg = pal.muted; const tooltipStyle: React.CSSProperties = { position: "absolute", bottom: "100%", left: "50%", transform: "translateX(-50%)", marginBottom: 8, padding: "4px 10px", borderRadius: 8, backgroundColor: tooltipBg, color: pal.card, fontSize: 11, fontWeight: 500, whiteSpace: "nowrap", pointerEvents: "none", boxShadow: "0 4px 12px rgba(0,0,0,0.15)", }; const arrowStyle: React.CSSProperties = { position: "absolute", top: "100%", left: "50%", transform: "translateX(-50%)", marginTop: -1, border: "4px solid transparent", borderTopColor: tooltipBg, }; let sembleUrl = ""; if (isSemble) { const parts = noteId.replace("at://", "").split("/"); const userHandle = parts[0] || ""; const rkey = parts[2] || ""; sembleUrl = `https://semble.so/profile/${userHandle}`; if (noteId.includes("network.cosmik.collection")) sembleUrl = `https://semble.so/profile/${userHandle}/collections/${rkey}`; } return ( <> {isSemble && ( · Semble {t("card.openInSemble")} )} {isLichen && ( · Lichen {t("card.lichenBookmark")} )} {isExternalBookmark && ( · {t("card.externalBookmark")} )} ); } export default function ReadingRoomCard({ note, pal, featured, variant, activeTag, onTagClick, roomHandle, linkable = true, }: { note: RRNote; pal: RRPalette; featured: boolean; variant: "card" | "row"; activeTag: string | null; onTagClick: (tag: string) => void; roomHandle: string; /** Set false on the note's own permalink page, where linking to itself is noise. */ linkable?: boolean; }) { const { t } = useTranslation(); const [copied, setCopied] = useState(false); const isHighlight = note.motivation === "highlighting"; const isBookmark = note.motivation === "bookmarking"; const kind = isHighlight ? "highlight" : isBookmark ? "bookmark" : "note"; const targetTitle = note.target?.title || ""; const targetSource = note.target?.source || ""; const bodyValue = note.body?.value || ""; const quoteText = note.target?.selector?.exact || ""; const tags = note.tags || []; let domain = ""; if (targetSource) { try { domain = new URL(targetSource).hostname.replace(/^www\./, ""); } catch { /* ignore unparseable source */ } } const cleanUrl = targetSource .replace(/^https?:\/\//, "") .replace(/^www\./, "") .replace(/\/$/, ""); const displayTitle = targetTitle || cleanUrl || domain; const onCustomDomain = typeof window !== "undefined" && !!(window as unknown as { __READING_ROOM_HANDLE__?: string }) .__READING_ROOM_HANDLE__; const notePath = onCustomDomain ? `/note?uri=${encodeURIComponent(note.id)}` : readingRoomNoteUrl(roomHandle, note.id); const noteUrl = linkable && note.id ? notePath : ""; const handleCopyLink = async () => { if (!note.id) return; const url = `${window.location.origin}${notePath}`; try { await navigator.clipboard.writeText(url); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { /* clipboard unavailable, e.g. insecure context */ } }; const fragUrl = textFragmentUrl(targetSource, note.target?.selector); const passageHref = fragUrl || targetSource || undefined; const hlBg = highlightBg(note.color, pal.bg); const hlInk = highlightInk(pal.bg); const Icon = isHighlight ? Quote : isBookmark ? Bookmark : MessageSquare; return (
{t(`readingRoom.type.${kind}`)} {note.id && } {featured && ( {t("readingRoom.featured")} )}
{displayTitle && (
{targetSource ? ( {displayTitle} ) : ( {displayTitle} )} {domain && targetTitle && ( {domain} )}
)} {isHighlight ? ( <> {quoteText && (passageHref ? ( {quoteText} ) : (

{quoteText}

))} {bodyValue && (

{bodyValue}

)} ) : ( <> {quoteText && (passageHref ? ( {quoteText} ) : null)} {bodyValue && (

{bodyValue}

)} )} {tags.length > 0 && (
{tags.slice(0, 6).map((tag, i) => { const active = activeTag === tag; return ( ); })}
)}
{note.created && (noteUrl ? ( {new Date(note.created).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", })} ) : ( {new Date(note.created).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", })} ))} {note.id && ( )}
); }