mirror of
https://github.com/ChenQihan666/MyBlog-Next.git
synced 2026-08-14 11:29:33 +08:00
2774045045
* fix: broken editPost link and update editPost logic Fixed broken editPost link. Removed the ability to update the editPost link inside each blog post for consistency. The editPost button per blog post can still be hidden. * docs: update docs for editPost
292 lines
8.3 KiB
Plaintext
292 lines
8.3 KiB
Plaintext
---
|
|
import { render, type CollectionEntry } from "astro:content";
|
|
import Layout from "@/layouts/Layout.astro";
|
|
import Header from "@/components/Header.astro";
|
|
import Footer from "@/components/Footer.astro";
|
|
import Tag from "@/components/Tag.astro";
|
|
import Datetime from "@/components/Datetime.astro";
|
|
import EditPost from "@/components/EditPost.astro";
|
|
import ShareLinks from "@/components/ShareLinks.astro";
|
|
import BackButton from "@/components/BackButton.astro";
|
|
import { getPath } from "@/utils/getPath";
|
|
import { slugifyStr } from "@/utils/slugify";
|
|
import IconChevronLeft from "@/assets/icons/IconChevronLeft.svg";
|
|
import IconChevronRight from "@/assets/icons/IconChevronRight.svg";
|
|
import { SITE } from "@/config";
|
|
|
|
export interface Props {
|
|
post: CollectionEntry<"blog">;
|
|
posts: CollectionEntry<"blog">[];
|
|
}
|
|
|
|
const { post, posts } = Astro.props;
|
|
|
|
const {
|
|
title,
|
|
author,
|
|
description,
|
|
ogImage: initOgImage,
|
|
canonicalURL,
|
|
pubDatetime,
|
|
modDatetime,
|
|
tags,
|
|
hideEditPost,
|
|
} = post.data;
|
|
|
|
const { Content } = await render(post);
|
|
|
|
let ogImageUrl: string | undefined;
|
|
|
|
// Determine OG image source
|
|
if (typeof initOgImage === "string") {
|
|
ogImageUrl = initOgImage; // Remote OG image (absolute URL)
|
|
} else if (initOgImage?.src) {
|
|
ogImageUrl = initOgImage.src; // Local asset
|
|
}
|
|
|
|
// Use dynamic OG image if enabled and no remote|local ogImage
|
|
if (!ogImageUrl && SITE.dynamicOgImage) {
|
|
ogImageUrl = `${getPath(post.id, post.filePath)}/index.png`;
|
|
}
|
|
|
|
// Resolve OG image URL (or fallback to SITE.ogImage / default `og.png`)
|
|
const ogImage = ogImageUrl
|
|
? new URL(ogImageUrl, Astro.url.origin).href
|
|
: undefined;
|
|
|
|
const layoutProps = {
|
|
title: `${title} | ${SITE.title}`,
|
|
author,
|
|
description,
|
|
pubDatetime,
|
|
modDatetime,
|
|
canonicalURL,
|
|
ogImage,
|
|
scrollSmooth: true,
|
|
};
|
|
|
|
/* ========== Prev/Next Posts ========== */
|
|
|
|
const allPosts = posts.map(({ data: { title }, id }) => ({
|
|
slug: id,
|
|
title,
|
|
}));
|
|
|
|
const currentPostIndex = allPosts.findIndex(a => a.slug === post.id);
|
|
|
|
const prevPost = currentPostIndex !== 0 ? allPosts[currentPostIndex - 1] : null;
|
|
const nextPost =
|
|
currentPostIndex !== allPosts.length ? allPosts[currentPostIndex + 1] : null;
|
|
---
|
|
|
|
<Layout {...layoutProps}>
|
|
<Header />
|
|
<BackButton />
|
|
<main
|
|
id="main-content"
|
|
class:list={[
|
|
"mx-auto w-full max-w-3xl px-4 pb-12",
|
|
{ "mt-8": !SITE.showBackButton },
|
|
]}
|
|
data-pagefind-body
|
|
>
|
|
<h1
|
|
transition:name={slugifyStr(title)}
|
|
class="inline-block text-2xl font-bold text-accent sm:text-3xl"
|
|
>
|
|
{title}
|
|
</h1>
|
|
<div class="flex items-center gap-4">
|
|
<Datetime
|
|
pubDatetime={pubDatetime}
|
|
modDatetime={modDatetime}
|
|
size="lg"
|
|
class="my-2"
|
|
/>
|
|
<EditPost class="max-sm:hidden" {hideEditPost} {post} />
|
|
</div>
|
|
<article id="article" class="mx-auto prose mt-8 max-w-3xl">
|
|
<Content />
|
|
</article>
|
|
|
|
<hr class="my-8 border-dashed" />
|
|
|
|
<EditPost class="sm:hidden" {hideEditPost} {post} />
|
|
|
|
<ul class="mt-4 mb-8 sm:my-8">
|
|
{tags.map(tag => <Tag tag={slugifyStr(tag)} tagName={tag} />)}
|
|
</ul>
|
|
|
|
<div
|
|
class="flex flex-col items-center justify-between gap-6 sm:flex-row sm:items-end sm:gap-4"
|
|
>
|
|
<ShareLinks />
|
|
|
|
<button
|
|
id="back-to-top"
|
|
class="focus-outline py-1 whitespace-nowrap hover:opacity-75"
|
|
>
|
|
<IconChevronLeft class="inline-block rotate-90" />
|
|
<span>Back to Top</span>
|
|
</button>
|
|
</div>
|
|
|
|
<hr class="my-6 border-dashed" />
|
|
|
|
<!-- Previous/Next Post Buttons -->
|
|
<div data-pagefind-ignore class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
|
{
|
|
prevPost && (
|
|
<a
|
|
href={`/posts/${prevPost.slug}`}
|
|
class="flex w-full gap-1 hover:opacity-75"
|
|
>
|
|
<IconChevronLeft class="inline-block flex-none" />
|
|
<div>
|
|
<span>Previous Post</span>
|
|
<div class="text-sm text-accent/85">{prevPost.title}</div>
|
|
</div>
|
|
</a>
|
|
)
|
|
}
|
|
{
|
|
nextPost && (
|
|
<a
|
|
href={`/posts/${nextPost.slug}`}
|
|
class="flex w-full justify-end gap-1 text-right hover:opacity-75 sm:col-start-2"
|
|
>
|
|
<div>
|
|
<span>Next Post</span>
|
|
<div class="text-sm text-accent/85">{nextPost.title}</div>
|
|
</div>
|
|
<IconChevronRight class="inline-block flex-none" />
|
|
</a>
|
|
)
|
|
}
|
|
</div>
|
|
</main>
|
|
<Footer />
|
|
</Layout>
|
|
|
|
<script is:inline data-astro-rerun>
|
|
/** Create a progress indicator
|
|
* at the top */
|
|
function createProgressBar() {
|
|
// Create the main container div
|
|
const progressContainer = document.createElement("div");
|
|
progressContainer.className =
|
|
"progress-container fixed top-0 z-10 h-1 w-full bg-background";
|
|
|
|
// Create the progress bar div
|
|
const progressBar = document.createElement("div");
|
|
progressBar.className = "progress-bar h-1 w-0 bg-accent";
|
|
progressBar.id = "myBar";
|
|
|
|
// Append the progress bar to the progress container
|
|
progressContainer.appendChild(progressBar);
|
|
|
|
// Append the progress container to the document body or any other desired parent element
|
|
document.body.appendChild(progressContainer);
|
|
}
|
|
createProgressBar();
|
|
|
|
/** Update the progress bar
|
|
* when user scrolls */
|
|
function updateScrollProgress() {
|
|
document.addEventListener("scroll", () => {
|
|
const winScroll =
|
|
document.body.scrollTop || document.documentElement.scrollTop;
|
|
const height =
|
|
document.documentElement.scrollHeight -
|
|
document.documentElement.clientHeight;
|
|
const scrolled = (winScroll / height) * 100;
|
|
if (document) {
|
|
const myBar = document.getElementById("myBar");
|
|
if (myBar) {
|
|
myBar.style.width = scrolled + "%";
|
|
}
|
|
}
|
|
});
|
|
}
|
|
updateScrollProgress();
|
|
|
|
/** Attaches links to headings in the document,
|
|
* allowing sharing of sections easily */
|
|
function addHeadingLinks() {
|
|
const headings = Array.from(
|
|
document.querySelectorAll("h2, h3, h4, h5, h6")
|
|
);
|
|
for (const heading of headings) {
|
|
heading.classList.add("group");
|
|
const link = document.createElement("a");
|
|
link.className =
|
|
"heading-link ml-2 opacity-0 group-hover:opacity-100 focus:opacity-100";
|
|
link.href = "#" + heading.id;
|
|
|
|
const span = document.createElement("span");
|
|
span.ariaHidden = "true";
|
|
span.innerText = "#";
|
|
link.appendChild(span);
|
|
heading.appendChild(link);
|
|
}
|
|
}
|
|
addHeadingLinks();
|
|
|
|
/** Attaches copy buttons to code blocks in the document,
|
|
* allowing users to copy code easily. */
|
|
function attachCopyButtons() {
|
|
const copyButtonLabel = "Copy";
|
|
const codeBlocks = Array.from(document.querySelectorAll("pre"));
|
|
|
|
for (const codeBlock of codeBlocks) {
|
|
const wrapper = document.createElement("div");
|
|
wrapper.style.position = "relative";
|
|
|
|
const copyButton = document.createElement("button");
|
|
copyButton.className =
|
|
"copy-code absolute right-3 -top-3 rounded bg-muted px-2 py-1 text-xs leading-4 text-foreground font-medium";
|
|
copyButton.innerHTML = copyButtonLabel;
|
|
codeBlock.setAttribute("tabindex", "0");
|
|
codeBlock.appendChild(copyButton);
|
|
|
|
// wrap codebock with relative parent element
|
|
codeBlock?.parentNode?.insertBefore(wrapper, codeBlock);
|
|
wrapper.appendChild(codeBlock);
|
|
|
|
copyButton.addEventListener("click", async () => {
|
|
await copyCode(codeBlock, copyButton);
|
|
});
|
|
}
|
|
|
|
async function copyCode(block, button) {
|
|
const code = block.querySelector("code");
|
|
const text = code?.innerText;
|
|
|
|
await navigator.clipboard.writeText(text ?? "");
|
|
|
|
// visual feedback that task is completed
|
|
button.innerText = "Copied";
|
|
|
|
setTimeout(() => {
|
|
button.innerText = copyButtonLabel;
|
|
}, 700);
|
|
}
|
|
}
|
|
attachCopyButtons();
|
|
|
|
/** Scrolls the document to the top when
|
|
* the "Back to Top" button is clicked. */
|
|
function backToTop() {
|
|
document.querySelector("#back-to-top")?.addEventListener("click", () => {
|
|
document.body.scrollTop = 0; // For Safari
|
|
document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
|
|
});
|
|
}
|
|
backToTop();
|
|
|
|
/* Go to page start after page swap */
|
|
document.addEventListener("astro:after-swap", () =>
|
|
window.scrollTo({ left: 0, top: 0, behavior: "instant" })
|
|
);
|
|
</script>
|