@guzhongren/sha 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/package.json +41 -0
- package/src/avatar.ts +3 -0
- package/src/components/Avatar.astro +23 -0
- package/src/components/CodeCopyEnhancer.astro +31 -0
- package/src/components/DiagramEnhancer.astro +96 -0
- package/src/components/EChartsEnhancer.astro +31 -0
- package/src/components/EmojiEnhancer.astro +30 -0
- package/src/components/Footer.astro +12 -0
- package/src/components/Header.astro +30 -0
- package/src/components/Pagination.astro +29 -0
- package/src/components/PostCard.astro +44 -0
- package/src/components/PostList.astro +15 -0
- package/src/components/ProfileIntro.astro +17 -0
- package/src/components/SearchEnhancer.astro +183 -0
- package/src/components/TableOfContents.astro +35 -0
- package/src/components/TagList.astro +19 -0
- package/src/components/ThemeToggle.astro +25 -0
- package/src/config.ts +62 -0
- package/src/content.ts +20 -0
- package/src/emoji.ts +9 -0
- package/src/env.d.ts +32 -0
- package/src/index.ts +136 -0
- package/src/layouts/BaseLayout.astro +59 -0
- package/src/pages/about.astro +25 -0
- package/src/pages/categories/[category].astro +30 -0
- package/src/pages/categories/index.astro +18 -0
- package/src/pages/index.astro +28 -0
- package/src/pages/posts/[...slug].astro +49 -0
- package/src/pages/posts/index.astro +24 -0
- package/src/pages/posts/page/[page].astro +37 -0
- package/src/pages/search.astro +34 -0
- package/src/pages/tags/[tag].astro +30 -0
- package/src/pages/tags/index.astro +18 -0
- package/src/shortcodes.ts +8 -0
- package/src/styles/global.css +579 -0
- package/src/types.ts +94 -0
- package/src/utils.ts +35 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from "astro:content";
|
|
3
|
+
import BaseLayout from "../layouts/BaseLayout.astro";
|
|
4
|
+
import ProfileIntro from "../components/ProfileIntro.astro";
|
|
5
|
+
import PostList from "../components/PostList.astro";
|
|
6
|
+
import config from "virtual:blog-theme/config";
|
|
7
|
+
import { isPublished, sortPosts } from "../utils";
|
|
8
|
+
|
|
9
|
+
const allPosts = sortPosts((await getCollection("posts")).filter(isPublished));
|
|
10
|
+
const posts = allPosts.slice(0, config.postsPerPage);
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<BaseLayout title="Home" description={config.site.description}>
|
|
14
|
+
<ProfileIntro />
|
|
15
|
+
<section class="mx-auto max-w-5xl px-4 pb-20 sm:px-6">
|
|
16
|
+
<div class="line-y bg-[var(--panel-bg)] px-3 py-5 sm:px-4">
|
|
17
|
+
<p class="font-mono text-xs font-medium uppercase tracking-widest text-[var(--text-muted)]">Latest writing</p>
|
|
18
|
+
</div>
|
|
19
|
+
<PostList posts={posts} featuredFirst={true} />
|
|
20
|
+
{allPosts.length > config.postsPerPage && (
|
|
21
|
+
<div class="mt-10 text-center">
|
|
22
|
+
<a class="text-sm font-medium text-[var(--text-muted)] hover:text-[var(--accent)]" href="/posts/">
|
|
23
|
+
View all posts →
|
|
24
|
+
</a>
|
|
25
|
+
</div>
|
|
26
|
+
)}
|
|
27
|
+
</section>
|
|
28
|
+
</BaseLayout>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection, render } from "astro:content";
|
|
3
|
+
import BaseLayout from "../../layouts/BaseLayout.astro";
|
|
4
|
+
import TableOfContents from "../../components/TableOfContents.astro";
|
|
5
|
+
import TagList from "../../components/TagList.astro";
|
|
6
|
+
import { renderEmojiShortcodes } from "../../emoji";
|
|
7
|
+
import { formatDate, isPublished } from "../../utils";
|
|
8
|
+
|
|
9
|
+
export async function getStaticPaths() {
|
|
10
|
+
const posts = (await getCollection("posts")).filter(isPublished);
|
|
11
|
+
return posts.map((post) => ({
|
|
12
|
+
params: { slug: post.id },
|
|
13
|
+
props: { post },
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { post } = Astro.props;
|
|
18
|
+
const { Content, headings } = await render(post);
|
|
19
|
+
const title = renderEmojiShortcodes(post.data.title);
|
|
20
|
+
const description = renderEmojiShortcodes(post.data.description);
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<BaseLayout title={title} description={description}>
|
|
24
|
+
<div class="mx-auto grid max-w-6xl grid-cols-1 xl:grid-cols-[minmax(0,1fr)_18rem]">
|
|
25
|
+
<article class="min-w-0 px-4 py-12 sm:px-6 lg:px-10" data-pagefind-body>
|
|
26
|
+
<p class="font-mono text-xs font-medium uppercase tracking-widest text-[var(--text-muted)]">
|
|
27
|
+
<a class="hover:text-[var(--accent)]" href={`/categories/${post.data.category}`} data-pagefind-meta="category">{post.data.category}</a>
|
|
28
|
+
</p>
|
|
29
|
+
<h1 class="mt-3 max-w-3xl text-4xl font-semibold tracking-tight text-[var(--text-strong)]">{title}</h1>
|
|
30
|
+
<p class="mt-5 max-w-2xl text-base/7 text-[var(--text-muted)]">{description}</p>
|
|
31
|
+
<div class="mt-6 flex flex-wrap items-center gap-4 text-sm text-[var(--text-muted)]">
|
|
32
|
+
{post.data.publishDate && <time datetime={post.data.publishDate.toISOString()} data-pagefind-meta="date">{formatDate(post.data.publishDate)}</time>}
|
|
33
|
+
{post.data.updatedDate && <span>Updated {formatDate(post.data.updatedDate)}</span>}
|
|
34
|
+
</div>
|
|
35
|
+
<div class="mt-6">
|
|
36
|
+
<TagList tags={post.data.tags} />
|
|
37
|
+
</div>
|
|
38
|
+
{
|
|
39
|
+
post.data.cover && (
|
|
40
|
+
<img class="mt-10 aspect-[16/8] w-full rounded-xl border border-[var(--border-soft)] object-cover" src={post.data.cover} alt="" />
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
<div class="prose mt-10 max-w-none" data-emoji-shortcodes>
|
|
44
|
+
<Content />
|
|
45
|
+
</div>
|
|
46
|
+
</article>
|
|
47
|
+
<TableOfContents headings={headings} />
|
|
48
|
+
</div>
|
|
49
|
+
</BaseLayout>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from "astro:content";
|
|
3
|
+
import BaseLayout from "../../layouts/BaseLayout.astro";
|
|
4
|
+
import Pagination from "../../components/Pagination.astro";
|
|
5
|
+
import PostList from "../../components/PostList.astro";
|
|
6
|
+
import config from "virtual:blog-theme/config";
|
|
7
|
+
import { isPublished, sortPosts } from "../../utils";
|
|
8
|
+
|
|
9
|
+
const allPosts = sortPosts((await getCollection("posts")).filter(isPublished));
|
|
10
|
+
const total = Math.ceil(allPosts.length / config.postsPerPage);
|
|
11
|
+
const posts = allPosts.slice(0, config.postsPerPage);
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<BaseLayout title="Posts" description="All posts">
|
|
15
|
+
<section class="mx-auto max-w-4xl px-4 py-12 sm:px-6">
|
|
16
|
+
<p class="font-mono text-xs font-medium uppercase tracking-widest text-[var(--text-muted)]">Archive</p>
|
|
17
|
+
<h1 class="mt-3 text-4xl font-semibold tracking-tight text-[var(--text-strong)]">Posts</h1>
|
|
18
|
+
<p class="mt-4 text-sm/7 text-[var(--text-muted)]">All published notes, essays, and technical write-ups.</p>
|
|
19
|
+
<div class="mt-8">
|
|
20
|
+
<PostList posts={posts} />
|
|
21
|
+
</div>
|
|
22
|
+
<Pagination current={1} total={total} base="/posts/" />
|
|
23
|
+
</section>
|
|
24
|
+
</BaseLayout>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from "astro:content";
|
|
3
|
+
import BaseLayout from "../../../layouts/BaseLayout.astro";
|
|
4
|
+
import Pagination from "../../../components/Pagination.astro";
|
|
5
|
+
import PostList from "../../../components/PostList.astro";
|
|
6
|
+
import config from "virtual:blog-theme/config";
|
|
7
|
+
import { isPublished, sortPosts } from "../../../utils";
|
|
8
|
+
|
|
9
|
+
export async function getStaticPaths() {
|
|
10
|
+
const allPosts = sortPosts((await getCollection("posts")).filter(isPublished));
|
|
11
|
+
const total = Math.ceil(allPosts.length / config.postsPerPage);
|
|
12
|
+
// Start from page 2 since page 1 is handled by /posts
|
|
13
|
+
const pages = Array.from({ length: total - 1 }, (_, i) => i + 2);
|
|
14
|
+
return pages.map((page) => ({
|
|
15
|
+
params: { page: String(page) },
|
|
16
|
+
props: {
|
|
17
|
+
page,
|
|
18
|
+
total,
|
|
19
|
+
posts: allPosts.slice((page - 1) * config.postsPerPage, page * config.postsPerPage),
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { page, total, posts } = Astro.props;
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<BaseLayout title={`Posts — Page ${page}`} description={`All posts, page ${page}`}>
|
|
28
|
+
<section class="mx-auto max-w-4xl px-4 py-12 sm:px-6">
|
|
29
|
+
<p class="font-mono text-xs font-medium uppercase tracking-widest text-[var(--text-muted)]">Archive</p>
|
|
30
|
+
<h1 class="mt-3 text-4xl font-semibold tracking-tight text-[var(--text-strong)]">Posts</h1>
|
|
31
|
+
<p class="mt-4 text-sm/7 text-[var(--text-muted)]">All published notes, essays, and technical write-ups.</p>
|
|
32
|
+
<div class="mt-8">
|
|
33
|
+
<PostList posts={posts} />
|
|
34
|
+
</div>
|
|
35
|
+
<Pagination current={page} total={total} base="/posts/" />
|
|
36
|
+
</section>
|
|
37
|
+
</BaseLayout>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from "astro:content";
|
|
3
|
+
import BaseLayout from "../layouts/BaseLayout.astro";
|
|
4
|
+
import PostList from "../components/PostList.astro";
|
|
5
|
+
import { isPublished, sortPosts } from "../utils";
|
|
6
|
+
|
|
7
|
+
const posts = sortPosts((await getCollection("posts")).filter(isPublished));
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<BaseLayout title="Search" description="Static article index">
|
|
11
|
+
<section class="mx-auto max-w-4xl px-4 py-12 sm:px-6">
|
|
12
|
+
<p class="font-mono text-xs font-medium uppercase tracking-widest text-[var(--text-muted)]">Search</p>
|
|
13
|
+
<h1 class="mt-3 text-4xl font-semibold tracking-tight text-[var(--text-strong)]">Search posts</h1>
|
|
14
|
+
<p class="mt-4 text-sm/7 text-[var(--text-muted)]">
|
|
15
|
+
Search across published article titles, descriptions, and body content.
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<div class="search-page mt-8" data-search-root data-search-mode="page" data-search-limit="20">
|
|
19
|
+
<label class="search-field">
|
|
20
|
+
<span class="sr-only">Search posts</span>
|
|
21
|
+
<input data-search-input type="search" placeholder="Search posts..." autocomplete="off" />
|
|
22
|
+
</label>
|
|
23
|
+
<p class="search-status" data-search-status>Type at least 2 characters to search.</p>
|
|
24
|
+
<ol class="search-results" data-search-results></ol>
|
|
25
|
+
<div data-search-fallback>
|
|
26
|
+
<div class="rule-fade my-8"></div>
|
|
27
|
+
<p class="font-mono text-xs font-medium uppercase tracking-widest text-[var(--text-muted)]">All published posts</p>
|
|
28
|
+
<div class="mt-4">
|
|
29
|
+
<PostList posts={posts} />
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</section>
|
|
34
|
+
</BaseLayout>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from "astro:content";
|
|
3
|
+
import BaseLayout from "../../layouts/BaseLayout.astro";
|
|
4
|
+
import PostList from "../../components/PostList.astro";
|
|
5
|
+
import { isPublished, sortPosts, uniqueSorted } from "../../utils";
|
|
6
|
+
|
|
7
|
+
export async function getStaticPaths() {
|
|
8
|
+
const allPosts = (await getCollection("posts")).filter(isPublished);
|
|
9
|
+
const tags = uniqueSorted(allPosts.flatMap((post) => post.data.tags));
|
|
10
|
+
return tags.map((tag) => ({
|
|
11
|
+
params: { tag },
|
|
12
|
+
props: {
|
|
13
|
+
tag,
|
|
14
|
+
posts: sortPosts(allPosts.filter((post) => post.data.tags.includes(tag))),
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { tag, posts } = Astro.props;
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<BaseLayout title={`Tag: ${tag}`} description={`Posts tagged ${tag}`}>
|
|
23
|
+
<section class="mx-auto max-w-4xl px-4 py-12 sm:px-6">
|
|
24
|
+
<p class="font-mono text-xs font-medium uppercase tracking-widest text-[var(--text-muted)]">Tag</p>
|
|
25
|
+
<h1 class="mt-3 text-4xl font-semibold tracking-tight text-[var(--text-strong)]">{tag}</h1>
|
|
26
|
+
<div class="mt-8">
|
|
27
|
+
<PostList posts={posts} />
|
|
28
|
+
</div>
|
|
29
|
+
</section>
|
|
30
|
+
</BaseLayout>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from "astro:content";
|
|
3
|
+
import BaseLayout from "../../layouts/BaseLayout.astro";
|
|
4
|
+
import { isPublished, uniqueSorted } from "../../utils";
|
|
5
|
+
|
|
6
|
+
const posts = (await getCollection("posts")).filter(isPublished);
|
|
7
|
+
const tags = uniqueSorted(posts.flatMap((post) => post.data.tags));
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<BaseLayout title="Tags" description="Browse posts by tag">
|
|
11
|
+
<section class="mx-auto max-w-4xl px-4 py-12 sm:px-6">
|
|
12
|
+
<p class="font-mono text-xs font-medium uppercase tracking-widest text-[var(--text-muted)]">Index</p>
|
|
13
|
+
<h1 class="mt-3 text-4xl font-semibold tracking-tight text-[var(--text-strong)]">Tags</h1>
|
|
14
|
+
<div class="mt-8 flex flex-wrap gap-3">
|
|
15
|
+
{tags.map((tag) => <a class="rounded-full border border-[var(--border-soft)] px-3 py-1.5 text-sm text-[var(--text-muted)] hover:text-[var(--accent)]" href={`/tags/${tag}`}>{tag}</a>)}
|
|
16
|
+
</div>
|
|
17
|
+
</section>
|
|
18
|
+
</BaseLayout>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const echartsShortcodePattern = /\{\{<\s*echarts\s*>\}\}([\s\S]*?)\{\{<\s*\/echarts\s*>\}\}/g;
|
|
2
|
+
|
|
3
|
+
export function transformContentShortcodes(source: string) {
|
|
4
|
+
return source.replace(echartsShortcodePattern, (_match, options: string) => {
|
|
5
|
+
const payload = options.trim();
|
|
6
|
+
return `\n\n<figure class="echarts-frame"><div class="echarts-canvas" data-echarts-options="${encodeURIComponent(payload)}"></div></figure>\n\n`;
|
|
7
|
+
});
|
|
8
|
+
}
|