published toggle

This commit is contained in:
Michael Freno
2025-12-31 00:49:10 -05:00
parent bd23646ac3
commit a1ed56478c
6 changed files with 78 additions and 27 deletions

View File

@@ -2,22 +2,7 @@ import { Show } from "solid-js";
import CardLinks from "./CardLinks";
import DeletePostButton from "./DeletePostButton";
import { Fire } from "~/components/icons/Fire";
export interface Post {
id: number;
title: string;
subtitle: string | null;
body: string | null;
banner_photo: string | null;
date: string;
published: boolean;
category: string;
author_id: string;
reads: number;
attachments: string | null;
total_likes: number;
total_comments: number;
}
import { Post } from "~/db/types";
export interface CardProps {
post: Post;

View File

@@ -1,5 +1,6 @@
import { For, Show, createMemo } from "solid-js";
import Card, { Post } from "./Card";
import Card from "./Card";
import { Post } from "~/db/types";
export interface Tag {
value: string;
@@ -13,10 +14,22 @@ export interface PostSortingProps {
filters?: string;
sort?: string;
include?: string;
status?: string;
}
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);
} else if (props.status === "unpublished") {
filtered = filtered.filter((post) => post.published === 0);
}
}
// Build map of post_id -> tags for that post
const postTags = new Map<number, Set<string>>();
props.tags.forEach((tag) => {
@@ -41,7 +54,7 @@ export default function PostSorting(props: PostSortingProps) {
const includeSet = new Set(includeList);
return props.posts.filter((post) => {
return filtered.filter((post) => {
const tags = postTags.get(post.id);
if (!tags || tags.size === 0) return false;
@@ -61,12 +74,12 @@ export default function PostSorting(props: PostSortingProps) {
// Empty blacklist means show everything
if (filterList.length === 0) {
return props.posts;
return filtered;
}
const filterSet = new Set(filterList);
return props.posts.filter((post) => {
return filtered.filter((post) => {
const tags = postTags.get(post.id);
if (!tags || tags.size === 0) return true; // Show posts with no tags
@@ -81,7 +94,7 @@ export default function PostSorting(props: PostSortingProps) {
}
// No filters: show all posts
return props.posts;
return filtered;
});
const sortedPosts = createMemo(() => {

View File

@@ -40,7 +40,7 @@ export default function PostSortingSelect(props: PostSortingSelectProps) {
<button
type="button"
onClick={() => setIsOpen(!isOpen())}
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"
class="focus-visible:border-blue focus-visible:ring-offset-blue 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">{selected().label}</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
@@ -62,7 +62,7 @@ export default function PostSortingSelect(props: PostSortingSelectProps) {
onClick={() => handleSelect(sort)}
class={`relative w-full cursor-default py-2 pr-4 pl-10 text-left select-none ${
selected().val === sort.val
? "bg-peach text-base brightness-75"
? "bg-blue text-base brightness-75"
: "text-text hover:brightness-125"
}`}
>
@@ -74,12 +74,12 @@ export default function PostSortingSelect(props: PostSortingSelectProps) {
{sort.label}
</span>
<Show when={selected().val === sort.val}>
<span class="text-peach absolute inset-y-0 left-0 flex items-center pl-3">
<span class="text-blue absolute inset-y-0 left-0 flex items-center pl-3">
<Check
strokeWidth={1}
height={24}
width={24}
class="stroke-text"
class="stroke-base"
/>
</span>
</Show>

View File

@@ -0,0 +1,45 @@
import { useSearchParams } from "@solidjs/router";
type PublishStatus = "all" | "published" | "unpublished";
export default function PublishStatusToggle() {
const [searchParams, setSearchParams] = useSearchParams();
const currentStatus = (): PublishStatus => {
return (searchParams.status as PublishStatus) || "all";
};
const handleStatusChange = (status: PublishStatus) => {
setSearchParams({ status });
};
const buttonClass = (status: PublishStatus) =>
`px-4 py-2 transition-all duration-300 ${
currentStatus() === status
? "bg-text text-base font-semibold"
: "bg-transparent hover:brightness-125"
}`;
return (
<div class="border-text mx-auto mt-2 flex overflow-hidden rounded border">
<button
onClick={() => handleStatusChange("all")}
class={buttonClass("all")}
>
All
</button>
<button
onClick={() => handleStatusChange("published")}
class={`${buttonClass("published")} border-text border-r border-l`}
>
Published
</button>
<button
onClick={() => handleStatusChange("unpublished")}
class={buttonClass("unpublished")}
>
Unpublished
</button>
</div>
);
}

View File

@@ -46,7 +46,7 @@ export interface Post {
body: string;
banner_photo?: string;
date?: string | null;
published: boolean;
published: number; // 0 or 1 (sqlite)
author_id: string;
reads: number;
attachments?: string;

View File

@@ -6,6 +6,7 @@ import { getRequestEvent } from "solid-js/web";
import PostSortingSelect from "~/components/blog/PostSortingSelect";
import TagSelector from "~/components/blog/TagSelector";
import PostSorting from "~/components/blog/PostSorting";
import PublishStatusToggle from "~/components/blog/PublishStatusToggle";
import { TerminalSplash } from "~/components/TerminalSplash";
const getPosts = query(async () => {
@@ -79,6 +80,8 @@ export default function BlogIndex() {
"filter" in searchParams ? searchParams.filter : undefined;
const include = () =>
"include" in searchParams ? searchParams.include : undefined;
const status = () =>
"status" in searchParams ? searchParams.status : undefined;
const data = createAsync(() => getPosts(), { deferStream: true });
@@ -90,13 +93,17 @@ export default function BlogIndex() {
<Show when={data()} fallback={<TerminalSplash />}>
{(loadedData) => (
<>
<div class="flex flex-row justify-around gap-4 px-4">
<div class="flex flex-col items-center gap-4 px-4 md:flex-row md:justify-around">
<PostSortingSelect />
<Show when={Object.keys(loadedData().tagMap).length > 0}>
<TagSelector tagMap={loadedData().tagMap} />
</Show>
<Show when={loadedData().privilegeLevel === "admin"}>
<PublishStatusToggle />
</Show>
<Show when={loadedData().privilegeLevel === "admin"}>
<div class="mt-2 flex justify-center md:mt-0 md:justify-end">
<A
@@ -121,6 +128,7 @@ export default function BlogIndex() {
filters={filters()}
sort={sort()}
include={include()}
status={status()}
/>
</div>
</Show>