diff --git a/src/components/Header.tsx b/src/components/Header.tsx index ab63837..623dc97 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -2,12 +2,16 @@ import { BlogOptions } from "../plugin/blog.ts"; import NavigationBar from "../islands/NavigationBar.tsx"; export default function Header( - props: { options: BlogOptions; active: string }, + props: { options: BlogOptions }, ) { - const isHome = props.active == "/"; return ( -
- -
+
+

+ + {props.options.title} + +

+ +
); } diff --git a/src/components/PostList.tsx b/src/components/PostList.tsx index 8e50f6a..08db215 100644 --- a/src/components/PostList.tsx +++ b/src/components/PostList.tsx @@ -6,16 +6,14 @@ export default function PostList( props: { posts: Post[]; showExcerpt: boolean; localization: Localization }, ) { return ( -
-
- {props.posts.map((post) => ( - - ))} -
-
+
+ {props.posts.map((post) => ( + + ))} +
); } diff --git a/src/components/PostSummary.tsx b/src/components/PostSummary.tsx index 7fa0b38..7a64796 100644 --- a/src/components/PostSummary.tsx +++ b/src/components/PostSummary.tsx @@ -1,39 +1,47 @@ import { Post } from "../utils/posts.ts"; import { Localization } from "../plugin/blog.ts"; +import TagLink from "./TagLink.tsx"; +import Tags from "./Tags.tsx"; export default function PostSummary( props: { post: Post; showExcerpt: boolean; localization: Localization }, ) { const { post, showExcerpt } = props; return ( -
+
-

+

{post.title}

-
+
{hasNonNullContent(post.author) && ( <> - + {props.localization.attribution} {reduceAuthors(post.author)} )} -
{showExcerpt && (post.excerpt != "") && (
{post.excerpt}
- + {props.localization.continueReading}
@@ -63,17 +71,3 @@ function authorElement(author: string) { const authorUrl = `/author/${author.toLowerCase().replace(/\s+/g, "-")}`; return {author}; } - -function tags(tags: string[]) { - return tags.map((tag) => { - const url = `/archive/${tag}`; - return ( - - {tag} - - ); - }); -} diff --git a/src/components/TagLink.tsx b/src/components/TagLink.tsx new file mode 100644 index 0000000..9d835d2 --- /dev/null +++ b/src/components/TagLink.tsx @@ -0,0 +1,11 @@ +export default function TagLink({ tag }: { tag: string }) { + return ( + + {tag} + + ); +} diff --git a/src/components/Tags.tsx b/src/components/Tags.tsx new file mode 100644 index 0000000..202a468 --- /dev/null +++ b/src/components/Tags.tsx @@ -0,0 +1,10 @@ +import TagLink from "./TagLink.tsx"; + +export default function Tags({ tags, id }: { tags: string[]; id?: string }) { + return ( +
+ {tags.some((x) => x != null) && + tags.map((tag, index) => )} +
+ ); +} diff --git a/src/islands/NavigationBar.tsx b/src/islands/NavigationBar.tsx index 77ddd60..652fa21 100644 --- a/src/islands/NavigationBar.tsx +++ b/src/islands/NavigationBar.tsx @@ -1,75 +1,67 @@ import { BlogOptions } from "../plugin/blog.ts"; -import { useSignal } from "https://esm.sh/*@preact/signals@1.2.2"; +import { useSignal } from "@preact/signals"; +import ThemeToggle from "./ThemeToggle.tsx"; export default function NavigationBar( - props: { active: string; class?: string; options: BlogOptions }, + props: { class?: string; options: BlogOptions }, ) { - const isHome = props.active == "/"; const isOpen = useSignal(false); return ( -
-
- - {props.options.title} - -
+ + +

Primary Navigation

+ +
); } diff --git a/src/islands/ThemeToggle.tsx b/src/islands/ThemeToggle.tsx new file mode 100644 index 0000000..72613e6 --- /dev/null +++ b/src/islands/ThemeToggle.tsx @@ -0,0 +1,61 @@ +import { useSignal } from "@preact/signals"; + +const sun = ( + +); + +const moon = ( + +); + +export default function ThemeToggle() { + const currentTheme = localStorage.getItem("theme") ?? "light"; + const isDarkMode = useSignal(currentTheme === "dark"); + + const toggleTheme = () => { + isDarkMode.value = !isDarkMode.value; + const theme = isDarkMode.value ? "dark" : "light"; + + document.documentElement.classList.toggle("dark", isDarkMode.value); + const markdownBody = document.getElementById("markdown-body"); + if (markdownBody) { + markdownBody.setAttribute( + "data-color-mode", + isDarkMode.value ? "dark" : "light", + ); + } + + document.cookie = `theme=${theme};path=/;max-age=31536000;SameSite=Lax`; + localStorage.setItem("theme", theme); + }; + + return ( + + ); +} diff --git a/src/plugin/blog.ts b/src/plugin/blog.ts index 7fc3af0..84571ec 100644 --- a/src/plugin/blog.ts +++ b/src/plugin/blog.ts @@ -130,7 +130,11 @@ export function blogPlugin( }], islands: { baseLocation: import.meta.url, - paths: ["../islands/NavigationBar.tsx", "../islands/Disqus.tsx"], + paths: [ + "../islands/NavigationBar.tsx", + "../islands/Disqus.tsx", + "../islands/ThemeToggle.tsx", + ], }, }; } diff --git a/src/routes/_app.tsx b/src/routes/_app.tsx index aa0f678..a8e2955 100644 --- a/src/routes/_app.tsx +++ b/src/routes/_app.tsx @@ -1,22 +1,23 @@ -import { PageProps } from "../../deps.ts"; +import { FreshContext } from "../../deps.ts"; import Header from "../components/Header.tsx"; import { BlogOptions } from "../plugin/blog.ts"; +import { themeFromRequest } from "../utils/theme.ts"; export function AppBuilder(options: BlogOptions) { - return (props: PageProps) => { - const { Component, route } = props; + // deno-lint-ignore require-await + return async (req: Request, ctx: FreshContext) => { + const themeClass = themeFromRequest(req); + + const { Component } = ctx; return ( - + - -
-
+ +
+
diff --git a/src/routes/archive/index.tsx b/src/routes/archive/index.tsx index 5989898..c933e4a 100644 --- a/src/routes/archive/index.tsx +++ b/src/routes/archive/index.tsx @@ -3,6 +3,8 @@ import { Post } from "../../utils/posts.ts"; import PostList from "../../components/PostList.tsx"; import { BlogState } from "../_middleware.ts"; import { Localization, PageOptions } from "../../plugin/blog.ts"; +import TagLink from "../../components/TagLink.tsx"; +import Tags from "../../components/Tags.tsx"; export const handler: Handlers = { GET(_req, ctx) { @@ -36,18 +38,8 @@ export function createArchivePage( {finalTitle} -
-
- {allTags.some((x) => x != null) && allTags.map((tag, index) => ( - - {tag} - - ))} -
+
+ = { + async GET(req, ctx) { + const theme = themeFromRequest(req); -export const handler: Handlers = { - async GET(_req, ctx) { const posts = ctx.state.context.posts; const post = posts.find((x) => x.slug === ctx.params.slug); if (!post) { @@ -34,7 +38,7 @@ export const handler: Handlers = { .map((block) => block.code.rich_text[0].plain_text); post.content = codeContent[0]; } - return ctx.render(post!); + return ctx.render({ theme, post: post! }); }, }; @@ -44,8 +48,8 @@ export function createPostPage( comments?: DisqusOptions, options?: PageOptions, ) { - return function PostPage(props: PageProps) { - const post = props.data; + return function PostPage(props: PageProps<{ theme: string; post: Post }>) { + const post = props.data.post; class CustomRenderer extends Renderer { list(body: string, ordered: boolean): string { const type = ordered ? "list-decimal" : "list-disc"; @@ -61,33 +65,45 @@ export function createPostPage( }, }); return ( - <> +
{options?.titleOverride || title} — {post.title}