removed excess comments

This commit is contained in:
Michael Freno
2026-01-04 11:14:54 -05:00
parent b81de6441b
commit 7e89e6dda2
68 changed files with 72 additions and 941 deletions

View File

@@ -25,16 +25,13 @@ export default function CommentBlock(props: CommentBlockProps) {
const [deletionLoading, setDeletionLoading] = createSignal(false);
const [userData, setUserData] = createSignal<UserPublicData | null>(null);
// Refs
let containerRef: HTMLDivElement | undefined;
let commentInputRef: HTMLDivElement | undefined;
// Auto-collapse at level 4+
createEffect(() => {
setCommentCollapsed(props.level >= 4);
});
// Find user data from comment map
createEffect(() => {
if (props.userCommentMap) {
props.userCommentMap.forEach((commentIds, user) => {
@@ -45,23 +42,19 @@ export default function CommentBlock(props: CommentBlockProps) {
}
});
// 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();
@@ -112,7 +105,6 @@ export default function CommentBlock(props: CommentBlockProps) {
);
};
// Computed values
const upvoteCount = () =>
reactions().filter((r) => r.type === "upVote").length;

View File

@@ -4,7 +4,6 @@ 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 = "";

View File

@@ -8,14 +8,12 @@ export default function CommentSorting(props: CommentSortingProps) {
new Map(props.topLevelComments?.map((comment) => [comment.id, true]))
);
// Update showing block when top level comments change
createEffect(() => {
setShowingBlock(
new Map(props.topLevelComments?.map((comment) => [comment.id, true]))
);
});
// Reset clickedOnce after timeout
createEffect(() => {
if (clickedOnce()) {
setTimeout(() => setClickedOnce(false), 300);
@@ -34,7 +32,6 @@ export default function CommentSorting(props: CommentSortingProps) {
}
};
// Comments are already sorted from server, no need for client-side sorting
return (
<For each={props.topLevelComments}>
{(topLevelComment) => (

View File

@@ -27,7 +27,6 @@ export default function CommentSortingSelect(props: CommentSortingSelectProps) {
props.setSorting(mode);
setIsOpen(false);
// Update URL with sortBy parameter
const url = new URL(window.location.href);
url.searchParams.set("sortBy", mode);
navigate(`${location.pathname}?${url.searchParams.toString()}#comments`, {

View File

@@ -18,7 +18,6 @@ export default function DeletePostButton(props: DeletePostButtonProps) {
setLoading(true);
try {
await api.database.deletePost.mutate({ id: props.postID });
// Refresh the page after successful deletion
window.location.reload();
} catch (error) {
alert("Failed to delete post");

View File

@@ -32,14 +32,12 @@ export default function MermaidRenderer() {
const id = `mermaid-${index}-${Math.random().toString(36).substr(2, 9)}`;
const { svg } = await mermaid.render(id, content);
// Replace the pre/code with rendered SVG
const wrapper = document.createElement("div");
wrapper.className = "mermaid-rendered";
wrapper.innerHTML = svg;
pre.replaceWith(wrapper);
} catch (err) {
console.error("Failed to render mermaid diagram:", err);
// Keep the original code block if rendering fails
pre.classList.add("mermaid-error");
}
});

View File

@@ -105,10 +105,8 @@ export default function PostBodyClient(props: PostBodyClientProps) {
const pre = codeBlock.parentElement;
if (!pre) return;
// Skip mermaid diagrams
if (pre.dataset.type === "mermaid") return;
// Check if already processed (has header with copy button)
const existingHeader = pre.previousElementSibling;
if (
existingHeader?.classList.contains("language-header") &&
@@ -117,53 +115,43 @@ export default function PostBodyClient(props: PostBodyClientProps) {
return;
}
// Set off-black background for code block
pre.style.backgroundColor = "#1a1a1a";
// Extract language from code block classes
const classes = Array.from(codeBlock.classList);
const languageClass = classes.find((cls) => cls.startsWith("language-"));
const language = languageClass?.replace("language-", "") || "";
// Create language header if language is detected
if (language) {
const languageHeader = document.createElement("div");
languageHeader.className = "language-header";
languageHeader.style.backgroundColor = "#1a1a1a";
// Add language label
const languageLabel = document.createElement("span");
languageLabel.textContent = language;
languageHeader.appendChild(languageLabel);
// Create copy button in header
const copyButton = document.createElement("button");
copyButton.className = "copy-button";
copyButton.textContent = "Copy";
copyButton.dataset.codeBlock = "true";
// Store reference to the code block for copying
copyButton.dataset.codeBlockId = `code-${Math.random().toString(36).substr(2, 9)}`;
codeBlock.dataset.codeBlockId = copyButton.dataset.codeBlockId;
languageHeader.appendChild(copyButton);
// Insert header before pre element
pre.parentElement?.insertBefore(languageHeader, pre);
}
// Add line numbers
const codeText = codeBlock.textContent || "";
const lines = codeText.split("\n");
const lineCount =
lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
if (lineCount > 0 && !pre.querySelector(".line-numbers")) {
// Create line numbers container
const lineNumbers = document.createElement("div");
lineNumbers.className = "line-numbers";
// Generate line numbers
for (let i = 1; i <= lineCount; i++) {
const lineNum = document.createElement("div");
lineNum.textContent = i.toString();
@@ -214,7 +202,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
}
});
// Look for the references section marker to get the custom heading name
const marker = contentRef.querySelector(
"span[id='references-section-start']"
) as HTMLElement | null;
@@ -233,13 +220,11 @@ export default function PostBodyClient(props: PostBodyClientProps) {
if (referencesSection) {
referencesSection.className = "text-2xl font-bold mb-4 text-text";
// Find the parent container and add styling
const parentDiv = referencesSection.parentElement;
if (parentDiv) {
parentDiv.classList.add("references-heading");
}
// Find all paragraphs after the References heading that start with [n]
let currentElement = referencesSection.nextElementSibling;
while (currentElement) {
@@ -251,29 +236,22 @@ export default function PostBodyClient(props: PostBodyClientProps) {
const refNumber = match[1];
const refId = `ref-${refNumber}`;
// Set the ID for linking
currentElement.id = refId;
// Add styling
currentElement.className =
"reference-item transition-colors duration-500 text-sm mb-3";
// Parse and style the content - get everything after [n]
let refText = text.substring(match[0].length);
// Remove any existing "↑ Back" text (including various Unicode arrow variants)
refText = refText.replace(/[↑⬆️]\s*Back\s*$/i, "").trim();
// Create styled content
currentElement.innerHTML = "";
// Add bold reference number
const refNumSpan = document.createElement("span");
refNumSpan.className = "text-blue font-semibold";
refNumSpan.textContent = `[${refNumber}]`;
currentElement.appendChild(refNumSpan);
// Add reference text
if (refText) {
const refTextSpan = document.createElement("span");
refTextSpan.className = "ml-2";
@@ -286,7 +264,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
currentElement.appendChild(refTextSpan);
}
// Add back button
const backLink = document.createElement("a");
backLink.href = `#ref-${refNumber}-back`;
backLink.className =
@@ -297,7 +274,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
const target = document.getElementById(`ref-${refNumber}-back`);
if (target) {
target.scrollIntoView({ behavior: "smooth", block: "center" });
// Highlight the reference link briefly
target.style.backgroundColor = "rgba(203, 166, 247, 0.2)";
setTimeout(() => {
target.style.backgroundColor = "";
@@ -308,7 +284,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
}
}
// Check if we've reached another heading (end of references)
if (
currentElement.tagName.match(/^H[1-6]$/) &&
currentElement !== referencesSection
@@ -321,14 +296,12 @@ export default function PostBodyClient(props: PostBodyClientProps) {
}
};
// Load highlight.js only when needed
createEffect(() => {
if (props.hasCodeBlock && !hljs()) {
loadHighlightJS().then(setHljs);
}
});
// Apply syntax highlighting when hljs loads and when body changes
createEffect(() => {
const hljsInstance = hljs();
if (hljsInstance && props.hasCodeBlock && contentRef) {
@@ -339,7 +312,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
}
});
// Process references after content is mounted and when body changes
onMount(() => {
setTimeout(() => {
processReferences();
@@ -348,14 +320,11 @@ export default function PostBodyClient(props: PostBodyClientProps) {
}
}, 150);
// Event delegation for copy buttons (single listener for all buttons)
if (contentRef) {
const handleCopyButtonInteraction = async (e: Event) => {
const target = e.target as HTMLElement;
// Handle click
if (e.type === "click" && target.classList.contains("copy-button")) {
// Find the code block using the stored ID
const codeBlockId = target.dataset.codeBlockId;
const codeBlock = codeBlockId
? contentRef?.querySelector(
@@ -389,13 +358,11 @@ export default function PostBodyClient(props: PostBodyClientProps) {
}
};
// Single event listener for all copy button interactions
contentRef.addEventListener("click", handleCopyButtonInteraction);
}
});
createEffect(() => {
// Re-process when body changes
if (props.body && contentRef) {
setTimeout(() => {
processReferences();

View File

@@ -21,7 +21,6 @@ export default function PostSorting(props: PostSortingProps) {
const filteredPosts = createMemo(() => {
let filtered = props.posts;
// Apply publication status filter (admin only)
if (props.privilegeLevel === "admin" && props.status) {
if (props.status === "published") {
filtered = filtered.filter((post) => post.published === 1);
@@ -30,24 +29,20 @@ export default function PostSorting(props: PostSortingProps) {
}
}
// Build map of post_id -> tags for that post
const postTags = new Map<number, Set<string>>();
props.tags.forEach((tag) => {
if (!postTags.has(tag.post_id)) {
postTags.set(tag.post_id, new Set());
}
// Tag values in DB have # prefix, remove it for comparison
const tagWithoutHash = tag.value.startsWith("#")
? tag.value.slice(1)
: tag.value;
postTags.get(tag.post_id)!.add(tagWithoutHash);
});
// WHITELIST MODE: Only show posts that have at least one of the included tags
if (props.include !== undefined) {
const includeList = props.include.split("|").filter(Boolean);
// Empty whitelist means show nothing
if (includeList.length === 0) {
return [];
}
@@ -58,7 +53,6 @@ export default function PostSorting(props: PostSortingProps) {
const tags = postTags.get(post.id);
if (!tags || tags.size === 0) return false;
// Post must have at least one tag from the include list
for (const tag of tags) {
if (includeSet.has(tag)) {
return true;
@@ -68,11 +62,9 @@ export default function PostSorting(props: PostSortingProps) {
});
}
// BLACKLIST MODE: Hide posts that have ANY of the filtered tags
if (props.filters !== undefined) {
const filterList = props.filters.split("|").filter(Boolean);
// Empty blacklist means show everything
if (filterList.length === 0) {
return filtered;
}
@@ -81,19 +73,17 @@ export default function PostSorting(props: PostSortingProps) {
return filtered.filter((post) => {
const tags = postTags.get(post.id);
if (!tags || tags.size === 0) return true; // Show posts with no tags
if (!tags || tags.size === 0) return true;
// Post must NOT have any blacklisted tags
for (const tag of tags) {
if (filterSet.has(tag)) {
return false; // Hide this post
return false;
}
}
return true; // Show this post
return true;
});
}
// No filters: show all posts
return filtered;
});
@@ -105,7 +95,6 @@ export default function PostSorting(props: PostSortingProps) {
sorted.reverse(); // Posts come oldest first from DB
break;
case "oldest":
// Already in oldest order from DB
break;
case "most_liked":
sorted.sort((a, b) => (b.total_likes || 0) - (a.total_likes || 0));

View File

@@ -19,7 +19,6 @@ export default function PostSortingSelect(props: PostSortingSelectProps) {
const location = useLocation();
const [searchParams] = useSearchParams();
// Derive selected from URL params instead of local state
const selected = () => {
const sortParam = searchParams.sort || "newest";
return sorting.find((s) => s.val === sortParam) || sorting[0];
@@ -28,7 +27,6 @@ export default function PostSortingSelect(props: PostSortingSelectProps) {
const handleSelect = (sort: { val: string; label: string }) => {
setIsOpen(false);
// Build new URL preserving all existing params
const params = new URLSearchParams(searchParams as Record<string, string>);
params.set("sort", sort.val);

View File

@@ -29,7 +29,6 @@ export default function TagSelector(props: TagSelectorProps) {
const currentInclude = () =>
searchParams.include?.split("|").filter(Boolean) || [];
// Get currently selected tags based on mode
const selectedTags = () => {
if (filterMode() === "whitelist") {
return currentInclude();
@@ -38,14 +37,12 @@ export default function TagSelector(props: TagSelectorProps) {
}
};
// Sync filter mode with URL params and ensure one is always present
createEffect(() => {
if ("include" in searchParams) {
setFilterMode("whitelist");
} else if ("filter" in searchParams) {
setFilterMode("blacklist");
} else {
// No filter param exists, default to blacklist mode with empty filter
const params = new URLSearchParams(
searchParams as Record<string, string>
);
@@ -66,7 +63,6 @@ export default function TagSelector(props: TagSelectorProps) {
Object.keys(props.tagMap).map((key) => key.slice(1))
);
// Check if a tag is currently selected
const isTagChecked = (tag: string) => {
return selectedTags().includes(tag);
};
@@ -106,26 +102,21 @@ export default function TagSelector(props: TagSelectorProps) {
let newSelected: string[];
if (isChecked) {
// Add tag to selection
newSelected = [...currentSelected, tag];
} else {
// Remove tag from selection
newSelected = currentSelected.filter((t) => t !== tag);
}
// Build URL preserving all existing params
const params = new URLSearchParams(searchParams as Record<string, string>);
const paramName = filterMode() === "whitelist" ? "include" : "filter";
const otherParamName = filterMode() === "whitelist" ? "filter" : "include";
// Remove the other mode's param
params.delete(otherParamName);
if (newSelected.length > 0) {
const paramValue = newSelected.join("|");
params.set(paramName, paramValue);
} else {
// Keep empty param to preserve mode (especially important for whitelist)
params.set(paramName, "");
}
@@ -137,14 +128,11 @@ export default function TagSelector(props: TagSelectorProps) {
const paramName = filterMode() === "whitelist" ? "include" : "filter";
const otherParamName = filterMode() === "whitelist" ? "filter" : "include";
// Remove the other mode's param
params.delete(otherParamName);
if (allChecked()) {
// Uncheck all: keep empty param to preserve mode
params.set(paramName, "");
} else {
// Check all: select all tags
const allTags = allTagKeys().join("|");
params.set(paramName, allTags);
}
@@ -153,7 +141,6 @@ export default function TagSelector(props: TagSelectorProps) {
};
const toggleFilterMode = () => {
// Get current tags BEFORE changing mode
const currentSelected = selectedTags();
const newMode = filterMode() === "whitelist" ? "blacklist" : "whitelist";
@@ -164,14 +151,12 @@ export default function TagSelector(props: TagSelectorProps) {
const newParamName = newMode === "whitelist" ? "include" : "filter";
const oldParamName = newMode === "whitelist" ? "filter" : "include";
// Remove old param and set new one
params.delete(oldParamName);
if (currentSelected.length > 0) {
const paramValue = currentSelected.join("|");
params.set(newParamName, paramValue);
} else {
// Always keep the param, even if empty
params.set(newParamName, "");
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,13 +13,11 @@ export const Mermaid = Node.create({
content: {
default: "",
parseHTML: (element) => {
// Try to get code element
const code = element.querySelector("code");
if (code) {
// Get text content, which strips out all HTML tags (including spans from syntax highlighting)
return code.textContent || "";
}
// Fallback to element's own text content
return element.textContent || "";
},
renderHTML: (attributes) => {
@@ -191,15 +189,12 @@ export const Mermaid = Node.create({
}
try {
// Dynamic import to avoid bundling issues
const mermaid = (await import("mermaid")).default;
await mermaid.parse(content);
// Valid - green indicator
statusIndicator.className =
"absolute top-2 left-2 w-3 h-3 rounded-full bg-green opacity-0 group-hover:opacity-100 transition-opacity duration-200";
statusIndicator.title = "Valid mermaid syntax";
} catch (err) {
// Invalid - red indicator
statusIndicator.className =
"absolute top-2 left-2 w-3 h-3 rounded-full bg-red opacity-0 group-hover:opacity-100 transition-opacity duration-200";
statusIndicator.title = `Invalid syntax: ${
@@ -208,7 +203,6 @@ export const Mermaid = Node.create({
}
};
// Run validation
validateSyntax();
// Edit button overlay - visible on mobile tap/selection, hover on desktop
@@ -240,22 +234,18 @@ export const Mermaid = Node.create({
const pos = getPos();
const { from, to } = editor.state.selection;
// Check if this node is selected
const nodeIsSelected = from === pos && to === pos + node.nodeSize;
if (nodeIsSelected !== isSelected) {
isSelected = nodeIsSelected;
if (isSelected) {
// Show button when selected (for mobile)
editBtn.style.opacity = "1";
} else {
// Hide button when not selected (reset to CSS control)
editBtn.style.opacity = "";
}
}
};
// Listen for selection changes
const plugin = editor.view.state.plugins.find(
(p: any) => p.spec?.key === "mermaidSelection"
);
@@ -266,10 +256,8 @@ export const Mermaid = Node.create({
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Check selection periodically when visible
updateInterval = setInterval(updateButtonVisibility, 100);
} else {
// Stop checking when not visible
if (updateInterval) {
clearInterval(updateInterval);
updateInterval = null;
@@ -282,7 +270,6 @@ export const Mermaid = Node.create({
observer.observe(dom);
// Also check on touch
dom.addEventListener("touchstart", () => {
setTimeout(updateButtonVisibility, 50);
});
@@ -299,7 +286,6 @@ export const Mermaid = Node.create({
return false;
}
code.textContent = updatedNode.attrs.content || "";
// Re-validate on update
validateSyntax();
updateButtonVisibility();
return true;