import { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { usePageTitle } from '../../hooks/usePageTitle'; import { Settings, Pencil, Trash2, Plus, Download, Braces } from 'lucide-react'; import { parse as yamlParse } from 'yaml'; import { snippets } from '../../api'; import type { Snippet } from '../../api'; import { Breadcrumbs, Button, Card, ContextMenu, MarkdownContent, Modal, Timestamp, UuidBadge, } from '../../ui'; import styles from '../Page.module.css'; function firstParagraphBlocks( markdown: string, limit = 2, ): { preview: string; truncated: boolean } { const blocks = markdown .split(/\n\s*\n/g) .map((block) => block.trim()) .filter(Boolean); const sliced = blocks.slice(0, limit); return { preview: sliced.join('\n\n'), truncated: blocks.length > limit, }; } function SnippetCard({ snippet, onDelete }: { snippet: Snippet; onDelete: (id: string) => void }) { const { t } = useTranslation(); const navigate = useNavigate(); const header = (
{snippet.title}
e.stopPropagation()}> } items={[ { label: t('snippets.action_edit'), icon: , onClick: () => navigate(`/snippets/${snippet.id}/edit`), }, { label: t('snippets.action_delete'), icon: , variant: 'danger', onClick: () => onDelete(snippet.id), }, ]} />
); const footer = (
); return ( navigate(`/snippets/${snippet.id}`)} >

{t('snippets.field_alias')}: {snippet.alias}

{snippet.description && (() => { const { preview, truncated } = firstParagraphBlocks(snippet.description, 2); if (!preview) return null; return ( <> {truncated &&

...

} ); })()}
); } function AddSnippetCard() { const { t } = useTranslation(); const navigate = useNavigate(); return (
); } export function SnippetsPage() { const { t } = useTranslation(); usePageTitle(t('snippets.title')); const navigate = useNavigate(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [deleteId, setDeleteId] = useState(null); const [deleting, setDeleting] = useState(false); const fileInputRef = useRef(null); const load = () => { snippets .list() .then((res) => setItems(res.data)) .finally(() => setLoading(false)); }; useEffect(() => { load(); }, []); const requestDelete = (id: string) => { setDeleteId(id); }; const handleDelete = async () => { if (!deleteId) return; setDeleting(true); try { await snippets.remove(deleteId); load(); setDeleteId(null); } finally { setDeleting(false); } }; const handleImport = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; e.target.value = ''; try { const text = await file.text(); const payload = yamlParse(text) as unknown; const imported = await snippets.importSnippet(payload); navigate(`/snippets/${imported.id}`); } catch { // Import errors are silently ignored } }; return (
}]} />
{loading &&

{t('snippets.loading')}

} {!loading && (
{items.map((s) => ( ))} {items.length === 0 &&

{t('snippets.empty')}

}
)} !deleting && setDeleteId(null)} footer={ <> } > {t('common.confirm_delete_snippet')}
); }