/** * @file Comment card component */ import { IonButton, IonCard, IonCardContent, IonIcon, IonItem, IonList, IonPopover, IonRouterLink, useIonActionSheet, } from "@ionic/react"; import { arrowDownOutline, arrowDownSharp, arrowUpOutline, arrowUpSharp, ellipsisVerticalOutline, ellipsisVerticalSharp, shareSocialOutline, shareSocialSharp, timeOutline, timeSharp, trashBinOutline, trashBinSharp, warningOutline, warningSharp, } from "ionicons/icons"; import {Duration} from "luxon"; import {FC, HTMLAttributes, useEffect, useId, useState} from "react"; import {Avatar} from "~/components/avatar"; import styles from "~/components/comment-card.module.css"; import {Markdown} from "~/components/markdown"; import {useEphemeralStore} from "~/lib/stores/ephemeral"; import {client} from "~/lib/supabase"; import {Comment, GlobalMessageMetadata} from "~/lib/types"; import {formatDuration, formatScalar} from "~/lib/utils"; /** * Copied link message metadata */ const COPIED_LINK_MESSAGE_METADATA: GlobalMessageMetadata = { symbol: Symbol("comment-card.copied-link"), name: "Copied link", description: "The link to the comment has been copied to your clipboard.", }; /** * Already reported message metadata */ const ALREADY_REPORTED_MESSAGE_METADATA: GlobalMessageMetadata = { symbol: Symbol("comment-card.already-reported"), name: "Already reported", description: "The comment has already been reported.", }; /** * New report message metadata */ const NEW_REPORT_MESSAGE_METADATA: GlobalMessageMetadata = { symbol: Symbol("comment-card.new-report"), name: "New report", description: "The comment has been reported. Thank you for your feedback.", }; /** * Comment card component props */ interface CommentCardProps extends HTMLAttributes { /** * Comment */ comment: Comment; /** * Comment load event handler */ onLoad?: () => void; /** * Toggle a vote on the comment * @param upvote Whether the vote is an upvote or a downvote */ toggleVote: (upvote: boolean) => void; /** * Comment deleted event handler */ onDeleted?: () => void; } /** * Comment card component * @param props Props * @returns JSX */ export const CommentCard: FC = ({ comment, onLoad, toggleVote, onDeleted, ...props }) => { // Variables const AvatarContainer = comment.commenter_id === null ? "div" : IonRouterLink; // Hooks const id = useId(); const [time, setTime] = useState(); const [present] = useIonActionSheet(); const setMessage = useEphemeralStore(state => state.setMessage); // Effects useEffect(() => { // Recompute ago every five seconds updateAgo(); setInterval(updateAgo, 5000); // Emit the load event onLoad?.(); }, []); // Methods /** * Update the ago time */ const updateAgo = () => { const duration = Date.now() - new Date(comment.created_at).getTime(); setTime( Duration.fromMillis(duration).as("days") < 1 ? `${formatDuration(duration)} ago` : new Date(comment.created_at).toLocaleDateString(), ); }; /** * Share the comment */ const shareComment = async () => { // Generate the URL const url = new URL(`/posts/${comment.post_id}`, window.location.origin); url.search = new URLSearchParams({ comment: comment.id.toString(), }).toString(); const strUrl = url.toString(); // Share await (navigator.share === undefined ? navigator.clipboard.writeText(strUrl) : navigator.share({ url: strUrl, })); // Display the message setMessage(COPIED_LINK_MESSAGE_METADATA); }; /** * Report the comment * @returns Promise */ const reportComment = () => present({ header: "Report Comment", subHeader: "Are you sure you want to report this comment? This action cannot be undone.", buttons: [ { text: "Cancel", role: "cancel", }, { text: "Report", role: "destructive", /** * Comment report handler */ handler: async () => { // Insert the report const {error} = await client.from("comment_reports").insert({ // eslint-disable-next-line camelcase comment_id: comment.id, }); // Handle error if (error !== null) { if (error.code === "23505") { // Display the message setMessage(ALREADY_REPORTED_MESSAGE_METADATA); } return; } // Display the message setMessage(NEW_REPORT_MESSAGE_METADATA); }, }, ], }); /** * Delete the comment * @returns Promise */ const deleteComment = () => present({ header: "Delete Comment", subHeader: "Are you sure you want to delete this comment? This action cannot be undone.", buttons: [ { text: "Cancel", role: "cancel", }, { text: "Delete", role: "destructive", /** * Comment delete handler */ handler: onDeleted, }, ], }); return (
{time !== undefined && ( <>

{time}

)}
{ event.preventDefault(); toggleVote(true); }} >

{formatScalar(comment.upvotes - comment.downvotes)}

{ event.preventDefault(); toggleVote(false); }} > event.preventDefault()} id={`${id}-options`} > { event.preventDefault(); shareComment(); }} > Share { event.preventDefault(); reportComment(); }} > Report {onDeleted !== undefined && ( { event.preventDefault(); deleteComment(); }} > Delete )}
); };