2 Commits
Author SHA1 Message Date
nxxy335 a08782a959 fix: 修复记得页 500 与留言板裁切,统一页头,完善友链/瞬间/暗色/排版 (v1.1.12)
记得(/important-dates)
- 修复 500:th:classappend 里 BEM 类名 wi-id__car 的两个 __ 触发 Thymeleaf
  __...__ 预处理,提取片段含引号导致 Could not parse as expression;
  皮肤修饰类改为 wi-id-skin-cool/cute/neutral(表达式内不再出现 __)
- 牵挂的人卡片:倒计时改到右上角与日期卡片一致,生日一行显示不再折行;
  网格改为桌面三列 / 平板两列 / 手机一列
- 页头与订阅/记得统一(标题-副标题-强调线间距)

友链(/links)
- 再次消除两处 __ 预处理隐患:回执修饰类改 wi-links-check-ok/error,
  验证码 onerror 由 th:onerror="|...|" 改为普通静态属性(不参与表达式解析)
- 新增反链/可达性检测徽章:读取 link.status.verification,头像右下角
  绿点=访问正常(反链有效),红点=访问异常或未检测到反链,未检测不显示

留言板
- 移除 -124px 负 margin 补偿(头部卡片顶部被导航盖住的根因),
  装饰引号 top 改为正值不再被 overflow 裁掉

瞬间
- 修复九宫格失效根因:lightgallery 处理时把每张图包进 <a>,导致
  :has(:only-child) 恒真、全部图片退化为单列大图;改为直接把 img
  作为图库项(data-src),瀑布流样式同样补上 4 图 2x2 规则

全局
- 暗色模式配色调为偏中性暖灰(bg #1e1c1a / raised #292623),降低黄褐闷感,
  同步 theme-color 与依赖旧色值的变量
- 正文排版:p 字号 1.05~1.15rem、行高 1.85,H1~H6 逐级放大、层级更清晰

新增「文章组件」插件适配
- 新增 src/styles/_widgets.scss,将插件 --xhhao-com-* 变量映射到主题变量,
  明暗模式自动跟随
2026-09-27 10:09:28 +08:00
nxxy335 28b1f90b26 fix: 修复 /links 页面 500(th:attr 受限求值)并校正友链申请回执 (v1.1.11)
问题:GET /links 抛 TemplateProcessingException ——
"Instantiation of new objects and access to static classes or parameters is forbidden"
定位到 links.html 第 126 行第 978 列:
  th:attr="value=${param.value != null and !#lists.isEmpty(param.value) ? param.value[0] : ''}"
Thymeleaf 对 th:attr 走受限表达式求值,禁止表达式对象的方法调用(#lists.isEmpty 等),
且校验发生在解析阶段,运行时短路(param.value 为 null)也救不了。同样的表达式
放在 th:with 里是合法的 —— 这正是本主题其他页面的既有写法。

修复:
- 全部取值逻辑移入 th:with(采用主题已验证写法 param?.x + x.length > 0 ? x[0] : null),
  输出改用单值属性 th:value,彻底不在 th:attr 里放复杂表达式
- 校正申请回执语义:插件实际回跳参数是 applied=success|error|disabled,
  另有 message / field / value(旧版误判为 applied=true)
  - 统一单条提示:有 message 显示 message,否则成功文案
  - applied=success 用强调色,error 用红色
  - applied=disabled 时隐藏表单,保留「未开放」提示
  - field/value 回填到对应输入框(displayName / url)
- 新增 .wi-links__apply-result--ok 样式
- 版本升至 1.1.11

同时沉淀校验:新增扫描脚本检查构建产物中 th:attr 是否含 #xxx. 方法调用 / new / T() / @bean,
全主题扫描结果为 0。
2026-09-26 20:33:54 +08:00
16 changed files with 262 additions and 151 deletions
+2 -1
View File
@@ -11,7 +11,7 @@
## 特点 ## 特点
1. **响应式设计** — 适配桌面端与移动端,支持暗色模式,移动端使用动态视口单位适配地址栏 1. **响应式设计** — 适配桌面端与移动端,支持暗色模式,移动端使用动态视口单位适配地址栏
2. **插件兼容性** — 适配 10 款常用 Halo 插件,安装即用;提供页面布局契约,插件前台页面自动复用主题外观 2. **插件兼容性** — 适配 11 款常用 Halo 插件,安装即用;提供页面布局契约,插件前台页面自动复用主题外观
3. **内置灯箱** — 主题内置 LightGallery 灯箱,支持 EXIF 信息展示;安装 lightgallery.js 插件后自动禁用 3. **内置灯箱** — 主题内置 LightGallery 灯箱,支持 EXIF 信息展示;安装 lightgallery.js 插件后自动禁用
4. **可配置性** — 17 个设置分组、122 个配置项,几乎所有模块可独立开关 4. **可配置性** — 17 个设置分组、122 个配置项,几乎所有模块可独立开关
5. **性能优化** — 服务端字数统计(API 扩展包)、图片懒加载与响应式缩略图、滚动吸附、CSS/HTML 压缩 5. **性能优化** — 服务端字数统计(API 扩展包)、图片懒加载与响应式缩略图、滚动吸附、CSS/HTML 压缩
@@ -34,6 +34,7 @@
| [Steam 信息展示](https://github.com/Tim0x0/halo-plugin-steam) | `PluginSteam` | `/steam` | Steam 游戏库页面,含用户资料、统计数据、最近游玩、游戏库分页 | | [Steam 信息展示](https://github.com/Tim0x0/halo-plugin-steam) | `PluginSteam` | `/steam` | Steam 游戏库页面,含用户资料、统计数据、最近游玩、游戏库分页 |
| [微信读书](https://github.com/yeora26/halo-weread-plugin) | `halo-weread-plugin` | 自定义页面 | 书架展示页面,见下方「自定义页面模板」 | | [微信读书](https://github.com/yeora26/halo-weread-plugin) | `halo-weread-plugin` | 自定义页面 | 书架展示页面,见下方「自定义页面模板」 |
| [记得](https://github.com/yyliucha/plugin-important-dates) | `plugin-important-dates` | `/important-dates` | 重要日期 / 牵挂的人 / 爱车双视图页面,见下方「扩展页面」 | | [记得](https://github.com/yyliucha/plugin-important-dates) | `plugin-important-dates` | `/important-dates` | 重要日期 / 牵挂的人 / 爱车双视图页面,见下方「扩展页面」 |
| [文章组件](https://github.com/acanyo/plugin-content-widgets) | `plugin-content-widgets` | 文章内 | 提示框、标签页、时间线等内容组件,主题已映射 `--xhhao-com-*` 变量,自动跟随明暗模式 |
| [API 扩展包](https://github.com/HowieHz/halo-plugin-extra-api) | `extra-api` | 全站 | 服务端字数统计、中英文混排排版、可选服务端代码高亮 | | [API 扩展包](https://github.com/HowieHz/halo-plugin-extra-api) | `extra-api` | 全站 | 服务端字数统计、中英文混排排版、可选服务端代码高亮 |
> **注意**:插件需要单独安装,安装后主题会自动适配对应页面。若未安装某插件,对应页面或功能自动隐藏/降级,不会报错。 > **注意**:插件需要单独安装,安装后主题会自动适配对应页面。若未安装某插件,对应页面或功能自动隐藏/降级,不会报错。
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"version": "1.1.10", "version": "1.1.12",
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">=22.12.0" "node": ">=22.12.0"
+7 -14
View File
@@ -236,24 +236,17 @@
} }
function processMomentsMedia(container) { function processMomentsMedia(container) {
// 注意:不要用 <a> 包裹瞬间图片。九宫格布局依赖
// .wi-moments-page__card-media 的直接子元素数量(:has/:nth-child/:only-child),
// 每张图各自包一层 <a> 后,img 永远是「唯一子元素」,全部图片都会退化成单列大图。
// 直接把 img 本身作为 lightgallery 项即可(data-src 提供原图)。
var images = container.querySelectorAll('img'); var images = container.querySelectorAll('img');
images.forEach(function(img) { images.forEach(function(img) {
var src = img.getAttribute('src'); var src = img.getAttribute('src');
if (!src) return; if (!src) return;
img.classList.add('wi-lightgallery-item');
var parentA = img.closest('a'); if (!img.getAttribute('data-src')) {
if (parentA) { img.setAttribute('data-src', src);
parentA.classList.add('wi-lightgallery-item');
if (!parentA.getAttribute('data-src')) {
parentA.setAttribute('data-src', src);
}
} else {
var a = document.createElement('a');
a.setAttribute('href', src);
a.setAttribute('data-src', src);
a.classList.add('wi-lightgallery-item');
img.parentNode.insertBefore(a, img);
a.appendChild(img);
} }
}); });
} }
+1 -1
View File
@@ -58,7 +58,7 @@
document.documentElement.setAttribute("data-color-scheme", isDark ? "dark" : "light"); document.documentElement.setAttribute("data-color-scheme", isDark ? "dark" : "light");
localStorage.setItem("wi-theme", isDark ? "dark" : "light"); localStorage.setItem("wi-theme", isDark ? "dark" : "light");
var tc = document.querySelector('meta[name="theme-color"]'); var tc = document.querySelector('meta[name="theme-color"]');
if (tc) tc.setAttribute("content", isDark ? "#1a1614" : "#faf7f2"); if (tc) tc.setAttribute("content", isDark ? "#1e1c1a" : "#faf7f2");
updateIcons(); updateIcons();
setTimeout(function() { setTimeout(function() {
document.documentElement.classList.remove("wi-theme-transition"); document.documentElement.classList.remove("wi-theme-transition");
+1 -1
View File
@@ -56,7 +56,7 @@ const fragmentAttr = layoutFragment ? "html (head, content)" : undefined;
document.documentElement.classList.add("dark"); document.documentElement.classList.add("dark");
document.documentElement.setAttribute("data-color-scheme", "dark"); document.documentElement.setAttribute("data-color-scheme", "dark");
var tc = document.querySelector('meta[name="theme-color"]'); var tc = document.querySelector('meta[name="theme-color"]');
if (tc) tc.setAttribute("content", "#1a1614"); if (tc) tc.setAttribute("content", "#1e1c1a");
} else { } else {
document.documentElement.classList.remove("dark"); document.documentElement.classList.remove("dark");
document.documentElement.setAttribute("data-color-scheme", "light"); document.documentElement.setAttribute("data-color-scheme", "light");
+55 -50
View File
@@ -150,7 +150,7 @@ import Layout from "../layouts/Layout.astro";
<article <article
class="wi-id__car" class="wi-id__car"
th:each="c : ${cars}" th:each="c : ${cars}"
th:classappend="${(carSkinEnabled != null and carSkinEnabled) ? 'wi-id__car--' + c.skin : 'wi-id__car--neutral'}" th:classappend="${carSkinEnabled != null and carSkinEnabled ? 'wi-id-skin-' + c.skin : 'wi-id-skin-neutral'}"
> >
<div class="wi-id__car-head"> <div class="wi-id__car-head">
<button <button
@@ -332,7 +332,11 @@ import Layout from "../layouts/Layout.astro";
</p> </p>
<div class="wi-id__grid" th:unless="${#lists.isEmpty(people)}"> <div class="wi-id__grid" th:unless="${#lists.isEmpty(people)}">
<article class="wi-id__memo" th:each="p : ${people}"> <article class="wi-id__memo wi-id__memo--person" th:each="p : ${people}">
<div class="wi-id__memo-days" th:if="${p.daysUntil >= 0}">
<strong th:text="${p.daysUntil}">61</strong>
<span th:text="'天后'">天后</span>
</div>
<div class="wi-id__person"> <div class="wi-id__person">
<span class="wi-id__avatar"> <span class="wi-id__avatar">
<span <span
@@ -367,22 +371,14 @@ import Layout from "../layouts/Layout.astro";
</p> </p>
</div> </div>
</div> </div>
<div class="wi-id__memo-foot"> <p class="wi-id__memo-date wi-id__memo-date--foot">
<span class="wi-id__memo-date"> 生日 <span th:text="${p.birthdayText}">1999-10-**</span>
生日 <span th:text="${p.birthdayText}">1999-10-**</span>
<span
th:if="${p.nextSolarDate != null}"
th:text="|(${p.nextSolarDate})|"
>(2026-10-**)</span
>
</span>
<span <span
class="wi-id__memo-count" th:if="${p.nextSolarDate != null}"
th:if="${p.daysUntil >= 0}" th:text="|(${p.nextSolarDate})|"
th:text="|${p.daysUntil} 天后|" >(2026-10-**)</span
>61 天后</span
> >
</div> </p>
</article> </article>
</div> </div>
</section> </section>
@@ -732,14 +728,14 @@ import Layout from "../layouts/Layout.astro";
.wi-id__title { .wi-id__title {
font-size: var(--text-4xl); font-size: var(--text-4xl);
margin-bottom: var(--space-xs); margin-bottom: var(--space-sm);
} }
.wi-id__subtitle { .wi-id__subtitle {
font-size: var(--text-sm); font-size: var(--text-sm);
color: var(--ink-3); color: var(--ink-3);
letter-spacing: var(--tracking-wide); letter-spacing: var(--tracking-wide);
margin-block-end: 0; margin-bottom: var(--space-sm);
} }
.wi-page-accent { .wi-page-accent {
@@ -879,10 +875,22 @@ import Layout from "../layouts/Layout.astro";
.wi-id__grid { .wi-id__grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); grid-template-columns: repeat(3, 1fr);
gap: var(--space-md); gap: var(--space-md);
} }
@media (max-width: 900px) {
.wi-id__grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 640px) {
.wi-id__grid {
grid-template-columns: 1fr;
}
}
/* ===== 便签卡片(重要日期 / 人) ===== */ /* ===== 便签卡片(重要日期 / 人) ===== */
.wi-id__memo { .wi-id__memo {
position: relative; position: relative;
@@ -984,6 +992,11 @@ import Layout from "../layouts/Layout.astro";
color: var(--ink-3); color: var(--ink-3);
} }
.wi-id__memo--person .wi-id__person {
/* 右上角是倒计时数字,人物行留出空间避免重叠 */
padding-right: 56px;
}
.wi-id__person { .wi-id__person {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1022,23 +1035,12 @@ import Layout from "../layouts/Layout.astro";
font-size: 1rem; font-size: 1rem;
} }
.wi-id__memo-foot { /* 生日一行放下:日期本身不折行,放不下时整行换行而不是把日期拆开 */
display: flex; .wi-id__memo-date--foot {
align-items: baseline; margin: 12px 0 0;
justify-content: space-between; white-space: nowrap;
gap: 10px; overflow: hidden;
margin-top: 10px; text-overflow: ellipsis;
}
.wi-id__memo-foot .wi-id__memo-date {
margin: 0;
}
.wi-id__memo-count {
flex: none;
font-size: var(--text-sm);
font-weight: 700;
color: var(--accent);
} }
.wi-id__badges { .wi-id__badges {
@@ -1206,53 +1208,53 @@ import Layout from "../layouts/Layout.astro";
} }
/* 皮肤(插件设置「座驾卡片皮肤」开启时生效) */ /* 皮肤(插件设置「座驾卡片皮肤」开启时生效) */
.wi-id__car--cool { .wi-id-skin-cool {
background: linear-gradient(150deg, #111827, #0f172a 55%, #1e3a8a); background: linear-gradient(150deg, #111827, #0f172a 55%, #1e3a8a);
color: #e5e7eb; color: #e5e7eb;
border-color: rgba(148, 163, 184, 0.4); border-color: rgba(148, 163, 184, 0.4);
} }
.wi-id__car--cool .wi-id__car-name, .wi-id-skin-cool .wi-id__car-name,
.wi-id__car--cool .wi-id__car-sub, .wi-id-skin-cool .wi-id__car-sub,
.wi-id__car--cool .wi-id__badge { .wi-id-skin-cool .wi-id__badge {
color: #e5e7eb; color: #e5e7eb;
border-color: rgba(147, 197, 253, 0.6); border-color: rgba(147, 197, 253, 0.6);
} }
.wi-id__car--cool .wi-id__event { .wi-id-skin-cool .wi-id__event {
background: rgba(96, 165, 250, 0.18); background: rgba(96, 165, 250, 0.18);
color: #bfdbfe; color: #bfdbfe;
} }
.wi-id__car--cool .wi-id__event.is-soon { .wi-id-skin-cool .wi-id__event.is-soon {
background: rgba(251, 191, 36, 0.18); background: rgba(251, 191, 36, 0.18);
color: #fde68a; color: #fde68a;
} }
.wi-id__car--cool .wi-id__event.is-overdue { .wi-id-skin-cool .wi-id__event.is-overdue {
background: rgba(248, 113, 113, 0.2); background: rgba(248, 113, 113, 0.2);
color: #fecaca; color: #fecaca;
} }
.wi-id__car--cute { .wi-id-skin-cute {
background: linear-gradient(150deg, #fff1f7, #ffe4ef 55%, #fdf2f8); background: linear-gradient(150deg, #fff1f7, #ffe4ef 55%, #fdf2f8);
color: #831843; color: #831843;
border-color: rgba(244, 114, 182, 0.4); border-color: rgba(244, 114, 182, 0.4);
} }
.wi-id__car--cute .wi-id__car-name, .wi-id-skin-cute .wi-id__car-name,
.wi-id__car--cute .wi-id__car-sub, .wi-id-skin-cute .wi-id__car-sub,
.wi-id__car--cute .wi-id__badge { .wi-id-skin-cute .wi-id__badge {
color: #831843; color: #831843;
border-color: rgba(236, 72, 153, 0.55); border-color: rgba(236, 72, 153, 0.55);
} }
.wi-id__car--cute .wi-id__event { .wi-id-skin-cute .wi-id__event {
background: rgba(236, 72, 153, 0.12); background: rgba(236, 72, 153, 0.12);
color: #9d174d; color: #9d174d;
} }
.wi-id__car--neutral { .wi-id-skin-neutral {
background: var(--bg-raised); background: var(--bg-raised);
} }
@@ -1348,7 +1350,6 @@ import Layout from "../layouts/Layout.astro";
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.wi-id__grid,
.wi-id__cars { .wi-id__cars {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@@ -1356,5 +1357,9 @@ import Layout from "../layouts/Layout.astro";
.wi-id__memo-title { .wi-id__memo-title {
margin-right: 56px; margin-right: 56px;
} }
.wi-id__memo--person .wi-id__memo-title {
margin-right: 0;
}
} }
</style> </style>
+124 -43
View File
@@ -53,23 +53,39 @@ import Layout from "../layouts/Layout.astro";
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="wi-links__card" class="wi-links__card"
th:with="verification=${link.status?.verification},
accessState=${verification?.access?.state != null ? verification.access.state.name() : null},
backlinkState=${verification?.backlink != null and verification.backlink.state != null ? verification.backlink.state.name() : null}"
> >
<div class="wi-links__card-glow"></div> <div class="wi-links__card-glow"></div>
<div class="wi-links__card-inner"> <div class="wi-links__card-inner">
<img <span class="wi-links__avatar-wrap">
th:if="${link.spec.logo}" <img
th:src="${link.spec.logo}" th:if="${link.spec.logo}"
th:alt="${link.spec.displayName}" th:src="${link.spec.logo}"
class="wi-links__avatar" th:alt="${link.spec.displayName}"
/> class="wi-links__avatar"
<div />
th:unless="${link.spec.logo}" <div
class="wi-links__avatar wi-links__avatar--placeholder" th:unless="${link.spec.logo}"
> class="wi-links__avatar wi-links__avatar--placeholder"
>
<span
th:text="${#strings.substring(link.spec.displayName, 0, 1)}"
></span>
</div>
<!-- 反链 / 可达性检测徽章(plugin-links 后台「友链检测」产生,未检测过则不显示) -->
<span <span
th:text="${#strings.substring(link.spec.displayName, 0, 1)}" class="wi-links__check wi-links__check--ok"
th:if="${accessState == 'ACCESSIBLE' and backlinkState != 'MISSING'}"
th:title="${backlinkState == 'FOUND' ? '访问正常 · 反链有效' : '访问正常'}"
></span> ></span>
</div> <span
class="wi-links__check wi-links__check--bad"
th:if="${accessState == 'INACCESSIBLE' or backlinkState == 'MISSING'}"
th:title="${accessState == 'INACCESSIBLE' ? '站点访问异常' : '未检测到反链'}"
></span>
</span>
<div class="wi-links__info"> <div class="wi-links__info">
<span <span
class="wi-links__name" class="wi-links__name"
@@ -87,42 +103,66 @@ import Layout from "../layouts/Layout.astro";
</div> </div>
</th:block> </th:block>
<!--
友链申请:表单 POST 到插件的 /links/apply/submit,插件以 303 回跳 /links,
并带上 applied(success / error / disabled)、message、field、value 四个查询参数。
注意(踩过坑):th:attr 的赋值里不能调用 #lists 等表达式对象的方法,
Thymeleaf 会对 th:attr 走受限求值并抛
"Instantiation of new objects and access to static classes or parameters is forbidden"。
取值逻辑一律先在 th:with 里算成变量,再用 th:value 之类的单值属性输出。
-->
<div <div
class="wi-links__apply" class="wi-links__apply"
th:if="${theme.config?.links?.links_apply_form_enabled != false}" th:if="${theme.config?.links?.links_apply_form_enabled != false}"
th:with="applied=${param.applied != null and !#lists.isEmpty(param.applied) and param.applied[0] == 'true'}, th:with="appliedList=${param?.applied},
applyMsg=${param.message != null and !#lists.isEmpty(param.message) ? param.message[0] : null}" applied=${appliedList != null and appliedList.length > 0 ? appliedList[0] : null},
messageList=${param?.message},
applyMsg=${messageList != null and messageList.length > 0 ? messageList[0] : null},
fieldList=${param?.field},
applyField=${fieldList != null and fieldList.length > 0 ? fieldList[0] : null},
valueList=${param?.value},
applyValue=${valueList != null and valueList.length > 0 ? valueList[0] : null}"
> >
<h2 class="wi-links__apply-title" th:text="'申请友链'">申请友链</h2> <h2 class="wi-links__apply-title" th:text="'申请友链'">申请友链</h2>
<p <p
class="wi-links__apply-result" class="wi-links__apply-result"
th:if="${applied}" th:if="${applied != null}"
th:text="'申请已提交,等待站长审核 🌙'" th:classappend="${applied == 'success'} ? 'wi-links-check-ok' : 'wi-links-check-error'"
th:text="${applyMsg != null ? applyMsg : '申请已提交,等待站长审核 🌙'}"
role="status" role="status"
></p> ></p>
<p
class="wi-links__apply-result wi-links__apply-result--error"
th:if="${not applied and applyMsg != null}"
th:text="${applyMsg}"
role="alert"
></p>
<form <th:block th:unless="${applied == 'disabled'}">
class="wi-links__apply-form" <form
method="post" class="wi-links__apply-form"
th:action="@{/links/apply/submit}" method="post"
> th:action="@{/links/apply/submit}"
<input type="hidden" name="_csrf" th:value="${csrfToken}" /> >
<div class="wi-links__apply-row"> <input type="hidden" name="_csrf" th:value="${csrfToken}" />
<label class="wi-links__apply-field"> <div class="wi-links__apply-row">
<span th:text="'站点名称 *'">站点名称 *</span> <label class="wi-links__apply-field">
<input type="text" name="displayName" required maxlength="50" placeholder="你的站点名称" th:attr="value=${param.value != null and !#lists.isEmpty(param.value) ? param.value[0] : ''}" /> <span th:text="'站点名称 *'">站点名称 *</span>
</label> <input
<label class="wi-links__apply-field"> type="text"
<span th:text="'站点链接 *'">站点链接 *</span> name="displayName"
<input type="url" name="url" required placeholder="https://" /> required
</label> maxlength="50"
</div> placeholder="你的站点名称"
th:value="${applyField == 'displayName' ? applyValue : null}"
/>
</label>
<label class="wi-links__apply-field">
<span th:text="'站点链接 *'">站点链接 *</span>
<input
type="url"
name="url"
required
placeholder="https://"
th:value="${applyField == 'url' ? applyValue : null}"
/>
</label>
</div>
<div class="wi-links__apply-row"> <div class="wi-links__apply-row">
<label class="wi-links__apply-field"> <label class="wi-links__apply-field">
<span th:text="'图标地址'">图标地址</span> <span th:text="'图标地址'">图标地址</span>
@@ -160,12 +200,19 @@ import Layout from "../layouts/Layout.astro";
loading="lazy" loading="lazy"
title="点击刷新验证码" title="点击刷新验证码"
onclick="this.src=this.src.split('?')[0]+'?ts='+Date.now()" onclick="this.src=this.src.split('?')[0]+'?ts='+Date.now()"
th:onerror="|this.closest('.wi-links__apply').classList.add('wi-links__apply--unavailable')|" onerror="var c=this.closest('.wi-links__apply');if(c)c.classList.add('wi-links__apply--unavailable')"
/> />
</div> </div>
</div> </div>
<button class="wi-links__apply-submit" type="submit" th:text="'提交申请'">提交申请</button> <button
</form> class="wi-links__apply-submit"
type="submit"
th:text="'提交申请'"
>
提交申请
</button>
</form>
</th:block>
<p <p
class="wi-links__apply-unavailable-tip" class="wi-links__apply-unavailable-tip"
th:text="'友链申请暂未开放。'" th:text="'友链申请暂未开放。'"
@@ -299,11 +346,18 @@ import Layout from "../layouts/Layout.astro";
margin-bottom: var(--space-md); margin-bottom: var(--space-md);
} }
.wi-links__apply-result--error { .wi-links__apply-result--ok,
.wi-links-check-ok {
color: var(--accent);
}
.wi-links__apply-result--error,
.wi-links-check-error {
color: #e6162d; color: #e6162d;
} }
html.dark .wi-links__apply-result--error { html.dark .wi-links__apply-result--error,
html.dark .wi-links-check-error {
color: #ff6b6b; color: #ff6b6b;
} }
@@ -514,6 +568,13 @@ import Layout from "../layouts/Layout.astro";
border-color: color-mix(in srgb, var(--accent) 40%, var(--glass-border)); border-color: color-mix(in srgb, var(--accent) 40%, var(--glass-border));
} }
.wi-links__avatar-wrap {
position: relative;
flex: none;
display: inline-flex;
line-height: 1;
}
.wi-links__avatar { .wi-links__avatar {
width: 48px; width: 48px;
height: 48px; height: 48px;
@@ -524,6 +585,26 @@ import Layout from "../layouts/Layout.astro";
transition: transform var(--duration-normal) var(--ease-out-expo); transition: transform var(--duration-normal) var(--ease-out-expo);
} }
/* 反链 / 可达性检测徽章:绿色正常,红色异常 */
.wi-links__check {
position: absolute;
right: -3px;
bottom: -3px;
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid var(--bg);
box-shadow: var(--shadow-sm);
}
.wi-links__check--ok {
background: #22c55e;
}
.wi-links__check--bad {
background: #ef4444;
}
.wi-links__card:hover .wi-links__avatar { .wi-links__card:hover .wi-links__avatar {
transform: scale(1.08); transform: scale(1.08);
} }
+3 -2
View File
@@ -715,8 +715,9 @@ import LightGallery from "../components/LightGallery.astro";
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
} }
.wi-moments-page__masonry-media:has(.wi-moments-page__masonry-image:nth-child(4)) { /* 微信九宫格规则:4 张时 2x2 排布 */
grid-template-columns: repeat(3, 1fr); .wi-moments-page__masonry-media:has(.wi-moments-page__masonry-image:nth-child(4)):not(:has(.wi-moments-page__masonry-image:nth-child(5))) {
grid-template-columns: repeat(2, 1fr);
} }
.wi-moments-page__masonry-footer { .wi-moments-page__masonry-footer {
+2 -4
View File
@@ -45,8 +45,6 @@ import Layout from "../layouts/Layout.astro";
.wi-messageboard { .wi-messageboard {
display: grid; display: grid;
gap: var(--space-lg); gap: var(--space-lg);
margin-top: calc(-1 * 100px - var(--space-lg));
padding-top: 100px;
padding-block-end: 1rem; padding-block-end: 1rem;
} }
@@ -70,8 +68,8 @@ import Layout from "../layouts/Layout.astro";
color: var(--accent); color: var(--accent);
opacity: 0.18; opacity: 0.18;
position: absolute; position: absolute;
top: -0.15em; top: 0.05em;
left: 0.15em; left: 0.2em;
pointer-events: none; pointer-events: none;
user-select: none; user-select: none;
} }
+3 -3
View File
@@ -897,15 +897,15 @@ import LightGallery from "../components/LightGallery.astro";
.wi-post__body { .wi-post__body {
width: 100%; width: 100%;
font-size: 1.05rem; font-size: clamp(1.05rem, 1rem + 0.25vw, 1.15rem);
line-height: 1.8; line-height: 1.85;
color: #3d3530; color: #3d3530;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
} }
html.dark .wi-post__body { html.dark .wi-post__body {
color: #d4cdc4; color: #d9d2c9;
} }
/* 正文本身及内部元素样式统一放在全局样式 src/styles/_content.scss, /* 正文本身及内部元素样式统一放在全局样式 src/styles/_content.scss,
+14 -5
View File
@@ -94,7 +94,7 @@ import Layout from "../layouts/Layout.astro";
</Layout> </Layout>
<style> <style>
/* 头部结构与订阅/图库/友链等页面保持一致:居中标题 + 副标题 + 强调线 */ /* 头部结构与订阅/图库/友链/记得等页面保持一致:居中标题 + 副标题 + 强调线 */
.wi-weread { .wi-weread {
padding-block: 1rem 3rem; padding-block: 1rem 3rem;
} }
@@ -106,14 +106,23 @@ import Layout from "../layouts/Layout.astro";
.wi-weread__title { .wi-weread__title {
font-size: var(--text-4xl); font-size: var(--text-4xl);
margin-bottom: var(--space-xs); margin-bottom: var(--space-sm);
} }
.wi-weread__subtitle { .wi-weread__subtitle {
font-size: var(--text-sm); font-size: var(--text-sm);
color: var(--ink-3); color: var(--ink-3);
letter-spacing: var(--tracking-wide); letter-spacing: var(--tracking-wide);
margin-block-end: 0; margin-bottom: var(--space-sm);
}
.wi-page-accent {
display: block;
width: 48px;
height: 3px;
margin: var(--space-sm) auto 0;
border-radius: 9999px;
background: var(--accent);
} }
.wi-weread__shelf { .wi-weread__shelf {
@@ -188,7 +197,7 @@ import Layout from "../layouts/Layout.astro";
--wr-ink: var(--ink); --wr-ink: var(--ink);
--wr-muted: var(--ink-2); --wr-muted: var(--ink-2);
--wr-bg: var(--bg-raised); --wr-bg: var(--bg-raised);
--wr-line: #4a4238; --wr-line: #4a443c;
--wr-fill: color-mix(in srgb, var(--ink) 8%, transparent); --wr-fill: color-mix(in srgb, var(--ink) 8%, transparent);
} }
@@ -207,7 +216,7 @@ import Layout from "../layouts/Layout.astro";
--wr-ink: var(--ink); --wr-ink: var(--ink);
--wr-muted: var(--ink-2); --wr-muted: var(--ink-2);
--wr-bg: var(--bg-raised); --wr-bg: var(--bg-raised);
--wr-line: #4a4238; --wr-line: #4a443c;
--wr-fill: color-mix(in srgb, var(--ink) 8%, transparent); --wr-fill: color-mix(in srgb, var(--ink) 8%, transparent);
} }
+20 -19
View File
@@ -21,24 +21,25 @@
} }
html.dark { html.dark {
--bg: #1a1614; /* 暗色基准:偏中性、只保留一丝暖意,避免大面积黄褐底色带来的闷感 */
--bg-raised: #26211d; --bg: #1e1c1a;
--bg-overlay: rgba(26, 22, 20, 0.85); --bg-raised: #292623;
--ink: #ede6de; --bg-overlay: rgba(30, 28, 26, 0.85);
--ink-2: #a2978c; --ink: #e9e4dd;
--ink-2: #aaa096;
/* 深色背景下人眼对低对比更敏感,次要文字与分隔线比浅色模式略提亮 */ /* 深色背景下人眼对低对比更敏感,次要文字与分隔线比浅色模式略提亮 */
--ink-3: #6f6459; --ink-3: #7c7268;
--rule: #38322b; --rule: #3b362f;
--accent: #e8955f; --accent: #e89a64;
--accent-hover: #f0a872; --accent-hover: #f2ad79;
--accent-bg: #2a1e16; --accent-bg: #2c231b;
--mist-pink: #2e2420; --mist-pink: #2e2823;
--sea-salt: #2e2822; --sea-salt: #302b26;
--caramel: #a08878; --caramel: #a38b7b;
--glass-bg: rgba(26, 22, 20, 0.72); --glass-bg: rgba(30, 28, 26, 0.72);
--glass-border: rgba(46, 40, 34, 0.5); --glass-border: rgba(59, 54, 47, 0.5);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.2); --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.24);
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.25); --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.35); --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.4);
--shadow-glow: 0 0 40px rgba(232, 149, 95, 0.12); --shadow-glow: 0 0 40px rgba(232, 154, 100, 0.12);
} }
+6 -6
View File
@@ -33,37 +33,37 @@
} }
h1 { h1 {
font-size: clamp(1.45rem, 1.3rem + 0.7vw, 1.75rem); font-size: clamp(1.6rem, 1.4rem + 1vw, 2rem);
margin-top: 2em; margin-top: 2em;
margin-bottom: 0.7em; margin-bottom: 0.7em;
} }
h2 { h2 {
font-size: clamp(1.3rem, 1.2rem + 0.5vw, 1.5rem); font-size: clamp(1.42rem, 1.3rem + 0.6vw, 1.68rem);
margin-top: 1.9em; margin-top: 1.9em;
margin-bottom: 0.65em; margin-bottom: 0.65em;
} }
h3 { h3 {
font-size: clamp(1.15rem, 1.08rem + 0.35vw, 1.28rem); font-size: clamp(1.24rem, 1.16rem + 0.4vw, 1.4rem);
margin-top: 1.7em; margin-top: 1.7em;
margin-bottom: 0.6em; margin-bottom: 0.6em;
} }
h4 { h4 {
font-size: clamp(1.05rem, 1rem + 0.25vw, 1.13rem); font-size: clamp(1.1rem, 1.05rem + 0.25vw, 1.2rem);
margin-top: 1.5em; margin-top: 1.5em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
h5 { h5 {
font-size: 1rem; font-size: 1.04rem;
margin-top: 1.4em; margin-top: 1.4em;
margin-bottom: 0.45em; margin-bottom: 0.45em;
} }
h6 { h6 {
font-size: 0.95rem; font-size: 0.98rem;
font-weight: 600; font-weight: 600;
color: var(--ink-3); color: var(--ink-3);
margin-top: 1.4em; margin-top: 1.4em;
+21
View File
@@ -0,0 +1,21 @@
/* ==========================================================================
「文章组件」插件(plugin-content-widgets)主题适配
--------------------------------------------------------------------------
插件自带样式与挂载逻辑,主题只需要把它的 --xhhao-com-* 变量
映射到主题变量上:组件即可自动跟随主题的暖色 / 暗色模式。
变量清单见插件 README:https://github.com/acanyo/plugin-content-widgets
========================================================================== */
:root {
--xhhao-com-text-1: var(--ink);
--xhhao-com-text-2: var(--ink-2);
--xhhao-com-bg: var(--bg);
--xhhao-com-bg-card: var(--bg-raised);
--xhhao-com-bg-2: var(--bg-raised);
--xhhao-com-border: var(--rule);
--xhhao-com-primary: var(--accent);
--xhhao-com-shadow: rgb(15 23 42 / 0.08);
}
/* 组件自带的浅色 fallback 偏冷灰,暗色下显突兀;
变量跟随主题后,暗色模式自动生效,无需单独覆盖。 */
+1
View File
@@ -8,6 +8,7 @@
@use 'comment'; @use 'comment';
@use 'search'; @use 'search';
@use 'lightgallery'; @use 'lightgallery';
@use 'widgets';
*, *,
*::before, *::before,
+1 -1
View File
@@ -28,7 +28,7 @@ spec:
description: 微信读书书架(依赖微信读书插件) description: 微信读书书架(依赖微信读书插件)
screenshot: screenshot:
file: weread.html file: weread.html
version: "1.1.10" version: "1.1.12"
requires: ">=2.24.0" requires: ">=2.24.0"
license: license:
- name: "GPL-3.0" - name: "GPL-3.0"