From 1bc57c61eb5daf8cf6cfc2a2634e26f36b264cd3 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Wed, 17 Dec 2025 22:47:19 -0500 Subject: [PATCH] migration of comments --- bun.lockb | Bin 326868 -> 328106 bytes package.json | 1 + src/app.css | 1 + src/components/ErrorBoundaryFallback.tsx | 11 +- src/components/Typewriter.tsx | 12 +- src/components/blog/CommentBlock.tsx | 388 ++++++++++++ src/components/blog/CommentDeletionPrompt.tsx | 146 +++++ src/components/blog/CommentInputBlock.tsx | 81 +++ src/components/blog/CommentSection.tsx | 112 ++++ src/components/blog/CommentSectionWrapper.tsx | 555 ++++++++++++++++++ src/components/blog/CommentSorting.tsx | 83 +++ src/components/blog/CommentSortingSelect.tsx | 89 +++ src/components/blog/EditCommentModal.tsx | 70 +++ src/components/blog/PostSortingSelect.tsx | 26 +- src/components/blog/ReactionBar.tsx | 77 +++ src/components/blog/TagSelector.tsx | 31 +- src/components/icons/emojis/Angry.tsx | 26 + src/components/icons/emojis/Blank.tsx | 22 + src/components/icons/emojis/Cry.tsx | 38 ++ src/components/icons/emojis/HeartEye.tsx | 30 + src/components/icons/emojis/MoneyEye.tsx | 30 + src/components/icons/emojis/Sick.tsx | 26 + src/components/icons/emojis/Tears.tsx | 40 ++ src/components/icons/emojis/ThumbsUp.tsx | 17 + src/components/icons/emojis/Tongue.tsx | 34 ++ src/components/icons/emojis/UpsideDown.tsx | 26 + src/components/icons/emojis/Worried.tsx | 42 ++ src/context/splash.tsx | 5 +- src/lib/comment-utils.ts | 255 ++++++++ src/routes/account.tsx | 202 ++++--- src/routes/blog/[title]/index.tsx | 395 +++++++------ src/routes/blog/create/index.tsx | 93 +-- src/routes/blog/edit/[id]/index.tsx | 101 ++-- src/routes/blog/index.tsx | 77 +-- src/routes/downloads.tsx | 34 +- src/routes/test.tsx | 54 +- src/types/comment.ts | 234 ++++++++ 37 files changed, 3022 insertions(+), 442 deletions(-) create mode 100644 src/components/blog/CommentBlock.tsx create mode 100644 src/components/blog/CommentDeletionPrompt.tsx create mode 100644 src/components/blog/CommentInputBlock.tsx create mode 100644 src/components/blog/CommentSection.tsx create mode 100644 src/components/blog/CommentSectionWrapper.tsx create mode 100644 src/components/blog/CommentSorting.tsx create mode 100644 src/components/blog/CommentSortingSelect.tsx create mode 100644 src/components/blog/EditCommentModal.tsx create mode 100644 src/components/blog/ReactionBar.tsx create mode 100644 src/components/icons/emojis/Angry.tsx create mode 100644 src/components/icons/emojis/Blank.tsx create mode 100644 src/components/icons/emojis/Cry.tsx create mode 100644 src/components/icons/emojis/HeartEye.tsx create mode 100644 src/components/icons/emojis/MoneyEye.tsx create mode 100644 src/components/icons/emojis/Sick.tsx create mode 100644 src/components/icons/emojis/Tears.tsx create mode 100644 src/components/icons/emojis/ThumbsUp.tsx create mode 100644 src/components/icons/emojis/Tongue.tsx create mode 100644 src/components/icons/emojis/UpsideDown.tsx create mode 100644 src/components/icons/emojis/Worried.tsx create mode 100644 src/lib/comment-utils.ts create mode 100644 src/types/comment.ts diff --git a/bun.lockb b/bun.lockb index 4de04246c431a2ba102d142d5650ce67f6d7c718..4dd853e0659280e3f0460c66d733e760b324f3ce 100755 GIT binary patch delta 46813 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 z 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∓$8tcjC<9G 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 WvqihoPv-4PKUog4O&4NhodE#q2p{SI diff --git a/package.json b/package.json index c1041dc..c3b2979 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "node": ">=22" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.19", "@types/bcrypt": "^6.0.0", "prettier": "^3.7.4", "prettier-plugin-tailwindcss": "^0.7.2", diff --git a/src/app.css b/src/app.css index b4147e5..0181aa0 100644 --- a/src/app.css +++ b/src/app.css @@ -1,4 +1,5 @@ @import "tailwindcss"; +@plugin "@tailwindcss/typography"; :root { /* Comments indicate what they are used for in vim/term diff --git a/src/components/ErrorBoundaryFallback.tsx b/src/components/ErrorBoundaryFallback.tsx index a77cadc..ed313b1 100644 --- a/src/components/ErrorBoundaryFallback.tsx +++ b/src/components/ErrorBoundaryFallback.tsx @@ -9,7 +9,16 @@ export interface ErrorBoundaryFallbackProps { export default function ErrorBoundaryFallback( props: ErrorBoundaryFallbackProps ) { - const navigate = useNavigate(); + // Try to get navigate, but handle case where we're outside router context + let navigate: ((path: string) => void) | undefined; + try { + navigate = useNavigate(); + } catch (e) { + // If we're outside router context, fallback to window.location + navigate = (path: string) => { + window.location.href = path; + }; + } const [glitchText, setGlitchText] = createSignal("ERROR"); createEffect(() => { diff --git a/src/components/Typewriter.tsx b/src/components/Typewriter.tsx index 9efe76b..6b062cc 100644 --- a/src/components/Typewriter.tsx +++ b/src/components/Typewriter.tsx @@ -14,7 +14,7 @@ export function Typewriter(props: { const [isTyping, setIsTyping] = createSignal(false); const [isDelaying, setIsDelaying] = createSignal(delay > 0); const [keepAliveCountdown, setKeepAliveCountdown] = createSignal( - typeof keepAlive === "number" ? keepAlive : -1, + typeof keepAlive === "number" ? keepAlive : -1 ); const resolved = children(() => props.children); const { showSplash } = useSplash(); @@ -33,7 +33,7 @@ export function Typewriter(props: { textNodes.push({ node: node as Text, text: text, - startIndex: totalChars, + startIndex: totalChars }); totalChars += text.length; @@ -45,7 +45,7 @@ export function Typewriter(props: { charSpan.style.opacity = "0"; charSpan.setAttribute( "data-char-index", - String(totalChars - text.length + i), + String(totalChars - text.length + i) ); span.appendChild(charSpan); }); @@ -60,7 +60,7 @@ export function Typewriter(props: { // Position cursor at the first character location const firstChar = containerRef.querySelector( - '[data-char-index="0"]', + '[data-char-index="0"]' ) as HTMLElement; if (firstChar && cursorRef) { // Insert cursor before the first character @@ -96,7 +96,7 @@ export function Typewriter(props: { const revealNextChar = () => { if (currentIndex < totalChars) { const charSpan = containerRef?.querySelector( - `[data-char-index="${currentIndex}"]`, + `[data-char-index="${currentIndex}"]` ) as HTMLElement; if (charSpan) { @@ -106,7 +106,7 @@ export function Typewriter(props: { if (cursorRef) { charSpan.parentNode?.insertBefore( cursorRef, - charSpan.nextSibling, + charSpan.nextSibling ); // Match the height of the current character diff --git a/src/components/blog/CommentBlock.tsx b/src/components/blog/CommentBlock.tsx new file mode 100644 index 0000000..a6f6357 --- /dev/null +++ b/src/components/blog/CommentBlock.tsx @@ -0,0 +1,388 @@ +import { + createSignal, + createEffect, + For, + Show, + onMount, + onCleanup +} from "solid-js"; +import { useLocation } from "@solidjs/router"; +import type { + CommentBlockProps, + CommentReaction, + UserPublicData +} from "~/types/comment"; +import { debounce } from "~/lib/comment-utils"; +import UserDefaultImage from "~/components/icons/UserDefaultImage"; +import ReplyIcon from "~/components/icons/ReplyIcon"; +import TrashIcon from "~/components/icons/TrashIcon"; +import EditIcon from "~/components/icons/EditIcon"; +import ThumbsUpEmoji from "~/components/icons/emojis/ThumbsUp"; +import LoadingSpinner from "~/components/LoadingSpinner"; +import CommentInputBlock from "./CommentInputBlock"; +import ReactionBar from "./ReactionBar"; + +export default function CommentBlock(props: CommentBlockProps) { + const location = useLocation(); + + // State signals + const [commentCollapsed, setCommentCollapsed] = createSignal(false); + const [showingReactionOptions, setShowingReactionOptions] = + createSignal(false); + const [replyBoxShowing, setReplyBoxShowing] = createSignal(false); + const [toggleHeight, setToggleHeight] = createSignal(0); + const [reactions, setReactions] = createSignal([]); + const [windowWidth, setWindowWidth] = createSignal(0); + const [deletionLoading, setDeletionLoading] = createSignal(false); + const [userData, setUserData] = createSignal(null); + + // Refs + let containerRef: HTMLDivElement | undefined; + let commentInputRef: HTMLDivElement | undefined; + + // Auto-collapse at level 4+ + createEffect(() => { + setCommentCollapsed(props.level >= 4); + }); + + // Window resize handler + onMount(() => { + const handleResize = debounce(() => { + setWindowWidth(window.innerWidth); + }, 200); + + window.addEventListener("resize", handleResize); + + onCleanup(() => { + window.removeEventListener("resize", handleResize); + }); + }); + + // Find user data from comment map + createEffect(() => { + if (props.userCommentMap) { + props.userCommentMap.forEach((commentIds, user) => { + if (commentIds.includes(props.comment.id)) { + setUserData(user); + } + }); + } + }); + + // Update toggle height based on container size + createEffect(() => { + if (containerRef) { + const correction = showingReactionOptions() ? 80 : 48; + setToggleHeight(containerRef.clientHeight + correction); + } + // Trigger on these dependencies + windowWidth(); + showingReactionOptions(); + }); + + // Update reactions from map + createEffect(() => { + setReactions(props.reactionMap.get(props.comment.id) || []); + }); + + // Event handlers + const collapseCommentToggle = (e: MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setCommentCollapsed(!commentCollapsed()); + }; + + const showingReactionOptionsToggle = (e: MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setShowingReactionOptions(!showingReactionOptions()); + }; + + const toggleCommentReplyBox = (e: MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setReplyBoxShowing(!replyBoxShowing()); + }; + + const deleteCommentTrigger = async (e: MouseEvent) => { + e.stopPropagation(); + setDeletionLoading(true); + const user = userData(); + props.toggleModification( + props.comment.id, + props.comment.commenter_id, + props.comment.body, + "delete", + user?.image, + user?.email, + user?.display_name + ); + setDeletionLoading(false); + }; + + const editCommentTrigger = (e: MouseEvent) => { + e.stopPropagation(); + const user = userData(); + props.toggleModification( + props.comment.id, + props.comment.commenter_id, + props.comment.body, + "edit", + user?.image, + user?.email, + user?.display_name + ); + }; + + // Computed values + const upvoteCount = () => + reactions().filter((r) => r.type === "upVote").length; + + const downvoteCount = () => + reactions().filter((r) => r.type === "downVote").length; + + const hasUpvoted = () => + reactions().some( + (r) => r.type === "upVote" && r.user_id === props.currentUserID + ); + + const hasDownvoted = () => + reactions().some( + (r) => r.type === "downVote" && r.user_id === props.currentUserID + ); + + const canDelete = () => + props.currentUserID === props.comment.commenter_id || + props.privilegeLevel === "admin"; + + const canEdit = () => props.currentUserID === props.comment.commenter_id; + + const isAnonymous = () => props.privilegeLevel === "anonymous"; + + const replyIconColor = () => + location.pathname.split("/")[1] === "blog" ? "#fb923c" : "#60a5fa"; + + return ( + <> + {/* Collapsed state */} + + + + + {/* Expanded state */} + +
+
+ {/* Vote buttons column */} +
+ {/* Upvote */} + + + {/* Vote count */} +
{upvoteCount() - downvoteCount()}
+ + {/* Downvote */} + +
+ + {/* Collapse toggle line */} + + + {/* Comment content */} +
+
+
+ {props.comment.body} +
+ +
Edited
+
+
+ + {/* User info */} +
+ + } + > + user-image + +
+ {userData()?.display_name || userData()?.email || "[removed]"} +
+ + {/* Delete button */} + + + +
+ + {/* Edit and Reply buttons */} +
+ + + + +
+ + {/* Reaction bar */} +
0 + ? "" + : "opacity-0" + } ml-16`} + > + +
+
+
+ + {/* Reply box */} + +
+ +
+
+ + {/* Recursive child comments */} +
+ + {(childComment) => ( + comment.parent_comment_id === childComment.id + )} + privilegeLevel={props.privilegeLevel} + currentUserID={props.currentUserID} + reactionMap={props.reactionMap} + level={props.level + 1} + socket={props.socket} + userCommentMap={props.userCommentMap} + toggleModification={props.toggleModification} + newComment={props.newComment} + commentSubmitLoading={props.commentSubmitLoading} + commentReaction={props.commentReaction} + /> + )} + +
+
+
+ + ); +} diff --git a/src/components/blog/CommentDeletionPrompt.tsx b/src/components/blog/CommentDeletionPrompt.tsx new file mode 100644 index 0000000..f6da23d --- /dev/null +++ b/src/components/blog/CommentDeletionPrompt.tsx @@ -0,0 +1,146 @@ +import { createSignal, Show } from "solid-js"; +import type { CommentDeletionPromptProps, DeletionType } from "~/types/comment"; +import UserDefaultImage from "~/components/icons/UserDefaultImage"; +import Xmark from "~/components/icons/Xmark"; + +export default function CommentDeletionPrompt( + props: CommentDeletionPromptProps +) { + const [normalDeleteChecked, setNormalDeleteChecked] = createSignal(false); + const [adminDeleteChecked, setAdminDeleteChecked] = createSignal(false); + const [fullDeleteChecked, setFullDeleteChecked] = createSignal(false); + + const handleNormalDeleteCheckbox = () => { + setNormalDeleteChecked(!normalDeleteChecked()); + setFullDeleteChecked(false); + setAdminDeleteChecked(false); + }; + + const handleAdminDeleteCheckbox = () => { + setAdminDeleteChecked(!adminDeleteChecked()); + setFullDeleteChecked(false); + setNormalDeleteChecked(false); + }; + + const handleFullDeleteCheckbox = () => { + setFullDeleteChecked(!fullDeleteChecked()); + setNormalDeleteChecked(false); + setAdminDeleteChecked(false); + }; + + const deletionWrapper = () => { + let deleteType: DeletionType = "user"; + if (normalDeleteChecked()) { + deleteType = "user"; + } else if (adminDeleteChecked()) { + deleteType = "admin"; + } else if (fullDeleteChecked()) { + deleteType = "database"; + } + props.deleteComment(props.commentID, props.commenterID, deleteType); + }; + + const isDeleteEnabled = () => + normalDeleteChecked() || adminDeleteChecked() || fullDeleteChecked(); + + return ( +
+
+
+ +
+ Comment Deletion +
+
+
+ {/* Comment body will be passed as prop */} +
+
+ + } + > + user-image + +
+ {props.commenterDisplayName || + props.commenterEmail || + "[removed]"} +
+
+
+
+
+ +
+ {props.privilegeLevel === "admin" + ? "Confirm User Delete?" + : "Confirm Delete?"} +
+
+
+ +
+
+ +
+ Confirm Admin Delete? +
+
+
+
+
+ +
+ Confirm Full Delete (removal from database)? +
+
+
+
+
+ +
+
+
+
+ ); +} diff --git a/src/components/blog/CommentInputBlock.tsx b/src/components/blog/CommentInputBlock.tsx new file mode 100644 index 0000000..5119d0e --- /dev/null +++ b/src/components/blog/CommentInputBlock.tsx @@ -0,0 +1,81 @@ +import { createEffect } from "solid-js"; +import type { CommentInputBlockProps } from "~/types/comment"; + +export default function CommentInputBlock(props: CommentInputBlockProps) { + let bodyRef: HTMLTextAreaElement | undefined; + + // Clear the textarea when comment is submitted + createEffect(() => { + if (!props.commentSubmitLoading && bodyRef) { + bodyRef.value = ""; + } + }); + + const newCommentWrapper = (e: SubmitEvent) => { + e.preventDefault(); + if (bodyRef && bodyRef.value.length > 0) { + props.newComment(bodyRef.value, props.parent_id); + } + }; + + if (props.privilegeLevel === "user" || props.privilegeLevel === "admin") { + return ( +
+
+
+
+