feat: add comment and reply posting, for a release.

I'm sorry for this mess . . .
This commit is contained in:
Kentai Radiquum 2024-08-10 17:39:35 +05:00
parent 4f0005f4f2
commit 6f4e6e2fa6
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
4 changed files with 347 additions and 169 deletions

View file

@ -1,10 +1,12 @@
import { unixToDate, sinceUnixDate } from "#/api/utils";
import { useEffect, useState } from "react";
import { useEffect, useState, useCallback } from "react";
import { ENDPOINTS } from "#/api/config";
import { Button } from "flowbite-react";
import Link from "next/link";
import { CommentsAddModal } from "./Comments.Add";
export const CommentsComment = (props: {
release_id: number;
profile: { login: string; avatar: string; id: number };
comment: {
id: number;
@ -19,17 +21,34 @@ export const CommentsComment = (props: {
};
isSubComment?: boolean;
token: string | null;
isReplying?: boolean;
parentComment?: any;
setShouldRender?: (shouldRender: boolean) => void;
setCommentSend?: (commentSend: boolean) => void;
}) => {
const [replies, setReplies] = useState([]);
const [likes, setLikes] = useState(props.comment.likes_count);
const [vote, setVote] = useState(props.comment.vote);
const [isAddCommentsOpen, setIsAddCommentsOpen] = useState(false);
const [isHidden, setIsHidden] = useState(
props.comment.isSpoiler || props.comment.likes_count < -5
!props.isReplying &&
(props.comment.isSpoiler || props.comment.likes_count < -5)
);
const [shouldRender, setShouldRender] = useState(true);
const [commentSend, setCommentSend] = useState(false);
let parentCommentId: number | null = null;
if (props.parentComment) {
parentCommentId = props.parentComment.id;
}
useEffect(() => {
async function _fetchReplies() {
let url = `${ENDPOINTS.release.info}/comment/replies/${props.comment.id}/0?sort=2`;
setReplies([]);
let url = `${ENDPOINTS.release.info}/comment/replies/${
parentCommentId || props.comment.id
}/0?sort=2`;
if (props.token) {
url += `&token=${props.token}`;
}
@ -39,10 +58,17 @@ export const CommentsComment = (props: {
setReplies(data.content);
});
}
if (!props.isSubComment && props.comment.reply_count > 0) {
if (
!props.isSubComment &&
!props.isReplying &&
shouldRender &&
(commentSend || props.comment.reply_count > 0)
) {
_fetchReplies();
setShouldRender(false);
setCommentSend(false);
}
}, []);
}, [commentSend]);
async function _sendVote(action: number) {
if (props.token) {
@ -81,144 +107,170 @@ export const CommentsComment = (props: {
}
return (
<article className={`${!props.isSubComment ? "p-6" : "pt-4"} text-sm bg-gray-100 rounded-lg sm:text-base dark:bg-gray-900`}>
<footer className="flex items-center justify-between mb-2">
<div className="flex flex-col items-start gap-1 sm:items-center sm:flex-row">
<Link
href={`/profile/${props.profile.id}`}
className="inline-flex items-center mr-3 text-sm font-semibold text-gray-900 dark:text-white hover:underline"
>
<img
className="w-6 h-6 mr-2 rounded-full"
src={props.profile.avatar}
alt=""
/>
{props.profile.login}
</Link>
<p className="text-sm text-gray-600 dark:text-gray-400">
<time
dateTime={props.comment.timestamp.toString()}
title={unixToDate(props.comment.timestamp, "full")}
>
{sinceUnixDate(props.comment.timestamp)}
</time>
</p>
</div>
</footer>
<div className="relative flex items-center py-2">
<p className="text-gray-800 whitespace-pre-wrap dark:text-gray-400">
{!props.comment.isDeleted
? props.comment.message
: "Комментарий был удалён."}
</p>
{isHidden && (
<button
className="absolute top-0 bottom-0 left-0 right-0"
onClick={() => setIsHidden(false)}
>
<div className="min-w-full min-h-full px-2 py-1.5 rounded-md bg-black text-white bg-opacity-50 backdrop-blur-[8px] flex flex-col justify-center items-center">
<p>
{props.comment.likes_count < -5
? "У комментария слишком низкий рейтинг."
: "Данный комментарий может содержать спойлер."}
</p>
<p className="font-bold">Нажмите, чтобы прочитать</p>
</div>
</button>
)}
</div>
<div
className={`flex items-center justify-between space-x-4 ${
isHidden ? "mt-4" : ""
}`}
<>
<article
className={`${
!props.isSubComment ? "p-6" : "pt-4"
} text-sm bg-gray-100 rounded-lg sm:text-base dark:bg-gray-900`}
>
<button
type="button"
className="flex items-center text-sm font-medium text-gray-500 hover:underline dark:text-gray-400"
>
<svg
className="mr-1.5 w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 18"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5 5h5M5 8h2m6-3h2m-5 3h6m2-7H2a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h3v5l5-5h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1Z"
/>
</svg>
Ответить
</button>
<div className="flex items-center">
{props.token && (
<Button
color="inline"
onClick={() => {
_updateVote("dislike");
}}
<footer className="flex items-center justify-between mb-2">
<div className="flex flex-col items-start gap-1 sm:items-center sm:flex-row">
<Link
href={`/profile/${props.profile.id}`}
className="inline-flex items-center mr-3 text-sm font-semibold text-gray-900 dark:text-white hover:underline"
>
<span
className={`w-6 h-6 iconify mdi--dislike ${
vote == 1
<img
className="w-6 h-6 mr-2 rounded-full"
src={props.profile.avatar}
alt=""
/>
{props.profile.login}
</Link>
<p className="text-sm text-gray-600 dark:text-gray-400">
<time
dateTime={props.comment.timestamp.toString()}
title={unixToDate(props.comment.timestamp, "full")}
>
{sinceUnixDate(props.comment.timestamp)}
</time>
</p>
</div>
</footer>
<div className="relative flex items-center py-2">
<p className="text-gray-800 whitespace-pre-wrap dark:text-gray-400">
{!props.comment.isDeleted
? props.comment.message
: "Комментарий был удалён."}
</p>
{isHidden && (
<button
className="absolute top-0 bottom-0 left-0 right-0"
onClick={() => setIsHidden(false)}
>
<div className="min-w-full min-h-full px-2 py-1.5 rounded-md bg-black text-white bg-opacity-50 backdrop-blur-[8px] flex flex-col justify-center items-center">
<p>
{props.comment.likes_count < -5
? "У комментария слишком низкий рейтинг."
: "Данный комментарий может содержать спойлер."}
</p>
<p className="font-bold">Нажмите, чтобы прочитать</p>
</div>
</button>
)}
</div>
{!props.isReplying && !props.comment.isDeleted && (
<div
className={`flex items-center justify-between space-x-4 ${
isHidden ? "mt-4" : ""
}`}
>
{props.token ? (
<button
type="button"
className="flex items-center text-sm font-medium text-gray-500 hover:underline dark:text-gray-400"
onClick={() => setIsAddCommentsOpen(true)}
>
<svg
className="mr-1.5 w-3.5 h-3.5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 18"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5 5h5M5 8h2m6-3h2m-5 3h6m2-7H2a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h3v5l5-5h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1Z"
/>
</svg>
Ответить
</button>
) : (
<span></span>
)}
<div className="flex items-center">
<Button
color="inline"
onClick={() => {
_updateVote("dislike");
}}
disabled={!props.token}
>
<span
className={`w-6 h-6 iconify mdi--dislike ${
vote == 1
? "text-red-500 dark:text-red-400"
: "text-gray-500 dark:text-gray-400"
}`}
></span>
</Button>
<p
className={`text-sm font-medium ${
likes > 0
? "text-green-500 dark:text-green-400"
: likes < 0
? "text-red-500 dark:text-red-400"
: "text-gray-500 dark:text-gray-400"
}`}
></span>
</Button>
)}
<p
className={`text-sm font-medium ${
likes > 0
? "text-green-500 dark:text-green-400"
: likes < 0
? "text-red-500 dark:text-red-400"
: "text-gray-500 dark:text-gray-400"
}`}
>
{likes}
</p>
{props.token && (
<Button
color="inline"
onClick={() => {
_updateVote("like");
>
{likes}
</p>
<Button
color="inline"
onClick={() => {
_updateVote("like");
}}
disabled={!props.token}
>
<span
className={`w-6 h-6 iconify mdi--like ${
vote == 2
? "text-green-500 dark:text-green-400"
: "text-gray-500 dark:text-gray-400"
}`}
></span>
</Button>
</div>
</div>
)}
{replies.length > 0 &&
replies.map((comment: any) => (
<CommentsComment
key={comment.id}
release_id={props.release_id}
profile={comment.profile}
comment={{
id: comment.id,
timestamp: comment.timestamp,
message: comment.message,
reply_count: comment.reply_count,
likes_count: comment.likes_count,
vote: comment.vote,
isSpoiler: comment.is_spoiler,
isEdited: comment.is_edited,
isDeleted: comment.is_deleted,
}}
>
<span
className={`w-6 h-6 iconify mdi--like ${
vote == 2
? "text-green-500 dark:text-green-400"
: "text-gray-500 dark:text-gray-400"
}`}
></span>
</Button>
)}
</div>
</div>
{replies.length > 0 &&
replies.map((comment: any) => (
<CommentsComment
key={comment.id}
profile={comment.profile}
comment={{
id: comment.id,
timestamp: comment.timestamp,
message: comment.message,
reply_count: comment.reply_count,
likes_count: comment.likes_count,
vote: comment.vote,
isSpoiler: comment.is_spoiler,
isEdited: comment.is_edited,
isDeleted: comment.is_deleted,
}}
isSubComment={true}
token={props.token}
/>
))}
</article>
isSubComment={true}
token={props.token}
parentComment={props.parentComment || props.comment}
setShouldRender={props.setShouldRender || setShouldRender}
setCommentSend={props.setCommentSend || setCommentSend}
/>
))}
</article>
<CommentsAddModal
isOpen={isAddCommentsOpen}
setIsOpen={setIsAddCommentsOpen}
release_id={props.release_id}
token={props.token}
isReply={true}
parentComment={props.comment}
parentProfile={props.profile}
setShouldRender={props.setShouldRender || setShouldRender}
setCommentSend={props.setCommentSend || setCommentSend}
/>
</>
);
};