@xingwangzhe/stalux 1.37.0 → 1.38.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/README.md +1 -1
- package/package.json +1 -1
- package/src/components/stalux/archives/toArchive.astro +5 -1
- package/src/components/stalux/common/Typewriter.astro +7 -1
- package/src/components/stalux/common/author.astro +2 -0
- package/src/components/stalux/common/date.astro +8 -1
- package/src/components/stalux/layout/PageHeading.astro +27 -0
- package/src/components/stalux/layout/footer.astro +6 -1
- package/src/components/stalux/layout/listPage.astro +2 -6
- package/src/components/stalux/links/socialMedia.astro +3 -0
- package/src/components/stalux/posts/ArticleSummary.astro +8 -2
- package/src/components/stalux/posts/PostTaxonomy.astro +10 -3
- package/src/components/stalux/posts/postCC.astro +3 -2
- package/src/components/stalux/posts/postContent.astro +46 -27
- package/src/components/stalux/posts/postLeft.astro +17 -3
- package/src/components/stalux/posts/postRight.astro +20 -4
- package/src/components/stalux/posts/postToc.astro +5 -2
- package/src/components/stalux/tags/tagHeader.astro +5 -1
- package/src/index.ts +67 -4
- package/src/internal/font-slices.ts +159 -24
- package/src/internal/page-output-cache.ts +3 -1
- package/src/internal/style-names.generated.ts +121 -120
- package/src/layouts/PostLayout.astro +12 -2
- package/src/layouts/Stalux.astro +14 -2
- package/src/pages/404.astro +0 -3
- package/src/pages/about.astro +3 -3
- package/src/pages/archives.astro +5 -7
- package/src/pages/categories/[category].astro +0 -3
- package/src/pages/categories/index.astro +2 -5
- package/src/pages/index.astro +6 -6
- package/src/pages/posts/[post].astro +0 -3
- package/src/pages/tags/[tag].astro +0 -3
- package/src/pages/tags/index.astro +2 -5
- package/src/pages/words.astro +5 -7
- package/src/scripts/navigation.ts +16 -1
- package/src/scripts/view-transitions.ts +46 -1
- package/src/styles/base.css +4 -3
- package/src/styles/generated.css +1 -1
- package/src/styles/motion.css +60 -0
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ bunx stalux init # Generate stalux/ content directory
|
|
|
34
34
|
Requires Node.js 26 or later. Then configure `astro.config.ts`. All plugins are bundled into the Stalux integration by default — **no manual configuration is needed**:
|
|
35
35
|
|
|
36
36
|
- **Markdown**: Mermaid (MDAST detection + HAST/SVG rendering), math formulas (Temml → MathML), word count / feature flags, and PhotoSwipe image lightbox are injected into the default `satteri()` processor automatically (math / frontmatter / gfm / smart punctuation are enabled by default).
|
|
37
|
-
- **Sitemap**: `@astrojs/sitemap` is bundled (published post `/posts/*.md` endpoints are included when `promote.export_md` is enabled; other Markdown mirrors stay filtered by default).
|
|
37
|
+
- **Sitemap**: Automatically generates `/sitemap.xml` after build (copies `sitemap-0.xml` for a single sitemap, or the index for multiple sitemaps). `@astrojs/sitemap` is bundled (published post `/posts/*.md` endpoints are included when `promote.export_md` is enabled; other Markdown mirrors stay filtered by default).
|
|
38
38
|
- **Expressive Code**: bundled with line numbers enabled by default.
|
|
39
39
|
|
|
40
40
|
```ts
|
package/package.json
CHANGED
|
@@ -9,6 +9,10 @@ const siteConfig = getSiteData(configEntries);
|
|
|
9
9
|
const { t } = createTranslator(siteConfig.lang || "zh-CN");
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
<div
|
|
12
|
+
<div
|
|
13
|
+
id={"stalux-to-archive-archs"}
|
|
14
|
+
transition:name="stalux-home-archive-link"
|
|
15
|
+
transition:animate="slide"
|
|
16
|
+
>
|
|
13
17
|
<a href="/archives" data-astro-prefetch>{t("archives.viewAll")}</a>
|
|
14
18
|
</div>
|
|
@@ -10,7 +10,13 @@ const texts = normalizeMessages(messages);
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
{texts.length > 0 && (
|
|
13
|
-
<div
|
|
13
|
+
<div
|
|
14
|
+
class={className}
|
|
15
|
+
data-stalux-typewriter
|
|
16
|
+
data-messages={JSON.stringify(texts)}
|
|
17
|
+
transition:name="stalux-home-typewriter"
|
|
18
|
+
transition:animate="fade"
|
|
19
|
+
>
|
|
14
20
|
<span class="stalux-typewriter-accessible">{texts.join(", ")}</span>
|
|
15
21
|
<span class="stalux-typewriter-reserve" aria-hidden="true">
|
|
16
22
|
{texts.map((text) => (
|
|
@@ -20,6 +20,8 @@ const navigationHref = isHomePage ? "/about" : "/";
|
|
|
20
20
|
<article
|
|
21
21
|
class="stalux-author stalux-u-flex stalux-u-justify-center stalux-u-flex-col"
|
|
22
22
|
transition:persist-props
|
|
23
|
+
transition:name={isHomePage ? "stalux-home-author" : undefined}
|
|
24
|
+
transition:animate={isHomePage ? "fade" : undefined}
|
|
23
25
|
>
|
|
24
26
|
<figure
|
|
25
27
|
class={`stalux-author-figure ${props.iscolum ? "stalux-author-figure-column stalux-u-flex-col" : ""}`}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
+
const isHomePage = Astro.url.pathname === "/";
|
|
2
3
|
---
|
|
3
4
|
|
|
4
|
-
<div
|
|
5
|
+
<div
|
|
6
|
+
id={"stalux-date-date-time"}
|
|
7
|
+
class="date-time"
|
|
8
|
+
transition:persist-props
|
|
9
|
+
transition:name={isHomePage ? "stalux-home-date-time" : undefined}
|
|
10
|
+
transition:animate={isHomePage ? "fade" : undefined}
|
|
11
|
+
>
|
|
5
12
|
<time id={"stalux-date-date"} data-ref="date" datetime=""></time>
|
|
6
13
|
<time id={"stalux-date-time"} data-ref="time" datetime=""></time>
|
|
7
14
|
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
heading: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const { heading, description } = Astro.props;
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<>
|
|
11
|
+
<h1
|
|
12
|
+
class="stalux-page-title stalux-u-text-center"
|
|
13
|
+
transition:name="stalux-page-title"
|
|
14
|
+
transition:animate="none"
|
|
15
|
+
>
|
|
16
|
+
{heading}
|
|
17
|
+
</h1>
|
|
18
|
+
{description && (
|
|
19
|
+
<div
|
|
20
|
+
class="stalux-page-description stalux-u-text-center"
|
|
21
|
+
transition:name="stalux-page-description"
|
|
22
|
+
transition:animate="none"
|
|
23
|
+
>
|
|
24
|
+
{description}
|
|
25
|
+
</div>
|
|
26
|
+
)}
|
|
27
|
+
</>
|
|
@@ -51,7 +51,12 @@ const totalWordCount = await getTotalWordCount(Astro.logger);
|
|
|
51
51
|
const formattedWordCount = formatWordCount(totalWordCount, siteConfig.lang || "zh-CN");
|
|
52
52
|
---
|
|
53
53
|
|
|
54
|
-
<footer
|
|
54
|
+
<footer
|
|
55
|
+
class="stalux-footer stalux-u-w-full stalux-u-text-center"
|
|
56
|
+
data-stalux-font-group="footer"
|
|
57
|
+
transition:persist="stalux-site-footer"
|
|
58
|
+
transition:persist-props
|
|
59
|
+
>
|
|
55
60
|
<div class="stalux-footer-content stalux-u-flex stalux-u-flex-col">
|
|
56
61
|
<!-- 版权信息 -->
|
|
57
62
|
{footerConfig?.copyright?.enabled && (
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from "astro:content";
|
|
3
|
-
import Navs from "@components/stalux/layout/navs.astro";
|
|
4
3
|
import Stalux from "@layouts/Stalux.astro";
|
|
5
|
-
|
|
6
4
|
import { getSiteData } from "@utils/config-utils";
|
|
5
|
+
import PageHeading from "./PageHeading.astro";
|
|
7
6
|
|
|
8
7
|
interface Props {
|
|
9
8
|
title: string;
|
|
@@ -18,9 +17,6 @@ const siteConfig = getSiteData(configEntries);
|
|
|
18
17
|
---
|
|
19
18
|
|
|
20
19
|
<Stalux title={`${title} | ${siteConfig.title}`} description={description}>
|
|
21
|
-
<
|
|
22
|
-
<Navs />
|
|
23
|
-
<h1 class="stalux-page-title stalux-u-text-center">{heading}</h1>
|
|
24
|
-
{subheading && <div class="stalux-page-description stalux-u-text-center">{subheading}</div>}
|
|
20
|
+
<PageHeading heading={heading} description={subheading} />
|
|
25
21
|
<slot />
|
|
26
22
|
</Stalux>
|
|
@@ -18,6 +18,7 @@ const configEntries = await getCollection("config");
|
|
|
18
18
|
const mediaLinksSection = getMediaLinksData(configEntries);
|
|
19
19
|
const mediaLinks = mediaLinksSection?.items ?? [];
|
|
20
20
|
const isPostPage = Astro.url.pathname.startsWith("/posts/");
|
|
21
|
+
const isHomePage = Astro.url.pathname === "/";
|
|
21
22
|
|
|
22
23
|
// 静态图标映射表:新增图标时只需添加对应的 simple-icons 导入和映射
|
|
23
24
|
const iconMap: Record<string, SimpleIcon> = {
|
|
@@ -37,6 +38,8 @@ function getIconByTitle(iconName: string): SimpleIcon | undefined {
|
|
|
37
38
|
<nav
|
|
38
39
|
class="stalux-social"
|
|
39
40
|
transition:persist-props
|
|
41
|
+
transition:name={isHomePage ? "stalux-home-social" : undefined}
|
|
42
|
+
transition:animate={isHomePage ? "fade" : undefined}
|
|
40
43
|
aria-label="Social media"
|
|
41
44
|
data-stalux-social-animate={String(!isPostPage)}
|
|
42
45
|
>
|
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
interface Props {
|
|
3
3
|
description?: string;
|
|
4
4
|
label: string;
|
|
5
|
+
transitionName: string;
|
|
5
6
|
}
|
|
6
|
-
const { description, label } = Astro.props;
|
|
7
|
+
const { description, label, transitionName } = Astro.props;
|
|
7
8
|
const summary = description?.trim();
|
|
8
9
|
---
|
|
9
10
|
|
|
10
11
|
{summary && (
|
|
11
|
-
<aside
|
|
12
|
+
<aside
|
|
13
|
+
class="stalux-article-summary"
|
|
14
|
+
aria-label={label}
|
|
15
|
+
transition:name={transitionName}
|
|
16
|
+
transition:animate="none"
|
|
17
|
+
>
|
|
12
18
|
<p class="stalux-article-summary-label">{label}</p>
|
|
13
19
|
<p class="stalux-article-summary-text">{summary}</p>
|
|
14
20
|
</aside>
|
|
@@ -46,11 +46,12 @@ const cfg = {
|
|
|
46
46
|
<nav
|
|
47
47
|
class="stalux-post-taxonomy-container"
|
|
48
48
|
transition:name={cfg.containerName}
|
|
49
|
+
transition:animate="none"
|
|
49
50
|
aria-label={title ?? cfg.heading}
|
|
50
51
|
>
|
|
51
|
-
|
|
52
|
+
<h3 class="stalux-post-taxonomy-title">{title ?? cfg.heading}</h3>
|
|
52
53
|
{items.length > 0 ? (
|
|
53
|
-
<div class={cfg.listClass} transition:name={cfg.contentName}>
|
|
54
|
+
<div class={cfg.listClass} transition:name={cfg.contentName} transition:animate="fade">
|
|
54
55
|
{items.map((item) => (
|
|
55
56
|
<a
|
|
56
57
|
href={`${cfg.prefix}${item}/`}
|
|
@@ -62,6 +63,12 @@ const cfg = {
|
|
|
62
63
|
))}
|
|
63
64
|
</div>
|
|
64
65
|
) : (
|
|
65
|
-
<div
|
|
66
|
+
<div
|
|
67
|
+
class="stalux-post-taxonomy-empty"
|
|
68
|
+
transition:name={cfg.contentName}
|
|
69
|
+
transition:animate="fade"
|
|
70
|
+
>
|
|
71
|
+
{cfg.empty}
|
|
72
|
+
</div>
|
|
66
73
|
)}
|
|
67
74
|
</nav>
|
|
@@ -11,6 +11,7 @@ interface Props {
|
|
|
11
11
|
author: string;
|
|
12
12
|
url: string;
|
|
13
13
|
cc?: string;
|
|
14
|
+
transitionName?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
const configEntries = await getCollection("config");
|
|
@@ -19,13 +20,13 @@ const siteConfig = (configEntries.find((e) => e.id === "site")?.data ?? {}) as R
|
|
|
19
20
|
unknown
|
|
20
21
|
>;
|
|
21
22
|
const { t } = createTranslator((siteConfig.lang as string) || "zh-CN");
|
|
22
|
-
const { title, author, url, cc = "CC-BY-NC-SA-4.0" } = Astro.props;
|
|
23
|
+
const { title, author, url, cc = "CC-BY-NC-SA-4.0", transitionName } = Astro.props;
|
|
23
24
|
|
|
24
25
|
const licenseLink = buildCCLink(cc);
|
|
25
26
|
const licenseName = buildCCName(cc, t);
|
|
26
27
|
---
|
|
27
28
|
|
|
28
|
-
<div class="stalux-license-box">
|
|
29
|
+
<div class="stalux-license-box" transition:name={transitionName} transition:animate="none">
|
|
29
30
|
<div class="stalux-license-content">
|
|
30
31
|
<p class="stalux-license-title">{title}</p>
|
|
31
32
|
<p class="stalux-license-author">{t("post.author")}{author}</p>
|
|
@@ -15,6 +15,7 @@ interface Props {
|
|
|
15
15
|
description?: string;
|
|
16
16
|
transitionName?: string;
|
|
17
17
|
titleTransitionName?: string;
|
|
18
|
+
transitionScope?: "article" | "about";
|
|
18
19
|
date?: string;
|
|
19
20
|
updated?: string;
|
|
20
21
|
readingMinutes?: number;
|
|
@@ -23,6 +24,7 @@ interface Props {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const props = Astro.props as Props;
|
|
27
|
+
const transitionPrefix = props.transitionScope === "about" ? "stalux-about" : "stalux-article";
|
|
26
28
|
const configEntries = await getCollection("config");
|
|
27
29
|
const siteConfig = getSiteData(configEntries);
|
|
28
30
|
const promoteConfig = getPromoteData(configEntries);
|
|
@@ -39,14 +41,23 @@ const mdSourceUrl = exportMd ? `${Astro.url.pathname.replace(/\/$/, "")}.md` : "
|
|
|
39
41
|
<article
|
|
40
42
|
class="stalux-article"
|
|
41
43
|
data-stalux-gallery={props.hasImage ? "true" : undefined}
|
|
42
|
-
transition:name={props.transitionName}
|
|
44
|
+
transition:name={props.transitionName ?? "stalux-article-container"}
|
|
45
|
+
transition:animate="none"
|
|
43
46
|
>
|
|
44
47
|
<!-- Article Header -->
|
|
45
|
-
<header
|
|
48
|
+
<header
|
|
49
|
+
class="stalux-post-article-header"
|
|
50
|
+
transition:name={`${transitionPrefix}-header`}
|
|
51
|
+
transition:animate="fade"
|
|
52
|
+
>
|
|
46
53
|
<h1 class="stalux-post-title" transition:name={props.titleTransitionName}>
|
|
47
54
|
{props.title}
|
|
48
55
|
</h1>
|
|
49
|
-
<div
|
|
56
|
+
<div
|
|
57
|
+
class="stalux-post-metadata"
|
|
58
|
+
transition:name={`${transitionPrefix}-metadata`}
|
|
59
|
+
transition:animate="none"
|
|
60
|
+
>
|
|
50
61
|
{props.date && (
|
|
51
62
|
<time
|
|
52
63
|
class="stalux-post-meta-item"
|
|
@@ -67,30 +78,32 @@ const mdSourceUrl = exportMd ? `${Astro.url.pathname.replace(/\/$/, "")}.md` : "
|
|
|
67
78
|
</time>
|
|
68
79
|
)}
|
|
69
80
|
</div>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<span class="wordCount">
|
|
84
|
-
{t("post.wordCount")}
|
|
85
|
-
{props.wordCount}
|
|
86
|
-
</span>
|
|
87
|
-
)}
|
|
88
|
-
<span id="vercount_container_page_pv">
|
|
89
|
-
{t("post.viewCount")}
|
|
90
|
-
<span id="vercount_value_page_pv">Loading...</span>
|
|
81
|
+
<div
|
|
82
|
+
class="stalux-post-page-stats"
|
|
83
|
+
transition:name={`${transitionPrefix}-stats`}
|
|
84
|
+
transition:animate="none"
|
|
85
|
+
>
|
|
86
|
+
{(props.readingMinutes ?? 0) > 0 && (
|
|
87
|
+
<span class="stalux-post-reading-time">
|
|
88
|
+
{t("post.readingTime")}
|
|
89
|
+
{props.readingMinutes && props.readingMinutes > 0
|
|
90
|
+
? props.readingMinutes < 1
|
|
91
|
+
? t("common.lessThanOneMinute")
|
|
92
|
+
: t("common.minute", { n: props.readingMinutes })
|
|
93
|
+
: ""}
|
|
91
94
|
</span>
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
)}
|
|
96
|
+
{(props.wordCount ?? 0) > 0 && (
|
|
97
|
+
<span class="wordCount">
|
|
98
|
+
{t("post.wordCount")}
|
|
99
|
+
{props.wordCount}
|
|
100
|
+
</span>
|
|
101
|
+
)}
|
|
102
|
+
<span id="vercount_container_page_pv">
|
|
103
|
+
{t("post.viewCount")}
|
|
104
|
+
<span id="vercount_value_page_pv">Loading...</span>
|
|
105
|
+
</span>
|
|
106
|
+
</div>
|
|
94
107
|
{exportMd && (
|
|
95
108
|
<div class="stalux-post-source-link-row">
|
|
96
109
|
<a
|
|
@@ -105,7 +118,11 @@ const mdSourceUrl = exportMd ? `${Astro.url.pathname.replace(/\/$/, "")}.md` : "
|
|
|
105
118
|
)}
|
|
106
119
|
</header>
|
|
107
120
|
|
|
108
|
-
<ArticleSummary
|
|
121
|
+
<ArticleSummary
|
|
122
|
+
description={props.description}
|
|
123
|
+
label={t("post.summary")}
|
|
124
|
+
transitionName={`${transitionPrefix}-summary`}
|
|
125
|
+
/>
|
|
109
126
|
|
|
110
127
|
<!-- Article Body -->
|
|
111
128
|
<section
|
|
@@ -113,6 +130,8 @@ const mdSourceUrl = exportMd ? `${Astro.url.pathname.replace(/\/$/, "")}.md` : "
|
|
|
113
130
|
data-pagefind-body
|
|
114
131
|
data-stalux-post-id={props.postId === undefined ? undefined : String(props.postId)}
|
|
115
132
|
data-pagefind-filter={props.postId === undefined ? undefined : "post[data-stalux-post-id]"}
|
|
133
|
+
transition:name={`${transitionPrefix}-body`}
|
|
134
|
+
transition:animate="none"
|
|
116
135
|
>
|
|
117
136
|
<slot />
|
|
118
137
|
</section>
|
|
@@ -12,11 +12,25 @@ import { getSidebarAnimation } from "@utils/view-transitions";
|
|
|
12
12
|
transition:name="left-sidebar"
|
|
13
13
|
transition:animate={getSidebarAnimation("left")}
|
|
14
14
|
>
|
|
15
|
-
<
|
|
16
|
-
|
|
15
|
+
<div
|
|
16
|
+
class="stalux-sidebar-container"
|
|
17
|
+
transition:name="stalux-article-left-author"
|
|
18
|
+
transition:animate="fade"
|
|
19
|
+
>
|
|
20
|
+
<PostAuthor />
|
|
21
|
+
</div>
|
|
22
|
+
<div
|
|
23
|
+
class="stalux-sidebar-container"
|
|
24
|
+
transition:name="stalux-article-left-social"
|
|
25
|
+
transition:animate="fade"
|
|
26
|
+
>
|
|
17
27
|
<SocialMedia />
|
|
18
28
|
</div>
|
|
19
|
-
<div
|
|
29
|
+
<div
|
|
30
|
+
class="stalux-sidebar-container"
|
|
31
|
+
transition:name="stalux-article-left-random"
|
|
32
|
+
transition:animate="fade"
|
|
33
|
+
>
|
|
20
34
|
<RandomPost />
|
|
21
35
|
</div>
|
|
22
36
|
</aside>
|
|
@@ -21,17 +21,33 @@ const { headings = [], tags = [], categories = [] } = Astro.props;
|
|
|
21
21
|
transition:name="right-sidebar"
|
|
22
22
|
transition:animate={getSidebarAnimation("right")}
|
|
23
23
|
>
|
|
24
|
-
<div
|
|
24
|
+
<div
|
|
25
|
+
class="stalux-sidebar-container"
|
|
26
|
+
transition:name="stalux-article-right-date"
|
|
27
|
+
transition:animate="none"
|
|
28
|
+
>
|
|
25
29
|
<PostDate />
|
|
26
30
|
<Weather />
|
|
27
31
|
</div>
|
|
28
|
-
<div
|
|
32
|
+
<div
|
|
33
|
+
class="stalux-sidebar-container"
|
|
34
|
+
transition:name="stalux-article-right-toc"
|
|
35
|
+
transition:animate="none"
|
|
36
|
+
>
|
|
29
37
|
<PostToc headings={headings} />
|
|
30
38
|
</div>
|
|
31
|
-
<div
|
|
39
|
+
<div
|
|
40
|
+
class="stalux-sidebar-container"
|
|
41
|
+
transition:name="stalux-article-right-tags"
|
|
42
|
+
transition:animate="none"
|
|
43
|
+
>
|
|
32
44
|
<PostTaxonomy kind="tag" items={tags} />
|
|
33
45
|
</div>
|
|
34
|
-
<div
|
|
46
|
+
<div
|
|
47
|
+
class="stalux-sidebar-container"
|
|
48
|
+
transition:name="stalux-article-right-categories"
|
|
49
|
+
transition:animate="none"
|
|
50
|
+
>
|
|
35
51
|
<PostTaxonomy kind="category" items={categories} />
|
|
36
52
|
</div>
|
|
37
53
|
</aside>
|
|
@@ -31,12 +31,13 @@ const tocItems = headings
|
|
|
31
31
|
|
|
32
32
|
<nav
|
|
33
33
|
transition:name="toc-container"
|
|
34
|
+
transition:animate="none"
|
|
34
35
|
data-toc-active-class="stalux-toc-active"
|
|
35
36
|
aria-label={t("taxonomy.toc")}
|
|
36
37
|
>
|
|
37
38
|
<h3 class="stalux-toc-title">{title}</h3>
|
|
38
39
|
{tocItems.length > 0 ? (
|
|
39
|
-
<ul class="stalux-toc-list" transition:name="toc-content">
|
|
40
|
+
<ul class="stalux-toc-list" transition:name="toc-content" transition:animate="fade">
|
|
40
41
|
{tocItems.map((heading) => (
|
|
41
42
|
<li>
|
|
42
43
|
<a
|
|
@@ -50,7 +51,9 @@ const tocItems = headings
|
|
|
50
51
|
))}
|
|
51
52
|
</ul>
|
|
52
53
|
) : (
|
|
53
|
-
<div class="stalux-toc-empty"
|
|
54
|
+
<div class="stalux-toc-empty" transition:name="toc-content" transition:animate="fade">
|
|
55
|
+
{t("taxonomy.noToc")}
|
|
56
|
+
</div>
|
|
54
57
|
)}
|
|
55
58
|
</nav>
|
|
56
59
|
|
|
@@ -27,7 +27,11 @@ if (typeKey === "category" || typeKey === "categories" || typeKey === "分类")
|
|
|
27
27
|
}
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
<header
|
|
30
|
+
<header
|
|
31
|
+
class="stalux-tag-header"
|
|
32
|
+
transition:name="stalux-taxonomy-page-header-shell"
|
|
33
|
+
transition:animate="none"
|
|
34
|
+
>
|
|
31
35
|
<h1 class="stalux-tag-page-title">
|
|
32
36
|
<span class="stalux-tag-category-icon">
|
|
33
37
|
<svg
|
package/src/index.ts
CHANGED
|
@@ -32,10 +32,14 @@ import { expressiveCode } from "./expressive-code";
|
|
|
32
32
|
import { staluxComponentsAlias } from "./internal/components-plugin";
|
|
33
33
|
import {
|
|
34
34
|
clearPageFontSubsets,
|
|
35
|
+
collectSharedFontGlyphs,
|
|
36
|
+
createSharedFontGlyphs,
|
|
35
37
|
readLinkedStylesheetText,
|
|
36
38
|
removePageFontStyles,
|
|
37
39
|
resolveFontInputs,
|
|
40
|
+
sharedFontCacheKey,
|
|
38
41
|
writePageFontSubsetIntoDocumentAsync,
|
|
42
|
+
writeSharedFontSubsetsAsync,
|
|
39
43
|
} from "./internal/font-slices";
|
|
40
44
|
import { findHtmlImages } from "./internal/html-images";
|
|
41
45
|
import { createHtmlMutationEmitter } from "./internal/html-output";
|
|
@@ -61,7 +65,7 @@ import { imageCaptions } from "./plugins/image-captions";
|
|
|
61
65
|
import { temml } from "./plugins/satteri-temml";
|
|
62
66
|
import { describeError } from "./utils/diagnostics";
|
|
63
67
|
|
|
64
|
-
// 代码字体通过 Astro Fonts API 注入;正文字体由 native N-API
|
|
68
|
+
// 代码字体通过 Astro Fonts API 注入;正文字体由 native N-API 生成共享壳层与页面子集。
|
|
65
69
|
function buildFontsConfig(codeNormal: string, codeItalic?: string) {
|
|
66
70
|
return [
|
|
67
71
|
{
|
|
@@ -308,7 +312,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
308
312
|
}
|
|
309
313
|
}
|
|
310
314
|
|
|
311
|
-
// 7.
|
|
315
|
+
// 7. 字体:解析本地字体输入;壳层共享子集与页面正文字形在 build:done 阶段生成。
|
|
312
316
|
try {
|
|
313
317
|
const sliced = await resolveFontInputs(process.cwd(), fontsLogger);
|
|
314
318
|
if (sliced) {
|
|
@@ -371,6 +375,26 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
371
375
|
const parserEntry = resolveModule("parse5");
|
|
372
376
|
const imageSizeEntry = resolveModule("image-size");
|
|
373
377
|
const nativeEntry = resolveModule("@xingwangzhe/cjk-font-split-native");
|
|
378
|
+
const pageEntries = htmlFiles.filter((entry) => entry.endsWith(".html"));
|
|
379
|
+
let sharedFonts: Awaited<ReturnType<typeof writeSharedFontSubsetsAsync>> = [];
|
|
380
|
+
let sharedFontKey = "";
|
|
381
|
+
if (pageFontData) {
|
|
382
|
+
const sharedGlyphs = createSharedFontGlyphs();
|
|
383
|
+
await fs.mkdir(pageFontData.outputDir, { recursive: true });
|
|
384
|
+
await fs.mkdir(pageFontData.cacheDir, { recursive: true });
|
|
385
|
+
// The common shell is static across soft navigations; scan once, then
|
|
386
|
+
// reuse each component's content-addressed subset on every route.
|
|
387
|
+
await forEachBounded(pageEntries, 4, async (entry) => {
|
|
388
|
+
const html = await fs.readFile(path.join(outDir, entry), "utf8");
|
|
389
|
+
collectSharedFontGlyphs(parse(html), sharedGlyphs);
|
|
390
|
+
});
|
|
391
|
+
sharedFonts = await writeSharedFontSubsetsAsync(
|
|
392
|
+
pageFontData.fontBuffer,
|
|
393
|
+
sharedGlyphs,
|
|
394
|
+
pageFontData.cacheDir,
|
|
395
|
+
);
|
|
396
|
+
sharedFontKey = sharedFontCacheKey(sharedFonts);
|
|
397
|
+
}
|
|
374
398
|
const outputCache = await createPageOutputCache(
|
|
375
399
|
path.resolve("node_modules/.astro/stalux-page-output"),
|
|
376
400
|
[
|
|
@@ -394,16 +418,24 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
394
418
|
pageFontData?.fontBuffer,
|
|
395
419
|
// Track referenced files regardless of extension, including SVG/AVIF.
|
|
396
420
|
htmlFiles.map((entry) => path.join(outDir, entry)),
|
|
421
|
+
sharedFontKey,
|
|
397
422
|
);
|
|
398
423
|
let cacheHits = 0;
|
|
399
424
|
let optimizedImagePages = 0;
|
|
400
425
|
let pageFontPages = 0;
|
|
426
|
+
let pageFontSubsetCacheHits = 0;
|
|
401
427
|
if (pageFontData) await fs.mkdir(pageFontData.outputDir, { recursive: true });
|
|
402
428
|
if (pageFontData) await fs.mkdir(pageFontData.cacheDir, { recursive: true });
|
|
403
429
|
const referencedPageFonts = new Set<string>();
|
|
430
|
+
for (const sharedFont of sharedFonts) {
|
|
431
|
+
copyFileSync(
|
|
432
|
+
sharedFont.sourcePath,
|
|
433
|
+
path.join(pageFontData?.outputDir ?? outDir, sharedFont.filename),
|
|
434
|
+
);
|
|
435
|
+
referencedPageFonts.add(sharedFont.filename);
|
|
436
|
+
}
|
|
404
437
|
const linkedCssTextCache = new Map<string, string>();
|
|
405
438
|
const dimensionsCache = new Map<string, Promise<ImageDimensions | undefined>>();
|
|
406
|
-
const pageEntries = htmlFiles.filter((entry) => entry.endsWith(".html"));
|
|
407
439
|
const processPage = async (entry: string) => {
|
|
408
440
|
const output = path.join(outDir, entry);
|
|
409
441
|
const before = await fs.readFile(output, "utf8");
|
|
@@ -454,9 +486,11 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
454
486
|
pageFontData.cacheDir,
|
|
455
487
|
"/_astro/fonts/",
|
|
456
488
|
linkedStylesheetText,
|
|
489
|
+
sharedFonts,
|
|
457
490
|
);
|
|
458
491
|
if (pageFont) {
|
|
459
492
|
pageFontPages++;
|
|
493
|
+
if (pageFont.cacheHit) pageFontSubsetCacheHits++;
|
|
460
494
|
cachedFont = {
|
|
461
495
|
filename: pageFont.filename,
|
|
462
496
|
sourcePath: pageFont.sourcePath,
|
|
@@ -492,8 +526,17 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
492
526
|
}
|
|
493
527
|
logger.info(`applied image loading hints to ${optimizedImagePages} HTML pages`);
|
|
494
528
|
logger.info(
|
|
495
|
-
`generated page-specific body font subsets for ${pageFontPages} HTML pages`,
|
|
529
|
+
`generated page-specific body font subsets for ${pageFontPages} HTML pages (subset cache ${pageFontSubsetCacheHits}/${pageFontPages})`,
|
|
496
530
|
);
|
|
531
|
+
if (sharedFonts.length > 0) {
|
|
532
|
+
const sharedSummary = sharedFonts
|
|
533
|
+
.map(
|
|
534
|
+
({ group, chars, cacheHit }) =>
|
|
535
|
+
`${group} ${chars.length} glyphs${cacheHit ? " (cached)" : ""}`,
|
|
536
|
+
)
|
|
537
|
+
.join(", ");
|
|
538
|
+
logger.info(`generated shared shell font subsets (${sharedSummary})`);
|
|
539
|
+
}
|
|
497
540
|
logger.info(
|
|
498
541
|
`HTML postprocessing ${(performance.now() - started).toFixed(1)}ms; cache ${cacheHits}/${htmlFiles.filter((entry) => entry.endsWith(".html")).length}`,
|
|
499
542
|
);
|
|
@@ -602,6 +645,26 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
602
645
|
customPages,
|
|
603
646
|
filter,
|
|
604
647
|
}),
|
|
648
|
+
{
|
|
649
|
+
name: "stalux/sitemap-alias",
|
|
650
|
+
hooks: {
|
|
651
|
+
// Run after @astrojs/sitemap has written its output.
|
|
652
|
+
"astro:build:done": async ({ dir, logger }) => {
|
|
653
|
+
const base = userSitemap?.filenameBase ?? "sitemap";
|
|
654
|
+
const source = new URL(`${base}-0.xml`, dir);
|
|
655
|
+
if (!existsSync(source)) return;
|
|
656
|
+
// Keep every URL discoverable when a site uses multiple chunks.
|
|
657
|
+
const index = new URL(`${base}-index.xml`, dir);
|
|
658
|
+
const indexXml = await fs.readFile(index, "utf8");
|
|
659
|
+
const hasMultipleSitemaps = (indexXml.match(/<sitemap>/g)?.length ?? 0) > 1;
|
|
660
|
+
await fs.copyFile(
|
|
661
|
+
hasMultipleSitemaps ? index : source,
|
|
662
|
+
new URL("sitemap.xml", dir),
|
|
663
|
+
);
|
|
664
|
+
logger.info("Generated sitemap.xml");
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
},
|
|
605
668
|
);
|
|
606
669
|
}
|
|
607
670
|
|