From 8c94c85f2bdddb17bb46e57c9879ecfed18dc439 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 22:37:35 +0000 Subject: [PATCH] feat: add copy button for NIP markdown - Add copy button to WindowToolbar for regular NIPs (appId: "nip") - Button appears in window toolbar next to edit button - Uses Copy/CopyCheck icons from lucide-react - Fetches NIP content via useNip hook - Shows toast notification on successful copy - Add copy button to CommunityNIPDetailRenderer for community NIPs (kind 30817) - Button appears in header next to title - Copies event.content (markdown) to clipboard - Uses same Copy/CopyCheck icon pattern - Shows toast notification on successful copy Both implementations use the existing useCopy hook for state management and maintain consistent styling with other toolbar buttons. --- src/components/WindowToolbar.tsx | 37 ++++++++++++++++++- .../kinds/CommunityNIPDetailRenderer.tsx | 28 +++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/components/WindowToolbar.tsx b/src/components/WindowToolbar.tsx index aee30ac..43779b4 100644 --- a/src/components/WindowToolbar.tsx +++ b/src/components/WindowToolbar.tsx @@ -1,4 +1,4 @@ -import { X, Pencil, MoreVertical, WandSparkles } from "lucide-react"; +import { X, Pencil, MoreVertical, WandSparkles, Copy, CopyCheck } from "lucide-react"; import { useSetAtom } from "jotai"; import { useState } from "react"; import { WindowInstance } from "@/types/app"; @@ -13,6 +13,8 @@ import { import { SpellDialog } from "@/components/nostr/SpellDialog"; import { reconstructCommand as reconstructReqCommand } from "@/lib/spell-conversion"; import { toast } from "sonner"; +import { useCopy } from "@/hooks/useCopy"; +import { useNip } from "@/hooks/useNip"; interface WindowToolbarProps { window?: WindowInstance; @@ -58,6 +60,22 @@ export function WindowToolbar({ setShowSpellDialog(true); }; + // Copy functionality for NIPs + const { copy, copied } = useCopy(); + const isNipWindow = window?.appId === "nip"; + + // Fetch NIP content for regular NIPs + const { content: nipContent } = useNip( + isNipWindow && window?.props?.number ? window.props.number : "" + ); + + const handleCopyNip = () => { + if (!window || !nipContent) return; + + copy(nipContent); + toast.success("NIP markdown copied to clipboard"); + }; + // Check if this is a REQ window for spell creation const isReqWindow = window?.appId === "req"; @@ -88,6 +106,23 @@ export function WindowToolbar({ + {/* Copy button for NIPs */} + {isNipWindow && ( + + )} + {/* More actions menu - only for REQ windows for now */} {isReqWindow && ( diff --git a/src/components/nostr/kinds/CommunityNIPDetailRenderer.tsx b/src/components/nostr/kinds/CommunityNIPDetailRenderer.tsx index 1a7c736..d82df2d 100644 --- a/src/components/nostr/kinds/CommunityNIPDetailRenderer.tsx +++ b/src/components/nostr/kinds/CommunityNIPDetailRenderer.tsx @@ -1,7 +1,10 @@ import { useMemo } from "react"; +import { Copy, CopyCheck } from "lucide-react"; import { getTagValue } from "applesauce-core/helpers"; import { UserName } from "../UserName"; import { MarkdownContent } from "../MarkdownContent"; +import { useCopy } from "@/hooks/useCopy"; +import { toast } from "sonner"; import type { NostrEvent } from "@/types/nostr"; /** @@ -29,12 +32,33 @@ export function CommunityNIPDetailRenderer({ event }: { event: NostrEvent }) { }, ); + // Copy functionality + const { copy, copied } = useCopy(); + const handleCopy = () => { + copy(event.content); + toast.success("Community NIP markdown copied to clipboard"); + }; + return (
{/* NIP Header */}
- {/* Title */} -

{title}

+ {/* Title with Copy Button */} +
+

{title}

+ +
{/* Metadata */}