feat: support sending and preview links

This commit is contained in:
talksik
2026-03-21 12:46:23 -07:00
parent 6bf2f2217a
commit d6b7ecc8fd
8 changed files with 431 additions and 60 deletions
+104
View File
@@ -0,0 +1,104 @@
import { Copy, ExternalLink, Globe } from "lucide-react";
import type { LinkMetadata } from "@/lib/link-metadata";
import { Skeleton } from "@/components/ui/skeleton";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
interface LinkPreviewCardProps {
metadata: LinkMetadata;
compact?: boolean;
}
export function LinkPreviewCard({ metadata, compact }: LinkPreviewCardProps) {
const handleOpen = (e: React.MouseEvent) => {
e.stopPropagation();
window.electronLink.openExternal(metadata.url);
};
const handleCopy = (e: React.MouseEvent) => {
e.stopPropagation();
navigator.clipboard.writeText(metadata.url);
};
return (
<div
className="max-w-sm overflow-hidden rounded-2xl bg-white/10 backdrop-blur-md"
onClick={(e) => e.stopPropagation()}
>
{metadata.image && (
<img
src={metadata.image}
alt=""
className="h-32 w-full object-cover"
onError={(e) => {
(e.target as HTMLImageElement).style.display = "none";
}}
/>
)}
<div className="flex flex-col gap-1 p-3">
<div className="flex items-center gap-1.5 text-xs text-white/50">
{metadata.favicon ? (
<img
src={metadata.favicon}
alt=""
className="size-4 rounded-sm"
onError={(e) => {
(e.target as HTMLImageElement).replaceWith(
document.createElement("span"),
);
}}
/>
) : (
<Globe className="size-4" />
)}
<span className="truncate">{metadata.domain}</span>
</div>
{metadata.title && (
<p className="truncate text-sm font-semibold leading-snug text-white">
{metadata.title}
</p>
)}
{metadata.description && (
<p className="line-clamp-2 text-xs leading-relaxed text-white/70">
{metadata.description}
</p>
)}
{!compact && (
<div className="mt-1.5 flex gap-2">
<Button
variant="ghost"
size="xs"
className="text-white/70 hover:bg-white/10 hover:text-white"
onClick={handleOpen}
>
<ExternalLink data-icon="inline-start" />
Open
</Button>
<Button
variant="ghost"
size="xs"
className="text-white/70 hover:bg-white/10 hover:text-white"
onClick={handleCopy}
>
<Copy data-icon="inline-start" />
Copy
</Button>
</div>
)}
</div>
</div>
);
}
export function LinkPreviewCardSkeleton() {
return (
<div className="max-w-sm overflow-hidden rounded-2xl bg-white/10 backdrop-blur-md">
<Skeleton className="h-32 w-full rounded-none bg-white/5" />
<div className="flex flex-col gap-2 p-3">
<Skeleton className="h-3 w-24 bg-white/10" />
<Skeleton className="h-4 w-48 bg-white/10" />
<Skeleton className="h-3 w-full bg-white/10" />
</div>
</div>
);
}