@fuxishi/vitepress-theme 1.0.10 → 1.0.11

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 CHANGED
@@ -7,17 +7,19 @@
7
7
  ## 特性
8
8
 
9
9
  - 🎵 **悬浮音乐球** — 毛玻璃质感的浮动播放器,支持单曲/列表模式,紫蓝渐变进度环与四重播放动效
10
- - 🎊 **点击彩纸** — 全局点击彩纸特效,支持金色星星、彩色烟花和自定义 Emoji
10
+ - 🎊 **点击彩纸** — 全局点击彩纸特效,支持金色星星、彩色纸片和自定义 Emoji
11
11
  - 🎨 **Hero 图片取色** — 自动提取首页 hero 图片颜色,动态应用到背景光晕、标题渐变、代码块光束和音乐球
12
12
  - ✨ **Feature 光效** — 首页 Feature 卡片鼠标追踪光晕效果
13
13
  - ✨ **光束边框** — 暗色模式下代码块和导航 hover 时展示紫青品牌色光束旋转边框
14
14
  - 🔄 **主题切换动画** — View Transition API 实现亮暗模式圆形扩散动画
15
15
  - 📜 **平滑滚动** — 右侧大纲导航点击时平滑滚动,指示条丝滑过渡
16
16
  - 🎨 **标题装饰** — h1-h4 各有不同颜色的下划线装饰
17
- - 📦 **代码块折叠** — 超过指定行数的代码块自动折叠,底部淡出遮罩,可配置折叠行数阈值
17
+ - 📦 **代码块折叠** — 超过指定行数的代码块自动折叠,底部毛玻璃模糊遮罩,可配置折叠行数阈值
18
18
  - ⚙️ **类型安全** — 完整的 TypeScript 类型定义,IDE 友好
19
19
 
20
- > **注意:** 当前版本仅适用于 VitePress `1.6.4` 版本。如果你使用 VitePress 2.x,请等待主题后续版本适配。
20
+ > **注意:** `1.0.9` 及之前的版本仅适用于 VitePress `1.6.4`。如果你使用 VitePress 2.x,请等待主题后续版本适配。
21
+ >
22
+ > ⚠️ `1.0.10` 版本的代码块折叠展开功能存在严重问题,即将废弃,请回退到 `1.0.9` 版本。`1.0.11` 版本将于 `2026-04-24 22:30` 更新。
21
23
 
22
24
  ## 安装
23
25
 
@@ -38,10 +40,10 @@ npm install vitepress
38
40
  创建 `.vitepress/theme/index.ts`:
39
41
 
40
42
  ```ts
41
- import FxTheme from "@fuxishi/vitepress-theme";
42
- import "@fuxishi/vitepress-theme/style.css";
43
+ import FxTheme from "@fuxishi/vitepress-theme"
44
+ import "@fuxishi/vitepress-theme/style.css"
43
45
 
44
- export default FxTheme;
46
+ export default FxTheme
45
47
  ```
46
48
 
47
49
  ### 2. 继承配置
@@ -49,9 +51,9 @@ export default FxTheme;
49
51
  创建 `.vitepress/config.mts`:
50
52
 
51
53
  ```ts
52
- import { defineConfigWithTheme } from "vitepress";
53
- import fxConfig from "@fuxishi/vitepress-theme/config";
54
- import type { FxThemeConfig } from "@fuxishi/vitepress-theme/config";
54
+ import { defineConfigWithTheme } from "vitepress"
55
+ import fxConfig from "@fuxishi/vitepress-theme/config"
56
+ import type { FxThemeConfig } from "@fuxishi/vitepress-theme/config"
55
57
 
56
58
  export default defineConfigWithTheme<FxThemeConfig>({
57
59
  extends: fxConfig,
@@ -71,8 +73,8 @@ export default defineConfigWithTheme<FxThemeConfig>({
71
73
  },
72
74
  // 启用彩纸效果
73
75
  confetti: true,
74
- // 彩色烟花
75
- // confetti: { shape: "firework" },
76
+ // 彩色纸片
77
+ // confetti: { shape: "colored-paper" },
76
78
  // 自定义 Emoji
77
79
  // confetti: { shapes: ["🌸", "🎀"] },
78
80
  // 启用 hero 图片取色
@@ -84,7 +86,7 @@ export default defineConfigWithTheme<FxThemeConfig>({
84
86
  // 自定义折叠行数
85
87
  // codeBlockFold: { lines: 20 },
86
88
  },
87
- });
89
+ })
88
90
  ```
89
91
 
90
92
  ### 3. 启动开发服务器
@@ -99,21 +101,22 @@ npx vitepress dev
99
101
 
100
102
  继承自 VitePress `DefaultTheme.Config`,新增以下字段:
101
103
 
102
- | 配置项 | 类型 | 默认值 | 说明 |
103
- | -------------------- | ---------------------- | -------- | ------------------------------------------------------ |
104
- | `musicBall.enable` | `boolean` | `true` | 是否启用音乐球 |
105
- | `musicBall.visible` | `boolean` | `true` | 是否可见 |
106
- | `musicBall.autoplay` | `boolean` | `false` | 自动播放 |
107
- | `musicBall.loop` | `boolean` | `true` | 循环播放(单曲模式) |
108
- | `musicBall.src` | `string` | `''` | 单首音乐地址 |
109
- | `musicBall.list` | `MusicItem[]` | — | 多首音乐列表 |
110
- | `confetti` | `boolean \| object` | `true` | 启用彩纸效果 |
111
- | `confetti.shape` | `'star' \| 'firework'` | `'star'` | 预设形状 |
112
- | `confetti.shapes` | `string \| string[]` | — | 自定义 Emoji 形状 |
113
- | `heroImageColor` | `boolean` | `false` | 自动提取 hero 图片颜色(影响光晕、标题、光束、音乐球) |
114
- | `smoothScroll` | `boolean` | `false` | 启用平滑滚动和大纲指示条过渡动画 |
115
- | `codeBlockFold` | `boolean \| object` | `true` | 代码块折叠,默认启用 |
116
- | `codeBlockFold.lines`| `number` | `10` | 超过多少行折叠 |
104
+ | 配置项 | 类型 | 默认值 | 说明 |
105
+ | --------------------- | --------------------------- | -------- | ------------------------------------------------------ |
106
+ | `musicBall.enable` | `boolean` | `true` | 是否启用音乐球 |
107
+ | `musicBall.visible` | `boolean` | `true` | 是否可见 |
108
+ | `musicBall.autoplay` | `boolean` | `false` | 自动播放 |
109
+ | `musicBall.loop` | `boolean` | `true` | 循环播放(单曲模式) |
110
+ | `musicBall.src` | `string` | `''` | 单首音乐地址 |
111
+ | `musicBall.list` | `MusicItem[]` | — | 多首音乐列表 |
112
+ | `confetti` | `boolean \| object` | `true` | 启用彩纸效果 |
113
+ | `confetti.shape` | `'star' \| 'colored-paper'` | `'star'` | 预设形状 |
114
+ | `confetti.shapes` | `string \| string[]` | — | 自定义 Emoji 形状 |
115
+ | `confetti.secondary` | `boolean` | `true` | 是否添加二次散射粒子 |
116
+ | `heroImageColor` | `boolean` | `false` | 自动提取 hero 图片颜色(影响光晕、标题、光束、音乐球) |
117
+ | `smoothScroll` | `boolean` | `false` | 启用平滑滚动和大纲指示条过渡动画 |
118
+ | `codeBlockFold` | `boolean \| object` | `true` | 代码块折叠,默认启用 |
119
+ | `codeBlockFold.lines` | `number` | `10` | 超过多少行折叠 |
117
120
 
118
121
  ## 导出
119
122
 
@@ -0,0 +1,57 @@
1
+ import { t as e } from "./icons-DtAsNqxj.js";
2
+ import { defineComponent as t, nextTick as n, onBeforeUnmount as r, onMounted as i, watch as a } from "vue";
3
+ import { useRoute as o } from "vitepress";
4
+ //#region .vitepress/components/FxCodeBlockFold.ts
5
+ var s = "fx-code-folded", c = "fx-code-fold-overlay", l = t({
6
+ props: { lines: {
7
+ type: Number,
8
+ default: 10
9
+ } },
10
+ setup(t) {
11
+ function l() {
12
+ let n = t.lines ?? 10;
13
+ document.querySelectorAll(".vp-adaptive-theme").forEach((t) => {
14
+ if (t.classList.contains(c) || t.classList.contains("vp-code-group")) return;
15
+ let r = t.querySelector("pre.vp-code code");
16
+ if (!r) return;
17
+ if (r.querySelectorAll(".line").length <= n) {
18
+ t.classList.add("fx-code-no-fold");
19
+ return;
20
+ }
21
+ if (t.querySelector(`.${c}`)) return;
22
+ t.classList.add(s), t.classList.add("fx-code-foldable");
23
+ let i = document.createElement("div");
24
+ i.className = c;
25
+ let a = document.createElement("span");
26
+ a.className = "fx-code-fold-label", a.innerHTML = `展开${e("fold")}`, i.appendChild(a), i.addEventListener("click", () => {
27
+ let n = t.classList.toggle(s);
28
+ i.classList.toggle("fx-code-fold-expanded", !n), a.innerHTML = n ? `展开${e("fold")}` : `收起${e("unfold")}`;
29
+ }), t.appendChild(i);
30
+ });
31
+ }
32
+ let u = null;
33
+ function d() {
34
+ u?.disconnect();
35
+ let e = document.querySelector(".vp-doc");
36
+ e && (u = new MutationObserver(() => {
37
+ requestAnimationFrame(l);
38
+ }), u.observe(e, {
39
+ childList: !0,
40
+ subtree: !0
41
+ }));
42
+ }
43
+ i(() => {
44
+ l(), d();
45
+ });
46
+ let f = o();
47
+ return a(() => f.path, () => {
48
+ n(() => {
49
+ l(), d();
50
+ });
51
+ }), r(() => {
52
+ u?.disconnect();
53
+ }), () => null;
54
+ }
55
+ });
56
+ //#endregion
57
+ export { l as default };
@@ -0,0 +1,87 @@
1
+ import { defineComponent as e, onBeforeUnmount as t, onMounted as n } from "vue";
2
+ import r from "canvas-confetti";
3
+ //#endregion
4
+ //#region .vitepress/components/FxConfetti.vue
5
+ var i = /* @__PURE__ */ e({
6
+ __name: "FxConfetti",
7
+ props: { confetti: { type: [Boolean, Object] } },
8
+ setup(e) {
9
+ let i = e, a = typeof i.confetti == "object" ? i.confetti : {}, o = a.shape || "star", s = a.secondary !== !1, c = a.shapes ? Array.isArray(a.shapes) ? a.shapes : [a.shapes] : null, l = [], u = [];
10
+ function d(e, t, n, i = "circle") {
11
+ let a = () => {
12
+ r({
13
+ ...n,
14
+ particleCount: 10,
15
+ scalar: 1.2
16
+ }), r({
17
+ ...n,
18
+ particleCount: 10,
19
+ scalar: .75,
20
+ ...s ? { shapes: [i] } : {}
21
+ });
22
+ };
23
+ u.push(setTimeout(a, 0)), u.push(setTimeout(a, 50)), u.push(setTimeout(a, 100));
24
+ }
25
+ let f = (e, t) => d(e, t, {
26
+ spread: 360,
27
+ ticks: 50,
28
+ gravity: 0,
29
+ decay: .94,
30
+ startVelocity: 10,
31
+ colors: [
32
+ "FFE400",
33
+ "FFBD00",
34
+ "E89400",
35
+ "FFCA6C",
36
+ "FDFFB8"
37
+ ],
38
+ origin: {
39
+ x: e,
40
+ y: t
41
+ },
42
+ shapes: ["star"]
43
+ }), p = (e, t) => d(e, t, {
44
+ spread: 360,
45
+ ticks: 50,
46
+ gravity: 0,
47
+ decay: .94,
48
+ startVelocity: 10,
49
+ colors: [
50
+ "#FF1461",
51
+ "#18FF92",
52
+ "#5A87FF",
53
+ "#FBF38C",
54
+ "#FF6B35"
55
+ ],
56
+ origin: {
57
+ x: e,
58
+ y: t
59
+ }
60
+ }, "square"), m = (e, t) => d(e, t, {
61
+ spread: 360,
62
+ ticks: 50,
63
+ gravity: 0,
64
+ decay: .94,
65
+ startVelocity: 10,
66
+ shapes: l,
67
+ origin: {
68
+ x: e,
69
+ y: t
70
+ }
71
+ });
72
+ function h(e) {
73
+ let t = e.clientX / window.innerWidth, n = e.clientY / window.innerHeight;
74
+ l.length > 0 ? m(t, n) : o === "colored-paper" ? p(t, n) : f(t, n);
75
+ }
76
+ return n(() => {
77
+ c && (l = c.map((e) => r.shapeFromText({
78
+ text: e,
79
+ scalar: 2
80
+ }))), window.addEventListener("click", h);
81
+ }), t(() => {
82
+ window.removeEventListener("click", h), u.forEach(clearTimeout);
83
+ }), (e, t) => null;
84
+ }
85
+ });
86
+ //#endregion
87
+ export { i as default };
@@ -0,0 +1,216 @@
1
+ import { defineComponent as e, nextTick as t, onBeforeUnmount as n, onMounted as r, watch as i } from "vue";
2
+ import { useData as a, useRoute as o } from "vitepress";
3
+ import { Vibrant as s } from "node-vibrant/browser";
4
+ //#region .vitepress/components/FxHeroImageBg.ts
5
+ var c = "fx-hero-colors";
6
+ function l() {
7
+ try {
8
+ let e = localStorage.getItem(c);
9
+ return e ? JSON.parse(e) : null;
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
14
+ function u(e) {
15
+ try {
16
+ localStorage.setItem(c, JSON.stringify(e));
17
+ } catch {}
18
+ }
19
+ function d(e) {
20
+ return [
21
+ e.Vibrant,
22
+ e.DarkVibrant,
23
+ e.LightVibrant,
24
+ e.Muted,
25
+ e.DarkMuted,
26
+ e.LightMuted
27
+ ].filter(Boolean);
28
+ }
29
+ function f(e) {
30
+ return e.slice().sort((e, t) => e.hsl[0] - t.hsl[0]).map((e) => e.hex);
31
+ }
32
+ function p(e) {
33
+ let t = d(e);
34
+ if (t.length === 0) return "-webkit-linear-gradient(-45deg, #bd34fe 50%, #47caff 50%)";
35
+ let n = f(t);
36
+ return `conic-gradient(from 0deg, ${[...n, n[0]].join(", ")})`;
37
+ }
38
+ function m(e, t) {
39
+ let n = Math.abs(e - t);
40
+ return Math.min(n, 360 - n);
41
+ }
42
+ function h(e) {
43
+ let t = d(e);
44
+ if (t.length < 2) return "linear-gradient(120deg, #bd34fe, #41d1ff)";
45
+ let n = -1, r = [t[0].hex, t[1].hex];
46
+ for (let e = 0; e < t.length; e++) for (let i = e + 1; i < t.length; i++) {
47
+ let a = m(t[e].hsl[0], t[i].hsl[0]);
48
+ a > n && (n = a, r = [t[e].hex, t[i].hex]);
49
+ }
50
+ return `linear-gradient(120deg, ${r[0]}, ${r[1]})`;
51
+ }
52
+ function g(e) {
53
+ return `${parseInt(e.slice(1, 3), 16)}, ${parseInt(e.slice(3, 5), 16)}, ${parseInt(e.slice(5, 7), 16)}`;
54
+ }
55
+ function _(e) {
56
+ let t = d(e);
57
+ if (t.length < 2) return ["189, 52, 254", "65, 209, 255"];
58
+ let n = -1, r = [t[0], t[1]];
59
+ for (let e = 0; e < t.length; e++) for (let i = e + 1; i < t.length; i++) {
60
+ let a = m(t[e].hsl[0], t[i].hsl[0]);
61
+ a > n && (n = a, r = [t[e], t[i]]);
62
+ }
63
+ return [g(r[0].hex), g(r[1].hex)];
64
+ }
65
+ function v() {
66
+ return document.documentElement.classList.contains("dark");
67
+ }
68
+ function y(e) {
69
+ let [t, n] = _(e);
70
+ return {
71
+ imageGradient: p(e),
72
+ nameGradient: h(e),
73
+ beamC1: t,
74
+ beamC2: n
75
+ };
76
+ }
77
+ function b(e) {
78
+ let t = document.documentElement;
79
+ t.style.setProperty("--vp-home-hero-image-background-image", e.imageGradient), t.style.setProperty("--vp-home-hero-name-background", e.nameGradient), t.style.setProperty("--fx-beam-c1", e.beamC1), t.style.setProperty("--fx-beam-c2", e.beamC2);
80
+ }
81
+ function x(e) {
82
+ let t = document.documentElement;
83
+ t.style.setProperty("--fx-beam-c1", e.beamC1), t.style.setProperty("--fx-beam-c2", e.beamC2);
84
+ }
85
+ var S = e({
86
+ props: { heroImageColor: {
87
+ type: Boolean,
88
+ default: !1
89
+ } },
90
+ render: () => null,
91
+ setup(e) {
92
+ if (!e.heroImageColor) return;
93
+ let d = null, f = o(), { frontmatter: p } = a();
94
+ function m() {
95
+ let e = document.documentElement;
96
+ e.style.removeProperty("--vp-home-hero-image-background-image"), e.style.removeProperty("--vp-home-hero-name-background"), e.style.removeProperty("--fx-beam-c1"), e.style.removeProperty("--fx-beam-c2");
97
+ }
98
+ function h(e) {
99
+ return v() ? e.dark || e.light : e.light || e.dark;
100
+ }
101
+ function g(e) {
102
+ let t = l();
103
+ return !t || !t.images ? !1 : e && (e.light && t.images.light && e.light !== t.images.light || e.dark && t.images.dark && e.dark !== t.images.dark) ? (localStorage.removeItem(c), !0) : !1;
104
+ }
105
+ function _() {
106
+ let e = l();
107
+ if (!e) return !1;
108
+ let t = h(e);
109
+ return t ? (b(t), !0) : !1;
110
+ }
111
+ function S() {
112
+ let e = l();
113
+ if (!e) return !1;
114
+ let t = h(e);
115
+ return t ? (x(t), !0) : !1;
116
+ }
117
+ function C() {
118
+ return p.value.fxHeroImages || {};
119
+ }
120
+ async function w() {
121
+ try {
122
+ let e = await import(
123
+ /* @vite-ignore */
124
+ "/index.md"
125
+ ), t = (typeof e.__pageData == "string" ? JSON.parse(e.__pageData) : e.__pageData)?.frontmatter?.hero?.image;
126
+ return t ? typeof t == "string" ? {
127
+ light: t,
128
+ dark: t
129
+ } : {
130
+ light: t.light,
131
+ dark: t.dark
132
+ } : null;
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+ async function T(e, t) {
138
+ let n = e.src;
139
+ if (n) try {
140
+ let e = y(await s.from(n).quality(1).getPalette());
141
+ (v() && t === "dark" || !v() && t === "light") && b(e);
142
+ let r = l() || {};
143
+ r[t] = e, r.images ||= {}, r.images[t] = n, u(r);
144
+ } catch {}
145
+ }
146
+ async function E(e, t) {
147
+ try {
148
+ let n = y(await s.from(e).quality(1).getPalette()), r = l() || {};
149
+ return r[t] = n, r.images ||= {}, r.images[t] = e, u(r), n;
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+ async function D(e) {
155
+ if (!e.light && !e.dark) return;
156
+ let t = {};
157
+ if (e.light) {
158
+ let n = await E(e.light, "light");
159
+ n && (t.light = n);
160
+ }
161
+ if (e.dark) {
162
+ let n = await E(e.dark, "dark");
163
+ n && (t.dark = n);
164
+ }
165
+ if (t.light || t.dark) {
166
+ let e = v() ? t.dark || t.light : t.light || t.dark;
167
+ e && b(e);
168
+ }
169
+ }
170
+ async function O() {
171
+ let e = C();
172
+ if (e.light || e.dark) {
173
+ await D(e);
174
+ return;
175
+ }
176
+ let t = await w();
177
+ if (t) {
178
+ await D(t);
179
+ return;
180
+ }
181
+ try {
182
+ let e = await (await fetch(window.location.origin + "/")).text(), t = new DOMParser().parseFromString(e, "text/html").querySelectorAll(".VPHero .image-container img.VPImage"), n = {};
183
+ t.forEach((e) => {
184
+ let t = e.getAttribute("src");
185
+ t && (e.classList.contains("dark") ? n.dark = t : n.light = t);
186
+ }), (n.light || n.dark) && await D(n);
187
+ } catch {}
188
+ }
189
+ function k() {
190
+ let e = document.querySelector(".VPHero .image-container img.VPImage.light"), t = document.querySelector(".VPHero .image-container img.VPImage.dark");
191
+ e || t ? (e && e.complete && e.naturalWidth > 0 ? T(e, "light") : e && e.addEventListener("load", () => T(e, "light"), { once: !0 }), t && t.complete && t.naturalWidth > 0 ? T(t, "dark") : t && t.addEventListener("load", () => T(t, "dark"), { once: !0 })) : (m(), S(), w().then((e) => {
192
+ if (e && (e.light || e.dark)) {
193
+ let t = l();
194
+ t?.images && (e.light && t.images.light && e.light !== t.images.light || e.dark && t.images.dark && e.dark !== t.images.dark) && (localStorage.removeItem(c), D(e));
195
+ }
196
+ }), O());
197
+ }
198
+ r(() => {
199
+ g(C()), _() || O(), k(), d = new MutationObserver((e) => {
200
+ for (let t of e) if (t.attributeName === "class") {
201
+ S(), k();
202
+ break;
203
+ }
204
+ }), d.observe(document.documentElement, {
205
+ attributes: !0,
206
+ attributeFilter: ["class"]
207
+ });
208
+ }), i(() => f.path, () => {
209
+ t(k);
210
+ }), n(() => {
211
+ d?.disconnect(), m();
212
+ });
213
+ }
214
+ });
215
+ //#endregion
216
+ export { S as default };
@@ -10,28 +10,30 @@ var r = e({ setup() {
10
10
  "#008EFF",
11
11
  "#8B5CF6",
12
12
  "#10B981"
13
- ];
14
- function r(e) {
15
- let t = e.target.closest(".VPFeature");
16
- if (!t) return;
17
- let n = t.getBoundingClientRect(), r = e.clientX - n.left, i = e.clientY - n.top;
18
- t.style.setProperty("--x", r + "px"), t.style.setProperty("--y", i + "px");
13
+ ], r = 0;
14
+ function i(e) {
15
+ cancelAnimationFrame(r), r = requestAnimationFrame(() => {
16
+ let t = e.target.closest(".VPFeature");
17
+ if (!t) return;
18
+ let n = t.getBoundingClientRect();
19
+ t.style.setProperty("--x", e.clientX - n.left + "px"), t.style.setProperty("--y", e.clientY - n.top + "px");
20
+ });
19
21
  }
20
- function i() {
22
+ function a() {
21
23
  document.querySelectorAll(".VPHomeFeatures .VPFeature").forEach((t, n) => {
22
24
  t.style.setProperty("--color", e[n % e.length]);
23
25
  });
24
26
  }
25
- let a = new MutationObserver(i);
27
+ let o = null;
26
28
  return n(() => {
27
- document.addEventListener("mousemove", r);
29
+ document.addEventListener("mousemove", i);
28
30
  let e = document.querySelector(".VPHomeFeatures");
29
- e && a.observe(e, {
31
+ e && (o = new MutationObserver(a), o.observe(e, {
30
32
  childList: !0,
31
33
  subtree: !0
32
- }), i();
34
+ })), a();
33
35
  }), t(() => {
34
- document.removeEventListener("mousemove", r), a.disconnect();
36
+ cancelAnimationFrame(r), document.removeEventListener("mousemove", i), o?.disconnect();
35
37
  }), () => null;
36
38
  } });
37
39
  //#endregion