feat: 实现页面路由及基础内容展示逻辑

This commit is contained in:
seahi committed 2026-07-29 10:49:01 +08:00
1 parent b91f52cf4a
commit 69d6129ea8
7 files changed
+192 -251

No files matched your search

+48
View File
@@ -0,0 +1,48 @@
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ platform }) => {
const db = platform?.env?.DB;
let aliases = {
post: '文章',
page: '页面',
category: '分类',
tag: '标签'
};
let site = {
title: 'BlogFlare',
description: 'A high-performance blog powered by Cloudflare D1, R2, and KV.'
};
if (db) {
try {
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.')) {
const type = row.key.split('.')[1];
if (type && type in aliases) {
(aliases as any)[type] = row.value;
}
} else if (row.key.startsWith('site.')) {
const type = row.key.split('.')[1];
if (type && type in site) {
(site as any)[type] = row.value;
}
}
});
}
} catch (e) {
console.error('Failed to load settings', e);
}
}
return {
aliases,
site
};
};
+6 -6
View File
@@ -28,7 +28,7 @@
</script>
<svelte:head>
<title>BlogFlare - 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')}
@@ -36,10 +36,10 @@
{:else}
<header class="header {scrolled ? 'scrolled' : ''}">
<div class="container wide">
<a href="/" class="brand">BlogFlare</a>
<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' : ''}>文章</a>
<a href="/archive" class={$page.url.pathname.startsWith('/posts') || $page.url.pathname.startsWith('/archive') ? 'active' : ''}>{$page.data.aliases?.post || '文章'}</a>
<a href="/about" class={$page.url.pathname.startsWith('/about') ? 'active' : ''}>关于</a>
<a href="/admin" class={$page.url.pathname.startsWith('/admin') ? 'active' : ''}>后台</a>
</nav>
@@ -51,8 +51,8 @@
<div class="home-avatar">
<img src="data:image/png;base64,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 truncated
</div>
<h1 class="home-title">BlogFlare</h1>
<p class="home-subtitle">A high-performance blog powered by Cloudflare D1, R2, and KV.</p>
<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}
@@ -65,7 +65,7 @@
<footer>
<div class="container wide">
<section>
<p>© {new Date().getFullYear()} <span class="brand">BlogFlare</span></p>
<p>© {new Date().getFullYear()} <span class="brand">{$page.data.site?.title || 'BlogFlare'}</span></p>
<p>感谢陪伴:<span id="uptime"></span></p>
</section>
<section>
+17 -3
View File
@@ -27,7 +27,7 @@ export const load: PageServerLoad = async ({ platform }) => {
try {
const { results } = await db.prepare(
`SELECT p.id, p.slug, p.title, p.excerpt, p.published_at as date
`SELECT p.id, p.slug, p.title, p.content, p.published_at as date
FROM posts p
ORDER BY p.published_at DESC LIMIT 10`
).all();
@@ -38,9 +38,23 @@ export const load: PageServerLoad = async ({ platform }) => {
const month = d.toLocaleString('en-US', { month: 'short' });
const day = d.getDate();
const year = d.getFullYear();
let excerpt = '';
const contentStr = post.content as string || '';
const parts = contentStr.split('<!--more-->');
if (parts.length > 1) {
excerpt = parts[0].trim();
} else {
excerpt = contentStr.replace(/[#*\`>~\\[\\]\\(\\)\\-]/g, '').substring(0, 150) + (contentStr.length > 150 ? '...' : '');
}
// Do not send full content to the list view to save bandwidth
const { content, ...restPost } = post as any;
return {
...post,
date: `${month} ${day}, ${year}`
...restPost,
date: `${month} ${day}, ${year}`,
excerpt
};
});
+5
View File
@@ -1,8 +1,13 @@
<script lang="ts">
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>
</svelte:head>
<section id="index-list">
<div class="post-list">
{#each data.posts as post}
+31
View File
@@ -0,0 +1,31 @@
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, platform }) => {
const db = platform?.env?.DB;
const slug = params.slug;
if (!db) {
throw error(500, '数据库未连接');
}
try {
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 }>();
if (!page) {
throw error(404, '该页面不存在');
}
return {
page
};
} catch (err: any) {
if (err?.status === 404) throw err;
console.error('Error loading page:', err);
throw error(500, '加载页面失败');
}
};
+80
View File
@@ -0,0 +1,80 @@
<script lang="ts">
import { page } from '$app/stores';
import type { PageData } from './$types';
import { onMount } from 'svelte';
import tocbot from 'tocbot';
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte';
let sidebarShow = $state(true);
let { data }: { data: PageData } = $props();
onMount(() => {
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 (sidebarShow) {
document.body.classList.add('sidebar-show');
} else {
document.body.classList.remove('sidebar-show');
}
return () => {
document.body.classList.remove('sidebar-show');
};
});
return () => {
tocbot.destroy();
};
});
</script>
<svelte:head>
<title>{data.page.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.page.title}</h1>
</div>
<MarkdownRenderer content={data.page.content} />
</article>
</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?.page || '页面'}目录">
<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>
</main>
+5 -242
View File
@@ -1,140 +1,14 @@
<script lang="ts">
import { page } from '$app/stores';
import type { PageData } from './$types';
import { Marked } from 'marked';
import hljs from 'highlight.js';
import 'highlight.js/styles/github.css';
import DOMPurify from 'dompurify';
import { onMount } from 'svelte';
import { Fancybox } from '@fancyapps/ui/dist/index.js';
import '@fancyapps/ui/dist/fancybox/fancybox.css';
import tocbot from 'tocbot';
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte';
let sidebarShow = $state(true);
// 创建独立 Marked 实例,避免 HMR 重复注册导致双重转义
const markedInstance = new Marked();
markedInstance.use({
renderer: {
code(token) {
const code = token.text;
const lang = (token.lang || '').split(' ')[0];
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
const highlighted = hljs.highlight(code, { language }).value;
const title = lang || 'text';
const encodedCode = encodeURIComponent(code);
// 将代码按行分割并包裹 code-line,以支持 CSS 计数器行号
// highlight.js 生成的 span 在单行内,跨行会被拆开,因此可以直接 split('\n')
const wrappedLines = highlighted
.split('\n')
.map((line) => `<span class="code-line">${line}</span>`)
.join('\n');
// chevron-right — 来自 DoIt fontawesome-free/svgs/solid/chevron-right.svg
const chevronRight = '<svg class="chevron-icon" viewBox="0 0 320 512" fill="currentColor" width="0.625em" height="1em"><path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"></path></svg>';
// list-ol — 来自 DoIt fontawesome-free/svgs/solid/list-ol.svg
const listOlIcon = '<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"></path></svg>';
// bars — 来自 DoIt fontawesome-free/svgs/solid/bars.svg
const barsIcon = '<svg viewBox="0 0 448 512" fill="currentColor" width="1em" height="1em"><path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"></path></svg>';
const copyIcon = '<svg viewBox="0 0 448 512" fill="currentColor" width="1em" height="1em"><path d="M433.941 65.941l-51.882-51.882A48 48 0 00348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 00-14.059-33.941zM266 464H54a6 6 0 01-6-6V150a6 6 0 016-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 01-6 6zm128-96H182a6 6 0 01-6-6V54a6 6 0 016-6h106v88c0 13.255 10.745 24 24 24h88v200a6 6 0 01-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 011.757 4.243V112z"></path></svg>';
const checkIcon = '<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>';
const ellipsisIcon = '<svg viewBox="0 0 512 512" fill="currentColor" width="1em" height="1em"><path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"></path></svg>';
return `<div class="code-block highlight is-open show-line-numbers">
<div class="code-block-header">
<button class="code-block-title-btn" aria-hidden="true">
${chevronRight}
<span class="code-title">${title}</span>
</button>
<div class="code-header-actions">
<button class="action-icon line-number-button is-active" title="Toggle line numbers">${listOlIcon}</button>
<button class="action-icon wrap-code-button" title="Toggle code wrap">${barsIcon}</button>
<button class="action-icon copy-code-button" title="Copy code" data-code="${encodedCode}">
<span class="copy-icon" style="display:block">${copyIcon}</span>
<span class="check-icon" style="display:none">${checkIcon}</span>
</button>
<span class="ellipsis-icon" aria-hidden="true">${ellipsisIcon}</span>
</div>
</div>
<pre style="counter-reset:codeblock;"><code class="hljs language-${language}">${wrappedLines}</code></pre>
</div>`;
},
image(token) {
const src = token.href;
const alt = token.text || '';
const title = token.title || alt;
let figcaption = '';
if (title) {
figcaption = `<figcaption>${title}</figcaption>`;
}
return `<figure><a data-fancybox="gallery" data-caption="${title}" href="${src}"><img src="${src}" alt="${alt}" title="${title}"></a>${figcaption}</figure>`;
},
heading(token) {
const text = token.text;
const rawText = text.replace(/<[^>]*>?/gm, ''); // Strip HTML tags
const level = token.depth;
const id = rawText.trim().toLowerCase().replace(/\s+/g, '-');
return `<h${level} id="${id}">${text}</h${level}>`;
}
}
});
let { data }: { data: PageData } = $props();
// Custom parsing for GitHub-style and Hugo-style Admonitions
function parseAdmonitions(markdown: string) {
const icons = {
note: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z"></path></svg>',
abstract: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M48 48a48 48 0 1 0 48 48 48.14 48.14 0 0 0-48-48zm0 160a48 48 0 1 0 48 48 48.14 48.14 0 0 0-48-48zm0 160a48 48 0 1 0 48 48 48.14 48.14 0 0 0-48-48zm448 16H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"></path></svg>',
info: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"></path></svg>',
tip: '<svg viewBox="0 0 352 512" fill="currentColor" height="1em" width="1em"><path d="M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0 0 26.64 14.28h61.71a31.99 31.99 0 0 0 26.64-14.28l17.09-25.69a31.989 31.989 0 0 0 5.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.64 18.99 42.74 58.8 52.42 92.16h160.03c9.68-33.35 35.78-73.16 52.42-92.16C335.55 260.85 352 220.37 352 176 352 78.8 273.2 0 176 0S0 78.8 0 176z"></path></svg>',
success: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg>',
question: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zM262.655 90c-54.497 0-89.255 22.957-116.549 63.758-3.536 5.286-2.353 12.415 2.715 16.258l34.699 26.31c5.205 3.947 12.621 3.008 16.665-2.122 17.864-22.658 30.113-35.797 57.303-35.797 20.429 0 45.698 13.148 45.698 32.958 0 14.976-12.363 22.667-32.534 33.976C247.128 238.528 216 254.941 216 296v4c0 6.627 5.373 12 12 12h56c6.627 0 12-5.373 12-12v-1.333c0-28.462 83.186-29.647 83.186-106.667 0-58.002-60.165-102-116.531-102zM256 338c-25.365 0-46 20.635-46 46 0 25.364 20.635 46 46 46s46-20.636 46-46c0-25.365-20.635-46-46-46z"></path></svg>',
warning: '<svg viewBox="0 0 576 512" fill="currentColor" height="1em" width="1em"><path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.466-32.004 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg>',
failure: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"></path></svg>',
danger: '<svg viewBox="0 0 448 512" fill="currentColor" height="1em" width="1em"><path d="M439.15 453.06L297.17 384l141.99-69.06c7.9-3.95 11.11-13.56 7.15-21.46L432 264.85c-3.95-7.9-13.56-11.11-21.47-7.16L224 348.41 37.47 257.69c-7.9-3.95-17.51-.75-21.47 7.16L1.69 293.48c-3.96 7.9-.76 17.51 7.15 21.46L150.83 384 8.85 453.06c-7.9 3.95-11.11 13.56-7.15 21.47l14.31 28.63c3.95 7.9 13.56 11.11 21.47 7.15L224 419.59l186.53 90.72c7.9 3.95 17.51.75 21.47-7.15l14.31-28.63c3.95-7.91.75-17.52-7.16-21.47zM150 237.28l-5.48 25.87c-2.67 12.62 5.42 24.85 16.45 24.85h126.08c11.03 0 19.12-12.23 16.45-24.85l-5.5-25.87c41.78-22.41 70-62.8 70-109.28C368 61.85 303.73 0 224 0S80 61.85 80 128c0 46.48 28.22 86.87 70 109.28zM280 112c17.65 0 32 14.35 32 32s-14.35 32-32 32-32-14.35-32-32 14.35-32 32-32zm-112 0c17.65 0 32 14.35 32 32s-14.35 32-32 32-32-14.35-32-32 14.35-32 32-32z"></path></svg>',
bug: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M511.98 256.03c0-34.92-27.46-64-62.24-64h-38.38c-8.91-44.57-30.82-84.34-60.77-116.14l27.13-27.13c12.5-12.5 12.5-32.76 0-45.25l-22.63-22.63c-12.5-12.5-32.76-12.5-45.25 0l-43.14 43.14A220.617 220.617 0 0 0 256 16.03c-3.69 0-7.38.15-11.05.35l-43.15-43.15c-12.5-12.5-32.76-12.5-45.25 0l-22.63 22.63c-12.5 12.5-12.5 32.76 0 45.25l27.13 27.13c-29.95 31.8-51.86 71.57-60.77 116.14H62.26c-34.78 0-62.24 29.08-62.24 64 0 34.39 26.89 62.48 61 63.92V336.1c0 51.52 19.34 98.4 51.05 133.72l-25.56 25.56c-12.5 12.5-12.5 32.76 0 45.25l22.63 22.63c12.5 12.5 32.76 12.5 45.25 0l42.66-42.66c30.2 16.92 64.71 27.42 101 27.42s70.8-10.5 101-27.42l42.66 42.66c12.5 12.5 32.76 12.5 45.25 0l22.63-22.63c12.5-12.5 12.5-32.76 0-45.25l-25.56-25.56C430.68 434.5 450 387.62 450 336.1v-23.79c34.11-1.44 61.98-29.53 61.98-63.92v-8.36zM152 280.03c0-13.25 10.75-24 24-24s24 10.75 24 24v56c0 13.25-10.75 24-24 24s-24-10.75-24-24v-56zm160 0c0-13.25 10.75-24 24-24s24 10.75 24 24v56c0 13.25-10.75 24-24 24s-24-10.75-24-24v-56z"></path></svg>',
example: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h34.71L15.32 424.64A29.85 29.85 0 0 0 8 444v16c0 26.51 21.49 48 48 48h24a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H56c-8.84 0-16-7.16-16-16v-4.52a6.01 6.01 0 0 1 1.76-4.24l19.24-19.24a48.28 48.28 0 0 0 7.58 3.06L64 448c0 35.35 28.65 64 64 64s64-28.65 64-64-28.65-64-64-64c-9.67 0-18.77 2.21-27.05 6.04L75.64 361.7A29.8 29.8 0 0 0 56 352h-7.66c10.37-4.14 17.66-14.24 17.66-26.34v-3.31c0-15.46-12.54-28-28-28H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22c2.21 0 4 1.79 4 4v3.31c0 2.21-1.79 4-4 4H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h15.4c17.21 0 33.15 6.94 44.97 19.46l5.4-6.23a43.83 43.83 0 0 1 7.23-7.19h-15zM480 352H208a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h272a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM208 112h272a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16H208a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zM480 208H208a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h272a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM88 32H16C7.16 32 0 39.16 0 48v20c0 8.84 7.16 16 16 16h16v64c0 8.84 7.16 16 16 16h16c8.84 0 16-7.16 16-16V48c0-8.84-7.16-16-16-16h-48c0-8.84 7.16-16 16-16zm64 160c0-35.35-28.65-64-64-64s-64 28.65-64 64 28.65 64 64 64 64-28.65 64-64zm-32 0c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32zm0-24c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32zm0-24c0 17.67-14.33 32-32 32s-32-14.33-32-32 14.33-32 32-32 32 14.33 32 32z"></path></svg>',
quote: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M464 256h-80v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8c-88.4 0-160 71.6-160 160v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48zm-288 0H96v-64c0-35.3 28.7-64 64-64h8c13.3 0 24-10.7 24-24V56c0-13.3-10.7-24-24-24h-8C71.6 32 0 103.6 0 192v240c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48z"></path></svg>',
important: '<svg viewBox="0 0 192 512" fill="currentColor" height="1em" width="1em"><path d="M176 432c0 44.112-35.888 80-80 80s-80-35.888-80-80 35.888-80 80-80 80 35.888 80 80zM25.26 25.33l7.12 256.33c.35 12.63 10.71 22.34 23.36 22.34h80.51c12.65 0 23.01-9.71 23.36-22.34l7.12-256.33c.37-13.37-10.36-24.33-23.75-24.33H49.02c-13.4 0-24.12 10.96-23.76 24.33z"></path></svg>',
caution: '<svg viewBox="0 0 512 512" fill="currentColor" height="1em" width="1em"><path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg>'
};
// Parse GitHub-style Admonitions
const githubRegex = /(?:^|\r?\n)> ?\[!(NOTE|ABSTRACT|INFO|TIP|SUCCESS|QUESTION|WARNING|FAILURE|DANGER|BUG|EXAMPLE|QUOTE|IMPORTANT|CAUTION)\][ \t]*(.*?)\r?\n((?:>.*\r?\n?)*)/gi;
let html = markdown.replace(githubRegex, (match, type, title, content) => {
const typeLower = type.toLowerCase();
const displayTitle = title ? title : type.charAt(0).toUpperCase() + type.slice(1).toLowerCase();
const icon = icons[typeLower as keyof typeof icons] || icons.note;
// Remove the leading "> " from each line of the content
const cleanContent = content.replace(/^> ?/gm, '');
return `\n<div class="details admonition ${typeLower} open">\n<div class="details-summary admonition-title"><span class="icon">${icon}</span><span>${displayTitle}</span></div>\n<div class="details-content">\n<div class="admonition-content">\n\n${cleanContent}\n\n</div>\n</div>\n</div>\n`;
});
// Parse Hugo-style Admonitions: {{< admonition type "title" >}} content {{< /admonition >}}
const hugoRegex = /\{\{<\s*admonition\s+([a-zA-Z]+)(?:\s+"(.*?)")?\s*>\}\}\r?\n([\s\S]*?)\{\{<\s*\/admonition\s*>\}\}/gi;
html = html.replace(hugoRegex, (match, type, title, content) => {
const typeLower = type.toLowerCase();
const displayTitle = title ? title : type.charAt(0).toUpperCase() + type.slice(1).toLowerCase();
const icon = icons[typeLower as keyof typeof icons] || icons.note;
return `\n<div class="details admonition ${typeLower} open">\n<div class="details-summary admonition-title"><span class="icon">${icon}</span><span>${displayTitle}</span></div>\n<div class="details-content">\n<div class="admonition-content">\n\n${content}\n\n</div>\n</div>\n</div>\n`;
});
return html;
}
const rawContent = data.post.content;
const preprocessedContent = parseAdmonitions(rawContent);
// Parse markdown synchronously so it renders on the server (SSR)
// and doesn't rely on client-side effects which were causing the empty content.
const htmlContent = markedInstance.parse(preprocessedContent) as string;
onMount(() => {
tocbot.init({
tocSelector: '.js-toc',
@@ -168,115 +42,6 @@
};
});
// 绑定 Fancybox
Fancybox.bind('[data-fancybox]', {});
// 初始化代码块交互(复刻 DoIt initCodeblocks + Admonition 风格动画)
document.querySelectorAll('.code-block').forEach((codeBlock) => {
const titleButton = codeBlock.querySelector('button.code-block-title-btn') as HTMLElement;
const codeEl = codeBlock.querySelector('pre code') as HTMLElement;
const copyCodeButton = codeBlock.querySelector('button.copy-code-button') as HTMLElement;
const lineNumButton = codeBlock.querySelector('button.line-number-button') as HTMLElement;
const wrapButton = codeBlock.querySelector('button.wrap-code-button') as HTMLElement;
const copyIconEl = copyCodeButton?.querySelector('span.copy-icon') as HTMLElement;
const checkIconEl = copyCodeButton?.querySelector('span.check-icon') as HTMLElement;
// 初始展开状态:maxHeight 设为 none,允许自适应高度
if (codeEl && codeBlock.classList.contains('is-open')) {
codeEl.style.maxHeight = 'none';
}
// 展开/收缩切换(与 Admonition 动画逻辑一致)
titleButton?.addEventListener('click', () => {
if (!codeEl) return;
if (codeBlock.classList.contains('is-open')) {
// 收缩:先设定像素高度,再触发过渡到 0
if (codeEl.style.maxHeight === 'none' || codeEl.style.maxHeight === '') {
codeEl.style.maxHeight = codeEl.scrollHeight + 'px';
void codeEl.offsetHeight; // 强制 reflow
}
codeEl.style.maxHeight = '0px';
codeBlock.classList.remove('is-open');
codeBlock.classList.add('is-closed');
} else {
// 展开
codeBlock.classList.remove('is-closed');
codeBlock.classList.add('is-open');
codeEl.style.maxHeight = codeEl.scrollHeight + 'px';
const handler = (e: Event) => {
if (e.target === codeEl && codeBlock.classList.contains('is-open')) {
codeEl.style.maxHeight = 'none';
}
codeEl.removeEventListener('transitionend', handler);
};
codeEl.addEventListener('transitionend', handler);
}
});
// 行号开关
lineNumButton?.addEventListener('click', () => {
codeBlock.classList.toggle('show-line-numbers');
lineNumButton.classList.toggle('is-active');
});
// 自动换行开关
wrapButton?.addEventListener('click', () => {
codeBlock.classList.toggle('is-wrap');
wrapButton.classList.toggle('is-active');
});
// 复制代码
copyCodeButton?.addEventListener('click', () => {
if (codeEl) {
navigator.clipboard.writeText(codeEl.textContent || '');
if (copyIconEl) copyIconEl.style.display = 'none';
if (checkIconEl) checkIconEl.style.display = 'block';
setTimeout(() => {
if (copyIconEl) copyIconEl.style.display = 'block';
if (checkIconEl) checkIconEl.style.display = 'none';
}, 3000);
}
});
});
// 初始化 Admonition 展开/收缩动画(DoIt style)
document.querySelectorAll('.details').forEach((detailsEl) => {
const summaryEl = detailsEl.querySelector('.details-summary') as HTMLElement;
if (!summaryEl) return;
const contentEl = summaryEl.nextElementSibling as HTMLElement;
if (!contentEl) return;
// If initially open, set max-height to none
if (detailsEl.classList.contains('open')) {
contentEl.style.maxHeight = 'none';
}
summaryEl.addEventListener('click', (e) => {
e.preventDefault();
if (detailsEl.classList.contains('open')) {
// Close it
if (contentEl.style.maxHeight === 'none' || contentEl.style.maxHeight === '') {
contentEl.style.maxHeight = contentEl.scrollHeight + 'px';
void contentEl.offsetHeight;
}
contentEl.style.maxHeight = '0px';
detailsEl.classList.remove('open');
} else {
// Open it
detailsEl.classList.add('open');
contentEl.style.maxHeight = contentEl.scrollHeight + 'px';
const transitionEndHandler = (e: Event) => {
if (e.target === contentEl && detailsEl.classList.contains('open')) {
contentEl.style.maxHeight = 'none';
}
contentEl.removeEventListener('transitionend', transitionEndHandler);
};
contentEl.addEventListener('transitionend', transitionEndHandler);
}
});
});
return () => {
tocbot.destroy();
};
@@ -285,7 +50,7 @@
</script>
<svelte:head>
<title>{data.post.title} - BlogFlare</title>
<title>{data.post.title} - {$page.data.site?.title || 'BlogFlare'}</title>
</svelte:head>
<main id="pjax-container">
@@ -299,9 +64,7 @@
</p>
</div>
<section class="articleBody article-content js-toc-content">
{@html htmlContent}
</section>
<MarkdownRenderer content={data.post.content} />
{#if data.tags && data.tags.length > 0}
<section class="tags">
@@ -317,7 +80,7 @@
<!-- 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="文章目录">
<div class="TOC" aria-label="{$page.data.aliases?.post || '文章'}目录">
<div class="js-toc"></div>
</div>