feat: 图库重做/瞬间详情/重要日子适配页/全站灯箱,修复豆瓣页白屏(v0.2.0)

- 图库页重做:首屏按相册分组展示,封面自动取相册第一张图,可在设置中按相册手动配置封面;点击相册进入 ?group= 瀑布流视图(一行三张、页面加宽),支持预览模式(lightgallery 放大,默认值可配置、前台可切换)
- 瞬间页:新增 /moments/{name} 详情页(完整内容、媒体、标签、作者信息、评论区);列表页发布人与发布时间同行对齐;补充单条标签
- 新增 plugin-important-dates 适配页 important-dates.html(即将到来提醒、全部日期倒计时、重要标记、人员生日)
- 全站集成 lightgallery.js 灯箱(动态模式、按需加载、挂载 body 避免层叠上下文遮挡),可在设置中关闭
- 友链页:「全部」视图按分组展示并显示分组标题(空分组自动隐藏);卡片简介限制两行,统一卡片高度与尺寸
- 修复 /douban 白屏:SpEL 不支持 th:with 中的嵌套 map 字面量(EL1041E),改为四段显式区块
- 版本号升至 0.2.0;README 同步更新

Co-Authored-By: WorkBuddy <noreply@workbuddy.ai>
This commit is contained in:
2026-09-30 09:10:32 +08:00
co-authored by WorkBuddy
parent c0ea6ec0df
commit 4e3001674a
17 changed files with 1125 additions and 160 deletions
+10 -4
View File
@@ -18,6 +18,7 @@
- **页脚可关闭的主题署名**:默认「Theme by WhiteSpace」并链接到主题仓库,可在设置中修改名称 / 链接或整体关闭 - **页脚可关闭的主题署名**:默认「Theme by WhiteSpace」并链接到主题仓库,可在设置中修改名称 / 链接或整体关闭
- **页脚全站数据统计**:文章 / 分类 / 标签 / 评论 / 访问量 / 字数,可按项开关,整体可关闭 - **页脚全站数据统计**:文章 / 分类 / 标签 / 评论 / 访问量 / 字数,可按项开关,整体可关闭
- **文章卡片字数 + 阅读时长**:可在设置中关闭(需 `halo-plugin-extra-api` 提供数据) - **文章卡片字数 + 阅读时长**:可在设置中关闭(需 `halo-plugin-extra-api` 提供数据)
- **全站 lightgallery.js 灯箱**:文章、页面、瞬间、图库等页面的图片点击放大预览(按需懒加载,可在设置中整体关闭)
- 多语言:`i18n/default.properties`(回退)+ `i18n/zh_CN.properties` - 多语言:`i18n/default.properties`(回退)+ `i18n/zh_CN.properties`
- 提供 `layout.html` 布局契约,供使用布局契约的插件页面复用主题导航 / 页脚 / 暗色样式 - 提供 `layout.html` 布局契约,供使用布局契约的插件页面复用主题导航 / 页脚 / 暗色样式
@@ -25,9 +26,10 @@
主题已为以下插件提供适配页面(在 `src/` 下以同名模板提供,构建后进入 `templates/`): 主题已为以下插件提供适配页面(在 `src/` 下以同名模板提供,构建后进入 `templates/`):
- **plugin-moments**:`/moments` 瞬间页 - **plugin-moments**:`/moments` 瞬间列表页 + `/moments/{name}` 瞬间详情页(完整内容、媒体、标签、作者信息与评论区);列表页发布人与发布时间同行对齐
- **plugin-photos**:`/photos` 图库页 - **plugin-photos**:`/photos` 图库页 —— 首屏按相册分组展示(封面默认自动取相册内第一张图,可在主题设置「图库」中为每个相册手动配置封面);点击相册进入 `?group=photo-group-*` 瀑布流视图(一行三张、页面加宽),支持**预览模式**(lightgallery 点击放大,默认值可在设置中配置,访客可在前台自行切换)
- **plugin-links**:`/links` 友链页;友链申请表单已**直接内嵌**在友链页底部(点击「申请友链」展开,调用插件公共 REST API,含图形验证码);并额外提供 - **plugin-important-dates**:`/important-dates` 重要日子页(即将到来提醒、全部日期倒计时、重要标记、人员生日)
- **plugin-links**:`/links` 友链页 —— 「全部」视图按分组展示并显示分组标题,卡片简介最多两行、卡片高度统一;友链申请表单已**直接内嵌**在友链页底部(点击「申请友链」展开,调用插件公共 REST API,含图形验证码);并额外提供
- `links-subscribe.html`:RSS 订阅动态聚合页(使用 `linkFeedFinder`);每条仅显示作者与发布时间,作者名链接到来源博客,摘要默认折叠为 5 行 - `links-subscribe.html`:RSS 订阅动态聚合页(使用 `linkFeedFinder`);每条仅显示作者与发布时间,作者名链接到来源博客,摘要默认折叠为 5 行
- **halo-weread-plugin**:`weread.html` 微信读书书架页(前端拉取插件 `/shelf-html` 并注入) - **halo-weread-plugin**:`weread.html` 微信读书书架页(前端拉取插件 `/shelf-html` 并注入)
- **plugin-douban**:`douban.html` 豆瓣收藏页(按「读过 / 在读 / 看过 / 在追」分栏展示书影音,使用 `doubanFinder.getMovies(...)`;未安装插件时显示提示)。在控制台新建单页并选择「豆瓣页面」模板即可启用 - **plugin-douban**:`douban.html` 豆瓣收藏页(按「读过 / 在读 / 看过 / 在追」分栏展示书影音,使用 `doubanFinder.getMovies(...)`;未安装插件时显示提示)。在控制台新建单页并选择「豆瓣页面」模板即可启用
@@ -88,8 +90,10 @@ pnpm check # 格式化 + Lint 修复
| basic | color_scheme | 默认配色:跟随系统 / 浅色 / 深色 | | basic | color_scheme | 默认配色:跟随系统 / 浅色 / 深色 |
| basic | footer_copyright | 页脚自定义版权(支持 HTML,留空不显示) | | basic | footer_copyright | 页脚自定义版权(支持 HTML,留空不显示) |
| basic | show_theme_credit | 页脚是否显示「Theme by」署名(默认开) | | basic | show_theme_credit | 页脚是否显示「Theme by」署名(默认开) |
| basic | theme_credit_text | 署名名称(默认「暖心向阳335」) | | basic | theme_credit_text | 署名名称(默认「WhiteSpace」,可改) |
| basic | theme_credit_url | 署名跳转链接(默认主题仓库地址) |
| basic | show_word_count | 文章卡片是否显示字数与阅读时长(需 extra-api)| | basic | show_word_count | 文章卡片是否显示字数与阅读时长(需 extra-api)|
| basic | enable_lightbox | 全站 lightgallery 图片灯箱(默认开) |
| featured| enable | 首页是否展示置顶文章 | | featured| enable | 首页是否展示置顶文章 |
| about | avatar / bio / github_url / twitter_url / contact_email | 关于页(自定义模板 `page_about.html`)内容 | | about | avatar / bio / github_url / twitter_url / contact_email | 关于页(自定义模板 `page_about.html`)内容 |
| seo | enable_metadata | 输出 canonical / Open Graph / Twitter Card | | seo | enable_metadata | 输出 canonical / Open Graph / Twitter Card |
@@ -99,6 +103,8 @@ pnpm check # 格式化 + Lint 修复
| stats | enable | 页脚是否显示全站数据统计(默认关) | | stats | enable | 页脚是否显示全站数据统计(默认关) |
| stats | show_posts / show_categories / show_tags / show_comments / show_visits | 各项统计的显隐开关 | | stats | show_posts / show_categories / show_tags / show_comments / show_visits | 各项统计的显隐开关 |
| stats | show_words | 全站总字数(需 extra-api 插件,默认开) | | stats | show_words | 全站总字数(需 extra-api 插件,默认开) |
| photos | preview_default | 相册瀑布流是否默认开启预览模式(默认开) |
| photos | album_covers | 各相册手动封面(「相册名称 + 封面图」列表,留空自动取第一张) |
## 约定与说明 ## 约定与说明
+3
View File
@@ -47,3 +47,6 @@ stats.comments={0} comments
stats.visits={0} visits stats.visits={0} visits
stats.words={0} words stats.words={0} words
douban.plugin_required=Douban plugin (plugin-douban) not detected. Please install and enable it, then set your Douban ID in the plugin settings. douban.plugin_required=Douban plugin (plugin-douban) not detected. Please install and enable it, then set your Douban ID in the plugin settings.
photos.photo_count={0} photos
moment.back_to_list=Back to moments
moment.tags=Tags
+3
View File
@@ -47,3 +47,6 @@ stats.comments={0} 条评论
stats.visits={0} 次访问 stats.visits={0} 次访问
stats.words={0} 字 stats.words={0} 字
douban.plugin_required=未检测到豆瓣插件(plugin-douban),请安装并启用后在插件设置中填写豆瓣 ID。 douban.plugin_required=未检测到豆瓣插件(plugin-douban),请安装并启用后在插件设置中填写豆瓣 ID。
photos.photo_count={0} 张照片
moment.back_to_list=返回瞬间列表
moment.tags=标签
+3
View File
@@ -25,5 +25,8 @@
"vite": "npm:@voidzero-dev/vite-plus-core@latest", "vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vitest": "npm:@voidzero-dev/vite-plus-test@latest" "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
} }
},
"dependencies": {
"lightgallery": "^2.9.0"
} }
} }
+10
View File
@@ -11,6 +11,10 @@ overrides:
importers: importers:
.: .:
dependencies:
lightgallery:
specifier: ^2.9.0
version: 2.9.0
devDependencies: devDependencies:
'@halo-dev/theme-package-cli': '@halo-dev/theme-package-cli':
specifier: ^1.0.1 specifier: ^1.0.1
@@ -761,6 +765,10 @@ packages:
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
engines: {node: '>= 0.6.3'} engines: {node: '>= 0.6.3'}
lightgallery@2.9.0:
resolution: {integrity: sha512-58Ud1DyhD2ao58t+kPEqSZrjFxg23tGd5ZKr75erm7q31g5xhUtWUJH3sTUkhHzlyJAKHj5eTrJ37HQRXG4Wbg==}
engines: {node: '>=6.0.0'}
lightningcss-android-arm64@1.32.0: lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
@@ -1629,6 +1637,8 @@ snapshots:
dependencies: dependencies:
readable-stream: 2.3.8 readable-stream: 2.3.8
lightgallery@2.9.0: {}
lightningcss-android-arm64@1.32.0: lightningcss-android-arm64@1.32.0:
optional: true optional: true
+27
View File
@@ -47,6 +47,11 @@ spec:
label: 文章卡片显示字数 label: 文章卡片显示字数
value: true value: true
help: 在文章卡片与置顶文章上展示字数及阅读时长(需 extra-api 插件提供数据) help: 在文章卡片与置顶文章上展示字数及阅读时长(需 extra-api 插件提供数据)
- $formkit: checkbox
name: enable_lightbox
label: 全站图片灯箱
value: true
help: 使用 lightgallery 为文章、瞬间、图库等页面的图片提供点击放大预览
- group: featured - group: featured
label: 置顶文章 label: 置顶文章
formSchema: formSchema:
@@ -153,3 +158,25 @@ spec:
name: show_words name: show_words
label: 全站总字数(需 extra-api 插件) label: 全站总字数(需 extra-api 插件)
value: true value: true
- group: photos
label: 图库
formSchema:
- $formkit: checkbox
name: preview_default
label: 相册默认开启预览模式
value: true
help: 进入相册瀑布流时默认开启点击放大预览,访客可在前台自行切换
- $formkit: repeater
name: album_covers
label: 相册封面
value: []
help: 为相册手动指定封面;不配置时自动使用相册内第一张图片。「相册名称」填写图库页面显示的相册名
children:
- $formkit: text
name: group
label: 相册名称
validation: required
- $formkit: attachment
name: cover
label: 封面图
validation: required
+122 -10
View File
@@ -80,19 +80,133 @@
<th:block th:if="${pluginFinder.available('plugin-douban')}"> <th:block th:if="${pluginFinder.available('plugin-douban')}">
<th:block <th:block
th:with="collections=${{ {'type':'book','status':'done','label':'读过'}, {'type':'book','status':'doing','label':'在读'}, {'type':'movie','status':'done','label':'看过'}, {'type':'drama','status':'doing','label':'在追'} }}" th:with="movies=${doubanFinder.getMovies(24, 0, 'book', 'done')}"
>
<th:block th:each="col : ${collections}">
<th:block
th:with="movies=${doubanFinder.getMovies(24, 0, col.type, col.status)}"
> >
<div <div
class="douban-section" class="douban-section"
th:if="${not #lists.isEmpty(movies)}" th:if="${not #lists.isEmpty(movies)}"
> >
<h2 class="douban-section-title" th:text="${col.label}"> <h2 class="douban-section-title">读过</h2>
读过 <div class="douban-grid">
</h2> <a
th:each="m : ${movies}"
class="douban-card"
th:href="${m.spec.link}"
target="_blank"
rel="noopener noreferrer"
>
<img
class="douban-cover"
th:src="${m.spec.poster}"
th:alt="${m.spec.name}"
loading="lazy"
/>
<p class="douban-name" th:text="${m.spec.name}">名称</p>
<p class="douban-meta">
<span
th:if="${not #strings.isEmpty(m.spec.year)}"
th:text="${m.spec.year}"
>年份</span
>
<span
th:if="${not #strings.isEmpty(m.spec.douban_score)}"
class="douban-score"
th:text="'★ ' + ${m.spec.douban_score}"
>评分</span
>
</p>
</a>
</div>
</div>
</th:block>
<th:block
th:with="movies=${doubanFinder.getMovies(24, 0, 'book', 'doing')}"
>
<div
class="douban-section"
th:if="${not #lists.isEmpty(movies)}"
>
<h2 class="douban-section-title">在读</h2>
<div class="douban-grid">
<a
th:each="m : ${movies}"
class="douban-card"
th:href="${m.spec.link}"
target="_blank"
rel="noopener noreferrer"
>
<img
class="douban-cover"
th:src="${m.spec.poster}"
th:alt="${m.spec.name}"
loading="lazy"
/>
<p class="douban-name" th:text="${m.spec.name}">名称</p>
<p class="douban-meta">
<span
th:if="${not #strings.isEmpty(m.spec.year)}"
th:text="${m.spec.year}"
>年份</span
>
<span
th:if="${not #strings.isEmpty(m.spec.douban_score)}"
class="douban-score"
th:text="'★ ' + ${m.spec.douban_score}"
>评分</span
>
</p>
</a>
</div>
</div>
</th:block>
<th:block
th:with="movies=${doubanFinder.getMovies(24, 0, 'movie', 'done')}"
>
<div
class="douban-section"
th:if="${not #lists.isEmpty(movies)}"
>
<h2 class="douban-section-title">看过</h2>
<div class="douban-grid">
<a
th:each="m : ${movies}"
class="douban-card"
th:href="${m.spec.link}"
target="_blank"
rel="noopener noreferrer"
>
<img
class="douban-cover"
th:src="${m.spec.poster}"
th:alt="${m.spec.name}"
loading="lazy"
/>
<p class="douban-name" th:text="${m.spec.name}">名称</p>
<p class="douban-meta">
<span
th:if="${not #strings.isEmpty(m.spec.year)}"
th:text="${m.spec.year}"
>年份</span
>
<span
th:if="${not #strings.isEmpty(m.spec.douban_score)}"
class="douban-score"
th:text="'★ ' + ${m.spec.douban_score}"
>评分</span
>
</p>
</a>
</div>
</div>
</th:block>
<th:block
th:with="movies=${doubanFinder.getMovies(24, 0, 'drama', 'doing')}"
>
<div
class="douban-section"
th:if="${not #lists.isEmpty(movies)}"
>
<h2 class="douban-section-title">在追</h2>
<div class="douban-grid"> <div class="douban-grid">
<a <a
th:each="m : ${movies}" th:each="m : ${movies}"
@@ -126,8 +240,6 @@
</div> </div>
</th:block> </th:block>
</th:block> </th:block>
</th:block>
</th:block>
<p <p
class="douban-empty" class="douban-empty"
+237
View File
@@ -0,0 +1,237 @@
<include src="partials/layout.html">
<template name="head">
<title th:text="|${title} - ${site.title}|">记得</title>
<style>
.dates-intro {
color: var(--color-text-secondary);
margin-bottom: 1.5rem;
}
.dates-section {
margin: 2rem 0;
}
.dates-section-title {
font-size: 1.15rem;
margin: 0 0 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--color-border);
}
.dates-reminders {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(15rem, 100%), 1fr));
gap: 1rem;
}
.dates-reminder {
padding: 1rem 1.25rem;
border: 1px solid var(--color-border);
border-left: 3px solid var(--color-primary);
border-radius: var(--radius-md);
background: var(--color-bg-secondary);
}
.dates-reminder-name {
margin: 0;
font-weight: 600;
}
.dates-reminder-text {
margin: 0.35rem 0 0;
font-size: 0.85rem;
color: var(--color-text-secondary);
}
.dates-list {
list-style: none;
margin: 0;
padding: 0;
}
.dates-item {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem 0.75rem;
padding: 0.9rem 0;
border-bottom: 1px solid var(--color-border);
}
.dates-item-name {
font-weight: 600;
}
.dates-item-date {
color: var(--color-text-secondary);
font-size: 0.9rem;
}
.dates-badge {
margin-left: auto;
padding: 0.15rem 0.6rem;
border-radius: 999px;
font-size: 0.78rem;
border: 1px solid var(--color-border);
color: var(--color-text-secondary);
white-space: nowrap;
}
.dates-badge.soon {
border-color: var(--color-primary);
color: var(--color-primary);
}
.dates-badge.today {
background: var(--color-primary);
border-color: var(--color-primary);
color: var(--color-bg);
}
.dates-badge.important {
border-color: #e5a50a;
color: #e5a50a;
}
.dates-persons {
display: inline-flex;
flex-wrap: wrap;
gap: 0.35rem;
font-size: 0.8rem;
color: var(--color-text-muted);
}
.people-list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(14rem, 100%), 1fr));
gap: 1rem;
}
.people-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.9rem 1rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-bg-secondary);
}
.people-item img {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.people-name {
font-weight: 600;
line-height: 1.3;
}
.people-meta {
margin: 0;
font-size: 0.8rem;
color: var(--color-text-muted);
}
.dates-empty {
color: var(--color-text-muted);
padding: 1rem 0;
}
</style>
</template>
<div class="container">
<section class="important-dates">
<h1 th:text="${title}">记得</h1>
<p class="dates-intro">重要的日子,值得被记住。</p>
<!-- 即将到来的提醒 -->
<th:block th:if="${not #lists.isEmpty(reminders)}">
<div class="dates-section">
<h2 class="dates-section-title">即将到来</h2>
<div class="dates-reminders">
<div class="dates-reminder" th:each="r : ${reminders}">
<p class="dates-reminder-name" th:text="${r.title}">名称</p>
<p class="dates-reminder-text">
<span
th:if="${not #strings.isEmpty(r.dateText)}"
th:text="${r.dateText}"
></span>
<span
th:if="${not #strings.isEmpty(r.stageText)}"
th:text="${r.stageText}"
></span>
</p>
</div>
</div>
</div>
</th:block>
<!-- 全部日期 -->
<div class="dates-section">
<h2 class="dates-section-title">全部日期</h2>
<ul class="dates-list" th:if="${not #lists.isEmpty(dates)}">
<li class="dates-item" th:each="d : ${dates}">
<span class="dates-item-name" th:text="${d.title}">名称</span>
<span
class="dates-badge important"
th:if="${showImportantTag and d.important}"
>重要</span
>
<span
class="dates-item-date"
th:if="${not #strings.isEmpty(d.dateText)}"
th:text="${d.dateText}"
></span>
<span
class="dates-item-date"
th:if="${d.daysValid and not #strings.isEmpty(d.nextSolarDate)}"
th:text="${d.nextSolarDate}"
></span>
<span
class="dates-persons"
th:if="${not #lists.isEmpty(d.personNames)}"
>
<span th:each="pn : ${d.personNames}" th:text="'@' + ${pn}"></span>
</span>
<span
class="dates-badge today"
th:if="${d.daysValid and d.daysUntil == 0}"
>今天</span
>
<span
class="dates-badge soon"
th:if="${d.daysValid and d.daysUntil > 0}"
th:text="|还有 ${d.daysUntil} 天|"
>还有 N 天</span
>
</li>
</ul>
<p class="dates-empty" th:if="${#lists.isEmpty(dates)}">暂无重要日期</p>
</div>
<!-- 人员与生日 -->
<th:block th:if="${not #lists.isEmpty(people)}">
<div class="dates-section">
<h2 class="dates-section-title">人员</h2>
<ul class="people-list">
<li class="people-item" th:each="p : ${people}">
<img
th:if="${showAvatar and not #strings.isEmpty(p.avatar)}"
th:src="${p.avatar}"
th:alt="${p.displayName}"
loading="lazy"
/>
<span>
<span class="people-name" th:text="${p.displayName}">姓名</span>
<p class="people-meta">
<span
th:if="${not #strings.isEmpty(p.relation)}"
th:text="${p.relation}"
></span>
<span
th:if="${not #strings.isEmpty(p.nextSolarDate) and p.daysUntil == 0}"
th:text="'生日 · 今天'"
></span>
<span
th:if="${not #strings.isEmpty(p.nextSolarDate) and p.daysUntil > 0}"
th:text="|生日还有 ${p.daysUntil} 天|"
></span>
<span
th:if="${not #strings.isEmpty(p.nextSolarDate)}"
th:text="${p.nextSolarDate}"
></span>
</p>
</span>
</li>
</ul>
</div>
</th:block>
</section>
</div>
</include>
+46
View File
@@ -0,0 +1,46 @@
/**
* whitespace 留白主题 - lightgallery 灯箱
* 动态模式:扫描带 data-lightbox 属性的容器内的图片,点击放大预览。
*/
import lightGallery from "lightgallery";
import lgZoom from "lightgallery/plugins/zoom";
import "lightgallery/css/lightgallery.css";
import "lightgallery/css/lg-zoom.css";
export function initLightbox(): void {
const containers = document.querySelectorAll<HTMLElement>("[data-lightbox]");
if (!containers.length) return;
containers.forEach((container) => {
const images = Array.from(container.querySelectorAll("img"));
if (!images.length) return;
const items = images
.map((img) => {
const src = img.getAttribute("data-full") || img.currentSrc || img.src;
if (!src) return null;
return { src, thumb: src, alt: img.alt || "" };
})
.filter((item): item is { src: string; thumb: string; alt: string } =>
Boolean(item),
);
if (!items.length) return;
// 挂载到 body,避免父级 stacking context 遮挡灯箱
const gallery = lightGallery(document.body, {
dynamic: true,
dynamicEl: items,
plugins: [lgZoom],
speed: 400,
});
images.forEach((img, index) => {
img.style.cursor = "zoom-in";
img.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
gallery.openGallery(index);
});
});
});
}
+15
View File
@@ -252,6 +252,20 @@ function initKeyboardShortcuts(): void {
}); });
} }
// ========================================
// lightgallery 灯箱(按需加载)
// ========================================
async function initLightbox(): Promise<void> {
if (!document.querySelector("[data-lightbox]")) return;
try {
const { initLightbox } = await import("./lightbox");
initLightbox();
} catch {
/* 加载失败时静默降级为普通图片展示 */
}
}
// ======================================== // ========================================
// 初始化 // 初始化
// ======================================== // ========================================
@@ -270,6 +284,7 @@ function init(): void {
initSmoothScroll(); initSmoothScroll();
initExternalLinks(); initExternalLinks();
initKeyboardShortcuts(); initKeyboardShortcuts();
void initLightbox();
} }
if (document.readyState === "loading") { if (document.readyState === "loading") {
+108 -6
View File
@@ -26,12 +26,15 @@
} }
.links-list li { .links-list li {
margin: 0; margin: 0;
display: flex;
} }
.links-list a { .links-list a {
display: flex; display: flex;
align-items: center; align-items: flex-start;
gap: 0.75rem; gap: 0.75rem;
padding: 1rem; padding: 1rem;
width: 100%;
box-sizing: border-box;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
background: var(--color-bg-secondary); background: var(--color-bg-secondary);
@@ -48,6 +51,11 @@
object-fit: cover; object-fit: cover;
flex-shrink: 0; flex-shrink: 0;
} }
.links-list .links-body {
display: flex;
flex-direction: column;
min-width: 0;
}
.links-list .links-name { .links-list .links-name {
font-weight: 600; font-weight: 600;
} }
@@ -55,6 +63,19 @@
margin: 0.25rem 0 0; margin: 0.25rem 0 0;
font-size: 0.85rem; font-size: 0.85rem;
color: var(--color-text-secondary); color: var(--color-text-secondary);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.links-group-title {
font-size: 1.1rem;
margin: 2rem 0 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--color-border);
}
.links-section:first-of-type .links-group-title {
margin-top: 0;
} }
.links-apply { .links-apply {
margin-top: 2rem; margin-top: 2rem;
@@ -138,16 +159,28 @@
></a> ></a>
</nav> </nav>
<ul th:if="${not #lists.isEmpty(links)}" class="links-list"> <th:block th:if="${group == null}">
<li th:each="link : ${links}"> <section class="links-section" th:each="grp : ${simpleGroups}">
<a th:href="@{${link.spec.url}}" target="_blank" rel="noopener noreferrer"> <h2 class="links-group-title" th:text="${grp.spec.displayName}">
分组
</h2>
<ul class="links-list">
<li
th:each="link : ${links}"
th:if="${link.spec.groupName == grp.metadata.name}"
>
<a
th:href="@{${link.spec.url}}"
target="_blank"
rel="noopener noreferrer"
>
<img <img
th:if="${not #strings.isEmpty(link.spec.logo)}" th:if="${not #strings.isEmpty(link.spec.logo)}"
th:src="@{${link.spec.logo}}" th:src="@{${link.spec.logo}}"
alt="" alt=""
loading="lazy" loading="lazy"
/> />
<span> <span class="links-body">
<span class="links-name" th:text="${link.spec.displayName}" <span class="links-name" th:text="${link.spec.displayName}"
>链接名称</span >链接名称</span
> >
@@ -160,7 +193,69 @@
</a> </a>
</li> </li>
</ul> </ul>
<p th:unless="${not #lists.isEmpty(links)}">暂无链接</p> </section>
<section class="links-section">
<h2 class="links-group-title">未分组</h2>
<ul class="links-list">
<li
th:each="link : ${links}"
th:if="${#strings.isEmpty(link.spec.groupName)}"
>
<a
th:href="@{${link.spec.url}}"
target="_blank"
rel="noopener noreferrer"
>
<img
th:if="${not #strings.isEmpty(link.spec.logo)}"
th:src="@{${link.spec.logo}}"
alt=""
loading="lazy"
/>
<span class="links-body">
<span class="links-name" th:text="${link.spec.displayName}"
>链接名称</span
>
<p
class="links-desc"
th:if="${not #strings.isEmpty(link.spec.description)}"
th:text="${link.spec.description}"
></p>
</span>
</a>
</li>
</ul>
</section>
</th:block>
<th:block th:unless="${group == null}">
<ul th:if="${not #lists.isEmpty(links)}" class="links-list">
<li th:each="link : ${links}">
<a
th:href="@{${link.spec.url}}"
target="_blank"
rel="noopener noreferrer"
>
<img
th:if="${not #strings.isEmpty(link.spec.logo)}"
th:src="@{${link.spec.logo}}"
alt=""
loading="lazy"
/>
<span class="links-body">
<span class="links-name" th:text="${link.spec.displayName}"
>链接名称</span
>
<p
class="links-desc"
th:if="${not #strings.isEmpty(link.spec.description)}"
th:text="${link.spec.description}"
></p>
</span>
</a>
</li>
</ul>
<p th:if="${#lists.isEmpty(links)}">暂无链接</p>
</th:block>
<div class="links-apply"> <div class="links-apply">
<button <button
@@ -221,6 +316,13 @@
<script th:inline="none"> <script th:inline="none">
(function () { (function () {
// 收起没有链接的分组区块
document.querySelectorAll(".links-section").forEach(function (sec) {
if (!sec.querySelector(".links-list li")) {
sec.remove();
}
});
var API = "/apis/api.link.halo.run/v1alpha1/link-applications"; var API = "/apis/api.link.halo.run/v1alpha1/link-applications";
var CAPTCHA = "/apis/api.link.halo.run/v1alpha1/link-applications/captcha"; var CAPTCHA = "/apis/api.link.halo.run/v1alpha1/link-applications/captcha";
var toggle = document.getElementById("link-apply-toggle"); var toggle = document.getElementById("link-apply-toggle");
+185
View File
@@ -0,0 +1,185 @@
<include src="partials/layout.html">
<template name="head">
<title th:text="|${title} - ${site.title}|">瞬间</title>
<style>
.moment-detail {
max-width: 46rem;
margin: 0 auto;
}
.moment-back {
display: inline-block;
margin-bottom: 1.5rem;
color: var(--color-link);
font-size: 0.9rem;
}
.moment-back:hover {
color: var(--color-link-hover);
}
.moment-card {
padding: 1.5rem 0;
}
.moment-card header {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem;
margin-bottom: 1rem;
color: var(--color-text-secondary);
}
.moment-owner {
display: inline-flex;
align-items: center;
gap: 0.6rem;
}
.moment-owner img {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
object-fit: cover;
}
.moment-owner-info {
display: flex;
flex-direction: column;
line-height: 1.3;
}
.moment-owner-name {
color: var(--color-text);
font-weight: 600;
}
.moment-owner-bio {
font-size: 0.8rem;
color: var(--color-text-muted);
}
.moment-meta {
display: inline-flex;
align-items: center;
gap: 0.75rem;
font-size: 0.85rem;
}
.moment-meta a {
color: var(--color-link);
}
.moment-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
font-size: 0.85rem;
}
.moment-tags a {
color: var(--color-link);
}
.moment-media img,
.moment-media video {
display: block;
max-width: 100%;
height: auto;
margin-top: 1rem;
border-radius: var(--radius-md);
}
.moment-media audio {
width: 100%;
margin-top: 1rem;
}
.comments-section {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid var(--color-border);
}
</style>
</template>
<div class="container">
<section class="moment-detail" th:if="${moment != null}">
<a class="moment-back" th:href="@{/moments}" th:text="#{moment.back_to_list}"
>返回瞬间列表</a
>
<article class="moment-card" th:with="content=${moment.spec.content}">
<header>
<span class="moment-owner" th:if="${moment.owner != null}">
<img
th:if="${not #strings.isEmpty(moment.owner.avatar)}"
th:src="${moment.owner.avatar}"
th:alt="${moment.owner.displayName}"
loading="lazy"
/>
<span class="moment-owner-info">
<span
class="moment-owner-name"
th:text="${moment.owner.displayName}"
>用户名</span
>
<span
class="moment-owner-bio"
th:if="${not #strings.isEmpty(moment.owner.bio)}"
th:text="${moment.owner.bio}"
></span>
</span>
</span>
<span class="moment-meta">
<time
th:if="${moment.spec.releaseTime != null}"
th:datetime="${moment.spec.releaseTime}"
th:text="${#temporals.format(moment.spec.releaseTime, 'yyyy-MM-dd HH:mm')}"
>发布时间</time
>
</span>
</header>
<div
class="moment-tags"
th:if="${not #lists.isEmpty(moment.spec.tags)}"
>
<a
th:each="tag : ${moment.spec.tags}"
th:href="@{/moments(tag=${tag})}"
th:text="'#' + ${tag}"
></a>
</div>
<div
class="article-content"
th:if="${not #strings.isEmpty(content.html)}"
th:attr="data-lightbox=${theme.config?.basic?.enable_lightbox != false ? 'true' : null}"
th:utext="${content.html}"
></div>
<div
class="moment-media"
th:attr="data-lightbox=${theme.config?.basic?.enable_lightbox != false ? 'true' : null}"
th:if="${not #lists.isEmpty(content.medium)}"
>
<th:block th:each="medium : ${content.medium}">
<img
th:if="${medium.type.name == 'PHOTO'}"
th:src="${medium.url}"
alt="瞬间图片"
loading="lazy"
/>
<video
th:if="${medium.type.name == 'VIDEO'}"
th:src="${medium.url}"
controls
></video>
<audio
th:if="${medium.type.name == 'AUDIO'}"
th:src="${medium.url}"
controls
></audio>
</th:block>
</div>
</article>
<div class="comments-section" th:if="${haloCommentEnabled}">
<halo:comment
group="moment.halo.run"
kind="Moment"
th:attr="name=${moment.metadata.name}"
/>
</div>
</section>
<p th:if="${moment == null}">瞬间不存在或不可见。</p>
</div>
</include>
+29
View File
@@ -26,11 +26,17 @@
.moments article header { .moments article header {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center;
gap: 0.75rem; gap: 0.75rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
opacity: 0.75; opacity: 0.75;
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
.moments article header > a {
display: inline-flex;
align-items: center;
line-height: 2rem;
}
.moments .moment-owner { .moments .moment-owner {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -42,6 +48,16 @@
border-radius: 50%; border-radius: 50%;
object-fit: cover; object-fit: cover;
} }
.moments .moment-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.75rem;
font-size: 0.85rem;
}
.moments .moment-tags a {
color: var(--color-link);
}
.moments .moment-media img, .moments .moment-media img,
.moments .moment-media video { .moments .moment-media video {
display: block; display: block;
@@ -102,13 +118,26 @@
</th:block> </th:block>
</header> </header>
<div
class="moment-tags"
th:if="${not #lists.isEmpty(moment.spec.tags)}"
>
<a
th:each="tag : ${moment.spec.tags}"
th:href="@{/moments(tag=${tag})}"
th:text="'#' + ${tag}"
></a>
</div>
<div <div
th:if="${not #strings.isEmpty(content.html)}" th:if="${not #strings.isEmpty(content.html)}"
th:attr="data-lightbox=${theme.config?.basic?.enable_lightbox != false ? 'true' : null}"
th:utext="${content.html}" th:utext="${content.html}"
></div> ></div>
<div <div
class="moment-media" class="moment-media"
th:attr="data-lightbox=${theme.config?.basic?.enable_lightbox != false ? 'true' : null}"
th:if="${not #lists.isEmpty(content.medium)}" th:if="${not #lists.isEmpty(content.medium)}"
> >
<th:block th:each="medium : ${content.medium}"> <th:block th:each="medium : ${content.medium}">
+5 -1
View File
@@ -26,7 +26,11 @@
<img th:src="${singlePage.spec.cover}" th:alt="${singlePage.spec.title}" /> <img th:src="${singlePage.spec.cover}" th:alt="${singlePage.spec.title}" />
</div> </div>
<div class="article-content" th:utext="${singlePage.content.content}"></div> <div
class="article-content"
th:attr="data-lightbox=${theme.config?.basic?.enable_lightbox != false ? 'true' : null}"
th:utext="${singlePage.content.content}"
></div>
</article> </article>
<!-- 评论区 --> <!-- 评论区 -->
+234 -55
View File
@@ -2,6 +2,10 @@
<template name="head"> <template name="head">
<title th:text="|${title} - ${site.title}|">图库</title> <title th:text="|${title} - ${site.title}|">图库</title>
<style> <style>
/* 图库页面适当加宽 */
.container {
max-width: 75rem;
}
:where(.photos-groups) { :where(.photos-groups) {
margin: 1.5rem 0; margin: 1.5rem 0;
} }
@@ -27,30 +31,121 @@
:where(.photos-group.is-active .photos-group__link) { :where(.photos-group.is-active .photos-group__link) {
font-weight: 600; font-weight: 600;
} }
:where(.photos-grid) {
/* 相册网格 */
:where(.albums-grid) {
list-style: none;
margin: 1.5rem 0 0;
padding: 0;
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr)); grid-template-columns: repeat(
gap: 1.25rem; auto-fill,
minmax(min(16rem, 100%), 1fr)
);
gap: 1.5rem;
} }
:where(.photos-card__image) { :where(.albums-card) {
display: block; display: block;
color: var(--color-text);
}
:where(.albums-card:hover .albums-card__name) {
color: var(--color-primary);
}
:where(.albums-card__cover) {
position: relative;
width: 100%; width: 100%;
aspect-ratio: 4 / 3; aspect-ratio: 4 / 3;
overflow: hidden;
border-radius: var(--radius-md);
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
}
:where(.albums-card__cover img) {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover; object-fit: cover;
}
:where(.albums-card__cover img.album-cover-custom) {
z-index: 1;
}
:where(.albums-card__name) {
margin: 0.75rem 0 0;
font-size: 1rem;
font-weight: 600;
}
:where(.albums-card__count) {
margin: 0.25rem 0 0;
font-size: 0.85rem;
color: var(--color-text-muted);
}
/* 瀑布流(一行三张) */
:where(.photos-masonry) {
list-style: none;
margin: 1.5rem 0 0;
padding: 0;
columns: 3;
column-gap: 1rem;
}
:where(.photos-masonry > li) {
break-inside: avoid;
margin: 0 0 1rem;
}
:where(.photos-masonry .photos-card__media) {
display: block;
}
:where(.photos-masonry img) {
display: block;
width: 100%;
height: auto;
border-radius: var(--radius-md); border-radius: var(--radius-md);
background: var(--color-bg-secondary); background: var(--color-bg-secondary);
} }
:where(.photos-card__title) { :where(.photos-masonry .photos-card__caption) {
margin: 0.75rem 0 0.25rem; margin: 0.4rem 0 0;
font-size: 1rem; font-size: 0.85rem;
}
:where(.photos-card__description, .photos-card__tags) {
margin-top: 0.5rem;
}
:where(.photos-card__tags) {
gap: 0.5rem;
font-size: 0.875rem;
color: var(--color-text-secondary); color: var(--color-text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@media (max-width: 900px) {
:where(.photos-masonry) {
columns: 2;
}
}
@media (max-width: 560px) {
:where(.photos-masonry) {
columns: 1;
}
}
/* 预览模式切换 */
:where(.photos-toolbar) {
display: flex;
justify-content: flex-end;
margin-top: 1rem;
}
:where(.photos-preview-toggle) {
padding: 0.4rem 0.9rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-bg-secondary);
color: var(--color-text);
font: inherit;
font-size: 0.85rem;
cursor: pointer;
}
:where(.photos-preview-toggle)[aria-pressed="true"] {
border-color: var(--color-primary);
color: var(--color-primary);
}
:where(.photos-empty) {
color: var(--color-text-muted);
padding: 2rem 0;
} }
:where(.photos-pagination) { :where(.photos-pagination) {
display: flex; display: flex;
@@ -63,10 +158,7 @@
</template> </template>
<div class="container"> <div class="container">
<section <section class="photos-page">
class="photos-page"
th:attr="data-template-id=${_templateId},data-group=${group},data-page=${photos.page},data-size=${photos.size},data-total=${photos.total}"
>
<header class="photos-header"> <header class="photos-header">
<h1 class="photos-header__title" th:text="${title}">图库</h1> <h1 class="photos-header__title" th:text="${title}">图库</h1>
</header> </header>
@@ -81,14 +173,13 @@
class="photos-group__link" class="photos-group__link"
th:href="${photoUrl.list()}" th:href="${photoUrl.list()}"
th:attr="aria-current=${#strings.isEmpty(group) ? 'page' : null}" th:attr="aria-current=${#strings.isEmpty(group) ? 'page' : null}"
>全部</a >全部相册</a
> >
</li> </li>
<li <li
class="photos-group" class="photos-group"
th:each="photoGroup : ${groups}" th:each="photoGroup : ${groups}"
th:classappend="${group == photoGroup.metadata.name} ? 'is-active' : ''" th:classappend="${group == photoGroup.metadata.name} ? 'is-active' : ''"
th:attr="data-group=${photoGroup.metadata.name}"
> >
<a <a
class="photos-group__link" class="photos-group__link"
@@ -98,11 +189,11 @@
<span <span
class="photos-group__name" class="photos-group__name"
th:text="${photoGroup.spec.displayName}" th:text="${photoGroup.spec.displayName}"
>分组</span >相册</span
> >
<span <span
class="photos-group__count" class="photos-group__count"
th:if="${photoGroup.status != null && photoGroup.status.photoCount != null}" th:if="${photoGroup.status != null and photoGroup.status.photoCount != null}"
th:text="|(${photoGroup.status.photoCount})|" th:text="|(${photoGroup.status.photoCount})|"
></span> ></span>
</a> </a>
@@ -110,48 +201,94 @@
</ul> </ul>
</nav> </nav>
<ul class="photos-grid" th:if="${!#lists.isEmpty(photos.items)}"> <!-- 相册视图:按分组展示各相册 -->
<li <th:block th:if="${#strings.isEmpty(group)}">
class="photos-grid__item" <th:block th:with="albums=${photoFinder.groupBy()}">
th:each="photo : ${photos.items}" <ul class="albums-grid" th:if="${not #lists.isEmpty(albums)}">
th:attr="data-photo-name=${photo.metadata.name},data-group=${photo.spec.groupName},data-priority=${photo.spec.priority}" <li th:each="album : ${albums}">
<a
class="albums-card"
th:href="${photoUrl.list(album.metadata.name)}"
>
<span class="albums-card__cover">
<img
th:if="${not #lists.isEmpty(album.photos)}"
th:src="${album.photos[0].spec.cover != null and !#strings.isEmpty(album.photos[0].spec.cover) ? album.photos[0].spec.cover : album.photos[0].spec.url}"
th:alt="${album.spec.displayName}"
loading="lazy"
/>
<th:block
th:if="${not #lists.isEmpty(theme.config?.photos?.album_covers)}"
>
<img
class="album-cover-custom"
th:each="cv : ${theme.config.photos.album_covers}"
th:if="${cv != null and cv.group == album.spec.displayName and not #strings.isEmpty(cv.cover)}"
th:src="${cv.cover}"
th:alt="${album.spec.displayName}"
loading="lazy"
/>
</th:block>
</span>
<p
class="albums-card__name"
th:text="${album.spec.displayName}"
>
相册
</p>
<p
class="albums-card__count"
th:if="${album.status != null and album.status.photoCount != null}"
th:text="#{photos.photo_count(${album.status.photoCount})}"
>
12 张照片
</p>
</a>
</li>
</ul>
<p class="photos-empty" th:if="${#lists.isEmpty(albums)}" role="status">
暂无相册
</p>
</th:block>
</th:block>
<!-- 相册内瀑布流视图 -->
<th:block th:unless="${#strings.isEmpty(group)}">
<div class="photos-toolbar" th:if="${not #lists.isEmpty(photos.items)}">
<button
class="photos-preview-toggle"
id="photos-preview-toggle"
type="button"
th:attr="data-default=${theme.config?.photos?.preview_default != false ? 'on' : 'off'}"
>
预览模式
</button>
</div>
<ul
class="photos-masonry"
th:if="${not #lists.isEmpty(photos.items)}"
th:attr="data-lightbox=${theme.config?.basic?.enable_lightbox != false ? 'true' : null}"
>
<li th:each="photo : ${photos.items}">
<a
class="photos-card__media"
th:href="${photoUrl.detail(photo)}"
th:title="${photo.spec.displayName}"
> >
<article class="photos-card">
<a class="photos-card__media" th:href="${photoUrl.detail(photo)}">
<img <img
class="photos-card__image"
th:src="${#strings.isEmpty(photo.spec.cover) ? photo.spec.url : photo.spec.cover}" th:src="${#strings.isEmpty(photo.spec.cover) ? photo.spec.url : photo.spec.cover}"
th:alt="${photo.spec.displayName}" th:alt="${photo.spec.displayName}"
loading="lazy" loading="lazy"
/> />
</a> </a>
<div class="photos-card__content">
<h2 class="photos-card__title">
<a
class="photos-card__title-link"
th:href="${photoUrl.detail(photo)}"
th:text="${photo.spec.displayName}"
>照片</a
>
</h2>
<p <p
class="photos-card__description" class="photos-card__caption"
th:if="${!#strings.isEmpty(photo.spec.description)}" th:if="${not #strings.isEmpty(photo.spec.displayName)}"
th:text="${photo.spec.description}" th:text="${photo.spec.displayName}"
></p>
<ul
class="photos-card__tags"
th:if="${!#lists.isEmpty(photo.spec.tags)}"
aria-label="照片标签"
> >
<li 照片
class="photos-card__tag" </p>
th:each="tag : ${photo.spec.tags}"
th:text="${tag}"
></li>
</ul>
</div>
</article>
</li> </li>
</ul> </ul>
<p <p
@@ -184,6 +321,48 @@
>下一页</a >下一页</a
> >
</nav> </nav>
</th:block>
</section> </section>
</div> </div>
<script th:inline="none">
(function () {
var btn = document.getElementById("photos-preview-toggle");
var masonry = document.querySelector(".photos-masonry");
if (!btn || !masonry) return;
var KEY = "whitespace-photo-preview";
var defaultOn = btn.getAttribute("data-default") === "on";
var stored = null;
try {
stored = localStorage.getItem(KEY);
} catch (e) {
/* ignore */
}
var mode = stored === null ? defaultOn : stored === "on";
function apply() {
if (mode) {
masonry.setAttribute("data-lightbox", "true");
} else {
masonry.removeAttribute("data-lightbox");
}
btn.textContent = mode ? "预览模式:开" : "预览模式:关";
btn.setAttribute("aria-pressed", mode ? "true" : "false");
}
btn.addEventListener("click", function () {
mode = !mode;
try {
localStorage.setItem(KEY, mode ? "on" : "off");
} catch (e) {
/* ignore */
}
// 重新加载以按新模式初始化灯箱
window.location.reload();
});
apply();
})();
</script>
</include> </include>
+5 -1
View File
@@ -67,7 +67,11 @@
<img th:src="${post.spec.cover}" th:alt="${post.spec.title}" /> <img th:src="${post.spec.cover}" th:alt="${post.spec.title}" />
</div> </div>
<div class="article-content" th:utext="${post.content.content}"></div> <div
class="article-content"
th:attr="data-lightbox=${theme.config?.basic?.enable_lightbox != false ? 'true' : null}"
th:utext="${post.content.content}"
></div>
<!-- 文章标签 --> <!-- 文章标签 -->
<div class="article-tags" th:if="${not #lists.isEmpty(post.tags)}"> <div class="article-tags" th:if="${not #lists.isEmpty(post.tags)}">
+1 -1
View File
@@ -14,7 +14,7 @@ spec:
issues: https://git.nxxy335.top/nxxy335/halo-theme-whitespace/issues issues: https://git.nxxy335.top/nxxy335/halo-theme-whitespace/issues
settingName: "whitespace-setting" settingName: "whitespace-setting"
configMapName: "whitespace-configMap" configMapName: "whitespace-configMap"
version: 0.1.0 version: 0.2.0
requires: ">=2.26.0" requires: ">=2.26.0"
license: license:
- name: "GPL-3.0" - name: "GPL-3.0"