summaryrefslogtreecommitdiff
path: root/src/layouts/BlogPost.astro
blob: 73f994bbcaa5e482fc680b9e4bb30eee6afafb1a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
---
import type { CollectionEntry } from "astro:content";

import Masthead from "@/components/blog/Masthead.astro";
import PostNavigation from "@/components/blog/PostNavigation.astro";
import { t } from "@/i18n/translations";

import BaseLayout from "./Base.astro";

interface Props {
	post: CollectionEntry<"post">;
	prevPost: CollectionEntry<"post"> | null | undefined;
	nextPost: CollectionEntry<"post"> | null | undefined;
}

const { post, prevPost, nextPost } = Astro.props;
const { ogImage, title, description, updatedDate, publishDate, language } = post.data;
const socialImage = ogImage ?? `/og-image/${post.id}.png`;
const articleDate = updatedDate?.toISOString() ?? publishDate.toISOString();
---

<BaseLayout
	meta={{
		articleDate,
		description,
		ogImage: socialImage,
		title,
		lang: language,
	}}
>
	<article class="grow break-words" data-pagefind-body>
		<div id="blog-hero" class="mb-12">
			<Masthead content={post} language={language} />
		</div>
		<div
			class="prose prose-sm prose-cactus 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 max-w-none"
		>
			<slot />
			{
				post.data.sourceUrl && (
					<p class="mt-8 border-t border-gray-200 pt-6 text-sm dark:border-gray-700">
						<a
							href={post.data.sourceUrl}
							class="cactus-link"
							target="_blank"
							rel="noopener noreferrer"
						>
							{t(language, "viewOriginalPost")}
						</a>
					</p>
				)
			}
		</div>
	</article>
	<PostNavigation prevPost={prevPost} nextPost={nextPost} language={language} />
	<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">{t(language, "backToTop")}</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>

<script>
	import mediumZoom from "medium-zoom";

	mediumZoom("[data-zoomable]", {
		margin: 8,
		background: "rgba(0, 0, 0, 0.2)",
	});
</script>