refactor: 优化前端路由数据加载逻辑与显示效果
This commit is contained in:
1 parent
1e2775fda2
commit
c902e5858d
12 files changed
+275
-151
No files matched your search
@@ -1,9 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
|
||||
export const load: LayoutServerLoad = async ({ platform }) => {
|
||||
const db = platform?.env?.DB;
|
||||
|
||||
let aliases = {
|
||||
|
||||
const aliases = {
|
||||
post: '文章',
|
||||
page: '页面',
|
||||
category: '分类',
|
||||
@@ -11,7 +13,7 @@ export const load: LayoutServerLoad = async ({ platform }) => {
|
||||
series: '系列'
|
||||
};
|
||||
|
||||
let site = {
|
||||
const site = {
|
||||
title: 'BlogFlare',
|
||||
description: 'A high-performance blog powered by Cloudflare D1, R2, and KV.',
|
||||
avatar_enabled: 'true',
|
||||
@@ -20,10 +22,10 @@ export const load: LayoutServerLoad = async ({ platform }) => {
|
||||
|
||||
if (db) {
|
||||
try {
|
||||
const { results } = await db.prepare(
|
||||
"SELECT key, value FROM settings WHERE key LIKE 'alias.%' OR key LIKE 'site.%'"
|
||||
).all();
|
||||
|
||||
const { results } = await db
|
||||
.prepare("SELECT key, value FROM settings WHERE key LIKE 'alias.%' OR key LIKE 'site.%'")
|
||||
.all();
|
||||
|
||||
if (results && results.length > 0) {
|
||||
results.forEach((row: any) => {
|
||||
if (row.key.startsWith('alias.')) {
|
||||
@@ -39,8 +41,8 @@ export const load: LayoutServerLoad = async ({ platform }) => {
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load settings', e);
|
||||
} catch (err: any) {
|
||||
console.error('Failed to load settings', err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+105
-57
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import '../app.css';
|
||||
let { children } = $props();
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let scrolled = $state(false);
|
||||
|
||||
@@ -12,7 +13,7 @@
|
||||
scrolled = window.scrollY > 50;
|
||||
};
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
@@ -21,69 +22,116 @@
|
||||
function scrollToTop() {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function toggleSingleCol() {
|
||||
document.body.classList.toggle('single-col');
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$page.data.site?.title || 'BlogFlare'} - {$page.data.site?.description || 'A Pure Cloudflare Blog'}</title>
|
||||
<title
|
||||
>{$page.data.site?.title || 'BlogFlare'} - {$page.data.site?.description ||
|
||||
'A Pure Cloudflare Blog'}</title
|
||||
>
|
||||
</svelte:head>
|
||||
|
||||
{#if $page.url.pathname.startsWith('/admin')}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<div class="frontend-layout">
|
||||
<header class="header {scrolled ? 'scrolled' : ''}">
|
||||
<div class="container wide">
|
||||
<a href="/" class="brand">{$page.data.site?.title || 'BlogFlare'}</a>
|
||||
<nav>
|
||||
<a href="/" class={$page.url.pathname === '/' ? 'active' : ''}>首页</a>
|
||||
<a href="/archive" class={$page.url.pathname.startsWith('/posts') || $page.url.pathname.startsWith('/archive') ? 'active' : ''}>{$page.data.aliases?.post || '文章'}</a>
|
||||
<a href="/series" class={$page.url.pathname.startsWith('/series') ? 'active' : ''}>{$page.data.aliases?.series || '系列'}</a>
|
||||
<a href="/about" class={$page.url.pathname.startsWith('/about') ? 'active' : ''}>关于</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
{#if $page.url.pathname === '/'}
|
||||
<div class="home-profile">
|
||||
{#if $page.data.site?.avatar_enabled !== 'false'}
|
||||
<div class="home-avatar">
|
||||
<img src={$page.data.site?.avatar_url || "data:image/png;base64,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 truncated
|
||||
</div>
|
||||
<div class="frontend-layout">
|
||||
<header class="header {scrolled ? 'scrolled' : ''}">
|
||||
<div class="container wide">
|
||||
<a href={resolve('/')} class="brand">{$page.data.site?.title || 'BlogFlare'}</a>
|
||||
<nav>
|
||||
<a href={resolve('/')} class={$page.url.pathname === '/' ? 'active' : ''}>首页</a>
|
||||
<a
|
||||
href={resolve('/archive')}
|
||||
class={$page.url.pathname.startsWith('/posts') ||
|
||||
$page.url.pathname.startsWith('/archive')
|
||||
? 'active'
|
||||
: ''}>{$page.data.aliases?.post || '文章'}</a
|
||||
>
|
||||
<a
|
||||
href={resolve('/series')}
|
||||
class={$page.url.pathname.startsWith('/series') ? 'active' : ''}
|
||||
>{$page.data.aliases?.series || '系列'}</a
|
||||
>
|
||||
<a
|
||||
href={resolve('/about')}
|
||||
class={$page.url.pathname.startsWith('/about') ? 'active' : ''}>关于</a
|
||||
>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
{#if $page.url.pathname === '/'}
|
||||
<div class="home-profile">
|
||||
{#if $page.data.site?.avatar_enabled !== 'false'}
|
||||
<div class="home-avatar">
|
||||
<img
|
||||
src={$page.data.site?.avatar_url ||
|
||||
'data:image/png;base64,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 truncated
|
||||
alt="avatar"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<h1 class="home-title">{$page.data.site?.title || 'BlogFlare'}</h1>
|
||||
<p class="home-subtitle">
|
||||
{$page.data.site?.description ||
|
||||
'A high-performance blog powered by Cloudflare D1, R2, and KV.'}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
<h1 class="home-title">{$page.data.site?.title || 'BlogFlare'}</h1>
|
||||
<p class="home-subtitle">{$page.data.site?.description || 'A high-performance blog powered by Cloudflare D1, R2, and KV.'}</p>
|
||||
|
||||
<main id="pjax-container" style="padding-top: {$page.url.pathname === '/' ? '30px' : '58px'}">
|
||||
<div class="wrapper container wide">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container wide">
|
||||
<section>
|
||||
<p>
|
||||
© {new Date().getFullYear()}
|
||||
<span class="brand">{$page.data.site?.title || 'BlogFlare'}</span>
|
||||
</p>
|
||||
<p>感谢陪伴:<span id="uptime"></span></p>
|
||||
</section>
|
||||
<section>
|
||||
<p>
|
||||
Powered by <a href="https://kit.svelte.dev/">SvelteKit</a> &
|
||||
<a href="https://developers.cloudflare.com/">Cloudflare</a>
|
||||
</p>
|
||||
<p>
|
||||
Theme inspired by <a href="https://github.com/AlanDecode/Typecho-Theme-VOID">VOID</a> &
|
||||
<a href="https://github.com/HEIGE-PCloud/DoIt">DoIt</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Floating Controllers (VOID style) -->
|
||||
<aside id="ctrler-panel">
|
||||
<div class="ctrler-item" title="返回顶部">
|
||||
<a
|
||||
href="#top"
|
||||
aria-label="返回顶部"
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
scrollToTop();
|
||||
}}
|
||||
>
|
||||
<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="M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<main id="pjax-container" style="padding-top: {$page.url.pathname === '/' ? '30px' : '58px'}">
|
||||
<div class="wrapper container wide">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container wide">
|
||||
<section>
|
||||
<p>© {new Date().getFullYear()} <span class="brand">{$page.data.site?.title || 'BlogFlare'}</span></p>
|
||||
<p>感谢陪伴:<span id="uptime"></span></p>
|
||||
</section>
|
||||
<section>
|
||||
<p>Powered by <a href="https://kit.svelte.dev/">SvelteKit</a> & <a href="https://developers.cloudflare.com/">Cloudflare</a></p>
|
||||
<p>Theme inspired by <a href="https://github.com/AlanDecode/Typecho-Theme-VOID">VOID</a> & <a href="https://github.com/HEIGE-PCloud/DoIt">DoIt</a></p>
|
||||
</section>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Floating Controllers (VOID style) -->
|
||||
<aside id="ctrler-panel">
|
||||
<div class="ctrler-item" title="返回顶部">
|
||||
<a href="#top" aria-label="返回顶部" onclick={(e) => { e.preventDefault(); scrollToTop(); }}>
|
||||
<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="M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
{/if}
|
||||
+31
-21
@@ -1,8 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ platform }) => {
|
||||
const db = platform?.env?.DB;
|
||||
|
||||
|
||||
if (!db) {
|
||||
// Fallback data if DB is not available (e.g. during dev without wrangler, or missing bindings)
|
||||
return {
|
||||
@@ -10,60 +12,68 @@ export const load: PageServerLoad = async ({ platform }) => {
|
||||
{
|
||||
id: 1,
|
||||
title: 'Hello, BlogFlare! (Fallback)',
|
||||
excerpt: 'This is my first post on BlogFlare. Welcome to my new blog powered by SvelteKit.',
|
||||
excerpt:
|
||||
'This is my first post on BlogFlare. Welcome to my new blog powered by SvelteKit.',
|
||||
date: 'Jul 12, 2026',
|
||||
slug: 'hello-world'
|
||||
slug: 'hello-world',
|
||||
cover: null
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'The Beauty of Typecho Theme VOID',
|
||||
excerpt: 'VOID is a minimalist, typography-oriented theme that brings elegance to your reading experience.',
|
||||
excerpt:
|
||||
'VOID is a minimalist, typography-oriented theme that brings elegance to your reading experience.',
|
||||
date: 'Jul 11, 2026',
|
||||
slug: 'beauty-of-void'
|
||||
slug: 'beauty-of-void',
|
||||
cover: null
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const { results } = await db.prepare(
|
||||
`SELECT p.id, p.slug, p.title, p.content, p.cover, p.published_at as date
|
||||
const { results } = await db
|
||||
.prepare(
|
||||
`SELECT p.id, p.slug, p.title, p.content, p.cover, p.published_at as date
|
||||
FROM posts p
|
||||
ORDER BY p.published_at DESC LIMIT 10`
|
||||
).all();
|
||||
)
|
||||
.all();
|
||||
|
||||
// Convert date to string for serialization
|
||||
const formattedPosts = results.map(post => {
|
||||
const formattedPosts = results.map((post) => {
|
||||
const d = new Date(post.date as string);
|
||||
const month = d.toLocaleString('en-US', { month: 'short' });
|
||||
const day = d.getDate();
|
||||
const year = d.getFullYear();
|
||||
|
||||
let excerpt = '';
|
||||
let contentStr = post.content as string || '';
|
||||
|
||||
|
||||
let excerpt;
|
||||
let contentStr = (post.content as string) || '';
|
||||
|
||||
// Filter out Hugo shortcodes like {{< admonition abstract "标题" >}}
|
||||
contentStr = contentStr.replace(/\{\{<[\s\S]*?>\}\}/g, '');
|
||||
|
||||
|
||||
const parts = contentStr.split('<!--more-->');
|
||||
if (parts.length > 1) {
|
||||
excerpt = parts[0].trim();
|
||||
} else {
|
||||
// Strip basic markdown syntax for the automatic excerpt
|
||||
let plainText = contentStr.replace(/[#*\`>~\\[\\]\\(\\)\\-]/g, '').trim();
|
||||
const plainText = contentStr.replace(/[#*`>~[\]()-]/g, '').trim();
|
||||
excerpt = plainText.substring(0, 150).trim();
|
||||
}
|
||||
|
||||
|
||||
// Append ... to indicate there is more to read
|
||||
if (excerpt && !excerpt.endsWith('...')) {
|
||||
excerpt += '...';
|
||||
}
|
||||
|
||||
|
||||
// Do not send full content to the list view to save bandwidth
|
||||
const { content, ...restPost } = post as any;
|
||||
|
||||
|
||||
return {
|
||||
...restPost,
|
||||
id: post.id as number,
|
||||
title: post.title as string,
|
||||
slug: post.slug as string,
|
||||
cover: post.cover as string | null,
|
||||
date: `${month} ${day}, ${year}`,
|
||||
excerpt
|
||||
};
|
||||
@@ -72,7 +82,7 @@ export const load: PageServerLoad = async ({ platform }) => {
|
||||
return {
|
||||
posts: formattedPosts
|
||||
};
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching posts from D1:', err);
|
||||
return { posts: [] };
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import type { PageData } from './$types';
|
||||
import { page } from '$app/stores';
|
||||
let { data }: { data: PageData } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$page.data.site?.title || 'BlogFlare'} - {$page.data.site?.description || 'A Pure Cloudflare Blog'}</title>
|
||||
<title
|
||||
>{$page.data.site?.title || 'BlogFlare'} - {$page.data.site?.description ||
|
||||
'A Pure Cloudflare Blog'}</title
|
||||
>
|
||||
</svelte:head>
|
||||
|
||||
<section id="index-list">
|
||||
<div class="post-list">
|
||||
{#each data.posts as post}
|
||||
<a href="/posts/{post.slug}" class="card-link">
|
||||
{#each data.posts as post (post.id || post)}
|
||||
<a href={resolve('/posts/{post.slug}')} class="card-link">
|
||||
<article class="card article-content" class:has-cover={!!post.cover}>
|
||||
{#if post.cover}
|
||||
<div class="post-cover">
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
@@ -10,11 +12,20 @@ export const load: PageServerLoad = async ({ params, platform }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const page = await db.prepare(
|
||||
`SELECT title, slug, content, published_at, updated_at
|
||||
const page = await db
|
||||
.prepare(
|
||||
`SELECT title, slug, content, published_at, updated_at
|
||||
FROM pages
|
||||
WHERE slug = ?`
|
||||
).bind(slug).first<{ title: string; slug: string; content: string; published_at: string; updated_at: string }>();
|
||||
)
|
||||
.bind(slug)
|
||||
.first<{
|
||||
title: string;
|
||||
slug: string;
|
||||
content: string;
|
||||
published_at: string;
|
||||
updated_at: string;
|
||||
}>();
|
||||
|
||||
if (!page) {
|
||||
throw error(404, '该页面不存在');
|
||||
@@ -23,8 +34,8 @@ export const load: PageServerLoad = async ({ params, platform }) => {
|
||||
return {
|
||||
page
|
||||
};
|
||||
} catch (err: any) {
|
||||
if (err?.status === 404) throw err;
|
||||
} catch (err: unknown) {
|
||||
if ((err as any)?.status === 404) throw err;
|
||||
console.error('Error loading page:', err);
|
||||
throw error(500, '加载页面失败');
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import type { PageData } from './$types';
|
||||
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte';
|
||||
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
</script>
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
<div class="banner-title">
|
||||
<h1 class="post-title">{data.page.title}</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<MarkdownRenderer content={data.page.content} />
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
||||
</main>
|
||||
@@ -9,21 +9,25 @@ export const load: PageServerLoad = async ({ platform }) => {
|
||||
|
||||
try {
|
||||
// Fetch all posts, ordered by published_at descending
|
||||
const result = await db.prepare(`
|
||||
const result = await db
|
||||
.prepare(
|
||||
`
|
||||
SELECT slug, title, published_at
|
||||
FROM posts
|
||||
ORDER BY published_at DESC
|
||||
`).all();
|
||||
`
|
||||
)
|
||||
.all();
|
||||
|
||||
const posts = result.results as Array<{ slug: string; title: string; published_at: string }>;
|
||||
|
||||
// Group posts by year
|
||||
const archives: Record<string, typeof posts> = {};
|
||||
|
||||
posts.forEach(post => {
|
||||
|
||||
posts.forEach((post) => {
|
||||
const date = new Date(post.published_at);
|
||||
const year = date.getFullYear().toString();
|
||||
|
||||
|
||||
if (!archives[year]) {
|
||||
archives[year] = [];
|
||||
}
|
||||
@@ -37,7 +41,7 @@ export const load: PageServerLoad = async ({ platform }) => {
|
||||
archives,
|
||||
years
|
||||
};
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
console.error('Error fetching archives:', err);
|
||||
throw error(500, 'Failed to fetch archives');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { page } from '$app/stores';
|
||||
import { resolve } from '$app/paths';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
@@ -23,14 +24,14 @@
|
||||
</div>
|
||||
|
||||
<div class="archive-timeline">
|
||||
{#each data.years as year}
|
||||
{#each data.years as year (year)}
|
||||
<div class="archive-year-group">
|
||||
<h2 class="archive-year">{year}</h2>
|
||||
<ul class="archive-list">
|
||||
{#each data.archives[year] as post}
|
||||
{#each data.archives[year] as post (post.slug)}
|
||||
<li class="archive-item">
|
||||
<time class="archive-date">{formatDate(post.published_at)}</time>
|
||||
<a href="/posts/{post.slug}" class="archive-title">{post.title}</a>
|
||||
<a href={resolve(`/posts/${post.slug}`)} class="archive-title">{post.title}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
@@ -70,7 +71,9 @@
|
||||
align-items: baseline;
|
||||
padding: 0.8rem 0;
|
||||
border-bottom: 1px dashed rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease, border-bottom-color 0.3s ease;
|
||||
transition:
|
||||
transform 0.3s ease,
|
||||
border-bottom-color 0.3s ease;
|
||||
}
|
||||
|
||||
.archive-item:hover {
|
||||
@@ -106,7 +109,7 @@
|
||||
.archive-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
.archive-date {
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 0.85rem;
|
||||
|
||||
@@ -47,7 +47,10 @@ export const load: PageServerLoad = async ({ platform, params }) => {
|
||||
post: {
|
||||
id: 1,
|
||||
slug,
|
||||
title: slug === 'hello-world' ? 'Hello, BlogFlare! (Fallback)' : 'The Beauty of Typecho Theme VOID',
|
||||
title:
|
||||
slug === 'hello-world'
|
||||
? 'Hello, BlogFlare! (Fallback)'
|
||||
: 'The Beauty of Typecho Theme VOID',
|
||||
content: dummyMarkdown,
|
||||
cover: null,
|
||||
date: '2026-07-12',
|
||||
@@ -63,8 +66,9 @@ export const load: PageServerLoad = async ({ platform, params }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const post = await db.prepare(
|
||||
`SELECT p.id, p.slug, p.title, p.content, p.cover, p.published_at as date,
|
||||
const post = await db
|
||||
.prepare(
|
||||
`SELECT p.id, p.slug, p.title, p.content, p.cover, p.published_at as date,
|
||||
c.name as category_name, c.slug as category_slug,
|
||||
u.username as author,
|
||||
s.id as series_id, s.name as series_name, s.slug as series_slug,
|
||||
@@ -74,18 +78,23 @@ export const load: PageServerLoad = async ({ platform, params }) => {
|
||||
LEFT JOIN users u ON p.author_id = u.id
|
||||
LEFT JOIN series s ON p.series_id = s.id
|
||||
WHERE p.slug = ?`
|
||||
).bind(slug).first<PostRow>();
|
||||
)
|
||||
.bind(slug)
|
||||
.first<PostRow>();
|
||||
|
||||
if (!post) {
|
||||
error(404, 'Post not found');
|
||||
}
|
||||
|
||||
const tagsQuery = await db.prepare(
|
||||
`SELECT t.name, t.id
|
||||
const tagsQuery = await db
|
||||
.prepare(
|
||||
`SELECT t.name, t.id
|
||||
FROM tags t
|
||||
JOIN post_tags pt ON t.id = pt.tag_id
|
||||
WHERE pt.post_id = ?`
|
||||
).bind(post.id).all<{ name: string; id: number }>();
|
||||
)
|
||||
.bind(post.id)
|
||||
.all<{ name: string; id: number }>();
|
||||
const tags = tagsQuery.results || [];
|
||||
let series: {
|
||||
id: number;
|
||||
@@ -107,12 +116,15 @@ export const load: PageServerLoad = async ({ platform, params }) => {
|
||||
slug: post.series_slug,
|
||||
description: post.series_description
|
||||
};
|
||||
const seriesQuery = await db.prepare(
|
||||
`SELECT id, slug, title, series_weight, published_at
|
||||
const seriesQuery = await db
|
||||
.prepare(
|
||||
`SELECT id, slug, title, series_weight, published_at
|
||||
FROM posts
|
||||
WHERE series_id = ?
|
||||
ORDER BY series_weight IS NULL, series_weight ASC, published_at ASC, id ASC`
|
||||
).bind(post.series_id).all();
|
||||
)
|
||||
.bind(post.series_id)
|
||||
.all();
|
||||
seriesPosts = seriesQuery.results as unknown as typeof seriesPosts;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,25 @@
|
||||
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);
|
||||
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');
|
||||
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(() => {
|
||||
@@ -47,7 +55,7 @@
|
||||
} else {
|
||||
document.body.classList.remove('sidebar-show');
|
||||
}
|
||||
|
||||
|
||||
return () => {
|
||||
document.body.classList.remove('sidebar-show');
|
||||
};
|
||||
@@ -57,7 +65,6 @@
|
||||
if (hasToc) tocbot.destroy();
|
||||
};
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -83,7 +90,9 @@
|
||||
{#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>
|
||||
{$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)}
|
||||
@@ -91,7 +100,9 @@
|
||||
{#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>
|
||||
<a href={resolve('/posts/[slug]', { slug: seriesPost.slug })}
|
||||
>{seriesPost.title}</a
|
||||
>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
@@ -99,22 +110,28 @@
|
||||
{#if previousSeriesPost || nextSeriesPost}
|
||||
<nav class="series-prev-next" aria-label="系列文章翻页">
|
||||
{#if previousSeriesPost}
|
||||
<a href={resolve('/posts/[slug]', { slug: previousSeriesPost.slug })}>← {previousSeriesPost.title}</a>
|
||||
<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>
|
||||
<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/${tag.name}`)} rel="tag" class="tag-item">{tag.name}</a>
|
||||
<a href={resolve(`/tags/${encodeURIComponent(tag.name)}`)} rel="tag" class="tag-item"
|
||||
>{tag.name}</a
|
||||
>
|
||||
{/each}
|
||||
</section>
|
||||
{/if}
|
||||
@@ -122,19 +139,31 @@
|
||||
</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>
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
|
||||
@@ -194,7 +223,11 @@
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.series-prev-next { grid-template-columns: 1fr; }
|
||||
.series-prev-next a:last-child { text-align: left; }
|
||||
.series-prev-next {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.series-prev-next a:last-child {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ platform }) => {
|
||||
const db = platform?.env?.DB;
|
||||
if (!db) throw error(500, '数据库未连接');
|
||||
|
||||
try {
|
||||
const { results } = await db
|
||||
.prepare(
|
||||
@@ -24,7 +25,7 @@ export const load: PageServerLoad = async ({ platform }) => {
|
||||
post_count: number;
|
||||
}>
|
||||
};
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching public series list:', err);
|
||||
throw error(500, '读取系列列表失败');
|
||||
}
|
||||
|
||||
@@ -4,14 +4,12 @@ import type { PageServerLoad } from './$types';
|
||||
export const load: PageServerLoad = async ({ platform, params }) => {
|
||||
const db = platform?.env?.DB;
|
||||
if (!db) throw error(500, '数据库未连接');
|
||||
|
||||
try {
|
||||
const series = await db
|
||||
.prepare('SELECT id, name, slug, description FROM series WHERE slug = ?')
|
||||
.bind(params.slug)
|
||||
.first<{ id: number; name: string; slug: string; description: string | null }>();
|
||||
if (!series) throw error(404, '系列不存在');
|
||||
|
||||
const { results } = await db
|
||||
.prepare(
|
||||
`SELECT id, slug, title, series_weight, published_at
|
||||
|
||||
Reference in new issue
Block a user