import { useEffect, useRef, useState } from "react"; import { useParams, useNavigate } from "react-router"; // 新增 useNavigate import showToast from "../components/toast"; import { network } from "../network/network"; import { Collection } from "../network/models"; import Markdown from "react-markdown"; import ResourcesView from "../components/resources_view"; import Loading from "../components/loading"; import { MdOutlineDelete, MdOutlineEdit, MdOutlineLock } from "react-icons/md"; import { app } from "../app"; import { useTranslation } from "../utils/i18n"; import Button from "../components/button"; import Badge from "../components/badge"; export default function CollectionPage() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [collection, setCollection] = useState(null); const [resourcesKey, setResourcesKey] = useState(0); const { t } = useTranslation(); const [isDeleting, setIsDeleting] = useState(false); const [editOpen, setEditOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); useEffect(() => { const idInt = parseInt(id || "0", 10); if (isNaN(idInt)) { showToast({ type: "error", message: "Invalid collection ID", }); return; } const prefetchData = app.getPreFetchData(); if (prefetchData?.collection?.id === idInt) { setCollection(prefetchData.collection); return; } network.getCollection(idInt).then((res) => { if (res.success) { setCollection(res.data!); } else { showToast({ type: "error", message: res.message || "Failed to load collection", }); } }); }, [id]); useEffect(() => { if (!collection) return; document.title = collection.title; }, [collection]); const toBeDeletedRID = useRef(null); const handleDeleteResource = (resourceId: number) => { toBeDeletedRID.current = resourceId; const dialog = document.getElementById( "deleteResourceDialog", ) as HTMLDialogElement | null; if (dialog) { dialog.showModal(); } }; const handleDeletedResourceConfirmed = () => { if (toBeDeletedRID.current === null) return; network .removeResourceFromCollection(collection!.id, toBeDeletedRID.current) .then((res) => { if (res.success) { showToast({ type: "success", message: "Resource deleted successfully", }); setResourcesKey((prev) => prev + 1); // Trigger re-render of ResourcesView } else { showToast({ type: "error", message: res.message || "Failed to delete resource", }); } }); toBeDeletedRID.current = null; const dialog = document.getElementById( "deleteResourceDialog", ) as HTMLDialogElement | null; if (dialog) { dialog.close(); } }; const handleDeleteCollection = () => setDeleteOpen(true); const handleDeleteCollectionConfirmed = async () => { if (!collection) return; setIsDeleting(true); const res = await network.deleteCollection(collection.id); setIsDeleting(false); if (res.success) { showToast({ type: "success", message: "Collection deleted successfully", }); setDeleteOpen(false); if (window.history.length > 1) { navigate(-1); } else { navigate("/", { replace: true }); } } else { showToast({ type: "error", message: res.message || "Failed to delete collection", }); setDeleteOpen(false); } }; const isOwner = collection?.user?.id === app?.user?.id; const openEditDialog = () => setEditOpen(true); const handleEditSaved = (newCollection: Collection) => { setCollection(newCollection); setEditOpen(false); }; if (!collection) { return ; } return ( <>

{collection?.title}

{isOwner && ( <> )} {!collection.isPublic && ( {" "} {t("Private")} )}
{ return network.listCollectionResources(collection!.id, page); }} actionBuilder={ isOwner ? (r) => { return ( ); } : undefined } key={resourcesKey} />

Remove Resource

Are you sure you want to remove this resource?

{deleteOpen && (

{t("Delete Collection")}

{t( "Are you sure you want to delete this collection? This action cannot be undone.", )}

)} {editOpen && collection && ( setEditOpen(false)} onSaved={handleEditSaved} /> )} ); } function CollectionContent({ content }: { content: string }) { const lines = content.split("\n"); for (let i = 0; i < lines.length; i++) { let line = lines[i]; if (!line.endsWith(" ")) { // Ensure that each line ends with two spaces for Markdown to recognize it as a line break lines[i] = line + " "; } } content = lines.join("\n"); return {content}; } function EditCollectionDialog({ open, collection, onClose, onSaved, }: { open: boolean; collection: Collection; onClose: () => void; onSaved: (newCollection: Collection) => void; }) { const [editTitle, setEditTitle] = useState(collection.title); const [editArticle, setEditArticle] = useState(collection.article); const [editIsPublic, setEditIsPublic] = useState(collection.isPublic); const [editLoading, setEditLoading] = useState(false); const { t } = useTranslation(); const handleEditSave = async () => { if (editTitle.trim() === "" || editArticle.trim() === "") { showToast({ type: "error", message: t("Title and description cannot be empty"), }); return; } setEditLoading(true); const res = await network.updateCollection( collection.id, editTitle, editArticle, editIsPublic, ); setEditLoading(false); if (res.success) { showToast({ type: "success", message: t("Edit successful") }); const getRes = await network.getCollection(collection.id); if (getRes.success) { onSaved(getRes.data!); } else { onSaved({ ...collection, title: editTitle, article: editArticle, isPublic: editIsPublic, }); } } else { showToast({ type: "error", message: res.message || t("Failed to save changes"), }); } }; if (!open) return null; return (

{t("Edit Collection")}

setEditTitle(e.target.value)} disabled={editLoading} />