import type { AttachmentListItemView, PageClassification } from '@dorfteich/shared'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import type { Editor } from '@tiptap/react'; import { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FormError } from '../components/forms'; import { apiDelete, apiGet, apiUploadFile } from '../lib/api'; import { fileGlyph, formatBytes, mediaUrl } from './file-format'; /** * A page's attachments section (issue #61): upload a file, see it listed with * its type icon, size, and uploader, insert it into the document as a download * link, or delete it. Uploads and deletes are page-write-gated in the api; in * read mode the panel is a download list (no upload/insert/delete controls). */ export function AttachmentsPanel({ pageId, editor, canEdit, classification, onClose, }: { pageId: string; editor: Editor | null; canEdit: boolean; /** VS-NfD level of the page (issue #213): a classified page shows the * upload warning naming the consequence — attachments inherit a level * their content cannot carry (#212). */ classification?: PageClassification; onClose: () => void; }): React.JSX.Element { const { t } = useTranslation('files'); const queryClient = useQueryClient(); const fileInput = useRef(null); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); const list = useQuery({ queryKey: ['page-files', pageId], queryFn: () => apiGet(`/pages/${pageId}/files`), }); async function refresh(): Promise { await queryClient.invalidateQueries({ queryKey: ['page-files', pageId] }); } async function onPick(event: React.ChangeEvent): Promise { const file = event.target.files?.[0]; event.target.value = ''; if (!file) return; setError(null); setBusy(true); try { await apiUploadFile(`/pages/${pageId}/files`, file); await refresh(); } catch (err) { setError(err); } finally { setBusy(false); } } async function remove(id: string): Promise { setError(null); try { await apiDelete(`/files/${id}`); await refresh(); } catch (err) { setError(err); } } function insertLink(item: AttachmentListItemView): void { if (!editor) return; editor .chain() .focus() .insertContent([ { type: 'text', text: item.fileName, marks: [{ type: 'link', attrs: { href: mediaUrl(item.id) } }], }, { type: 'text', text: ' ' }, ]) .run(); } const items = list.data ?? []; return ( ); }