hexo-theme-stellar 2.0.0-rc.5 → 2.0.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/CHANGELOG.md +66 -0
- package/README.md +8 -0
- package/README_EN.md +8 -0
- package/_config.yml +12 -1
- package/languages/en.yml +0 -2
- package/languages/zh-CN.yml +0 -2
- package/languages/zh-TW.yml +0 -2
- package/layout/_partial/cover/wiki_cover.ejs +9 -3
- package/layout/_partial/main/navbar/article_banner.ejs +9 -0
- package/layout/_partial/main/notebook/notebook_card.ejs +33 -13
- package/layout/_partial/primitives/navigation.ejs +3 -2
- package/layout/_partial/regions/widgets.ejs +1 -1
- package/layout/_partial/sidebar/menu.ejs +3 -0
- package/layout/notebooks.ejs +8 -6
- package/layout/settings.ejs +2 -4
- package/legal/THIRD-PARTY-NOTICES.md +2 -2
- package/package.json +1 -1
- package/scripts/events/lib/notebooks.js +5 -1
- package/scripts/lib/client-assets.js +1 -0
- package/scripts/lib/config-schema.js +2 -2
- package/scripts/lib/contribution-registry.js +14 -0
- package/scripts/lib/hero-effect-registry.js +50 -1
- package/scripts/lib/image-metadata.js +19 -4
- package/scripts/lib/internal-constants.js +1 -0
- package/scripts/lib/models/wiki.js +2 -0
- package/scripts/lib/regions.js +8 -1
- package/scripts/lib/source-config.js +6 -1
- package/scripts/schema/config-rules.js +3 -0
- package/scripts/schema/content-config-rules.js +4 -0
- package/scripts/schema/content-config-schema.js +2 -0
- package/scripts/schema/model-schema.js +4 -0
- package/source/css/_common/base.styl +1 -0
- package/source/css/_common/input.styl +12 -15
- package/source/css/_common/loading.styl +17 -0
- package/source/css/_components/pages/settings.styl +0 -1
- package/source/css/_components/partial/cover.styl +20 -0
- package/source/css/_components/sidebar/nav-area.styl +10 -0
- package/source/css/_components/sidebar/search.styl +65 -1
- package/source/css/_components/widgets/toc.styl +1 -1
- package/source/css/_components/wiki-card.styl +3 -1
- package/source/js/main.js +51 -4
- package/source/js/profile.js +1 -1
- package/source/js/runtime/extensions/hero-parallax.js +70 -0
- package/source/js/runtime/extensions/partial-navigation.js +18 -1
- package/source/js/runtime/extensions/settings.js +2 -3
- package/source/js/runtime/hero-effects/strands.js +302 -0
- package/source/js/runtime/image-color.js +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
> 发布日期:2026-09-30
|
|
6
|
+
|
|
7
|
+
### 新功能
|
|
8
|
+
|
|
9
|
+
- 配置、Collection YAML 与页面 Front Matter 采用声明式严格 Schema,字段类型、枚举与未知字段在构建前由 Doctor 报告来源与字段路径。
|
|
10
|
+
- Post、Topic、Wiki、Notebook、Note 与索引页共用同一内容模型与构建管线;`listed/searchable` 分别控制聚合展示与搜索收录,Article 与 Notebook 的列表、横幅、许可、分享、标签和排序默认值收敛到内容模型。
|
|
11
|
+
- 页面外壳统一为 Topbar、Leftbar、Rightbar Region,Brand、Menu、Widget、Footer 和 13 个 Profile 使用结构化配置与明确的覆盖语义;Leftbar 菜单支持 1–5 列,Tree、Linklist、Related 与右栏目录支持折叠,右栏随左栏收起显示紧凑目录。
|
|
12
|
+
- 新增 `card/glass/minimal/flat` 外观预设、明暗模式切换、颜色/渐变/排版/形状/背景设置,以及独立设置页。
|
|
13
|
+
- 浏览器功能改为原生 ES 模块按需加载,搜索、评论、动态效果、数据服务与请求缓存拥有明确的加载、取消与卸载生命周期;共享脚本、样式与图标不再逐页内联。
|
|
14
|
+
- 搜索改为注册式 provider(内置 `local` 与 `algolia`),入口位于 Leftbar Brand;本地搜索按章节组织结果并分批查询、渲染,浮层带开合转场与滚动条槽位补位,支持 `Command+K` / `Ctrl+K`。
|
|
15
|
+
- Wiki Hero 支持图片与视频背景、滚动视差,以及 `galaxy`、`light-rays`、`ferrofluid`、`Strands` 可选效果;标签目录新增 `gist`。
|
|
16
|
+
- 图片在 `hexo server` / `hexo generate` 时增量提取尺寸与 HSLA 平均色并持久缓存,补全生成 HTML 且不修改 Markdown 或原图;新增 `hexo stellar images`(含 `--dry-run/--page/--refresh`)。
|
|
17
|
+
- 新增 `hexo stellar doctor` 与 `hexo stellar new note`;目录驱动的完整 Blueprint 改由 Stellar Examples 仓库维护。
|
|
18
|
+
- 文章横幅背景、访客头像镜像 `services.gravatar.base_url`、Reveal 的 `duration/interval/distance/blur` 与卡片 Spotlight/Tilt 均可独立配置。
|
|
19
|
+
|
|
20
|
+
### 行为与样式变化
|
|
21
|
+
|
|
22
|
+
- 内容横幅与列表共用页面 `cover`;`article.banner.background`(默认 `true`)与 Front Matter `banner.background: false` 控制内容页横幅是否使用封面作为背景,关闭时只影响内容页,列表卡片、SEO 与分享仍使用 `cover`。博客列表页附加导航默认开启。
|
|
23
|
+
- 笔记本总索引卡片复用 Wiki 卡片的封面与项目栏视觉:`listing.cover` 作为封面、`listing.headline` 只取显式配置的大标题、`listing.caption` 作为项目副标题,未配置封面时降级为纯色卡片。
|
|
24
|
+
- 正文图片统一 `max-width: 100%` 与 `height: auto`,按原图比例约束高度;图片元数据只采集会被主题消费的图片,分享二维码、评论、侧边栏与页脚等页面框架图片不触发下载或写入缓存。
|
|
25
|
+
- 侧栏集合、Navbar、Dropdown、Grid、文章卡片、下载按钮与 Topbar 对齐改用统一的形状与状态反馈;设置页、单选框、标题装饰、导航下划线、目录指示条与移动端留白一并收敛。
|
|
26
|
+
- 运行环境升级为 Node.js 22+、Hexo 8+;源码读取兼容 Windows CRLF 与 BOM,避免 Front Matter 整体丢失。
|
|
27
|
+
|
|
28
|
+
### 升级注意(配置变更与破坏性改动)
|
|
29
|
+
|
|
30
|
+
- v1 的 `logo/menubar/site_tree/style/tag_plugins/plugins/data_services/search.service` 等结构不会被兼容读取;请按[公开迁移说明](https://xaoxuu.com/wiki/stellar/migration/v1-to-v2/)逐项迁移到 `topbar/leftbar/rightbar/footer/profiles/appearance/tags/features/services/search`。
|
|
31
|
+
- Collection 与 Front Matter 需要迁移为 `collection/route/navigation/listing/visibility/banner/article/comments/render/seo` 等分组字段;页面 `banner.image` 改为顶层 `cover`,横幅不再使用独立比例配置。未知字段、错误类型与非法枚举会在构建早期失败。
|
|
32
|
+
- 分享服务由 `[wechat, weibo, email, link]` 改为 `qrcode/weibo/x/telegram/whatsapp/email`,配置位置为 `article.footer.share` 或内容 `footer.share`。
|
|
33
|
+
- `plugins.card_hover.enable` 改为独立的 `features.card_hover.spotlight` 与 `features.card_hover.tilt`,默认均为 `false`;`plugins.adaptive_text` 改为固定开启的内置行为。
|
|
34
|
+
- Site Info、Rating、Vote 的服务 endpoint 默认留空,需要自行部署并填写地址;依赖这些服务的站点升级后请显式配置。
|
|
35
|
+
- `features.partial_navigation.enabled` 与 `features.image_optimization.enabled` 默认开启,首次处理远程图片会增加构建时间;Hero 背景默认启用 0.2 强度视差,设为 0 关闭位移。
|
|
36
|
+
- `about`、`users` 标签以及对应的 `chat_users.yml`、`fcircle` 适配器已移除;`tip` 改为单标签 `{% tip 词句 pop:注解 %}`,`quot` 支持直接图标键与 `color`。
|
|
37
|
+
- 博客列表页附加导航 `profiles.blog_index.listing_nav.enabled` 默认开启;语言文件不再使用 `settings.profile.provider_managed` 与 `settings.profile.provider_unknown`。
|
|
38
|
+
- 浏览器后处理必须原样保留 `public/js/runtime/**/*.js` 的 ESM 语义、相对 import 与 JavaScript MIME 类型。
|
|
39
|
+
- v2 源码从仓库 `main` 安装并需要执行 `npm install --prefix themes/stellar`;npm 安装的版本以实际公开版本为准。
|
|
40
|
+
|
|
41
|
+
Full Changelog: [1.44.0...2.0.0](https://github.com/xaoxuu/hexo-theme-stellar/compare/1.44.0...2.0.0)
|
|
42
|
+
|
|
43
|
+
## 2.0.0-rc.6
|
|
44
|
+
|
|
45
|
+
> 发布日期:2026-09-18
|
|
46
|
+
|
|
47
|
+
本节记录从公开候选 `2.0.0-rc.5` 到 `2.0.0-rc.6` 最终文件树的净变化。
|
|
48
|
+
|
|
49
|
+
### 新功能
|
|
50
|
+
|
|
51
|
+
- Wiki Hero 新增视频背景 `hero.background.video`,静音循环自动播放,图片可用作 poster;设置视频时不加载动态效果。
|
|
52
|
+
- Hero 新增滚动视差 `hero.background.parallax`,范围 0–1、默认 0.2,设为 0 关闭位移;图片与视频背景随滚动淡出,减少动态效果偏好下关闭视差位移。
|
|
53
|
+
- 新增 Strands WebGL 背景效果,支持光带配色、形态、发光及玻璃折射参数。
|
|
54
|
+
- Leftbar 新增 `menu_columns`,支持 1–5 列,默认 1;1–2 列显示图标与标题,3–5 列仅显示图标并保留无障碍名称。支持 Profile、Collection、Page 覆盖。
|
|
55
|
+
- 新增 `services.gravatar.base_url`,侧栏与设置页共享访客头像镜像地址,默认 `https://gravatar.com/avatar/`;自定义服务需兼容 SHA-256 并包含 `/avatar/` 路径。
|
|
56
|
+
|
|
57
|
+
### 修复与改进
|
|
58
|
+
|
|
59
|
+
- 同集合页面切换等待超过 150ms 时显示加载反馈,完成、失败或取消后清理,快速切换不闪烁。
|
|
60
|
+
- 修复目录活动指示条在 CSS 压缩后高度丢失的问题。
|
|
61
|
+
- 中英文安装说明补充 Windows PowerShell Blueprint 安装命令。
|
|
62
|
+
|
|
63
|
+
### 升级注意
|
|
64
|
+
|
|
65
|
+
- 现有 Hero 背景默认启用 0.2 强度视差;如需关闭位移,显式设置 `hero.background.parallax: 0`。
|
|
66
|
+
|
|
67
|
+
Full Changelog: [2.0.0-rc.5...2.0.0-rc.6](https://github.com/xaoxuu/hexo-theme-stellar/compare/2.0.0-rc.5...2.0.0-rc.6)
|
|
68
|
+
|
|
3
69
|
## 2.0.0-rc.5
|
|
4
70
|
|
|
5
71
|
> 发布日期:2026-09-12
|
package/README.md
CHANGED
|
@@ -49,10 +49,18 @@
|
|
|
49
49
|
|
|
50
50
|
准备好 Git 和 npm 后,一条命令即可启动交互式蓝图安装器:
|
|
51
51
|
|
|
52
|
+
macOS / Linux:
|
|
53
|
+
|
|
52
54
|
```bash
|
|
53
55
|
sh -c "$(curl -fsSL https://github.com/xaoxuu/hexo-theme-stellar-examples/raw/main/install.sh)"
|
|
54
56
|
```
|
|
55
57
|
|
|
58
|
+
Windows PowerShell 7+(`pwsh`):
|
|
59
|
+
|
|
60
|
+
```powershell
|
|
61
|
+
& ([scriptblock]::Create((Invoke-RestMethod https://github.com/xaoxuu/hexo-theme-stellar-examples/raw/main/install.ps1)))
|
|
62
|
+
```
|
|
63
|
+
|
|
56
64
|
安装器会读取示例仓库的 [`blueprints.json`](https://github.com/xaoxuu/hexo-theme-stellar-examples/blob/main/blueprints.json),展示当前可用的蓝图,并依次确认蓝图、项目目录、依赖安装和创建计划。
|
|
57
65
|
|
|
58
66
|
### 从 npm 安装
|
package/README_EN.md
CHANGED
|
@@ -49,10 +49,18 @@ Your writing preserves ideas while external data stays current. Remote content,
|
|
|
49
49
|
|
|
50
50
|
With Git and npm available, launch the interactive Blueprint installer with one command:
|
|
51
51
|
|
|
52
|
+
macOS / Linux:
|
|
53
|
+
|
|
52
54
|
```bash
|
|
53
55
|
sh -c "$(curl -fsSL https://github.com/xaoxuu/hexo-theme-stellar-examples/raw/main/install.sh)"
|
|
54
56
|
```
|
|
55
57
|
|
|
58
|
+
Windows PowerShell 7+ (`pwsh`):
|
|
59
|
+
|
|
60
|
+
```powershell
|
|
61
|
+
& ([scriptblock]::Create((Invoke-RestMethod https://github.com/xaoxuu/hexo-theme-stellar-examples/raw/main/install.ps1)))
|
|
62
|
+
```
|
|
63
|
+
|
|
56
64
|
The installer reads the example repository's [`blueprints.json`](https://github.com/xaoxuu/hexo-theme-stellar-examples/blob/main/blueprints.json), presents the available Blueprints, then confirms the Blueprint, project directory, dependency installation, and creation plan.
|
|
57
65
|
|
|
58
66
|
### Install from npm
|
package/_config.yml
CHANGED
|
@@ -58,6 +58,8 @@ leftbar:
|
|
|
58
58
|
tagline: null
|
|
59
59
|
# 设置品牌链接。
|
|
60
60
|
href: /
|
|
61
|
+
# 设置 Leftbar 菜单列数(1–5);1–2 列显示图标与标题,3–5 列仅显示居中的图标。
|
|
62
|
+
menu_columns: 1
|
|
61
63
|
# 设置 Leftbar 菜单项;数组会整体替换,空数组表示不显示菜单项。
|
|
62
64
|
menu:
|
|
63
65
|
# 声明默认的博客入口。
|
|
@@ -208,7 +210,7 @@ profiles:
|
|
|
208
210
|
# 配置博客列表页的附加导航。
|
|
209
211
|
listing_nav:
|
|
210
212
|
# 控制是否显示附加导航。
|
|
211
|
-
enabled:
|
|
213
|
+
enabled: true
|
|
212
214
|
# 设置附加导航标签;空数组表示不添加标签。
|
|
213
215
|
tabs: []
|
|
214
216
|
|
|
@@ -458,6 +460,11 @@ article:
|
|
|
458
460
|
style: tech
|
|
459
461
|
# 设置段落首行缩进策略;可选值为 auto、always 或 never。
|
|
460
462
|
paragraph_indent: auto
|
|
463
|
+
# 配置文章页顶部横幅。
|
|
464
|
+
banner:
|
|
465
|
+
# 控制文章页横幅是否使用页面 cover 作为背景图;false 表示只显示纯色横幅。
|
|
466
|
+
# 可在文章 Front Matter 用 banner.background 覆盖此默认值。
|
|
467
|
+
background: true
|
|
461
468
|
# 配置文章列表卡片。
|
|
462
469
|
listing:
|
|
463
470
|
# 设置置顶文章布局;可选值为 carousel 或 flat。
|
|
@@ -930,6 +937,10 @@ features:
|
|
|
930
937
|
|
|
931
938
|
# 配置主题使用的数据服务。
|
|
932
939
|
services:
|
|
940
|
+
# 配置访客身份头像使用的 Gravatar 服务(侧边栏与设置页共享)。
|
|
941
|
+
gravatar:
|
|
942
|
+
# 设置兼容 SHA-256 的头像镜像地址,包含 /avatar/ 路径;末尾斜杠可省略。
|
|
943
|
+
base_url: https://gravatar.com/avatar/
|
|
933
944
|
# 配置所有动态 Markdown 内容共享的 Marked 客户端。
|
|
934
945
|
markdown:
|
|
935
946
|
# 自定义脚本地址;null 使用默认资源。
|
package/languages/en.yml
CHANGED
|
@@ -98,8 +98,6 @@ settings:
|
|
|
98
98
|
url: URL
|
|
99
99
|
save: Save
|
|
100
100
|
logout: Log out
|
|
101
|
-
provider_managed: '%s manages comment identity outside the theme. Profile editing and logout are unavailable here.'
|
|
102
|
-
provider_unknown: The current comment provider
|
|
103
101
|
cache:
|
|
104
102
|
title: Cache
|
|
105
103
|
search: Search cache
|
package/languages/zh-CN.yml
CHANGED
package/languages/zh-TW.yml
CHANGED
|
@@ -13,6 +13,7 @@ function layoutWikiCover() {
|
|
|
13
13
|
}
|
|
14
14
|
var backgroundConfig = cover.background || {};
|
|
15
15
|
var background = typeof backgroundConfig.image === 'string' && backgroundConfig.image.length > 0 ? backgroundConfig.image : '';
|
|
16
|
+
var video = typeof backgroundConfig.video === 'string' ? backgroundConfig.video.trim() : '';
|
|
16
17
|
var effect = backgroundConfig.effect || {};
|
|
17
18
|
var preview = cover.preview || {};
|
|
18
19
|
var commands = Array.isArray(preview.commands) ? preview.commands.filter(function (item) {
|
|
@@ -25,7 +26,7 @@ function layoutWikiCover() {
|
|
|
25
26
|
var actions = Array.isArray(cover.actions) ? cover.actions.filter(function (item) {
|
|
26
27
|
return item && item.title && item.url;
|
|
27
28
|
}) : [];
|
|
28
|
-
var hasEffect = typeof effect.type === 'string' && effect.type.length > 0;
|
|
29
|
+
var hasEffect = !video && typeof effect.type === 'string' && effect.type.length > 0;
|
|
29
30
|
var isImageBackground = background.length > 0;
|
|
30
31
|
var effectOptions = effect.options && typeof effect.options === 'object' && !Array.isArray(effect.options) ? effect.options : {};
|
|
31
32
|
var effectRuntime = effect.runtime && typeof effect.runtime === 'object' && !Array.isArray(effect.runtime) ? effect.runtime : {};
|
|
@@ -37,18 +38,23 @@ function layoutWikiCover() {
|
|
|
37
38
|
var effectRuntimeJson = escape_html(JSON.stringify(effectRuntime));
|
|
38
39
|
var projectName = cover.projectName;
|
|
39
40
|
var adaptiveTextAttr = '';
|
|
40
|
-
if (
|
|
41
|
+
if (video) {
|
|
42
|
+
adaptiveTextAttr = ' data-text-adaptive="split" style="--adaptive-background:#000000"';
|
|
43
|
+
} else if (isImageBackground) {
|
|
41
44
|
adaptiveTextAttr = ' data-text-adaptive="split" style="--cover-url:url(\'' + escape_html(background.replace(/'/g, '%27')) + '\')"';
|
|
42
45
|
} else if (hasEffect) {
|
|
43
46
|
adaptiveTextAttr = ' data-text-adaptive="split" style="--adaptive-background:' + escape_html(effectBackground) + '"';
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
el += '<div class="cover-content wiki wiki-hero' + (hasPreview ? '' : ' without-preview') + '">';
|
|
47
|
-
el += '<div class="wiki-cover-background' + (hasEffect ? ' has-effect' : '') + (respectsReducedMotion ? ' respect-reduced-motion' : '') + (isImageBackground ? ' image' : '') + '"' + (hasEffect ? ' style="--hero-effect-background:' + escape_html(effectBackground) + '"' : '') + ' aria-hidden="true">';
|
|
50
|
+
el += '<div class="wiki-cover-background' + (hasEffect ? ' has-effect' : '') + (respectsReducedMotion ? ' respect-reduced-motion' : '') + (isImageBackground ? ' image' : '') + (video ? ' has-video' : '') + '" data-parallax="' + escape_html(String(backgroundConfig.parallax)) + '"' + (hasEffect ? ' style="--hero-effect-background:' + escape_html(effectBackground) + '"' : '') + ' aria-hidden="true">';
|
|
48
51
|
if (isImageBackground) {
|
|
49
52
|
el += '<img class="wiki-cover-background-image" src="' + escape_html(background) + '" alt="" />';
|
|
50
53
|
el += '<img class="wiki-cover-background-blur" src="' + escape_html(background) + '" alt="" />';
|
|
51
54
|
}
|
|
55
|
+
if (video) {
|
|
56
|
+
el += '<video class="wiki-cover-background-video" src="' + escape_html(video) + '"' + (isImageBackground ? ' poster="' + escape_html(background) + '"' : '') + ' autoplay muted loop playsinline preload="metadata" disablepictureinpicture tabindex="-1"></video>';
|
|
57
|
+
}
|
|
52
58
|
if (hasEffect) {
|
|
53
59
|
el += '<canvas data-hero-effect="' + escape_html(effect.type) + '" data-effect-resource="' + effectResourceJson + '" data-effect-options="' + effectOptionsJson + '" data-effect-runtime="' + effectRuntimeJson + '"></canvas>';
|
|
54
60
|
}
|
|
@@ -18,6 +18,15 @@ var banner = Object.assign({}, page.author
|
|
|
18
18
|
: postViewModel ? postViewModel.item.presentation.banner : pageConfig.banner || {})
|
|
19
19
|
|
|
20
20
|
var bannerImage = page.author ? page.author.cover : renderViewModel ? renderViewModel.item.cover : pageConfig.cover
|
|
21
|
+
// 横幅背景图由 Front Matter banner.background 覆盖全局默认值;作者页不受全局开关影响。
|
|
22
|
+
// 关闭时仅影响内容页横幅,列表卡片、SEO 与分享仍使用 cover。
|
|
23
|
+
var bannerBackground = banner.background
|
|
24
|
+
if (bannerBackground == null && !page.author) {
|
|
25
|
+
bannerBackground = stellar_config('article.banner.background')
|
|
26
|
+
}
|
|
27
|
+
if (bannerBackground === false) {
|
|
28
|
+
bannerImage = ''
|
|
29
|
+
}
|
|
21
30
|
|
|
22
31
|
// 标题
|
|
23
32
|
if (banner.headline == null) {
|
|
@@ -1,17 +1,37 @@
|
|
|
1
|
-
<%#
|
|
1
|
+
<%# 笔记本卡片:复用 Wiki 卡片的 .wiki-card 视觉(cover 同图模糊层、底部信息层与项目栏) %>
|
|
2
2
|
<% var listing = render.listing %>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<h2 class="post-title"><%= listing.headline || listing.name %></h2>
|
|
13
|
-
<% if (listing.description) { %>
|
|
14
|
-
<p><%= listing.description %></p>
|
|
3
|
+
<% var cover = listing.cover || '' %>
|
|
4
|
+
<% var headline = listing.headline || '' %>
|
|
5
|
+
<% var caption = listing.caption || '' %>
|
|
6
|
+
<%# 整个属性必须原样输出:EJS 转义输出会把属性里的引号变成实体,属性值带上一对引号,自适应颜色就失效 %>
|
|
7
|
+
<% var adaptiveAttr = cover ? ' data-text-adaptive="split"' : '' %>
|
|
8
|
+
<article class="wiki-card-content">
|
|
9
|
+
<div class="wiki-card-cover<%= cover ? '' : ' no-cover' %>" position="bottom">
|
|
10
|
+
<% if (cover) { %>
|
|
11
|
+
<img src="<%= cover %>" alt=""/>
|
|
15
12
|
<% } %>
|
|
13
|
+
<div class="wiki-card-info" position="bottom"<%- adaptiveAttr %>>
|
|
14
|
+
<%# headline 是可选的大标题,只在显式配置时渲染;name 固定在底部项目栏 %>
|
|
15
|
+
<% if (headline) { %>
|
|
16
|
+
<div class="wiki-card-summary">
|
|
17
|
+
<h2 class="headline"><%= headline %></h2>
|
|
18
|
+
</div>
|
|
19
|
+
<% } %>
|
|
20
|
+
<div class="wiki-project">
|
|
21
|
+
<div class="project-icon">
|
|
22
|
+
<% if (listing.icon) { %>
|
|
23
|
+
<img src="<%= listing.icon %>" alt=""/>
|
|
24
|
+
<% } else { %>
|
|
25
|
+
<%- icon('default:documents') %>
|
|
26
|
+
<% } %>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="project-text">
|
|
29
|
+
<div class="project-name"><%= listing.name %></div>
|
|
30
|
+
<% if (caption) { %>
|
|
31
|
+
<div class="project-subtitle"><%= caption %></div>
|
|
32
|
+
<% } %>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
16
36
|
</div>
|
|
17
37
|
</article>
|
|
@@ -5,8 +5,9 @@ if (!allowedPlacements.includes(placement)) {
|
|
|
5
5
|
}
|
|
6
6
|
var navigationItems = Array.isArray(entries) ? entries : []
|
|
7
7
|
var navigationBody = partial('_partial/components/collection', {options: {
|
|
8
|
-
layout: 'list',
|
|
9
|
-
|
|
8
|
+
layout: placement === 'leftbar' && typeof columns !== 'undefined' && columns > 1 ? 'grid' : 'list',
|
|
9
|
+
columns: typeof columns !== 'undefined' ? columns : 1,
|
|
10
|
+
variant: placement === 'leftbar' && typeof columns !== 'undefined' && columns > 2 ? 'icon' : 'nav',
|
|
10
11
|
items: navigationItems
|
|
11
12
|
}})
|
|
12
13
|
var html = ''
|
|
@@ -69,7 +69,7 @@ function stack(items, zone, options) {
|
|
|
69
69
|
<% if (leftbarBrandBody.trim().length > 0) { %><div class="widget-instance widget-instance--brand" data-widget-id="leftbar:fixed:brand" data-presentation="leftbar" data-leftbar-rail><%- leftbarBrandBody %></div><% } %>
|
|
70
70
|
</header>
|
|
71
71
|
<div class="site-region__body site-region__zone site-region__zone--body">
|
|
72
|
-
<% if (menuItems.length > 0) { %><div class="widget-instance widget-instance--menu" data-widget-id="leftbar:fixed:menu" data-presentation="leftbar" data-leftbar-rail><%- partial('../sidebar/menu', {where: 'leftbar', menu: menuItems, activeMenu: activeViewModel?.item.navigation.menu, viewModel: activeViewModel}) %></div><% } %>
|
|
72
|
+
<% if (menuItems.length > 0) { %><div class="widget-instance widget-instance--menu" data-widget-id="leftbar:fixed:menu" data-presentation="leftbar" data-leftbar-rail><%- partial('../sidebar/menu', {where: 'leftbar', menu: menuItems, menuColumns: regionState.menuColumns, activeMenu: activeViewModel?.item.navigation.menu, viewModel: activeViewModel}) %></div><% } %>
|
|
73
73
|
<%- stack(regionWidgets, 'body') %>
|
|
74
74
|
</div>
|
|
75
75
|
<footer class="site-region__footer">
|
|
@@ -9,6 +9,7 @@ function layoutDiv() {
|
|
|
9
9
|
var pathMenu = menu_active_by_path(page.path, menuItems)
|
|
10
10
|
if (pathMenu != null) currentMenu = pathMenu
|
|
11
11
|
}
|
|
12
|
+
var iconLayout = where === 'leftbar' && typeof menuColumns !== 'undefined' && menuColumns > 2
|
|
12
13
|
var entries = []
|
|
13
14
|
for (var menuItem of menuItems) {
|
|
14
15
|
if (menuItem == null || menuItem.id == null || menuItem.url == null || menuItem.title == null) {
|
|
@@ -18,6 +19,7 @@ function layoutDiv() {
|
|
|
18
19
|
href: pretty_url(menuItem.url),
|
|
19
20
|
title: __(menuItem.title),
|
|
20
21
|
icon: menuItem.icon,
|
|
22
|
+
attrs: iconLayout ? {'aria-label': __(menuItem.title)} : {},
|
|
21
23
|
theme: menuItem.accent,
|
|
22
24
|
active: menuItem.id == currentMenu,
|
|
23
25
|
className: menuItem.accent?.length > 0 ? 'is-themed' : ''
|
|
@@ -25,6 +27,7 @@ function layoutDiv() {
|
|
|
25
27
|
}
|
|
26
28
|
return partial('_partial/primitives/navigation', {
|
|
27
29
|
placement: ['main', 'topbar', 'rightbar'].includes(where) ? where : 'leftbar',
|
|
30
|
+
columns: where === 'leftbar' && typeof menuColumns !== 'undefined' ? menuColumns : 1,
|
|
28
31
|
entries: entries
|
|
29
32
|
})
|
|
30
33
|
}
|
package/layout/notebooks.ejs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
<%#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<%# 笔记本列表页主体部分:复用 Wiki 列表的网格与卡片组件 %>
|
|
2
|
+
<div class="post-list wiki">
|
|
3
|
+
<% for (var notebook of page.notebookIndex.items) { %>
|
|
4
|
+
<% var cover = notebook.cover || '' %>
|
|
5
|
+
<% var coverStyle = cover ? ` style="--cover-url:url('${escape_html(cover.replace(/'/g, '%27'))}')"` : '' %>
|
|
4
6
|
<div class="post-card-wrap<%= scrollreveal(' ') %>">
|
|
5
|
-
<a class="<%- ui_classes('
|
|
7
|
+
<a class="<%- ui_classes('wiki-card', 'hoverCard', cover ? '' : 'no-cover') %>"<%- coverStyle %> href="<%= pretty_url(notebook.href) %>">
|
|
6
8
|
<%- include('_partial/main/notebook/notebook_card', { render: {listing: notebook} }) %>
|
|
7
9
|
</a>
|
|
8
10
|
</div>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
<% } %>
|
|
12
|
+
</div>
|
package/layout/settings.ejs
CHANGED
|
@@ -29,6 +29,7 @@ function linkAttributes(url) {
|
|
|
29
29
|
<h1><%- escape_html(__('settings.title')) %></h1>
|
|
30
30
|
</header>
|
|
31
31
|
|
|
32
|
+
<% if (providerSupported) { %>
|
|
32
33
|
<section class="settings-section" aria-labelledby="settings-profile">
|
|
33
34
|
<h2 id="settings-profile"><%- escape_html(__('settings.profile.title')) %></h2>
|
|
34
35
|
<div class="settings-section__body">
|
|
@@ -38,9 +39,6 @@ function linkAttributes(url) {
|
|
|
38
39
|
<span data-profile-avatar-fallback hidden><%- icon('default:profile', '', true) %></span>
|
|
39
40
|
</div>
|
|
40
41
|
<div class="settings-profile__content">
|
|
41
|
-
<% if (!providerSupported) { %>
|
|
42
|
-
<p data-provider-note class="settings-provider-note"><%- escape_html(__('settings.profile.provider_managed', provider || __('settings.profile.provider_unknown'))) %></p>
|
|
43
|
-
<% } else { %>
|
|
44
42
|
<form data-profile-form class="settings-form" novalidate>
|
|
45
43
|
<label>
|
|
46
44
|
<span><%- escape_html(__('settings.profile.nickname')) %></span>
|
|
@@ -60,11 +58,11 @@ function linkAttributes(url) {
|
|
|
60
58
|
</div>
|
|
61
59
|
</form>
|
|
62
60
|
<p class="settings-result" data-profile-status role="status" aria-live="polite"></p>
|
|
63
|
-
<% } %>
|
|
64
61
|
</div>
|
|
65
62
|
</div>
|
|
66
63
|
</div>
|
|
67
64
|
</section>
|
|
65
|
+
<% } %>
|
|
68
66
|
|
|
69
67
|
<section class="settings-section" aria-labelledby="settings-cache">
|
|
70
68
|
<h2 id="settings-cache"><%- escape_html(__('settings.cache.title')) %></h2>
|
|
@@ -5,10 +5,10 @@ This project includes or adapts portions of the following third-party software.
|
|
|
5
5
|
## React Bits
|
|
6
6
|
|
|
7
7
|
- Project: [React Bits](https://github.com/DavidHDev/react-bits)
|
|
8
|
-
- Components: Ferrofluid, Galaxy and Spotlight Card
|
|
8
|
+
- Components: Ferrofluid, Galaxy, Strands and Spotlight Card
|
|
9
9
|
- License: MIT
|
|
10
10
|
- Copyright: Copyright (c) 2026 David Haz
|
|
11
|
-
- Usage: Adapted for the Wiki Ferrofluid and
|
|
11
|
+
- Usage: Adapted for the Wiki Ferrofluid, Galaxy and Strands WebGL backgrounds and Card Hover spotlight layer.
|
|
12
12
|
|
|
13
13
|
### MIT License
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -137,9 +137,13 @@ module.exports = (ctx, pipeline) => {
|
|
|
137
137
|
id: collectionId,
|
|
138
138
|
href: baseDir,
|
|
139
139
|
name: identity.name,
|
|
140
|
-
headline
|
|
140
|
+
// 大标题只取显式配置的 headline;identity.headline 会回退到 name,
|
|
141
|
+
// 直接透传会让「删掉 headline」仍然渲染出同名大标题。
|
|
142
|
+
headline: typeof collectionConfig.headline === "string" ? collectionConfig.headline : "",
|
|
143
|
+
caption: identity.tagline || identity.description,
|
|
141
144
|
description: identity.description,
|
|
142
145
|
icon: identity.icon || "",
|
|
146
|
+
cover: collection.cover || "",
|
|
143
147
|
order: collection.listing.order,
|
|
144
148
|
listed: collection.visibility.listed !== false,
|
|
145
149
|
navigation: {
|
|
@@ -22,6 +22,7 @@ function clientAssets(hexo) {
|
|
|
22
22
|
'default:settings', 'copy:copy', 'weibo:repeat', 'weibo:like'];
|
|
23
23
|
const clientIcons = Object.fromEntries(keys.map(key => [key, (icons[key] || '').replace(/<!--[\s\S]*?-->/g, '')]));
|
|
24
24
|
const services = JSON.parse(JSON.stringify(INTERNAL.assets.services));
|
|
25
|
+
services.gravatar = { baseUrl: config.services.gravatar.baseUrl };
|
|
25
26
|
services.siteinfo.api = resolveServiceProvider(config.services.siteInfo)?.endpoint || null;
|
|
26
27
|
const provider = config.search.provider;
|
|
27
28
|
const search = provider ? { service: provider === 'local' ? 'local_search' : provider } : null;
|
|
@@ -256,11 +256,11 @@ function validateEffectOptions(node, definition, options, source, path, issues)
|
|
|
256
256
|
issues.push(issue("invalid_value", source, optionPath, valueType(value), "six-digit hexadecimal color", node.migration));
|
|
257
257
|
} else if (rule.type === "hex_color_array" && (
|
|
258
258
|
!Array.isArray(value)
|
|
259
|
-
|| value.length < 1
|
|
259
|
+
|| value.length < (rule.minItems ?? 1)
|
|
260
260
|
|| value.length > 8
|
|
261
261
|
|| value.some(item => typeof item !== "string" || !/^#?[a-f\d]{6}$/i.test(item))
|
|
262
262
|
)) {
|
|
263
|
-
issues.push(issue("invalid_value", source, optionPath, valueType(value),
|
|
263
|
+
issues.push(issue("invalid_value", source, optionPath, valueType(value), `${rule.minItems ?? 1} to eight six-digit hexadecimal colors`, node.migration));
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
}
|
|
@@ -139,6 +139,20 @@ const CONTRIBUTIONS = defineContributions([
|
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
141
|
},
|
|
142
|
+
{
|
|
143
|
+
id: "hero-parallax",
|
|
144
|
+
scope: "region",
|
|
145
|
+
kind: "feature",
|
|
146
|
+
entry: runtimeEntry("/js/runtime/extensions/hero-parallax.js"),
|
|
147
|
+
resources: [],
|
|
148
|
+
activation: selector(".wiki-hero"),
|
|
149
|
+
schema: null,
|
|
150
|
+
i18n: null,
|
|
151
|
+
docs: { category: "Components", path: "docs/knowledge/03-内容系统/wiki-docs.md" },
|
|
152
|
+
tests: [RUNTIME_TEST, RUNTIME_CONSUMPTION_TEST],
|
|
153
|
+
defaultsOwner: null,
|
|
154
|
+
project: () => configResult({})
|
|
155
|
+
},
|
|
142
156
|
{
|
|
143
157
|
id: "hero-effect",
|
|
144
158
|
scope: "region",
|
|
@@ -32,7 +32,7 @@ function optionMatches(rule, value) {
|
|
|
32
32
|
if (rule.type === "hex_color") return typeof value === "string" && /^#?[a-f\d]{6}$/i.test(value);
|
|
33
33
|
if (rule.type === "hex_color_array") {
|
|
34
34
|
return Array.isArray(value)
|
|
35
|
-
&& value.length >= 1
|
|
35
|
+
&& value.length >= (rule.minItems ?? 1)
|
|
36
36
|
&& value.length <= 8
|
|
37
37
|
&& value.every(item => typeof item === "string" && /^#?[a-f\d]{6}$/i.test(item));
|
|
38
38
|
}
|
|
@@ -91,6 +91,55 @@ const LIGHT_RAYS_ORIGINS = Object.freeze([
|
|
|
91
91
|
]);
|
|
92
92
|
|
|
93
93
|
const DEFINITIONS = defineHeroEffects([
|
|
94
|
+
{
|
|
95
|
+
id: "strands",
|
|
96
|
+
label: "Strands",
|
|
97
|
+
module: INTERNAL.assets.heroEffects.strands,
|
|
98
|
+
options: {
|
|
99
|
+
colors: { type: "hex_color_array", minItems: 0 },
|
|
100
|
+
count: { type: "number" },
|
|
101
|
+
speed: { type: "number" },
|
|
102
|
+
amplitude: { type: "number" },
|
|
103
|
+
waviness: { type: "number" },
|
|
104
|
+
thickness: { type: "number" },
|
|
105
|
+
glow: { type: "number" },
|
|
106
|
+
taper: { type: "number" },
|
|
107
|
+
spread: { type: "number" },
|
|
108
|
+
hueShift: { type: "number" },
|
|
109
|
+
intensity: { type: "number" },
|
|
110
|
+
saturation: { type: "number" },
|
|
111
|
+
opacity: { type: "number" },
|
|
112
|
+
scale: { type: "number" },
|
|
113
|
+
glass: { type: "boolean" },
|
|
114
|
+
refraction: { type: "number" },
|
|
115
|
+
dispersion: { type: "number" },
|
|
116
|
+
glassSize: { type: "number" }
|
|
117
|
+
},
|
|
118
|
+
defaults: {
|
|
119
|
+
colors: ["#F97316", "#7C3AED", "#06B6D4"],
|
|
120
|
+
count: 3,
|
|
121
|
+
speed: 0.5,
|
|
122
|
+
amplitude: 1,
|
|
123
|
+
waviness: 1,
|
|
124
|
+
thickness: 0.7,
|
|
125
|
+
glow: 2.6,
|
|
126
|
+
taper: 3,
|
|
127
|
+
spread: 1,
|
|
128
|
+
hueShift: 0,
|
|
129
|
+
intensity: 0.6,
|
|
130
|
+
saturation: 1.5,
|
|
131
|
+
opacity: 1,
|
|
132
|
+
scale: 1.5,
|
|
133
|
+
glass: false,
|
|
134
|
+
refraction: 1,
|
|
135
|
+
dispersion: 1,
|
|
136
|
+
glassSize: 1
|
|
137
|
+
},
|
|
138
|
+
presentation() {
|
|
139
|
+
return { background: "#000000" };
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
94
143
|
{
|
|
95
144
|
id: "galaxy",
|
|
96
145
|
label: "Galaxy",
|
|
@@ -13,6 +13,20 @@ const MAX_BYTES = 32 * 1024 * 1024;
|
|
|
13
13
|
const RETRY_DELAY = 24 * 60 * 60 * 1000;
|
|
14
14
|
const stores = new WeakMap();
|
|
15
15
|
|
|
16
|
+
// The store serves two runtime consumers: article content images (dimensions
|
|
17
|
+
// for aspect ratio) and average-color backgrounds for adaptive text. Scan the
|
|
18
|
+
// rendered article region plus the adaptive-text owners, matching
|
|
19
|
+
// source/js/plugins/adaptive-text.js, so page chrome (share QR codes, comments,
|
|
20
|
+
// sidebars, footers) never triggers downloads or store entries.
|
|
21
|
+
const METADATA_IMAGE_SELECTOR = [
|
|
22
|
+
'article.md-text.content img',
|
|
23
|
+
'.banner img',
|
|
24
|
+
'.cover img',
|
|
25
|
+
'.wiki-card-cover img',
|
|
26
|
+
'.pin-slide img',
|
|
27
|
+
'.wiki-hero img'
|
|
28
|
+
].join(', ');
|
|
29
|
+
|
|
16
30
|
function metadataPath(ctx) {
|
|
17
31
|
return path.join(ctx.source_dir || path.join(ctx.base_dir || process.cwd(), 'source'), '_data', 'caches', DATA_FILE);
|
|
18
32
|
}
|
|
@@ -91,12 +105,13 @@ function complete(entry) {
|
|
|
91
105
|
return (dimensionsReady(entry?.dimensions) || entry?.dimensions?.status === 'unsupported') && colorDone;
|
|
92
106
|
}
|
|
93
107
|
|
|
94
|
-
// Scan rendered img elements, so Markdown, tags, Front
|
|
95
|
-
// covers use their actual output without maintaining
|
|
108
|
+
// Scan rendered img elements in scope for the store, so Markdown, tags, Front
|
|
109
|
+
// Matter and data-driven covers use their actual output without maintaining
|
|
110
|
+
// another source-field list, while page chrome stays out of scope.
|
|
96
111
|
function collectImages(html, base) {
|
|
97
112
|
const urls = new Set();
|
|
98
113
|
const $ = load(html, null, false);
|
|
99
|
-
$(
|
|
114
|
+
$(METADATA_IMAGE_SELECTOR).each((_, element) => {
|
|
100
115
|
const img = $(element);
|
|
101
116
|
const url = imageUrl(img.attr('data-src') || img.attr('src'), base);
|
|
102
117
|
if (url) urls.add(url);
|
|
@@ -247,7 +262,7 @@ function enrichImages(html, ctx, locals = {}) {
|
|
|
247
262
|
let changed = false;
|
|
248
263
|
const autoRatio = (ctx.stellar?.config?.features?.lazyLoading?.autoAspectRatio
|
|
249
264
|
?? ctx.theme?.config?.features?.lazy_loading?.auto_aspect_ratio) !== false;
|
|
250
|
-
$(
|
|
265
|
+
$(METADATA_IMAGE_SELECTOR).each((_, image) => {
|
|
251
266
|
const img = $(image);
|
|
252
267
|
const src = img.attr('data-src') || img.attr('src');
|
|
253
268
|
const entry = store.images[imageUrl(src, base)];
|
|
@@ -57,6 +57,7 @@ module.exports = deepFreeze({
|
|
|
57
57
|
settings: "/js/runtime/extensions/settings.js"
|
|
58
58
|
},
|
|
59
59
|
heroEffects: {
|
|
60
|
+
strands: "/js/runtime/hero-effects/strands.js",
|
|
60
61
|
ferrofluid: "/js/runtime/hero-effects/ferrofluid.js",
|
|
61
62
|
galaxy: "/js/runtime/hero-effects/galaxy.js",
|
|
62
63
|
lightRays: "/js/runtime/hero-effects/light-rays.js"
|
|
@@ -7,6 +7,7 @@ const { firstContentImage } = require("../seo");
|
|
|
7
7
|
const { caption } = require("../caption");
|
|
8
8
|
const { wikiReadmeHtml } = require("../wiki_readme");
|
|
9
9
|
const { filterShareServices } = require("../share-services");
|
|
10
|
+
const { COLLECTION_CONFIG_SCHEMA } = require("../../schema/content-config-schema");
|
|
10
11
|
const { projectHeroEffect } = require("../hero-effect-registry");
|
|
11
12
|
const { normalizeThemeComments } = require("../comments");
|
|
12
13
|
const { articleFooterDefaults, articlePresentationDefaults, collectionFooterDefaults, requireContentConfig } = require("../content-defaults");
|
|
@@ -14,6 +15,7 @@ const { cloneValue, deepFreeze, pick, mergeConfig, toContentNavigation, collecti
|
|
|
14
15
|
|
|
15
16
|
function heroBackground(value) {
|
|
16
17
|
const background = cloneValue(value || {});
|
|
18
|
+
background.parallax ??= COLLECTION_CONFIG_SCHEMA.properties.hero.properties.background.properties.parallax.default.value;
|
|
17
19
|
if (background.effect) background.effect = projectHeroEffect(background.effect);
|
|
18
20
|
return background;
|
|
19
21
|
}
|