feat: copy caption in post actions menu
This commit is contained in:
+1
-1
@@ -26,7 +26,7 @@ export default function HomePage() {
|
||||
const authed = !loading && !!user;
|
||||
|
||||
return (
|
||||
<main className="flex-1 max-w-2xl w-full mx-auto px-4 sm:px-6 py-16 sm:py-24">
|
||||
<main className="flex-1 max-w-2xl w-full mx-auto px-4 sm:px-6 py-16 sm:py-24 select-none">
|
||||
<div className="flex flex-col items-center text-center gap-5">
|
||||
<span
|
||||
aria-hidden
|
||||
|
||||
@@ -211,7 +211,7 @@ export default function ChantPage() {
|
||||
const pct = target > 0 ? Math.min(100, (count / target) * 100) : 0;
|
||||
|
||||
return (
|
||||
<main className="flex-1 w-full max-w-lg mx-auto px-4 sm:px-6 py-5 flex flex-col">
|
||||
<main className="flex-1 w-full max-w-lg mx-auto px-4 sm:px-6 py-5 flex flex-col select-none">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link
|
||||
href="/sadhna-streak"
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function SadhnaStreakPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex-1 w-full max-w-2xl mx-auto px-4 sm:px-6 py-5 flex flex-col gap-5">
|
||||
<main className="flex-1 w-full max-w-2xl mx-auto px-4 sm:px-6 py-5 flex flex-col gap-5 select-none">
|
||||
<header className="text-center space-y-0.5">
|
||||
<h1 className="font-(family-name:--font-title) text-2xl sm:text-3xl font-semibold text-foreground">
|
||||
Daily Sādhana
|
||||
|
||||
@@ -55,7 +55,7 @@ export function RewardCelebration({
|
||||
initial={{ scale: 0.85, opacity: 0, y: 16 }}
|
||||
animate={{ scale: 1, opacity: 1, y: 0 }}
|
||||
transition={{ type: "spring", stiffness: 220, damping: 20 }}
|
||||
className="relative z-10 w-full max-w-md max-h-[90vh] overflow-y-auto rounded-3xl border border-border bg-card p-6 text-center shadow-2xl"
|
||||
className="relative z-10 w-full max-w-md max-h-[90vh] overflow-y-auto rounded-3xl border border-border bg-card p-6 text-center shadow-2xl select-none"
|
||||
>
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-primary">
|
||||
A gift for your sādhana
|
||||
|
||||
@@ -124,7 +124,7 @@ function RewardDetail({
|
||||
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div className="relative z-10 w-full max-w-md max-h-[90vh] overflow-y-auto rounded-3xl border border-border bg-card p-6 text-center shadow-2xl">
|
||||
<div className="relative z-10 w-full max-w-md max-h-[90vh] overflow-y-auto rounded-3xl border border-border bg-card p-6 text-center shadow-2xl select-none">
|
||||
<div className="relative mx-auto mb-4 flex size-40 items-center justify-center">
|
||||
<div className="absolute inset-0 rounded-full bg-primary/10 blur-2xl" />
|
||||
<Image
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Ban, Flag, MoreVertical, Trash2 } from "lucide-react";
|
||||
import { Ban, Copy, Flag, MoreVertical, Trash2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Drawer,
|
||||
@@ -41,6 +41,8 @@ import { useAuthGate } from "@/components/auth/auth-gate";
|
||||
import { useMediaQuery } from "@/components/selection-toolbar/shared";
|
||||
import { ConfirmModal } from "@/components/ui/confirm-modal";
|
||||
import { ReportDialog, type ReportOption } from "@/components/social/report-dialog";
|
||||
import { createShortLink } from "@/lib/share";
|
||||
import { structuredReferenceForPost } from "@/lib/source-reference";
|
||||
|
||||
// Post report categories — mirrors the BFF allow-list (postReportCategories).
|
||||
const POST_REPORT_OPTIONS: ReportOption[] = [
|
||||
@@ -147,10 +149,45 @@ export function PostCardMenu({
|
||||
};
|
||||
}, [open, isMobile]);
|
||||
|
||||
// Copy the post's caption + source citation + a (short) link to the post —
|
||||
// the same payload the share sheet / footer produce. Available to every
|
||||
// viewer.
|
||||
async function copyCaption() {
|
||||
setOpen(false);
|
||||
try {
|
||||
const origin = window.location.origin;
|
||||
let url = "";
|
||||
if (authorHandle) {
|
||||
const longPath = `/u/${encodeURIComponent(authorHandle)}/posts#post-${post.id}`;
|
||||
const code = await createShortLink(longPath);
|
||||
url = code
|
||||
? new URL(`/share/${code}`, origin).toString()
|
||||
: new URL(longPath, origin).toString();
|
||||
}
|
||||
const ref = structuredReferenceForPost(post.source_url, post.source_title);
|
||||
const lines: string[] = [];
|
||||
if (post.caption) lines.push(`“${post.caption}”`, "");
|
||||
lines.push("Source:", ref.title);
|
||||
if (ref.detail) lines.push(ref.detail);
|
||||
if (url) lines.push(url);
|
||||
await navigator.clipboard.writeText(lines.join("\n"));
|
||||
toast.success("Caption copied");
|
||||
} catch {
|
||||
toast.error("Could not copy");
|
||||
}
|
||||
}
|
||||
|
||||
// Build the items list AFTER all hooks have run so a conditional
|
||||
// early-return doesn't break the rules-of-hooks contract on
|
||||
// re-renders where viewer state changes.
|
||||
const items: MenuItem[] = [];
|
||||
// Copy caption — first item for everyone (owner and non-owner alike).
|
||||
items.push({
|
||||
key: "copy",
|
||||
label: "Copy caption",
|
||||
icon: <Copy className="size-4" />,
|
||||
onClick: copyCaption,
|
||||
});
|
||||
if (isOwner) {
|
||||
if (onDelete) {
|
||||
items.push({
|
||||
|
||||
Reference in New Issue
Block a user