102 lines
3.1 KiB
TypeScript
102 lines
3.1 KiB
TypeScript
import { Link, useNavigate } from "react-router";
|
|
import type { Dump } from "../model.ts";
|
|
import { relativeTime } from "../utils/relativeTime.ts";
|
|
import { isDumpVisited, isRecent, markDumpVisited } from "../utils/visited.ts";
|
|
import FilePreview from "./FilePreview.tsx";
|
|
import RichContentCard from "./RichContentCard.tsx";
|
|
import { VoteButton } from "./VoteButton.tsx";
|
|
import { Markdown } from "./Markdown.tsx";
|
|
|
|
interface DumpCardProps {
|
|
dump: Dump;
|
|
voteCount: number;
|
|
voted: boolean;
|
|
canVote: boolean;
|
|
castVote: (id: string) => void;
|
|
removeVote: (id: string) => void;
|
|
className?: string;
|
|
isOwner?: boolean;
|
|
}
|
|
|
|
export function DumpCard(
|
|
{ dump, voteCount, voted, canVote, castVote, removeVote, className, isOwner }:
|
|
DumpCardProps,
|
|
) {
|
|
const navigate = useNavigate();
|
|
const unread = !isOwner && isRecent(dump.createdAt) &&
|
|
!isDumpVisited(dump.id);
|
|
|
|
function handleNavigate() {
|
|
markDumpVisited(dump.id);
|
|
navigate(`/dumps/${dump.id}`);
|
|
}
|
|
|
|
return (
|
|
<li className={`dump-card${className ? ` ${className}` : ""}`}>
|
|
<div
|
|
className="dump-card-inner"
|
|
onClick={handleNavigate}
|
|
>
|
|
<div
|
|
className="dump-card-preview"
|
|
onClick={dump.richContent ? (e) => e.stopPropagation() : undefined}
|
|
>
|
|
{dump.kind === "file"
|
|
? <FilePreview dump={dump} compact />
|
|
: dump.richContent
|
|
? <RichContentCard richContent={dump.richContent} compact />
|
|
: <span className="dump-card-preview-icon">🔗</span>}
|
|
</div>
|
|
|
|
<div className="dump-card-body">
|
|
<Link
|
|
to={`/dumps/${dump.id}`}
|
|
className="dump-card-title"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
markDumpVisited(dump.id);
|
|
}}
|
|
>
|
|
{unread && <span className="unread-dot" aria-hidden="true" />}
|
|
{dump.title}
|
|
</Link>
|
|
{dump.comment && (
|
|
<Markdown className="dump-card-comment" inline>
|
|
{dump.comment}
|
|
</Markdown>
|
|
)}
|
|
<div className="dump-card-meta">
|
|
<time
|
|
className="dump-card-date"
|
|
dateTime={dump.createdAt.toISOString()}
|
|
title={dump.createdAt.toLocaleString()}
|
|
>
|
|
{relativeTime(dump.createdAt)}
|
|
</time>
|
|
{dump.commentCount > 0 && (
|
|
<span className="dump-card-comment-count">
|
|
{dump.commentCount}{" "}
|
|
{dump.commentCount === 1 ? "comment" : "comments"}
|
|
</span>
|
|
)}
|
|
{dump.isPrivate && isOwner && (
|
|
<span className="dump-card-private-badge">private</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="dump-card-vote" onClick={(e) => e.stopPropagation()}>
|
|
<VoteButton
|
|
dumpId={dump.id}
|
|
count={voteCount}
|
|
voted={voted}
|
|
disabled={!canVote}
|
|
onCast={castVote}
|
|
onRemove={removeVote}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
);
|
|
}
|