diff --git a/frontend/src/network/models.ts b/frontend/src/network/models.ts index ea26592..28150d0 100644 --- a/frontend/src/network/models.ts +++ b/frontend/src/network/models.ts @@ -30,6 +30,7 @@ export interface PageResponse { export interface Tag { id: number; name: string; + description: string; } export interface CreateResourceParams { diff --git a/frontend/src/network/network.ts b/frontend/src/network/network.ts index 0e3bf0c..ab18c85 100644 --- a/frontend/src/network/network.ts +++ b/frontend/src/network/network.ts @@ -316,6 +316,34 @@ class Network { } } + async getTagByName(name: string): Promise> { + try { + const response = await axios.get(`${this.apiBaseUrl}/tag/${name}`) + return response.data + } catch (e: any) { + console.error(e) + return { + success: false, + message: e.toString(), + } + } + } + + async setTagDescription(tagId: number, description: string): Promise> { + try { + const response = await axios.putForm(`${this.apiBaseUrl}/tag/${tagId}/description`, { + description + }) + return response.data + } catch (e: any) { + console.error(e) + return { + success: false, + message: e.toString(), + } + } + } + /** * Upload image and return the image id */ @@ -453,7 +481,7 @@ class Network { async getResourceDetails(id: number): Promise> { try { const response = await axios.get(`${this.apiBaseUrl}/resource/${id}`) - let data = response.data + const data = response.data if (!data.related) { data.related = [] } diff --git a/frontend/src/pages/tagged_resources_page.tsx b/frontend/src/pages/tagged_resources_page.tsx index e1dd2c8..d8a9656 100644 --- a/frontend/src/pages/tagged_resources_page.tsx +++ b/frontend/src/pages/tagged_resources_page.tsx @@ -2,30 +2,118 @@ import { useParams } from "react-router"; import { ErrorAlert } from "../components/alert.tsx"; import ResourcesView from "../components/resources_view.tsx"; import { network } from "../network/network.ts"; -import { useEffect } from "react"; +import {useEffect, useState} from "react"; import { useTranslation } from "react-i18next"; +import {Tag} from "../network/models.ts"; +import Button from "../components/button.tsx"; +import Markdown from "react-markdown"; +import {app} from "../app.ts"; export default function TaggedResourcesPage() { - const { tag } = useParams() + const { tag: tagName } = useParams() const { t } = useTranslation(); - if (!tag) { - return
+ const [tag, setTag] = useState(null); + + useEffect(() => { + document.title = t("Tag: " + tagName); + }, [t, tagName]) + + useEffect(() => { + if (!tagName) { + return; + } + network.getTagByName(tagName).then((res) => { + if (res.success) { + setTag(res.data!); + } + }); + }, [tagName]); + + if (!tagName) { + return
} - useEffect(() => { - document.title = t("Tag: " + tag); - }, [tag]) - return
-

- Tag: {tag} -

+
+

+ {tagName} +

+ { + tag && { + setTag(t) + }} /> + } +
+ { + (tag?.description && app.canUpload()) &&
+ + {tag.description} + +
+ } { - return network.getResourcesByTag(tag, page) + return network.getResourcesByTag(tagName, page) }}>
+} + +function EditTagButton({tag, onEdited}: { tag: Tag, onEdited: (t: Tag) => void }) { + const [description, setDescription] = useState(tag.description); + + const [isLoading, setIsLoading] = useState(false); + + const [error, setError] = useState(null); + + useEffect(() => { + setDescription(tag.description) + }, [tag.description]); + + const submit = async () => { + if (description === tag.description) { + return; + } + if (description && description.length > 256) { + setError("Description is too long"); + return; + } + setIsLoading(true); + setError(null); + const res = await network.setTagDescription(tag.id, description); + setIsLoading(false); + if (res.success) { + const dialog = document.getElementById("edit_tag_dialog") as HTMLDialogElement; + dialog.close(); + onEdited(res.data!); + } else { + setError(res.message || "Unknown error"); + } + }; + + return <> + + +
+

Edit Tag

+

Set the description of the tag.

+

Use markdown format.

+