diff options
| author | Dawid Rycerz <dawid@rycerz.xyz> | 2025-07-03 10:56:21 +0300 |
|---|---|---|
| committer | Dawid Rycerz <dawid@rycerz.xyz> | 2025-07-03 10:56:21 +0300 |
| commit | 456cf011b36de91c9936994b1fa45703adcd309b (patch) | |
| tree | 8e60daf998f731ac50d100fa490eaecae1168042 /src/layouts/BlogPost.astro | |
Initial fork of chrismwilliams/astro-theme-cactus theme
Diffstat (limited to 'src/layouts/BlogPost.astro')
| -rw-r--r-- | src/layouts/BlogPost.astro | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/src/layouts/BlogPost.astro b/src/layouts/BlogPost.astro new file mode 100644 index 0000000..888e7a2 --- /dev/null +++ b/src/layouts/BlogPost.astro @@ -0,0 +1,80 @@ +--- +import { type CollectionEntry, render } from "astro:content"; + +import Masthead from "@/components/blog/Masthead.astro"; +import TOC from "@/components/blog/TOC.astro"; +import WebMentions from "@/components/blog/webmentions/index.astro"; + +import BaseLayout from "./Base.astro"; + +interface Props { + post: CollectionEntry<"post">; +} + +const { post } = Astro.props; +const { ogImage, title, description, updatedDate, publishDate } = post.data; +const socialImage = ogImage ?? `/og-image/${post.id}.png`; +const articleDate = updatedDate?.toISOString() ?? publishDate.toISOString(); +const { headings, remarkPluginFrontmatter } = await render(post); +const readingTime: string = remarkPluginFrontmatter.readingTime; +--- + +<BaseLayout + meta={{ + articleDate, + description, + ogImage: socialImage, + title, + }} +> + <article class="grow break-words" data-pagefind-body> + <div id="blog-hero" class="mb-12"><Masthead content={post} readingTime={readingTime} /></div> + <div class="flex flex-col gap-10 lg:flex-row lg:items-start"> + {!!headings.length && <TOC headings={headings} />} + <div + class="prose prose-sm prose-headings:font-semibold prose-headings:text-accent-2 prose-headings:before:absolute prose-headings:before:-ms-4 prose-headings:before:text-gray-600 prose-headings:hover:before:text-accent sm:prose-headings:before:content-['#'] sm:prose-th:before:content-none" + > + <slot /> + <WebMentions /> + </div> + </div> + </article> + <button + class="hover:border-link fixed end-4 bottom-8 z-90 flex h-10 w-10 translate-y-28 cursor-pointer items-center justify-center rounded-full border-2 border-transparent bg-zinc-200 text-3xl opacity-0 transition-all transition-discrete duration-300 data-[show=true]:translate-y-0 data-[show=true]:opacity-100 sm:end-8 sm:h-12 sm:w-12 dark:bg-zinc-700" + data-show="false" + id="to-top-btn" + > + <span class="sr-only">Back to top</span> + <svg + aria-hidden="true" + class="h-6 w-6" + fill="none" + focusable="false" + stroke="currentColor" + stroke-width="2" + viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg" + > + <path d="M4.5 15.75l7.5-7.5 7.5 7.5" stroke-linecap="round" stroke-linejoin="round"></path> + </svg> + </button> +</BaseLayout> + +<script> + const scrollBtn = document.getElementById("to-top-btn") as HTMLButtonElement; + const targetHeader = document.getElementById("blog-hero") as HTMLDivElement; + + function callback(entries: IntersectionObserverEntry[]) { + entries.forEach((entry) => { + // only show the scroll to top button when the heading is out of view + scrollBtn.dataset.show = (!entry.isIntersecting).toString(); + }); + } + + scrollBtn.addEventListener("click", () => { + document.documentElement.scrollTo({ behavior: "smooth", top: 0 }); + }); + + const observer = new IntersectionObserver(callback); + observer.observe(targetHeader); +</script> |
