styling fixes

This commit is contained in:
Michael Freno
2025-12-21 01:00:57 -05:00
parent 291971a1d7
commit 200037e7a0
9 changed files with 197 additions and 102 deletions

View File

@@ -159,7 +159,7 @@ export default function CommentBlock(props: CommentBlockProps) {
const isAnonymous = () => props.privilegeLevel === "anonymous";
const replyIconColor = () => "#fb923c";
const replyIconColor = () => "var(--color-peach)";
return (
<>
@@ -169,13 +169,13 @@ export default function CommentBlock(props: CommentBlockProps) {
onClick={collapseCommentToggle}
class="ml-5 w-full px-2 lg:w-3/4"
>
<div class="my-auto mt-1 mr-2 h-8 border-l-2 border-black dark:border-white" />
<div class="border-text my-auto mt-1 mr-2 h-8 border-l-2" />
</button>
</Show>
{/* Expanded state */}
<Show when={!commentCollapsed()}>
<div class="z-[500] transition-all duration-300 ease-in-out">
<div class="z-500 transition-all duration-300 ease-in-out">
<div class="my-4 flex w-full overflow-x-hidden overflow-y-hidden lg:w-3/4">
{/* Vote buttons column */}
<div
@@ -191,8 +191,8 @@ export default function CommentBlock(props: CommentBlockProps) {
<div
class={`h-5 w-5 ${
hasUpvoted()
? "fill-emerald-500"
: `fill-black hover:fill-emerald-500 dark:fill-white ${
? "fill-green"
: `fill-text hover:fill-green ${
isAnonymous() ? "tooltip z-50" : ""
}`
}`}
@@ -218,8 +218,8 @@ export default function CommentBlock(props: CommentBlockProps) {
<div
class={`h-5 w-5 ${
hasDownvoted()
? "fill-rose-500"
: `fill-black hover:fill-rose-500 dark:fill-white ${
? "fill-red"
: `fill-text hover:fill-red ${
isAnonymous() ? "tooltip z-50" : ""
}`
}`}
@@ -239,7 +239,7 @@ export default function CommentBlock(props: CommentBlockProps) {
{/* Collapse toggle line */}
<button onClick={collapseCommentToggle} class="z-0 px-2">
<div
class="border-l-2 border-black transition-all duration-300 ease-in-out dark:border-white"
class="border-text border-l-2 transition-all duration-300 ease-in-out"
style={{ height: `${toggleHeight()}px` }}
/>
</button>
@@ -292,7 +292,7 @@ export default function CommentBlock(props: CommentBlockProps) {
<TrashIcon
height={24}
width={24}
stroke="red"
stroke="var(--color-red)"
strokeWidth={1.5}
/>
</Show>

View File

@@ -45,10 +45,10 @@ export default function CommentDeletionPrompt(
return (
<div class="flex justify-center">
<div class="fixed top-48 z-50 h-fit w-11/12 sm:w-4/5 md:w-2/3">
<div class="fixed top-48 z-100 h-fit w-11/12 sm:w-4/5 md:w-2/3">
<div
id="delete_prompt"
class="fade-in rounded-md bg-red-400 px-8 py-4 shadow-lg dark:bg-red-900"
class="fade-in bg-red rounded-md px-8 py-4 shadow-lg brightness-110"
>
<button class="absolute right-4" onClick={() => {}}>
<Xmark strokeWidth={0.5} color="white" height={50} width={50} />
@@ -56,7 +56,7 @@ export default function CommentDeletionPrompt(
<div class="py-4 text-center text-3xl tracking-wide">
Comment Deletion
</div>
<div class="mx-auto w-3/4 rounded bg-zinc-50 px-6 py-4 dark:bg-zinc-800">
<div class="bg-surface0 mx-auto w-3/4 rounded px-6 py-4">
<div class="flex overflow-x-auto overflow-y-hidden select-text">
{/* Comment body will be passed as prop */}
</div>
@@ -132,9 +132,9 @@ export default function CommentDeletionPrompt(
disabled={props.commentDeletionLoading || !isDeleteEnabled()}
class={`${
props.commentDeletionLoading || !isDeleteEnabled()
? "bg-zinc-400"
: "border-orange-500 bg-orange-400 hover:bg-orange-500"
} rounded border px-4 py-2 text-white shadow-md transition-all duration-300 ease-in-out active:scale-90`}
? "bg-surface2 opacity-50"
: "border-red bg-red hover:brightness-125"
} rounded border px-4 py-2 text-base shadow-md transition-all duration-300 ease-in-out active:scale-90`}
>
Delete
</button>

View File

@@ -21,7 +21,7 @@ export default function CommentInputBlock(props: CommentInputBlockProps) {
if (props.privilegeLevel === "user" || props.privilegeLevel === "admin") {
return (
<div class="flex w-full justify-center select-none">
<div class="h-fit w-3/4 md:w-1/2">
<div class="h-fit w-3/4">
<form onSubmit={newCommentWrapper}>
<div class="textarea-group blog">
<textarea
@@ -43,9 +43,9 @@ export default function CommentInputBlock(props: CommentInputBlockProps) {
disabled={props.commentSubmitLoading}
class={`${
props.commentSubmitLoading
? "bg-zinc-400"
: "border-orange-500 bg-orange-400 hover:bg-orange-500"
} rounded border px-4 py-2 font-light text-white shadow-md transition-all duration-300 ease-in-out active:scale-90`}
? "bg-surface2 opacity-50"
: "border-sapphire bg-blue hover:brightness-125"
} rounded border px-4 py-2 text-base font-light shadow-md transition-all duration-300 ease-in-out active:scale-90`}
>
Submit
</button>

View File

@@ -130,7 +130,16 @@ export default function CommentSectionWrapper(
// Helper functions
const updateChannel = () => {
if (socket && socket.readyState === WebSocket.OPEN) {
if (!socket || socket.readyState !== WebSocket.OPEN) {
return;
}
if (!props.currentUserID || !props.id) {
console.warn("Cannot update channel: missing userID or postID");
return;
}
try {
socket.send(
JSON.stringify({
action: "channelUpdate",
@@ -139,25 +148,49 @@ export default function CommentSectionWrapper(
invoker_id: props.currentUserID
})
);
} catch (error) {
console.error("Error sending channel update:", error);
}
};
// Comment creation
const newComment = async (commentBody: string, parentCommentID?: number) => {
setCommentSubmitLoading(true);
if (!props.currentUserID) {
console.warn("Cannot create comment: user not authenticated");
setCommentSubmitLoading(false);
return;
}
if (commentBody && socket) {
socket.send(
JSON.stringify({
action: "commentCreation",
commentBody: commentBody,
postType: "blog",
postID: props.id,
parentCommentID: parentCommentID,
invokerID: props.currentUserID
})
);
try {
socket.send(
JSON.stringify({
action: "commentCreation",
commentBody: commentBody,
postType: "blog",
postID: props.id,
parentCommentID: parentCommentID,
invokerID: props.currentUserID
})
);
} catch (error) {
console.error("Error sending comment creation:", error);
// Fallback to HTTP API on WebSocket error
await fallbackCommentCreation(commentBody, parentCommentID);
}
} else {
// Fallback to HTTP API if WebSocket unavailable
await fallbackCommentCreation(commentBody, parentCommentID);
}
};
const fallbackCommentCreation = async (
commentBody: string,
parentCommentID?: number
) => {
try {
const domain = import.meta.env.VITE_DOMAIN;
const res = await fetch(
`${domain}/api/database/comments/create/blog/${props.id}`,
@@ -179,6 +212,9 @@ export default function CommentSectionWrapper(
commentParent: parentCommentID
});
}
} catch (error) {
console.error("Error in fallback comment creation:", error);
setCommentSubmitLoading(false);
}
};
@@ -237,17 +273,29 @@ export default function CommentSectionWrapper(
// Comment updating
const editComment = async (body: string, comment_id: number) => {
setCommentEditLoading(true);
if (!props.currentUserID) {
console.warn("Cannot edit comment: user not authenticated");
setCommentEditLoading(false);
return;
}
if (socket) {
socket.send(
JSON.stringify({
action: "commentUpdate",
commentBody: body,
postType: "blog",
postID: props.id,
commentID: comment_id,
invokerID: props.currentUserID
})
);
try {
socket.send(
JSON.stringify({
action: "commentUpdate",
commentBody: body,
postType: "blog",
postID: props.id,
commentID: comment_id,
invokerID: props.currentUserID
})
);
} catch (error) {
console.error("Error sending comment update:", error);
setCommentEditLoading(false);
}
}
};
@@ -290,17 +338,29 @@ export default function CommentSectionWrapper(
deletionType: DeletionType
) => {
setCommentDeletionLoading(true);
if (!props.currentUserID) {
console.warn("Cannot delete comment: user not authenticated");
setCommentDeletionLoading(false);
return;
}
if (socket) {
socket.send(
JSON.stringify({
action: "commentDeletion",
deleteType: deletionType,
commentID: commentID,
invokerID: props.currentUserID,
postType: "blog",
postID: props.id
})
);
try {
socket.send(
JSON.stringify({
action: "commentDeletion",
deleteType: deletionType,
commentID: commentID,
invokerID: props.currentUserID,
postType: "blog",
postID: props.id
})
);
} catch (error) {
console.error("Error sending comment deletion:", error);
setCommentDeletionLoading(false);
}
}
};
@@ -393,17 +453,26 @@ export default function CommentSectionWrapper(
// Reaction handling
const commentReaction = (reactionType: ReactionType, commentID: number) => {
if (!props.currentUserID) {
console.warn("Cannot react to comment: user not authenticated");
return;
}
if (socket) {
socket.send(
JSON.stringify({
action: "commentReaction",
postType: "blog",
postID: props.id,
commentID: commentID,
invokerID: props.currentUserID,
reactionType: reactionType
})
);
try {
socket.send(
JSON.stringify({
action: "commentReaction",
postType: "blog",
postID: props.id,
commentID: commentID,
invokerID: props.currentUserID,
reactionType: reactionType
})
);
} catch (error) {
console.error("Error sending comment reaction:", error);
}
}
};

View File

@@ -40,7 +40,7 @@ export default function CommentSorting(props: CommentSortingProps) {
{(topLevelComment) => (
<div
onClick={() => checkForDoubleClick(topLevelComment.id)}
class="mt-4 max-w-full rounded bg-white py-2 pl-2 shadow select-none sm:pl-4 md:pl-8 lg:pl-12 dark:bg-zinc-900"
class="bg-crust mt-4 max-w-full rounded py-2 pl-2 shadow-xl select-none sm:pl-4 md:pl-8 lg:pl-12"
>
<Show
when={showingBlock().get(topLevelComment.id)}

View File

@@ -43,7 +43,7 @@ export default function CommentSortingSelect(props: CommentSortingSelectProps) {
<button
type="button"
onClick={() => setIsOpen(!isOpen())}
class="focus-visible:ring-opacity-75 relative w-full cursor-default rounded-lg bg-white py-2 pr-10 pl-3 text-left shadow-md focus:outline-none focus-visible:border-orange-600 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm dark:bg-zinc-900"
class="focus-visible:border-peach focus-visible:ring-offset-peach bg-surface0 focus-visible:ring-opacity-75 relative w-full cursor-default rounded-lg py-2 pr-10 pl-3 text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 sm:text-sm"
>
<span class="block truncate">{selectedLabel()}</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
@@ -51,13 +51,13 @@ export default function CommentSortingSelect(props: CommentSortingSelectProps) {
strokeWidth={1.5}
height={24}
width={24}
class="fill-zinc-900 dark:fill-white"
class="fill-text"
/>
</span>
</button>
<Show when={isOpen()}>
<div class="ring-opacity-5 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black transition duration-100 ease-in focus:outline-none sm:text-sm dark:bg-zinc-900">
<div class="ring-opacity-5 bg-surface0 ring-overlay0 absolute mt-1 max-h-60 w-full overflow-auto rounded-md py-1 text-base shadow-lg ring-1 focus:outline-none sm:text-sm">
<For each={SORTING_OPTIONS}>
{(sort) => (
<button
@@ -65,8 +65,8 @@ export default function CommentSortingSelect(props: CommentSortingSelectProps) {
onClick={() => handleSelect(sort.val)}
class={`relative w-full cursor-default py-2 pr-4 pl-10 text-left select-none ${
props.selectedSorting.val === sort.val
? "bg-orange-100 text-orange-900"
: "text-zinc-900 hover:bg-orange-50 dark:text-white dark:hover:bg-zinc-800"
? "bg-peach text-base brightness-75"
: "text-text hover:brightness-125"
}`}
>
<span
@@ -79,12 +79,12 @@ export default function CommentSortingSelect(props: CommentSortingSelectProps) {
{sort.label}
</span>
<Show when={props.selectedSorting.val === sort.val}>
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-orange-600">
<span class="text-peach absolute inset-y-0 left-0 flex items-center pl-3">
<Check
strokeWidth={1}
height={24}
width={24}
class="stroke-zinc-900 dark:stroke-white"
class="stroke-text"
/>
</span>
</Show>

View File

@@ -25,12 +25,17 @@ export default function EditCommentModal(props: EditCommentModalProps) {
<div class="fixed top-48 h-fit w-11/12 sm:w-4/5 md:w-2/3">
<div
id="edit_prompt"
class="fade-in z-50 rounded-md bg-zinc-600 px-8 py-4 shadow-lg dark:bg-zinc-800"
class="fade-in bg-surface1 z-50 rounded-md px-8 py-4 shadow-lg"
>
<button class="absolute right-4" onClick={() => {}}>
<Xmark strokeWidth={0.5} color="white" height={50} width={50} />
<Xmark
strokeWidth={0.5}
color="var(--color-text)"
height={50}
width={50}
/>
</button>
<div class="py-4 text-center text-3xl tracking-wide text-zinc-50">
<div class="text-text py-4 text-center text-3xl tracking-wide">
Edit Comment
</div>
<form onSubmit={editCommentWrapper}>
@@ -40,7 +45,7 @@ export default function EditCommentModal(props: EditCommentModalProps) {
ref={bodyRef}
placeholder=" "
value={props.commentBody}
class="underlinedInput w-full bg-transparent text-blue-300"
class="underlinedInput text-blue w-full bg-transparent"
rows={4}
/>
<span class="bar" />
@@ -51,17 +56,17 @@ export default function EditCommentModal(props: EditCommentModalProps) {
type="submit"
disabled={props.editCommentLoading}
class={`${
props.editCommentLoading ? "bg-zinc-400" : ""
} rounded border px-4 py-2 text-white shadow-md transition-all duration-300 ease-in-out hover:border-blue-500 hover:bg-blue-400 active:scale-90`}
props.editCommentLoading
? "bg-surface2 opacity-50"
: "border-sapphire bg-blue hover:brightness-125"
} rounded border px-4 py-2 text-base shadow-md transition-all duration-300 ease-in-out active:scale-90`}
>
Submit
</button>
</div>
</form>
<Show when={showNoChange()}>
<div class="text-center text-red-500 italic">
No change detected
</div>
<div class="text-red text-center italic">No change detected</div>
</Show>
</div>
</div>

View File

@@ -49,7 +49,7 @@ export default function ReactionBar(props: ReactionBarProps) {
<div
class={`${
props.showingReactionOptions
? "bg-zinc-50 px-2 py-4 shadow-inner dark:bg-zinc-700"
? "bg-surface0 px-2 py-4 shadow-inner brightness-90"
: ""
} fade-in scrollYDisabled ml-2 flex min-h-[1.5rem] w-48 max-w-[1/4] flex-row overflow-scroll rounded-md py-1 sm:w-56 md:w-fit md:overflow-hidden`}
>
@@ -57,7 +57,7 @@ export default function ReactionBar(props: ReactionBarProps) {
{({ type, Component }) => (
<Show when={shouldShowEmoji(type)}>
<div class="fade-in mx-1 flex">
<div class={hasUserReacted(type) ? "text-green-500" : ""}>
<div class={hasUserReacted(type) ? "text-green" : ""}>
<Show when={getReactionCount(type) > 0}>
{getReactionCount(type)}
</Show>

View File

@@ -348,29 +348,50 @@ export default function TextEditor(props: TextEditorProps) {
const title = window.prompt("Section title:", "Click to expand");
if (title !== null) {
instance
.chain()
.focus()
.insertContent({
type: "details",
content: [
{
type: "detailsSummary",
content: [{ type: "text", text: title }]
},
{
type: "detailsContent",
content: [
{
type: "paragraph",
content: [{ type: "text", text: "Add your content here..." }]
}
]
}
]
})
.run();
if (title !== null && title.trim() !== "") {
const content = {
type: "details",
attrs: { open: true },
content: [
{
type: "detailsSummary",
content: [{ type: "text", text: title }]
},
{
type: "detailsContent",
content: [
{
type: "paragraph"
}
]
}
]
};
const { from } = instance.state.selection;
instance.chain().focus().insertContent(content).run();
// Move cursor to the paragraph inside detailsContent
// Structure: details (from+1) > detailsSummary > detailsContent > paragraph
// We need to position inside the paragraph which is roughly from + title.length + 3 nodes deep
setTimeout(() => {
const { state } = instance;
let targetPos = from;
// Navigate through the document to find the paragraph inside detailsContent
state.doc.nodesBetween(from, from + 200, (node, pos) => {
if (node.type.name === "detailsContent") {
// Position cursor at the start of the first child (paragraph)
targetPos = pos + 1;
return false; // Stop iteration
}
});
if (targetPos > from) {
instance.commands.setTextSelection(targetPos);
instance.commands.focus();
}
}, 10);
}
};