fix(gallery): 修灯箱右上关闭点不动,caption 支持字体与颜色

用户实测:「图库中的图片点击查看之后,点击右上角的关闭无法关闭,其次就是右侧
的文字是否支持字体的更换和颜色的更换」。

关闭失灵的根因(生产实测取证):桌面端信息栏 `.lb-l2` 是 top:0→bottom:0 的
右侧全高侧栏(宽 200px),且 DOM 顺序在关闭钮之后;`document.elementFromPoint`
在按钮中心命中 `.lb-meta.lb-l2` —— 点击被侧栏吃掉,`lb.close()` 永不触发
(实测 Playwright 点击 #lb-close 30s 超时、dialog 保持 open)。给 `#lb-close`
提层级 z-index:12 后「点击 → open=false」通过。

caption 富文本:
- 照片 caption 走全站同一条管线(markdown → 内容净化三层白名单 → class 白名单),
  构建期预渲染成 HTML 挂在卡片的 data-caption-html 上,换图时写 innerHTML
- 灯箱容器加 `.rich`,global.css 的 .rich .c-blue / .rich .font-kai 才生效(此前
  只有触发类名的选择器、容器无 .rich 前缀,故颜色字体都不生效)
- 深底适配:灯箱内的彩色取柔和对暗底的档位;段间距收紧、首尾不撑高
- 容器由 <p> 改 <div>:caption 可含多段落,<p> 套 <p> 会被浏览器拆散

验证: 构建 exit 0;生产同款探针实测关闭生效(此前超时);
      注入测试 caption 后产物含 class="c-blue"(保留)、font-kai(保留)、
      **加粗**→<strong>、<script> 被净化丢弃(验收数据已还原)

Spec: 2026-11-02_blog-5i-fonts-polish (T4)

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-11-03 11:18:13 +08:00
1 parent d30b5decbb
commit 138d3d976b
1 file changed
+22 -3
+22 -3
View File
@@ -5,6 +5,7 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
import { t } from '../../i18n/ui'; import { t } from '../../i18n/ui';
import type { Locale } from '../../lib/locale'; import type { Locale } from '../../lib/locale';
import type { PhotoExif } from '../../lib/exif'; import type { PhotoExif } from '../../lib/exif';
import { renderRich } from '../../lib/site-content';
// /en/ 侧由 T4 薄页传 locale='en';zh 路由无人传参,默认 zh 零回归 // /en/ 侧由 T4 薄页传 locale='en';zh 路由无人传参,默认 zh 零回归
interface Props { interface Props {
@@ -54,7 +55,10 @@ const cards = await Promise.all(
return { return {
group: photo.data.group, group: photo.data.group,
title: photo.data.title, title: photo.data.title,
// caption 支持 markdown + 排版类(字体/颜色):与全站文案同一条净化管线,
// 因此灯箱右侧文字也能换字体、换颜色(2026-11-02 用户要求)
caption: photo.data.caption ?? '', caption: photo.data.caption ?? '',
captionHtml: photo.data.caption ? await renderRich(photo.data.caption) : '',
alt: photo.data.alt ?? photo.data.title, alt: photo.data.alt ?? photo.data.title,
image: photo.data.image, image: photo.data.image,
exif: formatExif(photo.data.exif), exif: formatExif(photo.data.exif),
@@ -97,6 +101,7 @@ const first = cards[0];
class="shot-btn" class="shot-btn"
data-title={card.title} data-title={card.title}
data-caption={card.caption} data-caption={card.caption}
data-caption-html={card.captionHtml}
data-exif={card.exif} data-exif={card.exif}
data-full={card.fullSrc} data-full={card.fullSrc}
data-original={card.original} data-original={card.original}
@@ -143,7 +148,8 @@ const first = cards[0];
</svg> </svg>
</button> </button>
<div class="lb-meta lb-l2"> <div class="lb-meta lb-l2">
<p id="lb-caption" class="lb-caption">{first?.caption ?? ''}</p> <!-- rich:让 global.css 里的 .rich .c-blue / .rich .font-kai 生效(灯箱文字也能换字体/颜色) -->
<div id="lb-caption" class="lb-caption rich" set:html={first?.captionHtml ?? ''}></div>
<button <button
id="lb-cap-more" id="lb-cap-more"
class="lb-cap-more" class="lb-cap-more"
@@ -497,8 +503,10 @@ const first = cards[0];
titleEl.textContent = title; titleEl.textContent = title;
titleEl.style.display = title ? '' : 'none'; titleEl.style.display = title ? '' : 'none';
if (captionEl) { if (captionEl) {
captionEl.textContent = btn.dataset.caption ?? ''; // 富文本 caption:HTML 由 renderRich 在构建期净化(危险标签整棵丢弃),此处可直接写入
captionEl.style.display = captionEl.textContent ? '' : 'none'; const html = btn.dataset.captionHtml ?? '';
captionEl.innerHTML = html;
captionEl.style.display = captionEl.textContent?.trim() ? '' : 'none';
} }
// EXIF 字段级渲染:每段 nowrap(分隔符由 CSS ::after 粘在段尾,永不悬空行首/行尾) // EXIF 字段级渲染:每段 nowrap(分隔符由 CSS ::after 粘在段尾,永不悬空行首/行尾)
const exifText = btn.dataset.exif ?? ''; const exifText = btn.dataset.exif ?? '';
@@ -716,6 +724,14 @@ const first = cards[0];
color: #dfe6f3; color: #dfe6f3;
max-width: 640px; max-width: 640px;
} }
/* caption 富文本(动态写入,scoped 够不着):段间距收紧、首尾不撑高 */
.lb-caption :global(p) { margin: 0 0 0.4em; }
.lb-caption :global(> :first-child) { margin-top: 0; }
.lb-caption :global(> :last-child) { margin-bottom: 0; }
.lb-caption :global(strong) { color: #fff; }
.lb-caption :global(a) { color: #8ea2ff; }
/* 灯箱底是深色:彩色语义类在暗底上取 soft 档更稳(tokens 的 soft 值本就为暗底调过) */
.lb-caption :global(.c-blue) { color: #8ea2ff; }
.lb-row { .lb-row {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -804,6 +820,9 @@ const first = cards[0];
position: absolute; position: absolute;
top: var(--space-3); top: var(--space-3);
right: var(--space-3); right: var(--space-3);
/* 桌面端信息栏是 top:0→bottom:0 的右侧全高侧栏,且 DOM 在其后 → 命中测试被它吃掉,
右上关闭点不动(elementFromPoint 实锤命中 .lb-meta)。抬层级让按钮始终在最上 */
z-index: 12;
} }
@media (max-width: 819px) { @media (max-width: 819px) {
/* 触屏条:‹ 图 › 一行,caption/meta 整行在下(flex 生效后 order 才有意义) */ /* 触屏条:‹ 图 › 一行,caption/meta 整行在下(flex 生效后 order 才有意义) */