v0.4.0: 重做图库为瀑布流+分组导航,修复豆瓣 500 与瞬间样式,友链卡片重排+申请弹窗,标签云间距,移除关于模板
This commit is contained in:
+2
-1
@@ -26,4 +26,5 @@ dist-ssr
|
||||
|
||||
.gradle
|
||||
build
|
||||
templates
|
||||
templates
|
||||
.workbuddy/
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
|
||||
| 模板名 | 模板文件 | 说明 |
|
||||
| ---- | --------------------- | --------------------- |
|
||||
| 关于 | `page_about.html` | 关于页,头像 / 简介 / 社交链接可配置 |
|
||||
| 留言板 | `page_guestbook.html` | 以评论区作为留言板的单页 |
|
||||
| 微信读书 | `weread.html` | 微信读书书架展示,依赖微信读书插件 |
|
||||
| 豆瓣页面 | `douban.html` | 豆瓣记录页,依赖豆瓣插件 |
|
||||
@@ -129,7 +128,7 @@ pnpm check
|
||||
│ ├── error/ # 404 与通用错误页
|
||||
│ ├── css/main.css # 全部样式(双主题变量、响应式、正文排版)
|
||||
│ ├── js/ # main.ts 全站脚本 / post.ts 文章脚本 / lightbox.ts 灯箱
|
||||
│ ├── index.html post.html page.html page_about.html
|
||||
│ ├── index.html post.html page.html
|
||||
│ ├── archives.html category.html tag.html categories.html tags.html author.html
|
||||
│ ├── moments.html moment.html photos.html important-dates.html
|
||||
│ ├── weread.html douban.html links.html links-subscribe.html page_guestbook.html
|
||||
|
||||
@@ -30,9 +30,6 @@ error.404.message=Sorry, the page you visited does not exist or has been deleted
|
||||
error.back.home=Back to home
|
||||
error.back.prev=Go back
|
||||
footer.theme=Theme by {0}
|
||||
about.social.github=GitHub
|
||||
about.social.twitter=Twitter
|
||||
about.social.email=Email
|
||||
post.words={0} words
|
||||
post.reading_time=About {0} min
|
||||
archive.stats.words=Total words
|
||||
@@ -47,7 +44,5 @@ stats.comments={0} comments
|
||||
stats.visits={0} visits
|
||||
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.
|
||||
photos.photo_count={0} photos
|
||||
photos.back=Back to all albums
|
||||
moment.back_to_list=Back to moments
|
||||
moment.tags=Tags
|
||||
|
||||
@@ -30,9 +30,6 @@ error.404.message=抱歉,您访问的页面不存在或已被删除。
|
||||
error.back.home=返回首页
|
||||
error.back.prev=返回上页
|
||||
footer.theme=Theme by {0}
|
||||
about.social.github=GitHub
|
||||
about.social.twitter=Twitter
|
||||
about.social.email=邮箱
|
||||
post.words={0} 字
|
||||
post.reading_time=约 {0} 分钟
|
||||
archive.stats.words=总字数
|
||||
@@ -47,7 +44,5 @@ stats.comments={0} 条评论
|
||||
stats.visits={0} 次访问
|
||||
stats.words={0} 字
|
||||
douban.plugin_required=未检测到豆瓣插件(plugin-douban),请安装并启用后在插件设置中填写豆瓣 ID。
|
||||
photos.photo_count={0} 张照片
|
||||
photos.back=返回全部相册
|
||||
moment.back_to_list=返回瞬间列表
|
||||
moment.tags=标签
|
||||
|
||||
@@ -60,34 +60,6 @@ spec:
|
||||
label: 首页展示置顶文章
|
||||
value: true
|
||||
help: 在首页顶部以大标题样式展示最新的一篇置顶文章(需在文章设置中开启置顶)
|
||||
- group: about
|
||||
label: 关于页面
|
||||
formSchema:
|
||||
- $formkit: attachment
|
||||
name: avatar
|
||||
label: 博主头像
|
||||
value: ""
|
||||
help: 关于页面展示的头像,留空则使用站点 Logo
|
||||
- $formkit: textarea
|
||||
name: bio
|
||||
label: 个人简介
|
||||
value: "一个热爱生活、热爱技术的普通人"
|
||||
help: 关于页面头像下方显示的简介
|
||||
- $formkit: text
|
||||
name: github_url
|
||||
label: GitHub 链接
|
||||
value: ""
|
||||
placeholder: https://github.com/yourname
|
||||
- $formkit: text
|
||||
name: twitter_url
|
||||
label: Twitter 链接
|
||||
value: ""
|
||||
placeholder: https://x.com/yourname
|
||||
- $formkit: text
|
||||
name: contact_email
|
||||
label: 联系邮箱
|
||||
value: ""
|
||||
placeholder: mail@example.com
|
||||
- group: seo
|
||||
label: SEO
|
||||
formSchema:
|
||||
@@ -158,34 +130,3 @@ spec:
|
||||
name: show_words
|
||||
label: 全站总字数(需 extra-api 插件)
|
||||
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: select
|
||||
name: group
|
||||
label: 相册
|
||||
validation: required
|
||||
clearable: true
|
||||
searchable: true
|
||||
action: /apis/console.api.photo.halo.run/v1alpha1/photogroups
|
||||
requestOption:
|
||||
method: GET
|
||||
itemsField: ""
|
||||
labelField: spec.displayName
|
||||
valueField: metadata.name
|
||||
help: 选项自动取自图库插件中已创建的相册分组(需安装并启用图库插件)
|
||||
- $formkit: attachment
|
||||
name: cover
|
||||
label: 封面图
|
||||
validation: required
|
||||
|
||||
+47
-36
@@ -79,8 +79,9 @@
|
||||
></p>
|
||||
|
||||
<th:block th:if="${pluginFinder.available('plugin-douban')}">
|
||||
<!-- 读过 -->
|
||||
<th:block
|
||||
th:with="movies=${doubanFinder.getMovies(24, 0, 'book', 'done')}"
|
||||
th:with="res=${doubanFinder.list(1, 24, 'book', 'done')}, movies=${res != null ? res.items : null}"
|
||||
>
|
||||
<div
|
||||
class="douban-section"
|
||||
@@ -91,27 +92,28 @@
|
||||
<a
|
||||
th:each="m : ${movies}"
|
||||
class="douban-card"
|
||||
th:href="${m.spec.link}"
|
||||
th:href="${m.link}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
class="douban-cover"
|
||||
th:src="${m.spec.poster}"
|
||||
th:alt="${m.spec.name}"
|
||||
th:src="${m.poster}"
|
||||
th:alt="${m.name}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<p class="douban-name" th:text="${m.spec.name}">名称</p>
|
||||
<p class="douban-name" th:text="${m.name}">名称</p>
|
||||
<p class="douban-meta">
|
||||
<span
|
||||
th:if="${not #strings.isEmpty(m.spec.year)}"
|
||||
th:text="${m.spec.year}"
|
||||
th:if="${not #strings.isEmpty(m.year)}"
|
||||
th:text="${m.year}"
|
||||
>年份</span
|
||||
>
|
||||
<span
|
||||
th:if="${not #strings.isEmpty(m.spec.douban_score)}"
|
||||
th:if="${not #strings.isEmpty(m.score)}"
|
||||
class="douban-score"
|
||||
th:text="'★ ' + ${m.spec.douban_score}"
|
||||
th:text="'★ ' + ${m.score}"
|
||||
>评分</span
|
||||
>
|
||||
</p>
|
||||
@@ -119,8 +121,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<!-- 在读 -->
|
||||
<th:block
|
||||
th:with="movies=${doubanFinder.getMovies(24, 0, 'book', 'doing')}"
|
||||
th:with="res=${doubanFinder.list(1, 24, 'book', 'doing')}, movies=${res != null ? res.items : null}"
|
||||
>
|
||||
<div
|
||||
class="douban-section"
|
||||
@@ -131,27 +135,28 @@
|
||||
<a
|
||||
th:each="m : ${movies}"
|
||||
class="douban-card"
|
||||
th:href="${m.spec.link}"
|
||||
th:href="${m.link}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
class="douban-cover"
|
||||
th:src="${m.spec.poster}"
|
||||
th:alt="${m.spec.name}"
|
||||
th:src="${m.poster}"
|
||||
th:alt="${m.name}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<p class="douban-name" th:text="${m.spec.name}">名称</p>
|
||||
<p class="douban-name" th:text="${m.name}">名称</p>
|
||||
<p class="douban-meta">
|
||||
<span
|
||||
th:if="${not #strings.isEmpty(m.spec.year)}"
|
||||
th:text="${m.spec.year}"
|
||||
th:if="${not #strings.isEmpty(m.year)}"
|
||||
th:text="${m.year}"
|
||||
>年份</span
|
||||
>
|
||||
<span
|
||||
th:if="${not #strings.isEmpty(m.spec.douban_score)}"
|
||||
th:if="${not #strings.isEmpty(m.score)}"
|
||||
class="douban-score"
|
||||
th:text="'★ ' + ${m.spec.douban_score}"
|
||||
th:text="'★ ' + ${m.score}"
|
||||
>评分</span
|
||||
>
|
||||
</p>
|
||||
@@ -159,8 +164,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<!-- 看过 -->
|
||||
<th:block
|
||||
th:with="movies=${doubanFinder.getMovies(24, 0, 'movie', 'done')}"
|
||||
th:with="res=${doubanFinder.list(1, 24, 'movie', 'done')}, movies=${res != null ? res.items : null}"
|
||||
>
|
||||
<div
|
||||
class="douban-section"
|
||||
@@ -171,27 +178,28 @@
|
||||
<a
|
||||
th:each="m : ${movies}"
|
||||
class="douban-card"
|
||||
th:href="${m.spec.link}"
|
||||
th:href="${m.link}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
class="douban-cover"
|
||||
th:src="${m.spec.poster}"
|
||||
th:alt="${m.spec.name}"
|
||||
th:src="${m.poster}"
|
||||
th:alt="${m.name}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<p class="douban-name" th:text="${m.spec.name}">名称</p>
|
||||
<p class="douban-name" th:text="${m.name}">名称</p>
|
||||
<p class="douban-meta">
|
||||
<span
|
||||
th:if="${not #strings.isEmpty(m.spec.year)}"
|
||||
th:text="${m.spec.year}"
|
||||
th:if="${not #strings.isEmpty(m.year)}"
|
||||
th:text="${m.year}"
|
||||
>年份</span
|
||||
>
|
||||
<span
|
||||
th:if="${not #strings.isEmpty(m.spec.douban_score)}"
|
||||
th:if="${not #strings.isEmpty(m.score)}"
|
||||
class="douban-score"
|
||||
th:text="'★ ' + ${m.spec.douban_score}"
|
||||
th:text="'★ ' + ${m.score}"
|
||||
>评分</span
|
||||
>
|
||||
</p>
|
||||
@@ -199,8 +207,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<!-- 在追 -->
|
||||
<th:block
|
||||
th:with="movies=${doubanFinder.getMovies(24, 0, 'drama', 'doing')}"
|
||||
th:with="res=${doubanFinder.list(1, 24, 'drama', 'doing')}, movies=${res != null ? res.items : null}"
|
||||
>
|
||||
<div
|
||||
class="douban-section"
|
||||
@@ -211,27 +221,28 @@
|
||||
<a
|
||||
th:each="m : ${movies}"
|
||||
class="douban-card"
|
||||
th:href="${m.spec.link}"
|
||||
th:href="${m.link}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
class="douban-cover"
|
||||
th:src="${m.spec.poster}"
|
||||
th:alt="${m.spec.name}"
|
||||
th:src="${m.poster}"
|
||||
th:alt="${m.name}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<p class="douban-name" th:text="${m.spec.name}">名称</p>
|
||||
<p class="douban-name" th:text="${m.name}">名称</p>
|
||||
<p class="douban-meta">
|
||||
<span
|
||||
th:if="${not #strings.isEmpty(m.spec.year)}"
|
||||
th:text="${m.spec.year}"
|
||||
th:if="${not #strings.isEmpty(m.year)}"
|
||||
th:text="${m.year}"
|
||||
>年份</span
|
||||
>
|
||||
<span
|
||||
th:if="${not #strings.isEmpty(m.spec.douban_score)}"
|
||||
th:if="${not #strings.isEmpty(m.score)}"
|
||||
class="douban-score"
|
||||
th:text="'★ ' + ${m.spec.douban_score}"
|
||||
th:text="'★ ' + ${m.score}"
|
||||
>评分</span
|
||||
>
|
||||
</p>
|
||||
|
||||
+176
-48
@@ -31,37 +31,61 @@
|
||||
.links-list a {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem;
|
||||
gap: 0.8rem;
|
||||
padding: 1.1rem 1.25rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
border-radius: var(--radius-lg, 0.75rem);
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text);
|
||||
transition: border-color var(--transition-fast);
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
box-shadow var(--transition-fast);
|
||||
}
|
||||
.links-list a:hover {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.links-list img {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
object-fit: cover;
|
||||
.links-avatar {
|
||||
position: relative;
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.links-avatar img {
|
||||
display: block;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
border-radius: var(--radius-md);
|
||||
object-fit: cover;
|
||||
background: var(--color-bg);
|
||||
}
|
||||
/* 头像右下角状态圆点 */
|
||||
.links-avatar::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -0.1rem;
|
||||
bottom: -0.1rem;
|
||||
width: 0.55rem;
|
||||
height: 0.55rem;
|
||||
border-radius: 50%;
|
||||
background: #2ea043;
|
||||
border: 2px solid var(--color-bg-secondary);
|
||||
}
|
||||
.links-list .links-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
padding-top: 0.1rem;
|
||||
}
|
||||
.links-list .links-name {
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.links-list .links-desc {
|
||||
margin: 0.25rem 0 0;
|
||||
margin: 0.3rem 0 0;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.55;
|
||||
color: var(--color-text-secondary);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
@@ -77,18 +101,79 @@
|
||||
.links-section:first-of-type .links-group-title {
|
||||
margin-top: 0;
|
||||
}
|
||||
/* 申请友链按钮 */
|
||||
.links-apply {
|
||||
margin-top: 2rem;
|
||||
margin-top: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
.link-apply-panel {
|
||||
max-width: 32rem;
|
||||
margin: 1.25rem auto 0;
|
||||
text-align: left;
|
||||
.btn-apply {
|
||||
display: inline-block;
|
||||
padding: 0.6rem 1.75rem;
|
||||
border: 1px solid var(--color-primary);
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-size: 0.95rem;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
opacity var(--transition-fast),
|
||||
transform var(--transition-fast);
|
||||
}
|
||||
.btn-apply:hover {
|
||||
opacity: 0.88;
|
||||
}
|
||||
.btn-apply:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
/* 申请弹窗(运行时移至 body 下,避免被父级 stacking context 遮挡) */
|
||||
.link-apply-modal[hidden] {
|
||||
display: none;
|
||||
}
|
||||
.link-apply-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
z-index: 1000;
|
||||
}
|
||||
.link-apply-dialog {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: min(26rem, calc(100vw - 2rem));
|
||||
max-height: min(85vh, 44rem);
|
||||
overflow-y: auto;
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg, 0.75rem);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
|
||||
padding: 1.5rem;
|
||||
z-index: 1001;
|
||||
}
|
||||
.link-apply-close {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1.4rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.link-apply-close:hover {
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
.link-apply-title {
|
||||
margin: 0 0 0.75rem;
|
||||
margin: 0 0 1rem;
|
||||
font-size: 1.125rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
.apply-form .form-row {
|
||||
margin-bottom: 1rem;
|
||||
@@ -174,12 +259,14 @@
|
||||
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-avatar">
|
||||
<img
|
||||
th:if="${not #strings.isEmpty(link.spec.logo)}"
|
||||
th:src="@{${link.spec.logo}}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
</span>
|
||||
<span class="links-body">
|
||||
<span class="links-name" th:text="${link.spec.displayName}"
|
||||
>链接名称</span
|
||||
@@ -206,12 +293,14 @@
|
||||
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-avatar">
|
||||
<img
|
||||
th:if="${not #strings.isEmpty(link.spec.logo)}"
|
||||
th:src="@{${link.spec.logo}}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
</span>
|
||||
<span class="links-body">
|
||||
<span class="links-name" th:text="${link.spec.displayName}"
|
||||
>链接名称</span
|
||||
@@ -235,12 +324,14 @@
|
||||
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-avatar">
|
||||
<img
|
||||
th:if="${not #strings.isEmpty(link.spec.logo)}"
|
||||
th:src="@{${link.spec.logo}}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
</span>
|
||||
<span class="links-body">
|
||||
<span class="links-name" th:text="${link.spec.displayName}"
|
||||
>链接名称</span
|
||||
@@ -259,14 +350,35 @@
|
||||
|
||||
<div class="links-apply">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
class="btn-apply"
|
||||
id="link-apply-toggle"
|
||||
type="button"
|
||||
th:text="#{links.apply}"
|
||||
>申请友链</button>
|
||||
<div class="link-apply-panel apply-form" id="link-apply-panel" hidden>
|
||||
<h2 class="link-apply-title" th:text="#{links.apply}">申请友链</h2>
|
||||
<form id="link-apply-form" novalidate>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 申请友链弹窗:脚本运行时移至 body 下 -->
|
||||
<div class="link-apply-modal" id="link-apply-modal" hidden>
|
||||
<div class="link-apply-backdrop" data-close></div>
|
||||
<div
|
||||
class="link-apply-dialog"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="link-apply-title"
|
||||
>
|
||||
<button
|
||||
class="link-apply-close"
|
||||
type="button"
|
||||
data-close
|
||||
aria-label="关闭"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<h2 class="link-apply-title" id="link-apply-title" th:text="#{links.apply}">
|
||||
申请友链
|
||||
</h2>
|
||||
<form class="apply-form" id="link-apply-form" novalidate>
|
||||
<div class="form-row">
|
||||
<label for="f-url">网站地址 *</label>
|
||||
<input id="f-url" name="url" type="url" required placeholder="https://example.com" />
|
||||
@@ -302,12 +414,11 @@
|
||||
<input id="f-captcha" name="captchaCode" type="text" maxlength="5" required autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">提交申请</button>
|
||||
<button class="btn-apply" type="submit">提交申请</button>
|
||||
</form>
|
||||
<div class="apply-status" id="apply-status" role="alert"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="comments-section" th:if="${haloCommentEnabled}">
|
||||
<halo:comment group="plugin.halo.run" kind="Plugin" th:attr="name=${pluginName}" />
|
||||
@@ -326,12 +437,35 @@
|
||||
var API = "/apis/api.link.halo.run/v1alpha1/link-applications";
|
||||
var CAPTCHA = "/apis/api.link.halo.run/v1alpha1/link-applications/captcha";
|
||||
var toggle = document.getElementById("link-apply-toggle");
|
||||
var panel = document.getElementById("link-apply-panel");
|
||||
var modal = document.getElementById("link-apply-modal");
|
||||
var form = document.getElementById("link-apply-form");
|
||||
var img = document.getElementById("captcha-img");
|
||||
var status = document.getElementById("apply-status");
|
||||
var challengeId = null;
|
||||
|
||||
// 弹窗移至 body 下,避免被页面父级 stacking context 遮挡
|
||||
document.body.appendChild(modal);
|
||||
|
||||
function openModal() {
|
||||
modal.hidden = false;
|
||||
document.body.style.overflow = "hidden";
|
||||
if (!challengeId) loadCaptcha();
|
||||
}
|
||||
function closeModal() {
|
||||
modal.hidden = true;
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
|
||||
toggle.addEventListener("click", openModal);
|
||||
|
||||
modal.addEventListener("click", function (e) {
|
||||
if (e.target.closest("[data-close]")) closeModal();
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape" && !modal.hidden) closeModal();
|
||||
});
|
||||
|
||||
function loadCaptcha() {
|
||||
fetch(CAPTCHA, { method: "POST" })
|
||||
.then(function (r) {
|
||||
@@ -348,13 +482,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
toggle.addEventListener("click", function () {
|
||||
panel.hidden = !panel.hidden;
|
||||
if (!panel.hidden && !challengeId) {
|
||||
loadCaptcha();
|
||||
}
|
||||
});
|
||||
|
||||
img.addEventListener("click", loadCaptcha);
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
@@ -394,6 +521,7 @@
|
||||
loadCaptcha();
|
||||
status.className = "apply-status ok";
|
||||
status.textContent = "申请已提交,等待管理员审核。";
|
||||
setTimeout(closeModal, 1500);
|
||||
} else {
|
||||
return r.json().then(function (err) {
|
||||
throw err;
|
||||
|
||||
+25
-25
@@ -37,19 +37,10 @@
|
||||
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;
|
||||
@@ -69,17 +60,35 @@
|
||||
.moment-tags a {
|
||||
color: var(--color-link);
|
||||
}
|
||||
.moment-media img,
|
||||
.moment-media {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.3rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
.moment-media img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
.moment-media img:only-child {
|
||||
aspect-ratio: auto;
|
||||
width: auto;
|
||||
max-width: min(100%, 22rem);
|
||||
max-height: 26rem;
|
||||
}
|
||||
.moment-media video {
|
||||
grid-column: 1 / -1;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin-top: 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
.moment-media audio {
|
||||
grid-column: 1 / -1;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.comments-section {
|
||||
margin-top: 2rem;
|
||||
@@ -104,18 +113,9 @@
|
||||
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 class="moment-owner-name" th:text="${moment.owner.displayName}"
|
||||
>用户名</span
|
||||
>
|
||||
</span>
|
||||
<span class="moment-meta">
|
||||
<time
|
||||
|
||||
+36
-8
@@ -29,13 +29,15 @@
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
opacity: 0.75;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.moments article header > a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 2rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.moments article header > a:hover {
|
||||
color: var(--color-link-hover);
|
||||
}
|
||||
.moments .moment-owner {
|
||||
display: inline-flex;
|
||||
@@ -43,11 +45,15 @@
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.moments .moment-owner img {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.moments .moment-owner .moment-owner-name {
|
||||
color: var(--color-text);
|
||||
font-weight: 600;
|
||||
}
|
||||
.moments .moment-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -58,17 +64,39 @@
|
||||
.moments .moment-tags a {
|
||||
color: var(--color-link);
|
||||
}
|
||||
.moments .moment-media img,
|
||||
/* 图片排版:仿微信朋友圈(多图九宫格方图,单图放大保持原比例) */
|
||||
.moments .moment-media {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.3rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
.moments .moment-media img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
.moments .moment-media > :only-child {
|
||||
grid-column: auto;
|
||||
}
|
||||
.moments .moment-media img:only-child {
|
||||
aspect-ratio: auto;
|
||||
width: auto;
|
||||
max-width: min(100%, 22rem);
|
||||
max-height: 26rem;
|
||||
}
|
||||
.moments .moment-media video {
|
||||
grid-column: 1 / -1;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin-top: 1rem;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
.moments .moment-media audio {
|
||||
grid-column: 1 / -1;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
<include src="partials/layout.html">
|
||||
<template name="head">
|
||||
<title th:text="|${singlePage.spec.title} - ${site.title}|">
|
||||
About
|
||||
</title>
|
||||
<th:block
|
||||
th:with="seoTitle=${singlePage.spec.title}, seoUrl=${site.url + singlePage.status.permalink}, seoDesc=${singlePage.status.excerpt}, seoImage=${singlePage.spec.cover}, seoType='website'"
|
||||
>
|
||||
<include src="seo.html"></include>
|
||||
</th:block>
|
||||
</template>
|
||||
|
||||
<div class="container">
|
||||
<!-- 关于页面样式 -->
|
||||
<div class="page-content about-page">
|
||||
<div class="about-header">
|
||||
<div class="about-avatar">
|
||||
<img
|
||||
th:src="${not #strings.isEmpty(theme.config?.about?.avatar) ? theme.config.about.avatar : ((singlePage.owner != null and not #strings.isEmpty(singlePage.owner.avatar)) ? singlePage.owner.avatar : site.logo)}"
|
||||
th:alt="${singlePage.owner != null ? singlePage.owner.displayName : site.title}"
|
||||
/>
|
||||
</div>
|
||||
<h1
|
||||
class="about-name"
|
||||
th:text="${singlePage.owner != null ? singlePage.owner.displayName : site.title}"
|
||||
>
|
||||
About
|
||||
</h1>
|
||||
<p
|
||||
class="about-bio"
|
||||
th:if="${not #strings.isEmpty(theme.config?.about?.bio)}"
|
||||
th:text="${theme.config.about.bio}"
|
||||
></p>
|
||||
<div
|
||||
class="about-social"
|
||||
th:if="${not #strings.isEmpty(theme.config?.about?.github_url) or not #strings.isEmpty(theme.config?.about?.twitter_url) or not #strings.isEmpty(theme.config?.about?.contact_email)}"
|
||||
>
|
||||
<a
|
||||
th:if="${not #strings.isEmpty(theme.config?.about?.github_url)}"
|
||||
th:href="${theme.config.about.github_url}"
|
||||
class="social-link"
|
||||
th:title="#{about.social.github}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
th:if="${not #strings.isEmpty(theme.config?.about?.twitter_url)}"
|
||||
th:href="${theme.config.about.twitter_url}"
|
||||
class="social-link"
|
||||
th:title="#{about.social.twitter}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
th:if="${not #strings.isEmpty(theme.config?.about?.contact_email)}"
|
||||
th:href="|mailto:${theme.config.about.contact_email}|"
|
||||
class="social-link"
|
||||
th:title="#{about.social.email}"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
|
||||
></path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="about-content article-content">
|
||||
<th:block th:utext="${singlePage.content.content}"></th:block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</include>
|
||||
+139
-273
@@ -7,137 +7,87 @@
|
||||
max-width: 75rem;
|
||||
}
|
||||
|
||||
/* 相册内视图工具条 */
|
||||
:where(.photos-toolbar) {
|
||||
/* 分组导航(图库标题下方) */
|
||||
:where(.photos-nav) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.6rem;
|
||||
margin: 1.25rem 0 0;
|
||||
}
|
||||
:where(.photos-nav button) {
|
||||
padding: 0.3rem 1rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-secondary);
|
||||
font: inherit;
|
||||
font-size: 0.88rem;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color var(--transition-fast),
|
||||
border-color var(--transition-fast),
|
||||
background var(--transition-fast);
|
||||
}
|
||||
:where(.photos-nav button:hover) {
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
:where(.photos-nav button.is-active) {
|
||||
color: #fff;
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
/* 分组区块 */
|
||||
:where(.photos-group)[hidden] {
|
||||
display: none;
|
||||
}
|
||||
:where(.photos-group-title) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
:where(.photos-back) {
|
||||
color: var(--color-link);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
:where(.photos-back):hover {
|
||||
color: var(--color-link-hover);
|
||||
}
|
||||
:where(.photos-album-title) {
|
||||
margin: 0;
|
||||
gap: 0.5rem;
|
||||
margin: 2rem 0 0;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
:where(.photos-group-title::before) {
|
||||
content: "";
|
||||
width: 0.2rem;
|
||||
height: 1.1em;
|
||||
border-radius: 2px;
|
||||
background: var(--color-primary);
|
||||
}
|
||||
:where(.photos-group-section:first-of-type .photos-group-title) {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* 相册网格 */
|
||||
:where(.albums-grid) {
|
||||
list-style: none;
|
||||
margin: 1.5rem 0 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(
|
||||
auto-fill,
|
||||
minmax(min(16rem, 100%), 1fr)
|
||||
);
|
||||
gap: 1.5rem;
|
||||
}
|
||||
/* 打开某个相册后:只显示该相册,卡片收起、瀑布流展开 */
|
||||
:where(.albums-grid.is-album-open) {
|
||||
display: block;
|
||||
}
|
||||
:where(.albums-grid.is-album-open .albums-item) {
|
||||
display: none;
|
||||
}
|
||||
:where(.albums-grid.is-album-open .albums-item.is-active) {
|
||||
display: block;
|
||||
}
|
||||
:where(.albums-item.is-active .albums-card) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:where(.albums-card) {
|
||||
display: block;
|
||||
color: var(--color-text);
|
||||
}
|
||||
:where(.albums-card:hover .albums-card__name) {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
:where(.albums-card__cover) {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
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;
|
||||
}
|
||||
: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(.albums-card__more) {
|
||||
display: inline-block;
|
||||
margin-top: 0.4rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-primary);
|
||||
opacity: 0;
|
||||
transform: translateX(-0.25rem);
|
||||
transition: opacity var(--transition-fast), transform var(--transition-fast);
|
||||
}
|
||||
:where(.albums-card:hover .albums-card__more) {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* 瀑布流(一行三张) */
|
||||
/* 瀑布流(一行三张,图片自适应原比例) */
|
||||
:where(.photos-masonry) {
|
||||
list-style: none;
|
||||
margin: 1.5rem 0 0;
|
||||
margin: 1rem 0 0;
|
||||
padding: 0;
|
||||
columns: 3;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
:where(.photos-masonry[hidden]) {
|
||||
display: none;
|
||||
}
|
||||
:where(.photos-masonry > li) {
|
||||
break-inside: avoid;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
:where(.photos-masonry .photos-card__media) {
|
||||
:where(.photos-item) {
|
||||
display: block;
|
||||
}
|
||||
:where(.photos-masonry img) {
|
||||
:where(.photos-item img) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-bg-secondary);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
:where(.photos-masonry .photos-card__caption) {
|
||||
margin: 0.4rem 0 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-secondary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
:where(.photos-item img.is-loaded) {
|
||||
opacity: 1;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
:where(.photos-masonry) {
|
||||
@@ -150,25 +100,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 预览模式切换 */
|
||||
: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;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
@@ -179,89 +114,49 @@
|
||||
<h1 class="photos-header__title" th:text="${title}">图库</h1>
|
||||
</header>
|
||||
|
||||
<!-- 相册内视图工具条:打开具体相册时显示 -->
|
||||
<div class="photos-toolbar" id="photos-toolbar" hidden>
|
||||
<a
|
||||
class="photos-back"
|
||||
th:href="${photoUrl != null ? photoUrl.list() : '/photos'}"
|
||||
th:text="#{photos.back}"
|
||||
>返回全部相册</a
|
||||
>
|
||||
<h2 class="photos-album-title" id="photos-album-title"></h2>
|
||||
<button
|
||||
class="photos-preview-toggle"
|
||||
id="photos-preview-toggle"
|
||||
type="button"
|
||||
th:attr="data-default=${theme.config?.photos?.preview_default != false ? 'on' : 'off'},data-global=${theme.config?.basic?.enable_lightbox != false ? '1' : '0'}"
|
||||
>
|
||||
预览模式
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<th:block th:with="albums=${photoFinder.groupBy()}">
|
||||
<ul
|
||||
class="albums-grid"
|
||||
id="albums-grid"
|
||||
<th:block
|
||||
th:with="albums=${photoFinder.groupBy()}, anyCheck=${photoFinder.list(1, 1)}, anyPhotos=${anyCheck != null ? anyCheck.items : null}"
|
||||
>
|
||||
<!-- 分组导航 -->
|
||||
<nav
|
||||
class="photos-nav"
|
||||
id="photos-nav"
|
||||
aria-label="图库分组"
|
||||
th:if="${not #lists.isEmpty(albums)}"
|
||||
>
|
||||
<li
|
||||
class="albums-item"
|
||||
<button type="button" class="is-active" data-group="">全部</button>
|
||||
<button
|
||||
type="button"
|
||||
th:each="album : ${albums}"
|
||||
th:attr="data-album=${album.metadata.name}"
|
||||
th:data-title="${album.spec.displayName}"
|
||||
th:attr="data-group=${album.metadata.name}"
|
||||
th:text="${album.spec.displayName}"
|
||||
>
|
||||
分组
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<!-- 各分组照片,一次全部渲染;默认「全部」视图,前台脚本切换 -->
|
||||
<th:block th:if="${not #lists.isEmpty(albums)}">
|
||||
<section
|
||||
class="photos-group-section"
|
||||
th:each="album : ${albums}"
|
||||
th:attr="data-group=${album.metadata.name}"
|
||||
>
|
||||
<h2 class="photos-group-title" th:text="${album.spec.displayName}">
|
||||
分组
|
||||
</h2>
|
||||
<th:block th:with="rawPhotos=${album.photos}">
|
||||
<th:block
|
||||
th:with="albumPhotos=${(rawPhotos != null and !#lists.isEmpty(rawPhotos)) ? rawPhotos : photoFinder.listBy(album.metadata.name)}"
|
||||
th:with="photos=${(rawPhotos != null and !#lists.isEmpty(rawPhotos)) ? rawPhotos : photoFinder.listBy(album.metadata.name)}"
|
||||
>
|
||||
<a
|
||||
class="albums-card"
|
||||
th:href="${photoUrl.list(album.metadata.name)}"
|
||||
th:aria-label="|查看相册 ${album.spec.displayName}|"
|
||||
th:title="|查看相册 ${album.spec.displayName}|"
|
||||
>
|
||||
<span class="albums-card__cover">
|
||||
<img
|
||||
th:if="${not #lists.isEmpty(albumPhotos)}"
|
||||
th:src="${albumPhotos[0].spec.cover != null and !#strings.isEmpty(albumPhotos[0].spec.cover) ? albumPhotos[0].spec.cover : albumPhotos[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.metadata.name 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>
|
||||
<span class="albums-card__more">查看相册 →</span>
|
||||
</a>
|
||||
|
||||
<!-- 该相册的照片瀑布流:默认隐藏,由脚本按 URL 的 group 显示 -->
|
||||
<ul
|
||||
class="photos-masonry"
|
||||
hidden
|
||||
th:if="${not #lists.isEmpty(photos)}"
|
||||
th:attr="data-lightbox=${theme.config?.basic?.enable_lightbox != false ? 'true' : null}"
|
||||
>
|
||||
<li th:each="photo : ${albumPhotos}">
|
||||
<li th:each="photo : ${photos}">
|
||||
<a
|
||||
class="photos-card__media"
|
||||
class="photos-item"
|
||||
th:href="${photoUrl.detail(photo)}"
|
||||
th:title="${photo.spec.displayName}"
|
||||
>
|
||||
@@ -269,110 +164,81 @@
|
||||
th:src="${#strings.isEmpty(photo.spec.cover) ? photo.spec.url : photo.spec.cover}"
|
||||
th:alt="${photo.spec.displayName}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</a>
|
||||
<p
|
||||
class="photos-card__caption"
|
||||
th:if="${not #strings.isEmpty(photo.spec.displayName)}"
|
||||
th:text="${photo.spec.displayName}"
|
||||
>
|
||||
照片
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="photos-empty" th:if="${#lists.isEmpty(albums)}" role="status">
|
||||
暂无相册
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<p
|
||||
class="photos-empty"
|
||||
th:if="${#lists.isEmpty(anyPhotos)}"
|
||||
role="status"
|
||||
>
|
||||
暂无照片
|
||||
</p>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script th:inline="none">
|
||||
(function () {
|
||||
var grid = document.getElementById("albums-grid");
|
||||
if (!grid) return;
|
||||
var nav = document.getElementById("photos-nav");
|
||||
if (!nav) return;
|
||||
|
||||
var toolbar = document.getElementById("photos-toolbar");
|
||||
var titleEl = document.getElementById("photos-album-title");
|
||||
var sections = document.querySelectorAll(".photos-group-section");
|
||||
var buttons = nav.querySelectorAll("button");
|
||||
|
||||
// 按 URL 上的 group 参数决定显示哪个相册
|
||||
//(不依赖服务端 group 变量,避免页面缓存 / 插件版本差异导致打不开相册)
|
||||
var group = "";
|
||||
// 深链接:?group=xxx(仅前台读取,不依赖服务端参数)
|
||||
var initial = "";
|
||||
try {
|
||||
group = new URLSearchParams(window.location.search).get("group") || "";
|
||||
initial = new URLSearchParams(window.location.search).get("group") || "";
|
||||
} catch (e) {
|
||||
group = "";
|
||||
initial = "";
|
||||
}
|
||||
|
||||
var items = grid.querySelectorAll(".albums-item");
|
||||
var opened = false;
|
||||
|
||||
Array.prototype.forEach.call(items, function (item) {
|
||||
var masonry = item.querySelector(".photos-masonry");
|
||||
var isMatch = group !== "" && item.getAttribute("data-album") === group;
|
||||
if (isMatch) {
|
||||
item.classList.add("is-active");
|
||||
if (masonry) masonry.removeAttribute("hidden");
|
||||
if (titleEl) titleEl.textContent = item.getAttribute("data-title") || "";
|
||||
opened = true;
|
||||
} else {
|
||||
item.classList.remove("is-active");
|
||||
if (masonry) masonry.setAttribute("hidden", "");
|
||||
}
|
||||
});
|
||||
|
||||
if (opened) {
|
||||
grid.classList.add("is-album-open");
|
||||
if (toolbar) toolbar.removeAttribute("hidden");
|
||||
} else {
|
||||
grid.classList.remove("is-album-open");
|
||||
if (toolbar) toolbar.setAttribute("hidden", "");
|
||||
}
|
||||
|
||||
// 预览模式(作用于全部相册瀑布流)
|
||||
var btn = document.getElementById("photos-preview-toggle");
|
||||
if (!btn) return;
|
||||
|
||||
var KEY = "whitespace-photo-preview";
|
||||
var defaultOn = btn.getAttribute("data-default") === "on";
|
||||
var globalOn = btn.getAttribute("data-global") === "1";
|
||||
var stored = null;
|
||||
try {
|
||||
stored = localStorage.getItem(KEY);
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
var mode = stored === null ? defaultOn : stored === "on";
|
||||
|
||||
function apply() {
|
||||
var all = document.querySelectorAll(".photos-masonry");
|
||||
Array.prototype.forEach.call(all, function (m) {
|
||||
if (globalOn && mode) {
|
||||
m.setAttribute("data-lightbox", "true");
|
||||
function apply(group) {
|
||||
Array.prototype.forEach.call(buttons, function (btn) {
|
||||
var active = btn.getAttribute("data-group") === group;
|
||||
btn.classList.toggle("is-active", active);
|
||||
});
|
||||
Array.prototype.forEach.call(sections, function (sec) {
|
||||
var match = group === "" || sec.getAttribute("data-group") === group;
|
||||
if (match) {
|
||||
sec.removeAttribute("hidden");
|
||||
} else {
|
||||
m.removeAttribute("data-lightbox");
|
||||
sec.setAttribute("hidden", "");
|
||||
}
|
||||
});
|
||||
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();
|
||||
nav.addEventListener("click", function (e) {
|
||||
var btn = e.target.closest("button[data-group]");
|
||||
if (!btn) return;
|
||||
apply(btn.getAttribute("data-group"));
|
||||
});
|
||||
|
||||
apply();
|
||||
if (initial && nav.querySelector('button[data-group="' + initial + '"]')) {
|
||||
apply(initial);
|
||||
}
|
||||
|
||||
// 图片加载后淡入
|
||||
Array.prototype.forEach.call(
|
||||
document.querySelectorAll(".photos-item img"),
|
||||
function (img) {
|
||||
if (img.complete && img.naturalWidth > 0) {
|
||||
img.classList.add("is-loaded");
|
||||
} else {
|
||||
img.addEventListener("load", function () {
|
||||
img.classList.add("is-loaded");
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
})();
|
||||
</script>
|
||||
</include>
|
||||
|
||||
@@ -1,6 +1,34 @@
|
||||
<include src="partials/layout.html">
|
||||
<template name="head">
|
||||
<title th:text="|#{tags.title} - ${site.title}|">Tags</title>
|
||||
<style>
|
||||
/* 标签云:紧凑标签片,保留间距 */
|
||||
.tag-cloud {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.65rem 0.75rem;
|
||||
margin: 1.75rem 0 0;
|
||||
}
|
||||
.tag-cloud .tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.35rem 0.95rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
transition:
|
||||
color var(--transition-fast),
|
||||
border-color var(--transition-fast);
|
||||
}
|
||||
.tag-cloud .tag:hover {
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
</style>
|
||||
<th:block
|
||||
th:with="seoTitle=#{tags.title}, seoUrl=${site.url + site.routes.tagsUri}, seoDesc=null, seoImage=${site.logo}, seoType='website'"
|
||||
>
|
||||
|
||||
+1
-5
@@ -14,17 +14,13 @@ spec:
|
||||
issues: https://git.nxxy335.top/nxxy335/halo-theme-whitespace/issues
|
||||
settingName: "whitespace-setting"
|
||||
configMapName: "whitespace-configMap"
|
||||
version: 0.3.0
|
||||
version: 0.4.0
|
||||
requires: ">=2.26.0"
|
||||
license:
|
||||
- name: "GPL-3.0"
|
||||
url: "https://github.com/halo-dev/theme-vite-starter/blob/main/LICENSE"
|
||||
customTemplates:
|
||||
page:
|
||||
- name: 关于页面
|
||||
description: 关于页面布局,展示头像、个人简介与社交链接
|
||||
screenshot:
|
||||
file: page_about.html
|
||||
- name: 微信读书页面
|
||||
description: 适配 halo-weread-plugin,展示微信读书书架与读书笔记
|
||||
screenshot:
|
||||
|
||||
Reference in New Issue
Block a user