234 lines
6.2 KiB
Svelte
234 lines
6.2 KiB
Svelte
<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> •
|
||
<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>
|