diff --git a/README.md b/README.md index 2df3d92..b67d2e2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ 1. **响应式设计** — 适配桌面端与移动端,支持暗色模式,移动端使用动态视口单位适配地址栏 2. **插件兼容性** — 适配 11 款常用 Halo 插件,安装即用;提供页面布局契约,插件前台页面自动复用主题外观 3. **内置灯箱** — 主题内置 LightGallery 灯箱(GPLv3,随主题自托管打包),支持 EXIF 信息展示;安装 lightgallery.js 插件后自动禁用 -4. **可配置性** — 17 个设置分组、122 个配置项,几乎所有模块可独立开关 +4. **可配置性** — 17 个设置分组、126 个配置项,几乎所有模块可独立开关 5. **性能优化** — 服务端字数统计(API 扩展包)、图片懒加载与响应式缩略图、首屏整屏吸附(仅首屏边界接管,内容区保留原生滚动)、Hero 离屏停绘、CSS/HTML 压缩;详见[开发指南 - 性能与滚动顺滑](https://nxxy335.top/WarmIsland/customize/development.html) 6. **零默认外部请求** — 所有脚本、样式、字体、图标、句子库、主题图标均随主题自托管,默认不向任何第三方发起自动请求;详见[外部资源与隐私](#外部资源与隐私) 7. **页面布局契约** — 提供 `templates/layout.html`,插件页面不再回退到 Halo 内置布局 @@ -38,14 +38,29 @@ | 服务 / 资源 | 用途 | 涉及的数据类型 | 默认状态 | 关闭 / 替换方法 | | --- | --- | --- | --- | --- | | 主题自身静态资源 `/themes/warm-island/assets/*` | 样式、脚本、字体、图标、Logo、内置句子库 | 无(同域请求,不含用户数据) | 始终启用,渲染必需 | 不可关闭;均为随包自托管文件 | -| 自定义一言 API `hero_hitokoto_api` | Hero 首屏随机句子 | 无请求体;发起 GET 时第三方可见访客 IP / User-Agent | **关闭**:留空,使用 `assets/data/hitokoto.json` 内置本地句子库 | 主题设置 → Hero 首屏 → 清空「自定义一言 API」 | -| 自定义 IP 定位主 / 备选 API `welcome_popup_api` / `welcome_popup_fallback_api` | 欢迎弹窗显示「欢迎来自 XX 的朋友」 | **访客 IP** 与第三方返回的粗略地理位置 | **关闭**:两项均留空,弹窗只显示本地欢迎语;且欢迎弹窗开关本身默认关闭 | 主题设置 → 欢迎弹窗 → 关闭开关,或清空两个 API 字段 | +| 自定义一言 API `hero_hitokoto_api` | Hero 首屏随机句子 | 无请求体;发起 GET 时第三方可见访客 IP / User-Agent | **默认不启用**:「描述文案来源」默认「无」,一言元素不渲染,零请求;选择「一言」时自动填入 `https://v1.hitokoto.cn/` | 主题设置 → Hero 首屏 → 描述来源改为「无」或「自定义」,或清空该字段回退到 `assets/data/hitokoto.json` 内置本地句子库 | +| 自定义 IP 定位主 / 备选 API `welcome_popup_api` / `welcome_popup_fallback_api` | 欢迎弹窗显示「欢迎来自 XX 的朋友」 | **访客 IP** 与第三方返回的粗略地理位置 | **关闭**:两项均留空,弹窗只显示本地欢迎语;且欢迎弹窗开关本身默认关闭。可选接口见下方清单 | 主题设置 → 欢迎弹窗 → 关闭开关,或清空两个 API 字段 | | 分享按钮:QQ / 微博 / X / Facebook / Telegram | 分享文章到对应平台 | 文章 URL 与标题(写在目标站 URL 参数中) | 默认开启 `qq,wechat,weibo`;**仅在访客点击后**跳转,页面加载时不请求 | 主题设置 → 文章 → 关闭「显示分享按钮」,或在「分享渠道」取消勾选 | | 微信分享弹层 | 生成并复制文章链接 | 文章 URL(浏览器本地字符串,不发送) | 与分享按钮一致 | 同上;已移除二维码远程库,改为「复制链接」 | | 页脚备案 / Halo / 应用市场外链 | 备案信息、致谢、插件安装提示 | 无(访客主动点击才跳转) | 由站点配置决定,默认仅显示 Halo 致谢 | 主题设置 → 页脚 → 关闭对应显示项 | | 插件前台页面(豆瓣、Steam、朋友圈、微信读书等) | 展示插件提供的数据 | 由对应插件决定,主题不发起请求 | 安装并启用对应插件后才出现 | 卸载 / 停用对应插件;主题自动降级隐藏 | | 内置 LightGallery 灯箱 | 图片放大预览、EXIF 展示 | 无(本地打包,不联网) | 默认启用 | 安装 lightgallery.js 插件后主题自动让位 | +#### 可选第三方接口地址(均为候选值,主题不预置请求) + +| 能力 | 设置项 | 可选地址 | +| --- | --- | --- | +| Hero 一言 | `hero_hitokoto_api` | `https://v1.hitokoto.cn/`(选择「一言」时自动填入;清空则使用内置本地句子库) | +| IP 定位主接口 | `welcome_popup_api` | `https://myip.ipip.net/json` 或 `https://r.inews.qq.com/api/ip2city?otype=json` | +| IP 定位备选接口 | `welcome_popup_fallback_api` | 同上两个地址任选其一,主接口失败时兜底 | + +**描述文案来源默认为「无」**,可选「一言」「自定义」,此时不渲染一言元素、不发起任何请求。选择「一言」后自动填入 +`https://v1.hitokoto.cn/`;选择「一言」或「自定义」后若把 API 留空,则使用主题内置的本地句子库(零外部请求)。 +**只有填写后才会向该地址发起请求**,需自行在站点隐私说明中披露,详见本章节「逐项说明」与下方「隐私说明(5.1)」。 + +IP 定位接口同样默认留空;**填写后会把访客 IP 发送给该第三方服务换取地理位置**, +需自行在站点隐私说明中披露,详见本章节「逐项说明」与下方「隐私说明(5.1)」。主题不会代替站点做这项声明。 + > 自定义 API 属于**站点管理员主动配置**的可选能力:一旦填写,浏览器会按设置向该第三方发起请求。请自行评估服务商的隐私政策,并在站点隐私说明中补充披露;主题不会代替站点做这项声明。 ### 隐私说明(5.1) @@ -55,7 +70,7 @@ | 收集的数据类型 | 默认配置下**无**。主题不读取、不上传用户信息、站点内容、访问日志或订单数据 | | 数据用途 | 仅用于页面渲染;自定义 API 场景下由管理员自行决定 | | 保存位置与期限 | 主题不写入数据库、不上传数据、不生成服务端日志。前端仅在访客本机使用 `sessionStorage`(欢迎弹窗「同会话仅显示一次」)与 `localStorage`(点赞状态),可随时清除浏览器站点数据 | -| 是否传输到第三方 | 默认**否**。仅当管理员显式填写「自定义一言 API」或「自定义 IP 定位 API」时才会向该服务发起请求,其中 IP 定位会暴露访客 IP | +| 是否传输到第三方 | 默认**否**(描述文案来源为「无」、欢迎弹窗关闭、两个 API 字段留空)。仅当管理员显式填写「自定义一言 API」或「自定义 IP 定位 API」时才会向该服务发起请求,其中 IP 定位会暴露访客 IP | | 关闭 / 撤销方式 | 见上表;清空相应设置项或关闭对应开关即可立即停止,无需重装主题 | | 遥测 / 统计 / 崩溃上报 / 广告 / AI 分析 / 跨站追踪 | **均不存在**。主题不含任何埋点、指纹或第三方统计脚本 | @@ -63,10 +78,29 @@ - **已移除的远程依赖**:`cdn.jsdelivr.net` 预连接与预解析(原出现在全部模板)、PrismJS 1.29.0 核心与 `autoloader`(会按代码语言从 CDN 动态拉取并执行组件)、`qrcode-generator`、默认的一言与 IP 定位接口。 - **代码高亮**:主题不再自带 Prism.js 运行时。语法高亮请安装 Halo 应用市场的代码高亮插件,或启用「API 扩展包」的服务端代码高亮;主题只提供两套自托管的 `.token` 配色(`assets/vendor/prism-warm.css` / `prism-cold.css`),由「文章 → 代码高亮主题」切换。 -- **字体**:[霞鹜文楷 LXGW WenKai](https://github.com/lxgw/LxgwWenKai) 以 [SIL OFL 1.1](https://openfontlicense.org/) 发布,由 `cn-font-split` 切分为本地 woff2 分片随主题分发;字体来源与版权声明保留在 `public/assets/fonts/LXGWWenKai/result.css` 中,无远程字体请求。 +- **字体**:[霞鹜文楷 LXGW WenKai](https://github.com/lxgw/LxgwWenKai) 以 [SIL OFL 1.1](https://openfontlicense.org/) 发布,由 `cn-font-split` 切分为本地 woff2 分片随主题分发;字体来源与版权声明保留在 `public/assets/fonts/LXGWWenKai/result.css`,上游完整的版权声明、保留字体名条款与 **OFL 1.1 许可证全文**随包存放于 `public/assets/fonts/LXGWWenKai/OFL.txt`(构建后为 `templates/assets/fonts/LXGWWenKai/OFL.txt`)。上游的附加许可明确允许把字体切分 / 转换为 WOFF2 仅用于网页字体交付,本主题的使用方式与之相符;无远程字体请求。 - **灯箱**:LightGallery 2.9.0 以 GPLv3 发布,本主题同样采用 GPL-3.0,按开源许可条款使用(非商业授权);已去除占位 `licenseKey`,控制台不再出现 `license key is not valid for production use`。 - **图标**:来源与协议见[图标与字体来源](#图标与字体来源)。 +## 内容渲染安全(4.4.2) + +主题对「站点内容 / 用户生成内容」与「管理员显式配置」采取不同的渲染策略,避免把不可信字符串直接拼进可执行上下文: + +| 场景 | 处理方式 | +| --- | --- | +| 文章标题、摘要、作者、封面(结构化数据) | 不再用 `th:utext` 把变量拼进 `application/ld+json` 脚本文本。字段先经 Thymeleaf 转义写入 ``,前端读取后用 `JSON.stringify` + `textContent` 生成 JSON-LD,内容里的尖括号不会被解析为标签 | +| 瞬间正文 | 插件已渲染的 `content.html` 用 `th:utext` 原样呈现;`html` 缺失时的 `content.raw` 兜底改用 `th:text` 转义输出,不再当作 HTML 执行 | +| 文章正文、单页正文、评论 | 使用 Halo 核心 / 评论插件输出的已处理 HTML,主题不二次拼接脚本 | +| 常规模板变量 | 统一用 `th:text` / `th:attr`(转义输出);`th:utext` 仅用于上述受控 HTML 与管理员自定义内容 | +| 链接与图片 | `target="_blank"` 一律带 `rel="noopener noreferrer"`;所有 `` 提供 `alt` / `th:alt` | + +以下三类内容由**管理员在后台显式填写**,属于主题的设计能力,写入权限由 Halo 后台角色控制,不属于访客输入: + +- `style.accent_color` 与 6 个 Hero 配色项:`settings.yaml` 中声明为 `$formkit: color`,由颜色选择器约束为 `#RRGGBB`,仅用于生成内联 CSS 变量; +- `style.custom_css`(自定义 CSS)、`footer.footer_custom_html`(页脚自定义 HTML,如统计代码):按设计意图原样输出,用途已在设置项说明中标注。若站点允许多名作者或开放后台编辑权限,请自行评估这些字段的信任边界。 + +字符串截取前先判空(`#strings.substring('', 0, 1)` 会抛越界异常)已在全部 12 处友链 / 留言 / 订阅 / 插件页面兜底,避免空值导致页面 500;控制台无报错与键盘可达性、深浅色对比度一起列入下方人工回归清单。 + ## 已适配插件 | 插件名称 | 插件标识 | 适配页面路由 | 说明 | @@ -99,7 +133,7 @@ ### 首页 -- Hero 首屏:明暗双背景图、大标题/副标题独立配色、一言(默认使用内置本地句子库,可选自定义 API 与自动刷新)、下滑箭头平滑滚动 +- Hero 首屏:明暗双背景图、大标题/副标题独立配色、描述文案(默认「无」;选「一言」自动填入 `https://v1.hitokoto.cn/`,清空则使用内置本地句子库;可选自定义文案与自动刷新)、下滑箭头平滑滚动 - 欢迎弹窗:默认显示纯本地静态欢迎语,支持 5 种位置、会话去重开关;仅在显式配置自定义 IP 定位 API 时才显示访客所在地(见[外部资源与隐私](#外部资源与隐私)) - 滚动吸附:首屏与内容区之间滚轮/触摸自动吸附,进入内容区后恢复原生自由滚动 - 文章卡片:可配置显示字数(依赖 API 扩展包)、阅读量、分类、标签、摘要行数 @@ -113,7 +147,7 @@ ### 全局 -- 点击特效:核心价值观 / 爱心 / 烟花 / 粒子等 9 种可选 +- 点击特效:核心价值观 / 爱心 / 烟花 / 粒子等 8 种效果可选,也可关闭 - 页脚全站统计:文章、分类、评论、访问、点赞、总字数,支持开关与统计项选择(默认开启) - 动效:呼吸动画、滚动渐入,可独立关闭 - 备案信息:ICP 与公网安备号,公网安备自动带徽标 @@ -187,7 +221,7 @@ pnpm audit:external # 打包 dist/warm-island-.zip pnpm package -# 点击特效脚本冒烟测试 +# 点击特效脚本 + 内容渲染安全(JSON-LD)冒烟测试 pnpm smoke ``` @@ -206,11 +240,11 @@ pnpm smoke │ └── styles/ # SCSS 样式(正文样式在 _content.scss,全局生效) ├── public/ │ ├── assets/data/ # 内置本地一言句子库(静态 JSON) -│ ├── assets/fonts/ # LXGW WenKai 本地字体分片 +│ ├── assets/fonts/ # LXGW WenKai 本地字体分片 + OFL.txt 许可证全文 │ ├── assets/vendor/ # 自托管的代码高亮配色 CSS │ └── assets/logo.png # 主题图标(唯一一份,256×256 压缩版) ├── compat/assets/ # 历史版本哈希静态资源,构建时合并进 templates/assets -├── scripts/ # 构建后置、资源快照、打包、外部资源审计、冒烟测试 +├── scripts/ # 构建后置、资源快照、打包、外部资源与合规审计、冒烟测试 ├── templates/ # 构建产物,Halo 实际加载的模板 ├── settings.yaml # 后台设置项定义 └── theme.yaml # 主题清单与自定义页面模板注册 @@ -221,6 +255,8 @@ pnpm smoke - **静态资源文件名带内容哈希**:升级后旧文件被删除,被缓存的 HTML 仍会请求旧文件名并触发 500, 所以每个版本的哈希文件都要留一份(见 `compat/assets` 与 `pnpm snapshot`) - **不得引入远程脚本 / 样式 / 字体 / 预连接**:所有第三方资源必须自托管并标注许可,`pnpm package` 会拦截回归 +- **不得把站点内容用 `th:utext` 拼进 ` + + +
diff --git a/theme.yaml b/theme.yaml index 292b33f..e56b9f4 100644 --- a/theme.yaml +++ b/theme.yaml @@ -28,7 +28,7 @@ spec: description: 微信读书书架(依赖微信读书插件) screenshot: file: weread.html - version: "1.2.5" + version: "1.2.6" requires: ">=2.27.0" license: - name: "GPL-3.0"