diff --git a/src/components/SnippetCard.tsx b/src/components/SnippetCard.tsx index a3d5a0a..2dc337c 100644 --- a/src/components/SnippetCard.tsx +++ b/src/components/SnippetCard.tsx @@ -1,10 +1,22 @@ -import { useState, useMemo } from 'react' +import { useState, useMemo, useEffect } from 'react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' -import { Copy, Pencil, Trash, Eye } from '@phosphor-icons/react' -import { Snippet } from '@/lib/types' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { Copy, Pencil, Trash, Eye, DotsThree, FolderOpen } from '@phosphor-icons/react' +import { Snippet, Namespace } from '@/lib/types' import { strings, appConfig, LANGUAGE_COLORS } from '@/lib/config' +import { getAllNamespaces, moveSnippetToNamespace } from '@/lib/db' +import { toast } from 'sonner' interface SnippetCardProps { snippet: Snippet @@ -12,10 +24,26 @@ interface SnippetCardProps { onDelete: (id: string) => void onCopy: (code: string) => void onView: (snippet: Snippet) => void + onMove?: () => void } -export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: SnippetCardProps) { +export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView, onMove }: SnippetCardProps) { const [isCopied, setIsCopied] = useState(false) + const [namespaces, setNamespaces] = useState([]) + const [isMoving, setIsMoving] = useState(false) + + useEffect(() => { + loadNamespaces() + }, []) + + const loadNamespaces = async () => { + try { + const loadedNamespaces = await getAllNamespaces() + setNamespaces(loadedNamespaces) + } catch (error) { + console.error('Failed to load namespaces:', error) + } + } const snippetData = useMemo(() => { try { @@ -64,6 +92,28 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp onView(snippet) } + const handleMoveToNamespace = async (targetNamespaceId: string) => { + if (snippet.namespaceId === targetNamespaceId) { + toast.info('Snippet is already in this namespace') + return + } + + setIsMoving(true) + try { + await moveSnippetToNamespace(snippet.id, targetNamespaceId) + const targetNamespace = namespaces.find(n => n.id === targetNamespaceId) + toast.success(`Moved to ${targetNamespace?.name || 'namespace'}`) + if (onMove) { + onMove() + } + } catch (error) { + console.error('Failed to move snippet:', error) + toast.error('Failed to move snippet') + } finally { + setIsMoving(false) + } + } + if (!snippet) { return ( @@ -72,6 +122,9 @@ export function SnippetCard({ snippet, onEdit, onDelete, onCopy, onView }: Snipp ) } + const currentNamespace = namespaces.find(n => n.id === snippet.namespaceId) + const availableNamespaces = namespaces.filter(n => n.id !== snippet.namespaceId) + return ( - + + + + + + e.stopPropagation()}> + + + + Move to... + + + {availableNamespaces.length === 0 ? ( + + No other namespaces + + ) : ( + availableNamespaces.map((namespace) => ( + handleMoveToNamespace(namespace.id)} + > + {namespace.name} + {namespace.isDefault && ( + (Default) + )} + + )) + )} + + + + + + Delete + + + diff --git a/src/components/SnippetManager.tsx b/src/components/SnippetManager.tsx index d1da954..a9fb0f6 100644 --- a/src/components/SnippetManager.tsx +++ b/src/components/SnippetManager.tsx @@ -156,6 +156,18 @@ export function SnippetManager() { setViewerOpen(true) }, []) + const handleMoveSnippet = useCallback(async () => { + if (!selectedNamespaceId) return + + try { + const loadedSnippets = await getSnippetsByNamespace(selectedNamespaceId) + setSnippets(loadedSnippets) + } catch (error) { + console.error('Failed to reload snippets:', error) + toast.error('Failed to reload snippets') + } + }, [selectedNamespaceId]) + const handleCreateNew = useCallback(() => { setEditingSnippet(null) setDialogOpen(true) @@ -358,6 +370,7 @@ export function SnippetManager() { onDelete={handleDeleteSnippet} onCopy={handleCopyCode} onView={handleViewSnippet} + onMove={handleMoveSnippet} /> ))} diff --git a/src/lib/db.ts b/src/lib/db.ts index 1679acd..3616038 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1102,6 +1102,28 @@ export async function getNamespaceById(id: string): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + const snippet = await adapter.getSnippet(snippetId) + if (snippet) { + snippet.namespaceId = targetNamespaceId + snippet.updatedAt = Date.now() + await adapter.updateSnippet(snippet) + } + return + } + + const db = await initDB() + + db.run( + 'UPDATE snippets SET namespaceId = ?, updatedAt = ? WHERE id = ?', + [targetNamespaceId, Date.now(), snippetId] + ) + + await saveDB() +} + export async function validateDatabaseSchema(): Promise<{ valid: boolean; issues: string[] }> { try { const db = await initDB()