Files
blogflare/src/routes/posts/[slug]/+page.svelte
T

234 lines
6.2 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { page } from '$app/stores';
import { resolve } from '$app/paths';
import type { PageData } from './$types';
import { onMount, tick } from 'svelte';
import tocbot from 'tocbot';
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte';
let sidebarShow = $state(true);
let hasToc = $state(false);
let { data }: { data: PageData } = $props();
let currentSeriesIndex = $derived(data.seriesPosts.findIndex((post) => post.id === data.post.id));
let previousSeriesPost = $derived(
currentSeriesIndex > 0 ? data.seriesPosts[currentSeriesIndex - 1] : null
);
let nextSeriesPost = $derived(
currentSeriesIndex >= 0 && currentSeriesIndex < data.seriesPosts.length - 1
? data.seriesPosts[currentSeriesIndex + 1]
: null
);
onMount(() => {
const headers = document.querySelectorAll(
'.js-toc-content h1, .js-toc-content h2, .js-toc-content h3, .js-toc-content h4, .js-toc-content h5, .js-toc-content h6'
);
if (headers.length > 0) {
hasToc = true;
tick().then(() => {
tocbot.init({
tocSelector: '.js-toc',
contentSelector: '.js-toc-content',
headingSelector: 'h1, h2, h3, h4, h5',
hasInnerContainers: false,
linkClass: 'toc-link',
activeLinkClass: 'is-active-link',
listClass: 'toc-list',
isCollapsedClass: 'is-collapsed',
collapsibleClass: 'is-collapsible',
listItemClass: 'toc-list-item',
collapseDepth: 0,
scrollSmooth: true,
headingsOffset: 20
});
if (window.innerWidth < 1200) {
sidebarShow = false;
}
});
}
$effect(() => {
if (hasToc && sidebarShow) {
document.body.classList.add('sidebar-show');
} else {
document.body.classList.remove('sidebar-show');
}
return () => {
document.body.classList.remove('sidebar-show');
};
});
return () => {
if (hasToc) tocbot.destroy();
};
});
</script>
<svelte:head>
<title>{data.post.title} - {$page.data.site?.title || 'BlogFlare'}</title>
</svelte:head>
<main id="pjax-container">
<div class="wrapper container narrow contents-wrap">
<article class="article-detail yue">
<div class="banner-title">
<h1 class="post-title">{data.post.title}</h1>
<p class="post-meta">
<span><a href={resolve('/')}>{data.post.author}</a></span>&nbsp;&bull;&nbsp;
<time>{data.post.date}</time>
</p>
</div>
{#if data.post.cover}
<div class="post-cover detail-cover">
<img src={data.post.cover} alt={data.post.title} />
</div>
{/if}
{#if data.series && data.seriesPosts.length > 0}
<section class="series-navigation" aria-labelledby="series-navigation-title">
<h2 id="series-navigation-title">
{$page.data.aliases?.series || '系列'}:<a
href={resolve('/series/[slug]', { slug: data.series.slug })}>{data.series.name}</a
>
</h2>
<ol>
{#each data.seriesPosts as seriesPost (seriesPost.id)}
<li class:current={seriesPost.id === data.post.id}>
{#if seriesPost.id === data.post.id}
<span aria-current="page">{seriesPost.title}</span>
{:else}
<a href={resolve('/posts/[slug]', { slug: seriesPost.slug })}
>{seriesPost.title}</a
>
{/if}
</li>
{/each}
</ol>
{#if previousSeriesPost || nextSeriesPost}
<nav class="series-prev-next" aria-label="系列文章翻页">
{#if previousSeriesPost}
<a href={resolve('/posts/[slug]', { slug: previousSeriesPost.slug })}
>← {previousSeriesPost.title}</a
>
{:else}<span></span>{/if}
{#if nextSeriesPost}
<a href={resolve('/posts/[slug]', { slug: nextSeriesPost.slug })}
>{nextSeriesPost.title} →</a
>
{/if}
</nav>
{/if}
</section>
{/if}
<MarkdownRenderer content={data.post.content} />
{#if data.tags && data.tags.length > 0}
<section class="tags">
{#each data.tags as tag (tag.id)}
<a href={resolve(`/tags/${encodeURIComponent(tag.name)}`)} rel="tag" class="tag-item"
>{tag.name}</a
>
{/each}
</section>
{/if}
</article>
</div>
{#if hasToc}
<!-- TOC Overlay and Sidebar -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="toc-mask" onclick={() => (sidebarShow = false)}></div>
<div class="TOC" aria-label="{$page.data.aliases?.post || '文章'}目录">
<div class="js-toc"></div>
</div>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div id="toggle-toc" class="ctrler-item" onclick={() => (sidebarShow = !sidebarShow)}>
<svg
aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
width="16"
height="16"
><path
fill="currentColor"
d="M257.5 445.1l-22.2 22.2c-9.4 9.4-24.6 9.4-33.9 0L27 273c-9.4-9.4-9.4-24.6 0-33.9L201.4 44.7c9.4-9.4 24.6-9.4 33.9 0l22.2 22.2c9.5 9.5 9.3 25-.4 34.3L136.6 216H424c13.3 0 24 10.7 24 24v32c0 13.3-10.7 24-24 24H136.6l120.5 120.8c9.8 9.3 10 24.8.4 34.3z"
></path></svg
>
</div>
{/if}
</main>
<style>
.series-navigation {
margin: 0 0 2rem;
padding: 1.25rem 1.5rem;
border: 2px solid var(--text-color);
background: rgba(0, 0, 0, 0.015);
}
.series-navigation h2 {
margin: 0 0 1rem;
padding: 0;
border: 0;
font-size: 1.15rem;
}
.series-navigation h2 a,
.series-navigation li a,
.series-prev-next a {
color: var(--text-color);
text-decoration: none;
}
.series-navigation h2 a:hover,
.series-navigation li a:hover,
.series-prev-next a:hover {
color: var(--accent-color, #419b38);
}
.series-navigation ol {
margin: 0;
padding-left: 1.6rem;
}
.series-navigation li {
padding: 0.25rem 0;
}
.series-navigation li.current {
font-weight: 700;
color: var(--accent-color, #419b38);
}
.series-prev-next {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-color, rgba(0, 0, 0, 0.12));
}
.series-prev-next a:last-child {
text-align: right;
}
@media (max-width: 640px) {
.series-prev-next {
grid-template-columns: 1fr;
}
.series-prev-next a:last-child {
text-align: left;
}
}
</style>