This commit is contained in:
@@ -17,8 +17,8 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { TrackedLink } from "./util-tracked-link";
|
|
||||||
import { TrackedButton } from "./util-tracked-button";
|
import { TrackedButton } from "./util-tracked-button";
|
||||||
|
import { TrackedLink } from "./util-tracked-link";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
bibtexKey: string;
|
bibtexKey: string;
|
||||||
@@ -49,24 +49,9 @@ export function PublicationCard({
|
|||||||
"idle"
|
"idle"
|
||||||
);
|
);
|
||||||
const [imageError, setImageError] = useState(false);
|
const [imageError, setImageError] = useState(false);
|
||||||
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
|
|
||||||
const [isClient, setIsClient] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const handleCopy = () => {
|
||||||
setIsClient(true);
|
if (navigator.clipboard?.writeText) {
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (downloadUrl) {
|
|
||||||
URL.revokeObjectURL(downloadUrl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [downloadUrl]);
|
|
||||||
|
|
||||||
const handleCopy = (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (isClient && navigator.clipboard?.writeText) {
|
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(bibtex)
|
.writeText(bibtex)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -84,17 +69,16 @@ export function PublicationCard({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = (e: React.MouseEvent) => {
|
const handleDownload = () => {
|
||||||
e.stopPropagation();
|
|
||||||
const blob = new Blob([bibtex], { type: "text/plain" });
|
const blob = new Blob([bibtex], { type: "text/plain" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
setDownloadUrl(url);
|
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = `${bibtexKey}.bib`;
|
a.download = `${bibtexKey}.bib`;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url); // Clean up immediately
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCardClick = () => {
|
const handleCardClick = () => {
|
||||||
@@ -133,94 +117,93 @@ export function PublicationCard({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"cards flex items-start space-x-4 rounded-lg border p-4",
|
"cards flex items-start space-x-4 rounded-lg border p-4",
|
||||||
url,
|
url,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
id={bibtexKey}
|
id={bibtexKey}
|
||||||
onClick={handleCardClick}
|
onClick={handleCardClick}
|
||||||
>
|
>
|
||||||
<div className="pointer-events-none relative h-24 w-24 flex-shrink-0">
|
<div className="pointer-events-none relative h-24 w-24 flex-shrink-0">
|
||||||
{ImageContent}
|
{ImageContent}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pointer-events-none flex-grow space-y-1">
|
<div className="pointer-events-none flex-grow space-y-1">
|
||||||
<CardTitle className="text-base font-medium leading-snug">
|
<CardTitle className="text-base font-medium leading-snug">
|
||||||
<span className={cn(url && "hover:underline")}>{title}</span>
|
<span className={cn(url && "hover:underline")}>{title}</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{formattedAuthors}. <em>{journal}</em>. {year}
|
{formattedAuthors}. <em>{journal}</em>. {year}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="z-10 flex flex-shrink-0 flex-col space-y-1">
|
<div className="z-10 flex flex-shrink-0 flex-col space-y-1">
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<TrackedButton
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-7 w-7 cursor-pointer px-2"
|
|
||||||
onClick={handleDownload}
|
|
||||||
eventName={`${bibtexKey}-bibfile`}
|
|
||||||
>
|
|
||||||
<FileText className="h-4 w-4" />
|
|
||||||
</TrackedButton>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>Download BibTeX</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<TrackedButton
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="relative h-7 w-7 cursor-pointer px-2"
|
|
||||||
onClick={handleCopy}
|
|
||||||
eventName={`${bibtexKey}-copycite`}
|
|
||||||
>
|
|
||||||
{copyStatus === "idle" && <Copy className="h-4 w-4" />}
|
|
||||||
{copyStatus === "success" && (
|
|
||||||
<Check className="h-4 w-4 text-green-500" />
|
|
||||||
)}
|
|
||||||
{copyStatus === "error" && <X className="h-4 w-4 text-red-500" />}
|
|
||||||
</TrackedButton>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>Copy Citation</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{pdfUrl && pdfAvailable && (
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<TrackedButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-7 w-7 px-2"
|
className="h-7 w-7 cursor-pointer px-2"
|
||||||
asChild
|
onClick={handleDownload}
|
||||||
onClick={(e) => e.stopPropagation()}
|
eventName={`${bibtexKey}-bibfile`}
|
||||||
>
|
>
|
||||||
<TrackedLink
|
<FileText className="h-4 w-4" />
|
||||||
href={pdfUrl}
|
</TrackedButton>
|
||||||
target="_blank"
|
|
||||||
eventName={`${bibtexKey}-pdf`}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<BookOpen className="h-4 w-4" />
|
|
||||||
</TrackedLink>
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>Open PDF</p>
|
<p>Download BibTeX</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<TrackedButton
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="relative h-7 w-7 cursor-pointer px-2"
|
||||||
|
onClick={handleCopy}
|
||||||
|
eventName={`${bibtexKey}-copycite`}
|
||||||
|
>
|
||||||
|
{copyStatus === "idle" && <Copy className="h-4 w-4" />}
|
||||||
|
{copyStatus === "success" && (
|
||||||
|
<Check className="h-4 w-4 text-green-500" />
|
||||||
|
)}
|
||||||
|
{copyStatus === "error" && <X className="h-4 w-4 text-red-500" />}
|
||||||
|
</TrackedButton>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Copy Citation</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{pdfUrl && pdfAvailable && (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 w-7 px-2"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<TrackedLink
|
||||||
|
href={pdfUrl}
|
||||||
|
target="_blank"
|
||||||
|
eventName={`${bibtexKey}-pdf`}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<BookOpen className="h-4 w-4" />
|
||||||
|
</TrackedLink>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Open PDF</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
@@ -18,6 +18,9 @@ export const TrackedButton = ({
|
|||||||
const handleTrackedClick = (
|
const handleTrackedClick = (
|
||||||
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
if (window.umami) {
|
if (window.umami) {
|
||||||
window.umami.track(eventName);
|
window.umami.track(eventName);
|
||||||
}
|
}
|
||||||
|
|||||||
11
types/umami.d.ts
vendored
Normal file
11
types/umami.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// types/umami.d.ts
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
umami?: {
|
||||||
|
track: (event_name: string, data?: object) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
Reference in New Issue
Block a user