mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 08:27:27 +02:00
* 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. * refactor: use Button component and remove misleading shortcuts - Replace native button elements with Button component from shadcn/ui - Use variant="ghost" and size="icon" for consistent styling - Apply h-8 w-8 classes for uniform button sizing - Remove manual className styling in favor of component variants - Remove misleading keyboard shortcut hints from button titles - Changed "Edit command (Cmd+E)" to "Edit command" - Changed "Close window (Cmd+W)" to "Close window" - These shortcuts don't actually work, so they were misleading - Clean up imports and formatting - Format lucide-react imports across multiple lines - Add Button component import - Run prettier for consistent code style * refactor: use link variant and remove size class overrides - Change all Button components from variant="ghost" to variant="link" - Remove className="h-8 w-8" overrides to use default Button sizing - Maintains size="icon" for proper icon button behavior - Applies to WindowToolbar and CommunityNIPDetailRenderer * style: add muted color to window toolbar icon buttons - Add className="text-muted-foreground" to all Button components in WindowToolbar - Improves visual contrast for toolbar buttons - Applies to Edit, Copy NIP, More actions, and Close window buttons --------- Co-authored-by: Claude <noreply@anthropic.com>
186 lines
4.9 KiB
TypeScript
186 lines
4.9 KiB
TypeScript
import {
|
|
X,
|
|
Pencil,
|
|
MoreVertical,
|
|
WandSparkles,
|
|
Copy,
|
|
CopyCheck,
|
|
} from "lucide-react";
|
|
import { useSetAtom } from "jotai";
|
|
import { useState } from "react";
|
|
import { WindowInstance } from "@/types/app";
|
|
import { commandLauncherEditModeAtom } from "@/core/command-launcher-state";
|
|
import { reconstructCommand } from "@/lib/command-reconstructor";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { Button } from "@/components/ui/button";
|
|
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;
|
|
onClose?: () => void;
|
|
onEditCommand?: () => void; // Callback to open CommandLauncher
|
|
}
|
|
|
|
export function WindowToolbar({
|
|
window,
|
|
onClose,
|
|
onEditCommand,
|
|
}: WindowToolbarProps) {
|
|
const setEditMode = useSetAtom(commandLauncherEditModeAtom);
|
|
const [showSpellDialog, setShowSpellDialog] = useState(false);
|
|
|
|
const handleEdit = () => {
|
|
if (!window) return;
|
|
|
|
// Get command string (existing or reconstructed)
|
|
const commandString = window.commandString || reconstructCommand(window);
|
|
|
|
// Set edit mode state
|
|
setEditMode({
|
|
windowId: window.id,
|
|
initialCommand: commandString,
|
|
});
|
|
|
|
// Open CommandLauncher
|
|
if (onEditCommand) {
|
|
onEditCommand();
|
|
}
|
|
};
|
|
|
|
const handleTurnIntoSpell = () => {
|
|
if (!window) return;
|
|
|
|
// Only available for REQ windows
|
|
if (window.appId !== "req") {
|
|
toast.error("Only REQ windows can be turned into spells");
|
|
return;
|
|
}
|
|
|
|
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";
|
|
|
|
// Get REQ command for spell dialog
|
|
const reqCommand =
|
|
isReqWindow && window
|
|
? window.commandString ||
|
|
reconstructReqCommand(
|
|
window.props?.filter || {},
|
|
window.props?.relays,
|
|
undefined,
|
|
undefined,
|
|
window.props?.closeOnEose,
|
|
)
|
|
: "";
|
|
|
|
return (
|
|
<>
|
|
{window && (
|
|
<>
|
|
{/* Edit button */}
|
|
<Button
|
|
variant="link"
|
|
size="icon"
|
|
className="text-muted-foreground"
|
|
onClick={handleEdit}
|
|
title="Edit command"
|
|
aria-label="Edit command"
|
|
>
|
|
<Pencil className="size-4" />
|
|
</Button>
|
|
|
|
{/* Copy button for NIPs */}
|
|
{isNipWindow && (
|
|
<Button
|
|
variant="link"
|
|
size="icon"
|
|
className="text-muted-foreground"
|
|
onClick={handleCopyNip}
|
|
title="Copy NIP markdown"
|
|
aria-label="Copy NIP markdown"
|
|
disabled={!nipContent}
|
|
>
|
|
{copied ? <CopyCheck /> : <Copy />}
|
|
</Button>
|
|
)}
|
|
|
|
{/* More actions menu - only for REQ windows for now */}
|
|
{isReqWindow && (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="link"
|
|
size="icon"
|
|
className="text-muted-foreground"
|
|
title="More actions"
|
|
aria-label="More actions"
|
|
>
|
|
<MoreVertical className="size-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuItem onClick={handleTurnIntoSpell}>
|
|
<WandSparkles className="size-4 mr-2" />
|
|
Save as spell
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)}
|
|
|
|
{/* Spell Dialog */}
|
|
{isReqWindow && (
|
|
<SpellDialog
|
|
open={showSpellDialog}
|
|
onOpenChange={setShowSpellDialog}
|
|
mode="create"
|
|
initialCommand={reqCommand}
|
|
onSuccess={() => {
|
|
toast.success("Spell published successfully!");
|
|
}}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
{onClose && (
|
|
<Button
|
|
variant="link"
|
|
size="icon"
|
|
className="text-muted-foreground"
|
|
onClick={onClose}
|
|
title="Close window"
|
|
aria-label="Close window"
|
|
>
|
|
<X className="size-4" />
|
|
</Button>
|
|
)}
|
|
</>
|
|
);
|
|
}
|