187 lines
5.3 KiB
TypeScript
187 lines
5.3 KiB
TypeScript
import clsx from "clsx";
|
|
import { Suspense } from "react";
|
|
import { Await } from "react-router";
|
|
import MicroModal from "micromodal";
|
|
import { ICONS_PREPEND } from "#env/env-vars";
|
|
import { Image } from "#components/images";
|
|
import { KemonoLink, LocalLink } from "#components/links";
|
|
import { Timestamp } from "#components/dates";
|
|
import { Preformatted } from "#components/formatting";
|
|
import { IComment } from "#entities/posts";
|
|
|
|
import * as styles from "./overview.module.scss";
|
|
import { IPostOverviewProps } from "./types";
|
|
|
|
interface IPostFooterProps extends Pick<IPostOverviewProps, "comments"> {
|
|
service: string;
|
|
profileID: string;
|
|
profileName?: string;
|
|
}
|
|
|
|
export function PostFooter({
|
|
comments,
|
|
service,
|
|
profileID,
|
|
profileName,
|
|
}: IPostFooterProps) {
|
|
return (
|
|
<footer className="post__footer">
|
|
<h2 className="site-section__subheading">Comments</h2>
|
|
{/* TODO: comment filters */}
|
|
<Suspense fallback={<p>Loading comments...</p>}>
|
|
<Await resolve={comments} errorElement={<></>}>
|
|
{(comments: IComment[]) => (
|
|
<div
|
|
className={clsx(
|
|
"post__comments",
|
|
!comments && "post__comments--no-comments"
|
|
)}
|
|
>
|
|
{!comments ? (
|
|
<p className="subtitle">No comments found for this post.</p>
|
|
) : (
|
|
comments.map((comment) => (
|
|
<PostComment
|
|
comment={comment}
|
|
service={service}
|
|
postProfileID={profileID}
|
|
postProfileName={profileName}
|
|
/>
|
|
))
|
|
)}
|
|
</div>
|
|
)}
|
|
</Await>
|
|
</Suspense>
|
|
</footer>
|
|
);
|
|
}
|
|
|
|
interface IPostCommentProps {
|
|
comment: IComment;
|
|
postProfileID: string;
|
|
postProfileName?: string;
|
|
service: string;
|
|
}
|
|
|
|
function PostComment({
|
|
comment,
|
|
postProfileID,
|
|
postProfileName,
|
|
service,
|
|
}: IPostCommentProps) {
|
|
const {
|
|
id,
|
|
commenter,
|
|
commenter_name,
|
|
revisions,
|
|
parent_id,
|
|
content,
|
|
published,
|
|
} = comment;
|
|
const isProfileComment = commenter === postProfileID;
|
|
const modalID = `comment-revisions-${id}`;
|
|
|
|
return (
|
|
<article
|
|
className={clsx("comment", isProfileComment && "comment--user")}
|
|
id={id}
|
|
>
|
|
<header className="comment__header">
|
|
{!isProfileComment ? (
|
|
<LocalLink id={id} className="comment__name">
|
|
{commenter_name ?? "Anonymous"}
|
|
</LocalLink>
|
|
) : (
|
|
<>
|
|
{/* TODO: a proper local link */}
|
|
<KemonoLink url={id} className="comment__icon">
|
|
<Image
|
|
src={`${ICONS_PREPEND}/icons/${service}/${postProfileID}`}
|
|
/>
|
|
</KemonoLink>
|
|
<LocalLink id={id} className="comment__name">
|
|
{postProfileName ?? "Post's profile"}
|
|
</LocalLink>
|
|
</>
|
|
)}
|
|
|
|
{revisions && revisions.length !== 0 && (
|
|
<>
|
|
<span
|
|
className="comment-has-revisions fancy-link"
|
|
data-micromodal-trigger={modalID}
|
|
onClick={() => MicroModal.show(modalID)}
|
|
>
|
|
(
|
|
<a className="fancy-link" href={`#${id}`}>
|
|
edited
|
|
</a>
|
|
)
|
|
</span>
|
|
<div className="comment-revisions-dialog" id={modalID}>
|
|
<div tabIndex={-1} data-micromodal-close>
|
|
<div
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby={`comment-revisions-${id}-title`}
|
|
>
|
|
<section>
|
|
<header>
|
|
<h3 id={`comment-revisions-${id}-title`}>
|
|
Comment edits
|
|
</h3>
|
|
|
|
<button
|
|
data-micromodal-close
|
|
onClick={() => MicroModal.close(modalID)}
|
|
></button>
|
|
</header>
|
|
|
|
<div id={`comment-revisions-${id}-content`}>
|
|
{[...revisions, comment].map((revision) => (
|
|
<article
|
|
id={`comment-${comment.id}-revision-${revision.id}`}
|
|
>
|
|
<span className="timestamp">
|
|
{revision.published ?? revision.added}
|
|
</span>
|
|
<span className="prose">{revision.content}</span>
|
|
</article>
|
|
))}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</header>
|
|
|
|
<section className="comment__body">
|
|
{parent_id && (
|
|
<div className="comment__reply">
|
|
<a href={`#${parent_id}`}>
|
|
{">>"}
|
|
{parent_id}
|
|
</a>
|
|
</div>
|
|
)}
|
|
<Preformatted>
|
|
<p className="comment__message">
|
|
{service !== "boosty" ? (
|
|
content
|
|
) : (
|
|
<Preformatted className={styles.content} dangerouslySetInnerHTML={{__html: content}}/>
|
|
)}
|
|
</p>
|
|
</Preformatted>
|
|
</section>
|
|
|
|
<footer className="comment__footer">
|
|
<Timestamp time={published} />
|
|
</footer>
|
|
</article>
|
|
);
|
|
}
|