mirror of
https://github.com/wgh136/nysoure.git
synced 2025-09-28 04:27:24 +00:00
Support comment reply.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { network } from "../network/network";
|
||||
import showToast from "../components/toast";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
@@ -7,6 +7,10 @@ import { CommentWithRef, Resource } from "../network/models";
|
||||
import Loading from "../components/loading";
|
||||
import Markdown from "react-markdown";
|
||||
import Badge from "../components/badge";
|
||||
import { CommentInput } from "../components/comment_input";
|
||||
import { CommentTile } from "../components/comment_tile";
|
||||
import { Comment } from "../network/models";
|
||||
import Pagination from "../components/pagination";
|
||||
|
||||
export default function CommentPage() {
|
||||
const params = useParams();
|
||||
@@ -16,6 +20,7 @@ export default function CommentPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
setComment(null);
|
||||
const id = parseInt(commentId || "0");
|
||||
if (isNaN(id) || id <= 0) {
|
||||
showToast({
|
||||
@@ -34,7 +39,7 @@ export default function CommentPage() {
|
||||
});
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
}, [commentId]);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t("Comment Details");
|
||||
@@ -46,28 +51,36 @@ export default function CommentPage() {
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold my-2">{t("Comment")}</h1>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate(`/user/${encodeURIComponent(comment.user.username)}`);
|
||||
}}
|
||||
className="border-b-2 py-1 cursor-pointer border-transparent hover:border-primary transition-colors duration-200 ease-in-out"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="avatar">
|
||||
<div className="w-6 rounded-full">
|
||||
<img src={network.getUserAvatar(comment.user)} alt={"avatar"} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-2"></div>
|
||||
<div className="text-sm">{comment.user.username}</div>
|
||||
</div>
|
||||
</button>
|
||||
{comment.resource && <ResourceCard resource={comment.resource} />}
|
||||
<div className="flex"></div>
|
||||
<div className="flex items-center mt-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate(`/user/${encodeURIComponent(comment.user.username)}`);
|
||||
}}
|
||||
className="border-b-2 py-1 cursor-pointer border-transparent hover:border-primary transition-colors duration-200 ease-in-out"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="avatar">
|
||||
<div className="w-6 rounded-full">
|
||||
<img src={network.getUserAvatar(comment.user)} alt={"avatar"} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-2"></div>
|
||||
<div className="text-sm">{comment.user.username}</div>
|
||||
</div>
|
||||
</button>
|
||||
<span className="text-xs text-base-content/80 ml-2">
|
||||
{t("Commented on")}
|
||||
{new Date(comment.created_at).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
<article>
|
||||
<CommentContent content={comment.content} />
|
||||
</article>
|
||||
<div className="h-4" />
|
||||
<div className="border-t border-base-300" />
|
||||
<div className="h-4" />
|
||||
<CommentReply comment={comment} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -99,7 +112,7 @@ function ResourceCard({ resource }: { resource: Resource }) {
|
||||
return (
|
||||
<a
|
||||
href="link"
|
||||
className="flex flex-row w-full card bg-base-200 shadow overflow-clip my-2"
|
||||
className="flex flex-row w-full card bg-base-200 shadow-xs hover:shadow overflow-clip my-2"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
navigate(link);
|
||||
@@ -115,7 +128,7 @@ function ResourceCard({ resource }: { resource: Resource }) {
|
||||
<div className="flex flex-col p-4 flex-1">
|
||||
<h2 className="card-title w-full break-all">{resource.title}</h2>
|
||||
<div className="h-2"></div>
|
||||
<p>
|
||||
<p className="mb-2">
|
||||
{tags.map((tag) => {
|
||||
return (
|
||||
<Badge key={tag.id} className={"m-0.5"}>
|
||||
@@ -138,3 +151,82 @@ function ResourceCard({ resource }: { resource: Resource }) {
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function CommentReply({ comment }: { comment: CommentWithRef }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [maxPage, setMaxPage] = useState(0);
|
||||
const [listKey, setListKey] = useState(0);
|
||||
|
||||
const reload = useCallback(() => {
|
||||
setPage(1);
|
||||
setMaxPage(0);
|
||||
setListKey((prev) => prev + 1);
|
||||
}, []);
|
||||
|
||||
return <>
|
||||
<h2 className="text-xl font-bold my-2">{t("Replies")}</h2>
|
||||
<CommentInput replyTo={comment.id} reload={reload} />
|
||||
<CommentsList
|
||||
commentId={comment.id}
|
||||
page={page}
|
||||
maxPageCallback={(maxPage: number) => {
|
||||
setMaxPage(maxPage);
|
||||
}}
|
||||
key={listKey}
|
||||
reload={reload}
|
||||
/>
|
||||
{maxPage ? (
|
||||
<div className={"w-full flex justify-center"}>
|
||||
<Pagination page={page} setPage={setPage} totalPages={maxPage} />
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
|
||||
function CommentsList({
|
||||
commentId,
|
||||
page,
|
||||
maxPageCallback,
|
||||
reload,
|
||||
}: {
|
||||
commentId: number;
|
||||
page: number;
|
||||
maxPageCallback: (maxPage: number) => void;
|
||||
reload: () => void;
|
||||
}) {
|
||||
const [comments, setComments] = useState<Comment[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
network.listCommentReplies(commentId, page).then((res) => {
|
||||
if (res.success) {
|
||||
setComments(res.data!);
|
||||
maxPageCallback(res.totalPages || 1);
|
||||
} else {
|
||||
showToast({
|
||||
message: res.message,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [maxPageCallback, page, commentId]);
|
||||
|
||||
if (comments == null) {
|
||||
return (
|
||||
<div className={"w-full"}>
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{comments.map((comment) => {
|
||||
return (
|
||||
<CommentTile elevation="high" comment={comment} key={comment.id} onUpdated={reload} />
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
@@ -295,7 +295,7 @@ export default function EditResourcePage() {
|
||||
/>
|
||||
<div className={"flex items-center py-1 "}>
|
||||
<MdOutlineInfo className={"inline mr-1"} />
|
||||
<span className={"text-sm"}>{t("Use Markdown format")}</span>
|
||||
<span className={"text-sm"}>{t("Use markdown format")}</span>
|
||||
</div>
|
||||
<div className={"h-4"}></div>
|
||||
<p className={"my-1"}>{t("Images")}</p>
|
||||
|
@@ -306,7 +306,7 @@ export default function PublishPage() {
|
||||
/>
|
||||
<div className={"flex items-center py-1 "}>
|
||||
<MdOutlineInfo className={"inline mr-1"} />
|
||||
<span className={"text-sm"}>{t("Use Markdown format")}</span>
|
||||
<span className={"text-sm"}>{t("Use markdown format")}</span>
|
||||
</div>
|
||||
<div className={"h-4"}></div>
|
||||
<p className={"my-1"}>{t("Images")}</p>
|
||||
|
Reference in New Issue
Block a user