Compare commits
10
Commits
v1.1.1
..
a127d5a314
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a127d5a314 | ||
|
|
a08782a959 | ||
|
|
28b1f90b26 | ||
|
|
d3ba189a7d | ||
|
|
9f677a9f0d | ||
|
|
c1950e142a | ||
|
|
032b5601ac | ||
|
|
d01664197f | ||
|
|
bcc221c3bd | ||
|
|
430d107d44 |
+11
-2
@@ -1,18 +1,24 @@
|
|||||||
# build output
|
# build output
|
||||||
dist/
|
dist/
|
||||||
|
templates/
|
||||||
|
|
||||||
# generated types
|
# generated types
|
||||||
.astro/
|
.astro/
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
# docs site
|
||||||
|
docs/.vitepress/cache/
|
||||||
|
docs/.vitepress/dist/
|
||||||
|
docs/node_modules/
|
||||||
|
|
||||||
# logs
|
# logs
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
# environment variables
|
# environment variables
|
||||||
.env
|
.env
|
||||||
.env.production
|
.env.production
|
||||||
@@ -23,4 +29,7 @@ pnpm-debug.log*
|
|||||||
# jetbrains setting folder
|
# jetbrains setting folder
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
templates/
|
# AI working artifacts
|
||||||
|
llms.txt
|
||||||
|
|
||||||
|
.workbuddy/
|
||||||
|
|||||||
@@ -4,13 +4,19 @@
|
|||||||
|
|
||||||
> 本项目由 AI 开发完成,人工仅进行部分功能测试及问题找寻
|
> 本项目由 AI 开发完成,人工仅进行部分功能测试及问题找寻
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
- Halo **2.26.0 及以上**(页面布局契约、微信读书页面均依赖 2.26 的新能力)
|
||||||
|
- v1.2.0 起内置主题配色系统(纯 CSS 变量实现);更低版本的 Halo 请使用主题 v1.1.x
|
||||||
|
|
||||||
## 特点
|
## 特点
|
||||||
|
|
||||||
1. **响应式设计** — 适配桌面端与移动端,支持暗色模式
|
1. **响应式设计** — 适配桌面端与移动端,支持暗色模式,移动端使用动态视口单位适配地址栏
|
||||||
2. **插件兼容性** — 适配 8 款常用 Halo 插件,安装即用
|
2. **插件兼容性** — 适配 11 款常用 Halo 插件,安装即用;提供页面布局契约,插件前台页面自动复用主题外观
|
||||||
3. **内置灯箱** — 主题内置 LightGallery 灯箱,支持 EXIF 信息展示;安装 lightgallery.js 插件后自动禁用内置灯箱
|
3. **内置灯箱** — 主题内置 LightGallery 灯箱,支持 EXIF 信息展示;安装 lightgallery.js 插件后自动禁用
|
||||||
4. **可配置性** — 丰富的后台设置项,支持自定义首页模块、文章卡片、导航栏样式等
|
4. **可配置性** — 17 个设置分组、122 个配置项,几乎所有模块可独立开关
|
||||||
5. **性能优化** — 图片懒加载、CSS/HTML 压缩、requestAnimationFrame 节流、Vue 组件按需加载
|
5. **性能优化** — 服务端字数统计(API 扩展包)、图片懒加载与响应式缩略图、滚动吸附、CSS/HTML 压缩
|
||||||
|
6. **页面布局契约** — 提供 `templates/layout.html`,插件页面不再回退到 Halo 内置布局
|
||||||
|
|
||||||
## 预览
|
## 预览
|
||||||
|
|
||||||
@@ -20,15 +26,53 @@
|
|||||||
|
|
||||||
| 插件名称 | 插件标识 | 适配页面路由 | 说明 |
|
| 插件名称 | 插件标识 | 适配页面路由 | 说明 |
|
||||||
|----------|---------|-------------|------|
|
|----------|---------|-------------|------|
|
||||||
| [瞬间](https://halo.run/store/apps/app-SnwWD) | `PluginMoments` | `/moments` | 瞬间动态页面,支持点赞、评论、标签筛选 |
|
| [瞬间](https://halo.run/store/apps/app-SnwWD) | `PluginMoments` | `/moments` | 瞬间动态页面,支持点赞、评论、标签筛选、顶部封面 |
|
||||||
| [图库](https://halo.run/store/apps/app-BmQJW) | `PluginPhotos` | `/photos` | 图片展示页面,支持分组、灯箱预览 |
|
| [图库](https://halo.run/store/apps/app-BmQJW) | `PluginPhotos` | `/photos` | 图片展示页面,支持分组、灯箱预览 |
|
||||||
| [友情链接](https://halo.run/store/apps/app-hqbe) | `PluginLinks` | `/links` | 友链展示页面,支持分组展示 |
|
| [友情链接](https://halo.run/store/apps/app-hfbQg) | `PluginLinks` | `/links` | 友链展示页面,支持随机排列、随机传送门、评论区开关 |
|
||||||
| [朋友圈](https://github.com/halo-sigs/plugin-friends) | `plugin-friends` | `/friends` | RSS 订阅朋友圈页面 |
|
| [朋友圈](https://github.com/halo-sigs/plugin-friends) | `plugin-friends` | `/friends` | RSS 订阅朋友圈页面 |
|
||||||
| [装备管理](https://github.com/chengzhongxue/plugin-equipment) | `equipment` | `/equipments` | 装备展示页面,按分组展示,支持封面图 |
|
| [装备管理](https://github.com/chengzhongxue/plugin-equipment) | `equipment` | `/equipments` | 装备展示页面,按分组展示,支持封面图 |
|
||||||
| [豆瓣](https://github.com/chengzhongxue/plugin-douban) | `plugin-douban` | `/douban` | 豆瓣记录页面,支持类型/状态/标签筛选,默认电影 |
|
| [豆瓣](https://github.com/chengzhongxue/plugin-douban) | `plugin-douban` | `/douban` | 豆瓣记录页面,支持类型/状态/标签筛选,默认音乐;需插件 **v1.2.6+**(SQLite 数据结构) |
|
||||||
| [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/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` | 全站 | 服务端字数统计、中英文混排排版、可选服务端代码高亮 |
|
||||||
|
|
||||||
> **注意**:插件需要单独安装,安装后主题会自动适配对应页面。若未安装某插件,对应页面不会出现。
|
> **注意**:插件需要单独安装,安装后主题会自动适配对应页面。若未安装某插件,对应页面或功能自动隐藏/降级,不会报错。
|
||||||
|
|
||||||
|
## 自定义页面模板
|
||||||
|
|
||||||
|
在 Halo 后台「页面 → 新建 → 自定义模板」中选择使用:
|
||||||
|
|
||||||
|
| 模板名 | 模板文件 | 说明 |
|
||||||
|
|--------|---------|------|
|
||||||
|
| 留言板 | `page_messageboard.html` | 留言墙页面,标题与副标题可配置 |
|
||||||
|
| 订阅 | `page_subscriptions.html` | 博友文章订阅动态,依赖链接管理插件的 RSS 订阅,支持分页/下滑加载与列表/网格布局 |
|
||||||
|
| 微信读书 | `weread.html` | 微信读书书架展示,依赖微信读书插件,访客免登录查看 |
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
### 首页
|
||||||
|
|
||||||
|
- Hero 首屏:明暗双背景图、大标题/副标题独立配色、一言(支持自动刷新与淡入淡出过渡)、下滑箭头平滑滚动
|
||||||
|
- 欢迎弹窗:基于访客 IP 地理定位的欢迎提示,支持 5 种位置、自定义 API 与会话去重开关
|
||||||
|
- 滚动吸附:首屏与内容区之间滚轮/触摸自动吸附
|
||||||
|
- 文章卡片:可配置显示字数(依赖 API 扩展包)、阅读量、分类、标签、摘要行数
|
||||||
|
|
||||||
|
### 文章
|
||||||
|
|
||||||
|
- 目录(TOC):桌面端侧栏 + 移动端抽屉,滚动高亮、展开模式可选
|
||||||
|
- 上下篇导航:悬停反馈与方向箭头动画
|
||||||
|
- 相关推荐、分享按钮、赞赏、版权声明可独立开关
|
||||||
|
- 字数与阅读时间:优先使用 API 扩展包的服务端统计,未安装时前端计算兜底
|
||||||
|
|
||||||
|
### 全局
|
||||||
|
|
||||||
|
- 点击特效:核心价值观 / 爱心 / 烟花 / 粒子等 9 种可选
|
||||||
|
- 页脚全站统计:文章、分类、评论、访问、点赞、总字数,支持开关与统计项选择(默认开启)
|
||||||
|
- 动效:呼吸动画、滚动渐入,可独立关闭
|
||||||
|
- 备案信息:ICP 与公网安备号,公网安备自动带徽标
|
||||||
|
- 鼠标光感跟随已移除(性能考量)
|
||||||
|
|
||||||
## 灯箱集成
|
## 灯箱集成
|
||||||
|
|
||||||
@@ -56,33 +100,53 @@
|
|||||||
或从源码构建:
|
或从源码构建:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/warm-island/theme-warm-island.git
|
git clone https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland.git
|
||||||
cd theme-warm-island
|
cd halo-theme-WarmIsland
|
||||||
npm install
|
pnpm install
|
||||||
npm run build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
构建产物在 `templates/` 目录下,将其复制到 Halo 主题目录即可。
|
构建产物在 `templates/` 目录下,同时会生成 `dist/` 内的安装包。
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
npm install
|
pnpm install
|
||||||
|
|
||||||
# 开发模式(监听文件变化)
|
# 开发模式(监听文件变化,自动构建到 templates/)
|
||||||
npm run dev
|
pnpm dev
|
||||||
|
|
||||||
# 构建
|
# 构建
|
||||||
npm run build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
├── src/
|
||||||
|
│ ├── components/ # 组件(导航、页脚、Hero、弹窗、特效等)
|
||||||
|
│ ├── layouts/ # Layout.astro(含页面布局契约渲染模式)
|
||||||
|
│ ├── pages/ # 页面模板(构建后输出到 templates/)
|
||||||
|
│ └── styles/ # SCSS 样式(正文样式在 _content.scss,全局生效)
|
||||||
|
├── templates/ # 构建产物,Halo 实际加载的模板
|
||||||
|
├── settings.yaml # 后台设置项定义(16 分组 / 117 项)
|
||||||
|
└── theme.yaml # 主题清单与自定义页面模板注册
|
||||||
|
```
|
||||||
|
|
||||||
|
### 主题开发注意事项
|
||||||
|
|
||||||
|
- **Thymeleaf 字符串字面量必须加单引号**:`th:text="'文本'"`,裸中文遇到逗号/空格/emoji 会导致页面 500
|
||||||
|
- **正文样式必须写在全局样式表**(`src/styles/_content.scss`):Astro scoped 样式对 Thymeleaf 运行时注入的内容无效
|
||||||
|
- **内联脚本统一加 `th:inline="none"`**:避免 Thymeleaf 解析脚本中的 `[[` / `[(` / `${}`
|
||||||
|
- 修改主题外观无需单独维护 `layout.astro`:页面布局契约复用 `Layout.astro`,构建时自动生成
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
- [Halo](https://halo.run) 2.24+
|
- [Halo](https://halo.run) 2.26+
|
||||||
- [Astro](https://astro.build) + [vite-plugin-halo-theme](https://github.com/halo-sigs/vite-plugin-halo-theme)
|
- [Astro](https://astro.build) + [vite-plugin-halo-theme](https://github.com/halo-sigs/vite-plugin-halo-theme)
|
||||||
- [Thymeleaf](https://www.thymeleaf.org) 模板引擎
|
- [Thymeleaf](https://www.thymeleaf.org) 模板引擎
|
||||||
- [Vue 3](https://vuejs.org) Islands 交互组件
|
- [Sass](https://sass-lang.com/) 样式
|
||||||
|
|
||||||
## 许可
|
## 许可
|
||||||
|
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
const http = require('http');
|
|
||||||
|
|
||||||
function apiCall(method, path, data) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const options = {
|
|
||||||
hostname: 'localhost',
|
|
||||||
port: 8090,
|
|
||||||
path: path,
|
|
||||||
method: method,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const req = http.request(options, (res) => {
|
|
||||||
let body = '';
|
|
||||||
res.on('data', chunk => body += chunk);
|
|
||||||
res.on('end', () => {
|
|
||||||
try {
|
|
||||||
const cookies = res.headers['set-cookie'];
|
|
||||||
resolve({ status: res.statusCode, data: JSON.parse(body), cookies });
|
|
||||||
} catch(e) {
|
|
||||||
resolve({ status: res.statusCode, data: body, cookies: res.headers['set-cookie'] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', reject);
|
|
||||||
if (data) req.write(JSON.stringify(data));
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
// Login first
|
|
||||||
const loginRes = await apiCall('POST', '/api/auth/signin', { username: 'admin', password: 'admin' });
|
|
||||||
console.log('Login status:', loginRes.status);
|
|
||||||
|
|
||||||
// Get the session cookie
|
|
||||||
const cookies = loginRes.cookies || [];
|
|
||||||
const cookieStr = cookies.map(c => c.split(';')[0]).join('; ');
|
|
||||||
console.log('Cookies:', cookieStr);
|
|
||||||
|
|
||||||
// Now get theme settings using the cookie
|
|
||||||
const http2 = require('http');
|
|
||||||
function apiWithCookie(method, path, data) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const options = {
|
|
||||||
hostname: 'localhost',
|
|
||||||
port: 8090,
|
|
||||||
path: path,
|
|
||||||
method: method,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Cookie': cookieStr
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const req = http2.request(options, (res) => {
|
|
||||||
let body = '';
|
|
||||||
res.on('data', chunk => body += chunk);
|
|
||||||
res.on('end', () => {
|
|
||||||
try {
|
|
||||||
resolve({ status: res.statusCode, data: JSON.parse(body) });
|
|
||||||
} catch(e) {
|
|
||||||
resolve({ status: res.statusCode, data: body });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', reject);
|
|
||||||
if (data) req.write(JSON.stringify(data));
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get activated theme
|
|
||||||
const themeRes = await apiWithCookie('GET', '/apis/api.console.halo.run/v1alpha1/themes?sort=creationTimestamp%2Cdesc');
|
|
||||||
console.log('Themes status:', themeRes.status);
|
|
||||||
|
|
||||||
if (themeRes.data && themeRes.data.items) {
|
|
||||||
const activeTheme = themeRes.data.items.find(t => t.spec && t.spec.activationTime);
|
|
||||||
if (activeTheme) {
|
|
||||||
console.log('Active theme:', activeTheme.metadata.name);
|
|
||||||
|
|
||||||
// Get theme settings
|
|
||||||
const settingsRes = await apiWithCookie('GET', `/apis/api.console.halo.run/v1alpha1/themes/${activeTheme.metadata.name}/setting`);
|
|
||||||
console.log('Settings status:', settingsRes.status);
|
|
||||||
console.log('Settings:', JSON.stringify(settingsRes.data).substring(0, 500));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(console.error);
|
|
||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"hash": "0ed7ed0d",
|
|
||||||
"configHash": "c707d7b6",
|
|
||||||
"lockfileHash": "4f412479",
|
|
||||||
"browserHash": "3aefc297",
|
|
||||||
"optimized": {
|
|
||||||
"vue": {
|
|
||||||
"src": "../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
|
||||||
"file": "vue.js",
|
|
||||||
"fileHash": "9027419d",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vitepress > @vue/devtools-api": {
|
|
||||||
"src": "../../../node_modules/@vue/devtools-api/dist/index.js",
|
|
||||||
"file": "vitepress___@vue_devtools-api.js",
|
|
||||||
"fileHash": "3cd67bf3",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vitepress > @vueuse/core": {
|
|
||||||
"src": "../../../node_modules/@vueuse/core/index.mjs",
|
|
||||||
"file": "vitepress___@vueuse_core.js",
|
|
||||||
"fileHash": "cda81787",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vitepress > @vueuse/integrations/useFocusTrap": {
|
|
||||||
"src": "../../../node_modules/@vueuse/integrations/useFocusTrap.mjs",
|
|
||||||
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
|
|
||||||
"fileHash": "7b4ec920",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vitepress > mark.js/src/vanilla.js": {
|
|
||||||
"src": "../../../node_modules/mark.js/src/vanilla.js",
|
|
||||||
"file": "vitepress___mark__js_src_vanilla__js.js",
|
|
||||||
"fileHash": "3efbcd2b",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"vitepress > minisearch": {
|
|
||||||
"src": "../../../node_modules/minisearch/dist/es/index.js",
|
|
||||||
"file": "vitepress___minisearch.js",
|
|
||||||
"fileHash": "8ca06377",
|
|
||||||
"needsInterop": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"chunks": {
|
|
||||||
"chunk-4USRTV5H": {
|
|
||||||
"file": "chunk-4USRTV5H.js"
|
|
||||||
},
|
|
||||||
"chunk-XY26LBYQ": {
|
|
||||||
"file": "chunk-XY26LBYQ.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-9719
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-13035
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-3
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,583 +0,0 @@
|
|||||||
import {
|
|
||||||
DefaultMagicKeysAliasMap,
|
|
||||||
StorageSerializers,
|
|
||||||
TransitionPresets,
|
|
||||||
assert,
|
|
||||||
breakpointsAntDesign,
|
|
||||||
breakpointsBootstrapV5,
|
|
||||||
breakpointsElement,
|
|
||||||
breakpointsMasterCss,
|
|
||||||
breakpointsPrimeFlex,
|
|
||||||
breakpointsQuasar,
|
|
||||||
breakpointsSematic,
|
|
||||||
breakpointsTailwind,
|
|
||||||
breakpointsVuetify,
|
|
||||||
breakpointsVuetifyV2,
|
|
||||||
breakpointsVuetifyV3,
|
|
||||||
bypassFilter,
|
|
||||||
camelize,
|
|
||||||
clamp,
|
|
||||||
cloneFnJSON,
|
|
||||||
computedAsync,
|
|
||||||
computedEager,
|
|
||||||
computedInject,
|
|
||||||
computedWithControl,
|
|
||||||
containsProp,
|
|
||||||
controlledRef,
|
|
||||||
createEventHook,
|
|
||||||
createFetch,
|
|
||||||
createFilterWrapper,
|
|
||||||
createGlobalState,
|
|
||||||
createInjectionState,
|
|
||||||
createRef,
|
|
||||||
createReusableTemplate,
|
|
||||||
createSharedComposable,
|
|
||||||
createSingletonPromise,
|
|
||||||
createTemplatePromise,
|
|
||||||
createUnrefFn,
|
|
||||||
customStorageEventName,
|
|
||||||
debounceFilter,
|
|
||||||
defaultDocument,
|
|
||||||
defaultLocation,
|
|
||||||
defaultNavigator,
|
|
||||||
defaultWindow,
|
|
||||||
executeTransition,
|
|
||||||
extendRef,
|
|
||||||
formatDate,
|
|
||||||
formatTimeAgo,
|
|
||||||
get,
|
|
||||||
getLifeCycleTarget,
|
|
||||||
getSSRHandler,
|
|
||||||
hasOwn,
|
|
||||||
hyphenate,
|
|
||||||
identity,
|
|
||||||
increaseWithUnit,
|
|
||||||
injectLocal,
|
|
||||||
invoke,
|
|
||||||
isClient,
|
|
||||||
isDef,
|
|
||||||
isDefined,
|
|
||||||
isIOS,
|
|
||||||
isObject,
|
|
||||||
isWorker,
|
|
||||||
makeDestructurable,
|
|
||||||
mapGamepadToXbox360Controller,
|
|
||||||
noop,
|
|
||||||
normalizeDate,
|
|
||||||
notNullish,
|
|
||||||
now,
|
|
||||||
objectEntries,
|
|
||||||
objectOmit,
|
|
||||||
objectPick,
|
|
||||||
onClickOutside,
|
|
||||||
onElementRemoval,
|
|
||||||
onKeyDown,
|
|
||||||
onKeyPressed,
|
|
||||||
onKeyStroke,
|
|
||||||
onKeyUp,
|
|
||||||
onLongPress,
|
|
||||||
onStartTyping,
|
|
||||||
pausableFilter,
|
|
||||||
promiseTimeout,
|
|
||||||
provideLocal,
|
|
||||||
provideSSRWidth,
|
|
||||||
pxValue,
|
|
||||||
rand,
|
|
||||||
reactify,
|
|
||||||
reactifyObject,
|
|
||||||
reactiveComputed,
|
|
||||||
reactiveOmit,
|
|
||||||
reactivePick,
|
|
||||||
refAutoReset,
|
|
||||||
refDebounced,
|
|
||||||
refDefault,
|
|
||||||
refThrottled,
|
|
||||||
refWithControl,
|
|
||||||
resolveRef,
|
|
||||||
resolveUnref,
|
|
||||||
set,
|
|
||||||
setSSRHandler,
|
|
||||||
syncRef,
|
|
||||||
syncRefs,
|
|
||||||
templateRef,
|
|
||||||
throttleFilter,
|
|
||||||
timestamp,
|
|
||||||
toArray,
|
|
||||||
toReactive,
|
|
||||||
toRef,
|
|
||||||
toRefs,
|
|
||||||
toValue,
|
|
||||||
tryOnBeforeMount,
|
|
||||||
tryOnBeforeUnmount,
|
|
||||||
tryOnMounted,
|
|
||||||
tryOnScopeDispose,
|
|
||||||
tryOnUnmounted,
|
|
||||||
unrefElement,
|
|
||||||
until,
|
|
||||||
useActiveElement,
|
|
||||||
useAnimate,
|
|
||||||
useArrayDifference,
|
|
||||||
useArrayEvery,
|
|
||||||
useArrayFilter,
|
|
||||||
useArrayFind,
|
|
||||||
useArrayFindIndex,
|
|
||||||
useArrayFindLast,
|
|
||||||
useArrayIncludes,
|
|
||||||
useArrayJoin,
|
|
||||||
useArrayMap,
|
|
||||||
useArrayReduce,
|
|
||||||
useArraySome,
|
|
||||||
useArrayUnique,
|
|
||||||
useAsyncQueue,
|
|
||||||
useAsyncState,
|
|
||||||
useBase64,
|
|
||||||
useBattery,
|
|
||||||
useBluetooth,
|
|
||||||
useBreakpoints,
|
|
||||||
useBroadcastChannel,
|
|
||||||
useBrowserLocation,
|
|
||||||
useCached,
|
|
||||||
useClipboard,
|
|
||||||
useClipboardItems,
|
|
||||||
useCloned,
|
|
||||||
useColorMode,
|
|
||||||
useConfirmDialog,
|
|
||||||
useCountdown,
|
|
||||||
useCounter,
|
|
||||||
useCssVar,
|
|
||||||
useCurrentElement,
|
|
||||||
useCycleList,
|
|
||||||
useDark,
|
|
||||||
useDateFormat,
|
|
||||||
useDebounceFn,
|
|
||||||
useDebouncedRefHistory,
|
|
||||||
useDeviceMotion,
|
|
||||||
useDeviceOrientation,
|
|
||||||
useDevicePixelRatio,
|
|
||||||
useDevicesList,
|
|
||||||
useDisplayMedia,
|
|
||||||
useDocumentVisibility,
|
|
||||||
useDraggable,
|
|
||||||
useDropZone,
|
|
||||||
useElementBounding,
|
|
||||||
useElementByPoint,
|
|
||||||
useElementHover,
|
|
||||||
useElementSize,
|
|
||||||
useElementVisibility,
|
|
||||||
useEventBus,
|
|
||||||
useEventListener,
|
|
||||||
useEventSource,
|
|
||||||
useEyeDropper,
|
|
||||||
useFavicon,
|
|
||||||
useFetch,
|
|
||||||
useFileDialog,
|
|
||||||
useFileSystemAccess,
|
|
||||||
useFocus,
|
|
||||||
useFocusWithin,
|
|
||||||
useFps,
|
|
||||||
useFullscreen,
|
|
||||||
useGamepad,
|
|
||||||
useGeolocation,
|
|
||||||
useIdle,
|
|
||||||
useImage,
|
|
||||||
useInfiniteScroll,
|
|
||||||
useIntersectionObserver,
|
|
||||||
useInterval,
|
|
||||||
useIntervalFn,
|
|
||||||
useKeyModifier,
|
|
||||||
useLastChanged,
|
|
||||||
useLocalStorage,
|
|
||||||
useMagicKeys,
|
|
||||||
useManualRefHistory,
|
|
||||||
useMediaControls,
|
|
||||||
useMediaQuery,
|
|
||||||
useMemoize,
|
|
||||||
useMemory,
|
|
||||||
useMounted,
|
|
||||||
useMouse,
|
|
||||||
useMouseInElement,
|
|
||||||
useMousePressed,
|
|
||||||
useMutationObserver,
|
|
||||||
useNavigatorLanguage,
|
|
||||||
useNetwork,
|
|
||||||
useNow,
|
|
||||||
useObjectUrl,
|
|
||||||
useOffsetPagination,
|
|
||||||
useOnline,
|
|
||||||
usePageLeave,
|
|
||||||
useParallax,
|
|
||||||
useParentElement,
|
|
||||||
usePerformanceObserver,
|
|
||||||
usePermission,
|
|
||||||
usePointer,
|
|
||||||
usePointerLock,
|
|
||||||
usePointerSwipe,
|
|
||||||
usePreferredColorScheme,
|
|
||||||
usePreferredContrast,
|
|
||||||
usePreferredDark,
|
|
||||||
usePreferredLanguages,
|
|
||||||
usePreferredReducedMotion,
|
|
||||||
usePreferredReducedTransparency,
|
|
||||||
usePrevious,
|
|
||||||
useRafFn,
|
|
||||||
useRefHistory,
|
|
||||||
useResizeObserver,
|
|
||||||
useSSRWidth,
|
|
||||||
useScreenOrientation,
|
|
||||||
useScreenSafeArea,
|
|
||||||
useScriptTag,
|
|
||||||
useScroll,
|
|
||||||
useScrollLock,
|
|
||||||
useSessionStorage,
|
|
||||||
useShare,
|
|
||||||
useSorted,
|
|
||||||
useSpeechRecognition,
|
|
||||||
useSpeechSynthesis,
|
|
||||||
useStepper,
|
|
||||||
useStorage,
|
|
||||||
useStorageAsync,
|
|
||||||
useStyleTag,
|
|
||||||
useSupported,
|
|
||||||
useSwipe,
|
|
||||||
useTemplateRefsList,
|
|
||||||
useTextDirection,
|
|
||||||
useTextSelection,
|
|
||||||
useTextareaAutosize,
|
|
||||||
useThrottleFn,
|
|
||||||
useThrottledRefHistory,
|
|
||||||
useTimeAgo,
|
|
||||||
useTimeout,
|
|
||||||
useTimeoutFn,
|
|
||||||
useTimeoutPoll,
|
|
||||||
useTimestamp,
|
|
||||||
useTitle,
|
|
||||||
useToNumber,
|
|
||||||
useToString,
|
|
||||||
useToggle,
|
|
||||||
useTransition,
|
|
||||||
useUrlSearchParams,
|
|
||||||
useUserMedia,
|
|
||||||
useVModel,
|
|
||||||
useVModels,
|
|
||||||
useVibrate,
|
|
||||||
useVirtualList,
|
|
||||||
useWakeLock,
|
|
||||||
useWebNotification,
|
|
||||||
useWebSocket,
|
|
||||||
useWebWorker,
|
|
||||||
useWebWorkerFn,
|
|
||||||
useWindowFocus,
|
|
||||||
useWindowScroll,
|
|
||||||
useWindowSize,
|
|
||||||
watchArray,
|
|
||||||
watchAtMost,
|
|
||||||
watchDebounced,
|
|
||||||
watchDeep,
|
|
||||||
watchIgnorable,
|
|
||||||
watchImmediate,
|
|
||||||
watchOnce,
|
|
||||||
watchPausable,
|
|
||||||
watchThrottled,
|
|
||||||
watchTriggerable,
|
|
||||||
watchWithFilter,
|
|
||||||
whenever
|
|
||||||
} from "./chunk-4USRTV5H.js";
|
|
||||||
import "./chunk-XY26LBYQ.js";
|
|
||||||
export {
|
|
||||||
DefaultMagicKeysAliasMap,
|
|
||||||
StorageSerializers,
|
|
||||||
TransitionPresets,
|
|
||||||
assert,
|
|
||||||
computedAsync as asyncComputed,
|
|
||||||
refAutoReset as autoResetRef,
|
|
||||||
breakpointsAntDesign,
|
|
||||||
breakpointsBootstrapV5,
|
|
||||||
breakpointsElement,
|
|
||||||
breakpointsMasterCss,
|
|
||||||
breakpointsPrimeFlex,
|
|
||||||
breakpointsQuasar,
|
|
||||||
breakpointsSematic,
|
|
||||||
breakpointsTailwind,
|
|
||||||
breakpointsVuetify,
|
|
||||||
breakpointsVuetifyV2,
|
|
||||||
breakpointsVuetifyV3,
|
|
||||||
bypassFilter,
|
|
||||||
camelize,
|
|
||||||
clamp,
|
|
||||||
cloneFnJSON,
|
|
||||||
computedAsync,
|
|
||||||
computedEager,
|
|
||||||
computedInject,
|
|
||||||
computedWithControl,
|
|
||||||
containsProp,
|
|
||||||
computedWithControl as controlledComputed,
|
|
||||||
controlledRef,
|
|
||||||
createEventHook,
|
|
||||||
createFetch,
|
|
||||||
createFilterWrapper,
|
|
||||||
createGlobalState,
|
|
||||||
createInjectionState,
|
|
||||||
reactify as createReactiveFn,
|
|
||||||
createRef,
|
|
||||||
createReusableTemplate,
|
|
||||||
createSharedComposable,
|
|
||||||
createSingletonPromise,
|
|
||||||
createTemplatePromise,
|
|
||||||
createUnrefFn,
|
|
||||||
customStorageEventName,
|
|
||||||
debounceFilter,
|
|
||||||
refDebounced as debouncedRef,
|
|
||||||
watchDebounced as debouncedWatch,
|
|
||||||
defaultDocument,
|
|
||||||
defaultLocation,
|
|
||||||
defaultNavigator,
|
|
||||||
defaultWindow,
|
|
||||||
computedEager as eagerComputed,
|
|
||||||
executeTransition,
|
|
||||||
extendRef,
|
|
||||||
formatDate,
|
|
||||||
formatTimeAgo,
|
|
||||||
get,
|
|
||||||
getLifeCycleTarget,
|
|
||||||
getSSRHandler,
|
|
||||||
hasOwn,
|
|
||||||
hyphenate,
|
|
||||||
identity,
|
|
||||||
watchIgnorable as ignorableWatch,
|
|
||||||
increaseWithUnit,
|
|
||||||
injectLocal,
|
|
||||||
invoke,
|
|
||||||
isClient,
|
|
||||||
isDef,
|
|
||||||
isDefined,
|
|
||||||
isIOS,
|
|
||||||
isObject,
|
|
||||||
isWorker,
|
|
||||||
makeDestructurable,
|
|
||||||
mapGamepadToXbox360Controller,
|
|
||||||
noop,
|
|
||||||
normalizeDate,
|
|
||||||
notNullish,
|
|
||||||
now,
|
|
||||||
objectEntries,
|
|
||||||
objectOmit,
|
|
||||||
objectPick,
|
|
||||||
onClickOutside,
|
|
||||||
onElementRemoval,
|
|
||||||
onKeyDown,
|
|
||||||
onKeyPressed,
|
|
||||||
onKeyStroke,
|
|
||||||
onKeyUp,
|
|
||||||
onLongPress,
|
|
||||||
onStartTyping,
|
|
||||||
pausableFilter,
|
|
||||||
watchPausable as pausableWatch,
|
|
||||||
promiseTimeout,
|
|
||||||
provideLocal,
|
|
||||||
provideSSRWidth,
|
|
||||||
pxValue,
|
|
||||||
rand,
|
|
||||||
reactify,
|
|
||||||
reactifyObject,
|
|
||||||
reactiveComputed,
|
|
||||||
reactiveOmit,
|
|
||||||
reactivePick,
|
|
||||||
refAutoReset,
|
|
||||||
refDebounced,
|
|
||||||
refDefault,
|
|
||||||
refThrottled,
|
|
||||||
refWithControl,
|
|
||||||
resolveRef,
|
|
||||||
resolveUnref,
|
|
||||||
set,
|
|
||||||
setSSRHandler,
|
|
||||||
syncRef,
|
|
||||||
syncRefs,
|
|
||||||
templateRef,
|
|
||||||
throttleFilter,
|
|
||||||
refThrottled as throttledRef,
|
|
||||||
watchThrottled as throttledWatch,
|
|
||||||
timestamp,
|
|
||||||
toArray,
|
|
||||||
toReactive,
|
|
||||||
toRef,
|
|
||||||
toRefs,
|
|
||||||
toValue,
|
|
||||||
tryOnBeforeMount,
|
|
||||||
tryOnBeforeUnmount,
|
|
||||||
tryOnMounted,
|
|
||||||
tryOnScopeDispose,
|
|
||||||
tryOnUnmounted,
|
|
||||||
unrefElement,
|
|
||||||
until,
|
|
||||||
useActiveElement,
|
|
||||||
useAnimate,
|
|
||||||
useArrayDifference,
|
|
||||||
useArrayEvery,
|
|
||||||
useArrayFilter,
|
|
||||||
useArrayFind,
|
|
||||||
useArrayFindIndex,
|
|
||||||
useArrayFindLast,
|
|
||||||
useArrayIncludes,
|
|
||||||
useArrayJoin,
|
|
||||||
useArrayMap,
|
|
||||||
useArrayReduce,
|
|
||||||
useArraySome,
|
|
||||||
useArrayUnique,
|
|
||||||
useAsyncQueue,
|
|
||||||
useAsyncState,
|
|
||||||
useBase64,
|
|
||||||
useBattery,
|
|
||||||
useBluetooth,
|
|
||||||
useBreakpoints,
|
|
||||||
useBroadcastChannel,
|
|
||||||
useBrowserLocation,
|
|
||||||
useCached,
|
|
||||||
useClipboard,
|
|
||||||
useClipboardItems,
|
|
||||||
useCloned,
|
|
||||||
useColorMode,
|
|
||||||
useConfirmDialog,
|
|
||||||
useCountdown,
|
|
||||||
useCounter,
|
|
||||||
useCssVar,
|
|
||||||
useCurrentElement,
|
|
||||||
useCycleList,
|
|
||||||
useDark,
|
|
||||||
useDateFormat,
|
|
||||||
refDebounced as useDebounce,
|
|
||||||
useDebounceFn,
|
|
||||||
useDebouncedRefHistory,
|
|
||||||
useDeviceMotion,
|
|
||||||
useDeviceOrientation,
|
|
||||||
useDevicePixelRatio,
|
|
||||||
useDevicesList,
|
|
||||||
useDisplayMedia,
|
|
||||||
useDocumentVisibility,
|
|
||||||
useDraggable,
|
|
||||||
useDropZone,
|
|
||||||
useElementBounding,
|
|
||||||
useElementByPoint,
|
|
||||||
useElementHover,
|
|
||||||
useElementSize,
|
|
||||||
useElementVisibility,
|
|
||||||
useEventBus,
|
|
||||||
useEventListener,
|
|
||||||
useEventSource,
|
|
||||||
useEyeDropper,
|
|
||||||
useFavicon,
|
|
||||||
useFetch,
|
|
||||||
useFileDialog,
|
|
||||||
useFileSystemAccess,
|
|
||||||
useFocus,
|
|
||||||
useFocusWithin,
|
|
||||||
useFps,
|
|
||||||
useFullscreen,
|
|
||||||
useGamepad,
|
|
||||||
useGeolocation,
|
|
||||||
useIdle,
|
|
||||||
useImage,
|
|
||||||
useInfiniteScroll,
|
|
||||||
useIntersectionObserver,
|
|
||||||
useInterval,
|
|
||||||
useIntervalFn,
|
|
||||||
useKeyModifier,
|
|
||||||
useLastChanged,
|
|
||||||
useLocalStorage,
|
|
||||||
useMagicKeys,
|
|
||||||
useManualRefHistory,
|
|
||||||
useMediaControls,
|
|
||||||
useMediaQuery,
|
|
||||||
useMemoize,
|
|
||||||
useMemory,
|
|
||||||
useMounted,
|
|
||||||
useMouse,
|
|
||||||
useMouseInElement,
|
|
||||||
useMousePressed,
|
|
||||||
useMutationObserver,
|
|
||||||
useNavigatorLanguage,
|
|
||||||
useNetwork,
|
|
||||||
useNow,
|
|
||||||
useObjectUrl,
|
|
||||||
useOffsetPagination,
|
|
||||||
useOnline,
|
|
||||||
usePageLeave,
|
|
||||||
useParallax,
|
|
||||||
useParentElement,
|
|
||||||
usePerformanceObserver,
|
|
||||||
usePermission,
|
|
||||||
usePointer,
|
|
||||||
usePointerLock,
|
|
||||||
usePointerSwipe,
|
|
||||||
usePreferredColorScheme,
|
|
||||||
usePreferredContrast,
|
|
||||||
usePreferredDark,
|
|
||||||
usePreferredLanguages,
|
|
||||||
usePreferredReducedMotion,
|
|
||||||
usePreferredReducedTransparency,
|
|
||||||
usePrevious,
|
|
||||||
useRafFn,
|
|
||||||
useRefHistory,
|
|
||||||
useResizeObserver,
|
|
||||||
useSSRWidth,
|
|
||||||
useScreenOrientation,
|
|
||||||
useScreenSafeArea,
|
|
||||||
useScriptTag,
|
|
||||||
useScroll,
|
|
||||||
useScrollLock,
|
|
||||||
useSessionStorage,
|
|
||||||
useShare,
|
|
||||||
useSorted,
|
|
||||||
useSpeechRecognition,
|
|
||||||
useSpeechSynthesis,
|
|
||||||
useStepper,
|
|
||||||
useStorage,
|
|
||||||
useStorageAsync,
|
|
||||||
useStyleTag,
|
|
||||||
useSupported,
|
|
||||||
useSwipe,
|
|
||||||
useTemplateRefsList,
|
|
||||||
useTextDirection,
|
|
||||||
useTextSelection,
|
|
||||||
useTextareaAutosize,
|
|
||||||
refThrottled as useThrottle,
|
|
||||||
useThrottleFn,
|
|
||||||
useThrottledRefHistory,
|
|
||||||
useTimeAgo,
|
|
||||||
useTimeout,
|
|
||||||
useTimeoutFn,
|
|
||||||
useTimeoutPoll,
|
|
||||||
useTimestamp,
|
|
||||||
useTitle,
|
|
||||||
useToNumber,
|
|
||||||
useToString,
|
|
||||||
useToggle,
|
|
||||||
useTransition,
|
|
||||||
useUrlSearchParams,
|
|
||||||
useUserMedia,
|
|
||||||
useVModel,
|
|
||||||
useVModels,
|
|
||||||
useVibrate,
|
|
||||||
useVirtualList,
|
|
||||||
useWakeLock,
|
|
||||||
useWebNotification,
|
|
||||||
useWebSocket,
|
|
||||||
useWebWorker,
|
|
||||||
useWebWorkerFn,
|
|
||||||
useWindowFocus,
|
|
||||||
useWindowScroll,
|
|
||||||
useWindowSize,
|
|
||||||
watchArray,
|
|
||||||
watchAtMost,
|
|
||||||
watchDebounced,
|
|
||||||
watchDeep,
|
|
||||||
watchIgnorable,
|
|
||||||
watchImmediate,
|
|
||||||
watchOnce,
|
|
||||||
watchPausable,
|
|
||||||
watchThrottled,
|
|
||||||
watchTriggerable,
|
|
||||||
watchWithFilter,
|
|
||||||
whenever
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=vitepress___@vueuse_core.js.map
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
-1352
File diff suppressed because it is too large
Load Diff
-7
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-1813
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Vendored
-347
@@ -1,347 +0,0 @@
|
|||||||
import {
|
|
||||||
BaseTransition,
|
|
||||||
BaseTransitionPropsValidators,
|
|
||||||
Comment,
|
|
||||||
DeprecationTypes,
|
|
||||||
EffectScope,
|
|
||||||
ErrorCodes,
|
|
||||||
ErrorTypeStrings,
|
|
||||||
Fragment,
|
|
||||||
KeepAlive,
|
|
||||||
ReactiveEffect,
|
|
||||||
Static,
|
|
||||||
Suspense,
|
|
||||||
Teleport,
|
|
||||||
Text,
|
|
||||||
TrackOpTypes,
|
|
||||||
Transition,
|
|
||||||
TransitionGroup,
|
|
||||||
TriggerOpTypes,
|
|
||||||
VueElement,
|
|
||||||
assertNumber,
|
|
||||||
callWithAsyncErrorHandling,
|
|
||||||
callWithErrorHandling,
|
|
||||||
camelize,
|
|
||||||
capitalize,
|
|
||||||
cloneVNode,
|
|
||||||
compatUtils,
|
|
||||||
compile,
|
|
||||||
computed,
|
|
||||||
createApp,
|
|
||||||
createBaseVNode,
|
|
||||||
createBlock,
|
|
||||||
createCommentVNode,
|
|
||||||
createElementBlock,
|
|
||||||
createHydrationRenderer,
|
|
||||||
createPropsRestProxy,
|
|
||||||
createRenderer,
|
|
||||||
createSSRApp,
|
|
||||||
createSlots,
|
|
||||||
createStaticVNode,
|
|
||||||
createTextVNode,
|
|
||||||
createVNode,
|
|
||||||
customRef,
|
|
||||||
defineAsyncComponent,
|
|
||||||
defineComponent,
|
|
||||||
defineCustomElement,
|
|
||||||
defineEmits,
|
|
||||||
defineExpose,
|
|
||||||
defineModel,
|
|
||||||
defineOptions,
|
|
||||||
defineProps,
|
|
||||||
defineSSRCustomElement,
|
|
||||||
defineSlots,
|
|
||||||
devtools,
|
|
||||||
effect,
|
|
||||||
effectScope,
|
|
||||||
getCurrentInstance,
|
|
||||||
getCurrentScope,
|
|
||||||
getCurrentWatcher,
|
|
||||||
getTransitionRawChildren,
|
|
||||||
guardReactiveProps,
|
|
||||||
h,
|
|
||||||
handleError,
|
|
||||||
hasInjectionContext,
|
|
||||||
hydrate,
|
|
||||||
hydrateOnIdle,
|
|
||||||
hydrateOnInteraction,
|
|
||||||
hydrateOnMediaQuery,
|
|
||||||
hydrateOnVisible,
|
|
||||||
initCustomFormatter,
|
|
||||||
initDirectivesForSSR,
|
|
||||||
inject,
|
|
||||||
isMemoSame,
|
|
||||||
isProxy,
|
|
||||||
isReactive,
|
|
||||||
isReadonly,
|
|
||||||
isRef,
|
|
||||||
isRuntimeOnly,
|
|
||||||
isShallow,
|
|
||||||
isVNode,
|
|
||||||
markRaw,
|
|
||||||
mergeDefaults,
|
|
||||||
mergeModels,
|
|
||||||
mergeProps,
|
|
||||||
nextTick,
|
|
||||||
nodeOps,
|
|
||||||
normalizeClass,
|
|
||||||
normalizeProps,
|
|
||||||
normalizeStyle,
|
|
||||||
onActivated,
|
|
||||||
onBeforeMount,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onBeforeUpdate,
|
|
||||||
onDeactivated,
|
|
||||||
onErrorCaptured,
|
|
||||||
onMounted,
|
|
||||||
onRenderTracked,
|
|
||||||
onRenderTriggered,
|
|
||||||
onScopeDispose,
|
|
||||||
onServerPrefetch,
|
|
||||||
onUnmounted,
|
|
||||||
onUpdated,
|
|
||||||
onWatcherCleanup,
|
|
||||||
openBlock,
|
|
||||||
patchProp,
|
|
||||||
popScopeId,
|
|
||||||
provide,
|
|
||||||
proxyRefs,
|
|
||||||
pushScopeId,
|
|
||||||
queuePostFlushCb,
|
|
||||||
reactive,
|
|
||||||
readonly,
|
|
||||||
ref,
|
|
||||||
registerRuntimeCompiler,
|
|
||||||
render,
|
|
||||||
renderList,
|
|
||||||
renderSlot,
|
|
||||||
resolveComponent,
|
|
||||||
resolveDirective,
|
|
||||||
resolveDynamicComponent,
|
|
||||||
resolveFilter,
|
|
||||||
resolveTransitionHooks,
|
|
||||||
setBlockTracking,
|
|
||||||
setDevtoolsHook,
|
|
||||||
setTransitionHooks,
|
|
||||||
shallowReactive,
|
|
||||||
shallowReadonly,
|
|
||||||
shallowRef,
|
|
||||||
ssrContextKey,
|
|
||||||
ssrUtils,
|
|
||||||
stop,
|
|
||||||
toDisplayString,
|
|
||||||
toHandlerKey,
|
|
||||||
toHandlers,
|
|
||||||
toRaw,
|
|
||||||
toRef,
|
|
||||||
toRefs,
|
|
||||||
toValue,
|
|
||||||
transformVNodeArgs,
|
|
||||||
triggerRef,
|
|
||||||
unref,
|
|
||||||
useAttrs,
|
|
||||||
useCssModule,
|
|
||||||
useCssVars,
|
|
||||||
useHost,
|
|
||||||
useId,
|
|
||||||
useModel,
|
|
||||||
useSSRContext,
|
|
||||||
useShadowRoot,
|
|
||||||
useSlots,
|
|
||||||
useTemplateRef,
|
|
||||||
useTransitionState,
|
|
||||||
vModelCheckbox,
|
|
||||||
vModelDynamic,
|
|
||||||
vModelRadio,
|
|
||||||
vModelSelect,
|
|
||||||
vModelText,
|
|
||||||
vShow,
|
|
||||||
version,
|
|
||||||
warn,
|
|
||||||
watch,
|
|
||||||
watchEffect,
|
|
||||||
watchPostEffect,
|
|
||||||
watchSyncEffect,
|
|
||||||
withAsyncContext,
|
|
||||||
withCtx,
|
|
||||||
withDefaults,
|
|
||||||
withDirectives,
|
|
||||||
withKeys,
|
|
||||||
withMemo,
|
|
||||||
withModifiers,
|
|
||||||
withScopeId
|
|
||||||
} from "./chunk-XY26LBYQ.js";
|
|
||||||
export {
|
|
||||||
BaseTransition,
|
|
||||||
BaseTransitionPropsValidators,
|
|
||||||
Comment,
|
|
||||||
DeprecationTypes,
|
|
||||||
EffectScope,
|
|
||||||
ErrorCodes,
|
|
||||||
ErrorTypeStrings,
|
|
||||||
Fragment,
|
|
||||||
KeepAlive,
|
|
||||||
ReactiveEffect,
|
|
||||||
Static,
|
|
||||||
Suspense,
|
|
||||||
Teleport,
|
|
||||||
Text,
|
|
||||||
TrackOpTypes,
|
|
||||||
Transition,
|
|
||||||
TransitionGroup,
|
|
||||||
TriggerOpTypes,
|
|
||||||
VueElement,
|
|
||||||
assertNumber,
|
|
||||||
callWithAsyncErrorHandling,
|
|
||||||
callWithErrorHandling,
|
|
||||||
camelize,
|
|
||||||
capitalize,
|
|
||||||
cloneVNode,
|
|
||||||
compatUtils,
|
|
||||||
compile,
|
|
||||||
computed,
|
|
||||||
createApp,
|
|
||||||
createBlock,
|
|
||||||
createCommentVNode,
|
|
||||||
createElementBlock,
|
|
||||||
createBaseVNode as createElementVNode,
|
|
||||||
createHydrationRenderer,
|
|
||||||
createPropsRestProxy,
|
|
||||||
createRenderer,
|
|
||||||
createSSRApp,
|
|
||||||
createSlots,
|
|
||||||
createStaticVNode,
|
|
||||||
createTextVNode,
|
|
||||||
createVNode,
|
|
||||||
customRef,
|
|
||||||
defineAsyncComponent,
|
|
||||||
defineComponent,
|
|
||||||
defineCustomElement,
|
|
||||||
defineEmits,
|
|
||||||
defineExpose,
|
|
||||||
defineModel,
|
|
||||||
defineOptions,
|
|
||||||
defineProps,
|
|
||||||
defineSSRCustomElement,
|
|
||||||
defineSlots,
|
|
||||||
devtools,
|
|
||||||
effect,
|
|
||||||
effectScope,
|
|
||||||
getCurrentInstance,
|
|
||||||
getCurrentScope,
|
|
||||||
getCurrentWatcher,
|
|
||||||
getTransitionRawChildren,
|
|
||||||
guardReactiveProps,
|
|
||||||
h,
|
|
||||||
handleError,
|
|
||||||
hasInjectionContext,
|
|
||||||
hydrate,
|
|
||||||
hydrateOnIdle,
|
|
||||||
hydrateOnInteraction,
|
|
||||||
hydrateOnMediaQuery,
|
|
||||||
hydrateOnVisible,
|
|
||||||
initCustomFormatter,
|
|
||||||
initDirectivesForSSR,
|
|
||||||
inject,
|
|
||||||
isMemoSame,
|
|
||||||
isProxy,
|
|
||||||
isReactive,
|
|
||||||
isReadonly,
|
|
||||||
isRef,
|
|
||||||
isRuntimeOnly,
|
|
||||||
isShallow,
|
|
||||||
isVNode,
|
|
||||||
markRaw,
|
|
||||||
mergeDefaults,
|
|
||||||
mergeModels,
|
|
||||||
mergeProps,
|
|
||||||
nextTick,
|
|
||||||
nodeOps,
|
|
||||||
normalizeClass,
|
|
||||||
normalizeProps,
|
|
||||||
normalizeStyle,
|
|
||||||
onActivated,
|
|
||||||
onBeforeMount,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onBeforeUpdate,
|
|
||||||
onDeactivated,
|
|
||||||
onErrorCaptured,
|
|
||||||
onMounted,
|
|
||||||
onRenderTracked,
|
|
||||||
onRenderTriggered,
|
|
||||||
onScopeDispose,
|
|
||||||
onServerPrefetch,
|
|
||||||
onUnmounted,
|
|
||||||
onUpdated,
|
|
||||||
onWatcherCleanup,
|
|
||||||
openBlock,
|
|
||||||
patchProp,
|
|
||||||
popScopeId,
|
|
||||||
provide,
|
|
||||||
proxyRefs,
|
|
||||||
pushScopeId,
|
|
||||||
queuePostFlushCb,
|
|
||||||
reactive,
|
|
||||||
readonly,
|
|
||||||
ref,
|
|
||||||
registerRuntimeCompiler,
|
|
||||||
render,
|
|
||||||
renderList,
|
|
||||||
renderSlot,
|
|
||||||
resolveComponent,
|
|
||||||
resolveDirective,
|
|
||||||
resolveDynamicComponent,
|
|
||||||
resolveFilter,
|
|
||||||
resolveTransitionHooks,
|
|
||||||
setBlockTracking,
|
|
||||||
setDevtoolsHook,
|
|
||||||
setTransitionHooks,
|
|
||||||
shallowReactive,
|
|
||||||
shallowReadonly,
|
|
||||||
shallowRef,
|
|
||||||
ssrContextKey,
|
|
||||||
ssrUtils,
|
|
||||||
stop,
|
|
||||||
toDisplayString,
|
|
||||||
toHandlerKey,
|
|
||||||
toHandlers,
|
|
||||||
toRaw,
|
|
||||||
toRef,
|
|
||||||
toRefs,
|
|
||||||
toValue,
|
|
||||||
transformVNodeArgs,
|
|
||||||
triggerRef,
|
|
||||||
unref,
|
|
||||||
useAttrs,
|
|
||||||
useCssModule,
|
|
||||||
useCssVars,
|
|
||||||
useHost,
|
|
||||||
useId,
|
|
||||||
useModel,
|
|
||||||
useSSRContext,
|
|
||||||
useShadowRoot,
|
|
||||||
useSlots,
|
|
||||||
useTemplateRef,
|
|
||||||
useTransitionState,
|
|
||||||
vModelCheckbox,
|
|
||||||
vModelDynamic,
|
|
||||||
vModelRadio,
|
|
||||||
vModelSelect,
|
|
||||||
vModelText,
|
|
||||||
vShow,
|
|
||||||
version,
|
|
||||||
warn,
|
|
||||||
watch,
|
|
||||||
watchEffect,
|
|
||||||
watchPostEffect,
|
|
||||||
watchSyncEffect,
|
|
||||||
withAsyncContext,
|
|
||||||
withCtx,
|
|
||||||
withDefaults,
|
|
||||||
withDirectives,
|
|
||||||
withKeys,
|
|
||||||
withMemo,
|
|
||||||
withModifiers,
|
|
||||||
withScopeId
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=vue.js.map
|
|
||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
@@ -36,6 +36,7 @@ export default defineConfig({
|
|||||||
{ text: '图库', link: '/config/photos' },
|
{ text: '图库', link: '/config/photos' },
|
||||||
{ text: '友情链接', link: '/config/links' },
|
{ text: '友情链接', link: '/config/links' },
|
||||||
{ text: '朋友圈', link: '/config/friends' },
|
{ text: '朋友圈', link: '/config/friends' },
|
||||||
|
{ text: '记得', link: '/config/important-dates' },
|
||||||
{ text: '留言板', link: '/config/messageboard' },
|
{ text: '留言板', link: '/config/messageboard' },
|
||||||
{ text: '评论', link: '/config/comment' },
|
{ text: '评论', link: '/config/comment' },
|
||||||
{ text: '页脚', link: '/config/footer' },
|
{ text: '页脚', link: '/config/footer' },
|
||||||
@@ -53,6 +54,7 @@ export default defineConfig({
|
|||||||
{ text: '图库', link: '/pages/photos' },
|
{ text: '图库', link: '/pages/photos' },
|
||||||
{ text: '友情链接', link: '/pages/links' },
|
{ text: '友情链接', link: '/pages/links' },
|
||||||
{ text: '朋友圈', link: '/pages/friends' },
|
{ text: '朋友圈', link: '/pages/friends' },
|
||||||
|
{ text: '记得', link: '/pages/important-dates' },
|
||||||
{ text: '自定义页面', link: '/pages/custom-page' },
|
{ text: '自定义页面', link: '/pages/custom-page' },
|
||||||
{ text: '扩展页面', link: '/pages/extended' },
|
{ text: '扩展页面', link: '/pages/extended' },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,31 @@
|
|||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
## v1.2.0
|
||||||
|
|
||||||
|
### 主题配色系统
|
||||||
|
|
||||||
|
- ✨ 新增「主题配色」设置(总体样式 → 主题配色),内置 11 套官方预调配色:暖屿·赤陶(默认)、抹茶、黛蓝、樱粉、暮紫、海盐、琥珀、梅酒、石墨、可拿铁、天青
|
||||||
|
- 🎨 每套配色包含浅色 / 深色两套完整色板,玻璃拟态、遮罩、阴影色调自动跟随当前配色
|
||||||
|
- 🎨 自定义「强调色」改为留空跟随所选配色;填写后仍可单独覆盖
|
||||||
|
- 📱 浏览器地址栏 theme-color 随配色与明暗模式联动(含切换按钮)
|
||||||
|
|
||||||
|
### Hero 首屏
|
||||||
|
|
||||||
|
- ✨ 一言文字新增独立颜色配置(浅色 / 深色模式各一项,此前只能跟随副标题颜色)
|
||||||
|
- ✨ 新增「文字显示效果」设置:阴影 / 光晕 / 半透明遮罩层 / 渐变遮罩 / 容器(半透明边框圆角矩形 + 加大留白)
|
||||||
|
- 🐛 默认改为无任何效果:移除标题、副标题与一言自带的白色散光投影
|
||||||
|
|
||||||
|
### 问题修复
|
||||||
|
|
||||||
|
- 🐛 修复 /douban 页面 500:适配豆瓣插件 v1.2.6 的 SQLite 数据结构(字段由 `item.spec.*` / `item.faves.*` 扁平化为 `item.*` / `item.faves*`)
|
||||||
|
- 🐛 修复友情链接卡片悬浮光圈被裁切的问题(卡片容器不再裁切悬浮阴影与上移位移)
|
||||||
|
- 🐛 修复瞬间页面顶图与最新一条瞬间卡片的边框贴合的问题,顶图下方增加间距
|
||||||
|
- 🐛 友情链接卡片徽章简化为仅展示站点可达性(绿点 = 可访问,红点 = 异常),不再联合反链检测结果判断
|
||||||
|
|
||||||
|
### 其他
|
||||||
|
|
||||||
|
- ⬆️ Halo 版本要求提升至 **2.26+**
|
||||||
|
|
||||||
## v1.1.1
|
## v1.1.1
|
||||||
|
|
||||||
### 瞬间页面
|
### 瞬间页面
|
||||||
|
|||||||
@@ -13,7 +13,17 @@ Hero 首屏是 WarmIsland 主题的标志性区域,占据首页首屏的全部
|
|||||||
| `hero_hitokoto_api` | URL | `https://v1.hitokoto.cn/` | 一言 API 地址 |
|
| `hero_hitokoto_api` | URL | `https://v1.hitokoto.cn/` | 一言 API 地址 |
|
||||||
| `hero_hitokoto_categories` | 多选 | `d, i, k` | 一言句子类型 |
|
| `hero_hitokoto_categories` | 多选 | `d, i, k` | 一言句子类型 |
|
||||||
| `hero_custom_description` | 多行文本 | — | 自定义描述文案 |
|
| `hero_custom_description` | 多行文本 | — | 自定义描述文案 |
|
||||||
|
| `hero_hitokoto_auto_refresh` | 开关 | `false` | 一言自动刷新 |
|
||||||
|
| `hero_hitokoto_refresh_interval` | 下拉 | `10000` | 一言刷新间隔(秒) |
|
||||||
| `hero_background_image` | 附件 | — | 背景图片 |
|
| `hero_background_image` | 附件 | — | 背景图片 |
|
||||||
|
| `hero_background_image_dark` | 附件 | — | 深色模式背景图片 |
|
||||||
|
| `hero_title_color_light` | 颜色 | — | 大标题颜色(浅色模式),留空使用主题默认 |
|
||||||
|
| `hero_title_color_dark` | 颜色 | — | 大标题颜色(深色模式),留空使用主题默认 |
|
||||||
|
| `hero_subtitle_color_light` | 颜色 | — | 副标题颜色(浅色模式),留空使用主题默认 |
|
||||||
|
| `hero_subtitle_color_dark` | 颜色 | — | 副标题颜色(深色模式),留空使用主题默认 |
|
||||||
|
| `hero_quote_color_light` | 颜色 | — | 一言文字颜色(浅色模式),留空使用主题默认 |
|
||||||
|
| `hero_quote_color_dark` | 颜色 | — | 一言文字颜色(深色模式),留空使用主题默认 |
|
||||||
|
| `hero_text_effect` | 下拉 | `none` | 文字显示效果:`none` / `shadow` / `glow` / `scrim` / `gradient` / `frame` |
|
||||||
|
|
||||||
## 详细说明
|
## 详细说明
|
||||||
|
|
||||||
@@ -102,6 +112,31 @@ hero_custom_description: "用文字记录生活,用代码构建世界。"
|
|||||||
背景图片会自动应用模糊和暗化处理,以确保前景文字的可读性。建议选择色彩柔和、不包含大量细节的图片作为背景,避免干扰文案阅读。
|
背景图片会自动应用模糊和暗化处理,以确保前景文字的可读性。建议选择色彩柔和、不包含大量细节的图片作为背景,避免干扰文案阅读。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
### 文字颜色
|
||||||
|
|
||||||
|
标题、副标题、一言的颜色均可分别配置浅色 / 深色模式的取值,留空使用主题默认:
|
||||||
|
|
||||||
|
- **`hero_title_color_light` / `hero_title_color_dark`** — 大标题颜色。
|
||||||
|
- **`hero_subtitle_color_light` / `hero_subtitle_color_dark`** — 副标题颜色。
|
||||||
|
- **`hero_quote_color_light` / `hero_quote_color_dark`** — 一言文字颜色(`hero_description_mode` 为 `hitokoto` 或 `custom` 时生效)。
|
||||||
|
|
||||||
|
### 文字显示效果(hero_text_effect)
|
||||||
|
|
||||||
|
为 Hero 文字(标题 / 副标题 / 一言)叠加辅助可读效果。**默认 `none` 不使用任何效果**——v1.2.0 起,文字周围默认不再有淡淡的白色散光投影:
|
||||||
|
|
||||||
|
| 值 | 效果 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `none` | 无效果(默认) | 纯净文字,无任何附加投影与衬底 |
|
||||||
|
| `shadow` | 阴影 | 文字下方叠加柔和的深色投影,适合复杂背景图 |
|
||||||
|
| `glow` | 光晕 | 文字周围叠加白色光晕,暗色背景图上更醒目 |
|
||||||
|
| `scrim` | 半透明遮罩层 | 文字背后垫一块毛玻璃圆角衬底,整体提升可读性 |
|
||||||
|
| `gradient` | 渐变遮罩 | 文字背后垫斜向渐变衬底,左深右浅自然过渡 |
|
||||||
|
| `frame` | 容器 | 把文字"装"进一个带半透明边框的圆角矩形,加大内边距与字距留白 |
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
遮罩类(`scrim` / `gradient`)与容器(`frame`)作用于整个文字区块,浅色模式为白色系衬底、深色模式为黑色系衬底;`frame` 会在浅色模式下使用墨色描边、深色模式下使用白色描边。
|
||||||
|
:::
|
||||||
|
|
||||||
## 视觉效果
|
## 视觉效果
|
||||||
|
|
||||||
Hero 区域包含以下精心设计的视觉效果:
|
Hero 区域包含以下精心设计的视觉效果:
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# 记得
|
||||||
|
|
||||||
|
「记得」模块对应 [plugin-important-dates](https://github.com/yyliucha/plugin-important-dates) 插件的前台页面 `/important-dates`,用于展示重要日期、牵挂的人与座驾(爱车)信息。
|
||||||
|
|
||||||
|
## 配置项一览
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `idates_page_title` | 文本 | `记得` | 页面标题,留空时使用插件自带标题 |
|
||||||
|
| `idates_page_subtitle` | 文本 | `记着重要的日子、惦记的人,还有陪你跑的爱车` | 页面副标题 |
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
idates_page_title: "记得" # 默认
|
||||||
|
idates_page_title: "纪念日" # 自定义
|
||||||
|
idates_page_subtitle: "记着重要的日子、惦记的人,还有陪你跑的爱车"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前置依赖
|
||||||
|
|
||||||
|
::: danger
|
||||||
|
本页依赖 Halo 的 **plugin-important-dates**(记得)插件。未安装插件时 `/important-dates` 路由不存在,页面不会显示。
|
||||||
|
|
||||||
|
安装方式:Halo 后台 → **插件** → **安装** → 上传 jar,或直接前往 [GitHub Releases](https://github.com/yyliucha/plugin-important-dates/releases) 下载。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 数据来源
|
||||||
|
|
||||||
|
页面数据全部由插件提供,主题不读取任何隐私字段:
|
||||||
|
|
||||||
|
- `dates` — 重要日期(名称、日期、农历标记、关联人员、倒计时)
|
||||||
|
- `people` — 牵挂的人(昵称、关系、脱敏生日、生日倒计时、大头贴)
|
||||||
|
- `cars` — 座驾(分类、品牌型号、脱敏车牌、到期事项、相册)
|
||||||
|
- `reminders` / `carEvents` — 顶部到期提醒横幅
|
||||||
|
|
||||||
|
是否展示「重要」标记、大头贴、爱车视图等,均由插件自己的「设置」控制,主题侧只负责呈现。
|
||||||
|
|
||||||
|
## 相关页面
|
||||||
|
|
||||||
|
- [扩展页面](/pages/extended) — 全部插件扩展页面一览
|
||||||
|
- [记得页面](/pages/important-dates) — 页面结构与模板覆盖说明
|
||||||
+25
-2
@@ -7,7 +7,8 @@
|
|||||||
| 配置项 | 类型 | 默认值 | 说明 |
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `color_scheme` | 单选 | `system` | 配色方案:`system` / `light` / `dark` |
|
| `color_scheme` | 单选 | `system` | 配色方案:`system` / `light` / `dark` |
|
||||||
| `accent_color` | 颜色 | `#d4764e` | 全局强调色 |
|
| `color_palette` | 下拉 | `warm-island` | 主题配色(官方预设调色板,含深浅两套色板) |
|
||||||
|
| `accent_color` | 颜色 | — | 自定义强调色;留空跟随所选主题配色 |
|
||||||
| `border_radius` | 下拉 | `medium` | 圆角风格:`small` / `medium` / `large` |
|
| `border_radius` | 下拉 | `medium` | 圆角风格:`small` / `medium` / `large` |
|
||||||
| `layout_container_width` | 下拉 | `medium` | 容器宽度:`narrow` / `medium` / `wide` |
|
| `layout_container_width` | 下拉 | `medium` | 容器宽度:`narrow` / `medium` / `wide` |
|
||||||
| `animation_enabled` | 开关 | `true` | 全局动画总开关 |
|
| `animation_enabled` | 开关 | `true` | 全局动画总开关 |
|
||||||
@@ -32,6 +33,28 @@
|
|||||||
选择 `system` 时,主题会监听操作系统的 `prefers-color-scheme` 媒体查询,实时响应用户的系统设置变化。访问者也可以通过导航栏的主题切换按钮手动覆盖当前模式。
|
选择 `system` 时,主题会监听操作系统的 `prefers-color-scheme` 媒体查询,实时响应用户的系统设置变化。访问者也可以通过导航栏的主题切换按钮手动覆盖当前模式。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
### 主题配色(color_palette)
|
||||||
|
|
||||||
|
v1.2.0 起主题内置配色系统,提供 11 套官方预调配色,每套均包含浅色与深色两套完整色板(背景、文字、分隔线、强调色、玻璃拟态、阴影色调等会整体联动):
|
||||||
|
|
||||||
|
| 值 | 名称 | 色彩倾向 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `warm-island` | 暖屿 · 赤陶(默认) | 暖橘陶土,米白纸感 |
|
||||||
|
| `matcha` | 抹茶 | 清新草绿,米绿纸感 |
|
||||||
|
| `indigo` | 黛蓝 | 沉稳蓝调,冷白纸感 |
|
||||||
|
| `sakura` | 樱粉 | 柔和粉调,樱白纸感 |
|
||||||
|
| `violet` | 暮紫 | 温柔紫调,淡紫纸感 |
|
||||||
|
| `sea` | 海盐 | 青碧蓝绿,浅青纸感 |
|
||||||
|
| `amber` | 琥珀 | 蜂蜜金黄,暖黄纸感 |
|
||||||
|
| `plum` | 梅酒 | 酒红梅紫,粉白纸感 |
|
||||||
|
| `graphite` | 石墨 | 中性灰黑,极简无彩 |
|
||||||
|
| `cocoa` | 可拿铁 | 咖啡可可,奶咖纸感 |
|
||||||
|
| `azure` | 天青 | 明快天蓝,浅蓝纸感 |
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
下方「强调色」留空时,将使用所选配色自带的强调色。**老站点升级前如果保存过强调色(默认 `#d4764e`),切换配色后强调色不会变化——清空强调色并保存即可恢复跟随配色。**
|
||||||
|
:::
|
||||||
|
|
||||||
### 强调色(accent_color)
|
### 强调色(accent_color)
|
||||||
|
|
||||||
强调色是 WarmIsland 主题的灵魂,它会影响全站以下元素的颜色:
|
强调色是 WarmIsland 主题的灵魂,它会影响全站以下元素的颜色:
|
||||||
@@ -43,7 +66,7 @@
|
|||||||
- 选中状态颜色
|
- 选中状态颜色
|
||||||
- Hero 区域渐变色的组成部分
|
- Hero 区域渐变色的组成部分
|
||||||
|
|
||||||
默认值 `#d4764e` 是暖屿标志性的暖橘色,与主题的温暖气质相呼应。
|
留空时跟随上方「主题配色」自带的强调色(暖屿·赤陶配色即 `#d4764e` 暖橘色)。
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
选择强调色时,建议同时考虑浅色和深色模式下的可读性。可以在 [CSS 变量](/customize/css-variables) 页面查看所有受影响的变量,进行更精细的调整。
|
选择强调色时,建议同时考虑浅色和深色模式下的可读性。可以在 [CSS 变量](/customize/css-variables) 页面查看所有受影响的变量,进行更精细的调整。
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ WarmIsland 主题深度集成了多款 Halo 插件,提供了丰富的扩展页
|
|||||||
| 装备 | [equipment](https://www.halo.run/store/apps/app-ytygyqml) | /equipments |
|
| 装备 | [equipment](https://www.halo.run/store/apps/app-ytygyqml) | /equipments |
|
||||||
| 足迹 | [footprint](https://www.halo.run/store/apps/app-XCtAQ) | /footprints |
|
| 足迹 | [footprint](https://www.halo.run/store/apps/app-XCtAQ) | /footprints |
|
||||||
| Steam | [steam](https://www.halo.run/store/apps/app-0ojqyzfh) | /steam |
|
| Steam | [steam](https://www.halo.run/store/apps/app-0ojqyzfh) | /steam |
|
||||||
|
| 记得 | [plugin-important-dates](https://github.com/yyliucha/plugin-important-dates) | /important-dates |
|
||||||
|
|
||||||
## 豆瓣页面
|
## 豆瓣页面
|
||||||
[豆瓣插件](https://www.halo.run/store/apps/app-srBOL)
|
[豆瓣插件](https://www.halo.run/store/apps/app-srBOL)
|
||||||
@@ -36,6 +37,18 @@ WarmIsland 主题深度集成了多款 Halo 插件,提供了丰富的扩展页
|
|||||||
- 徽章展示
|
- 徽章展示
|
||||||
- 最近游戏
|
- 最近游戏
|
||||||
|
|
||||||
|
## 记得页面
|
||||||
|
|
||||||
|
[记得插件](https://github.com/yyliucha/plugin-important-dates)
|
||||||
|
记录重要的日子、牵挂的人与座驾,「生活 / 爱车」双视图展示:
|
||||||
|
|
||||||
|
- 重要日期(含农历)与倒计时便签
|
||||||
|
- 牵挂的人:大头贴、昵称、关系与生日倒计时
|
||||||
|
- 座驾:脱敏车牌、到期事项徽章、车主与驾驶人
|
||||||
|
- 相册:点击车辆封面全屏只读浏览
|
||||||
|
|
||||||
|
详见 [记得页面](/pages/important-dates)。
|
||||||
|
|
||||||
::: info
|
::: info
|
||||||
扩展页面需要安装对应的 Halo 插件才能使用。如果插件未安装,页面会显示友好的提示信息。
|
扩展页面需要安装对应的 Halo 插件才能使用。如果插件未安装,页面会显示友好的提示信息。
|
||||||
:::
|
:::
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# 记得页面
|
||||||
|
|
||||||
|
`/important-dates` 由 [plugin-important-dates](https://github.com/yyliucha/plugin-important-dates) 插件提供路由,主题提供同名模板 `templates/important-dates.html` 覆盖插件默认样式,使页面完全融入 WarmIsland 的外观(导航、页脚、暗色模式)。
|
||||||
|
|
||||||
|
## 页面结构
|
||||||
|
|
||||||
|
```
|
||||||
|
记得(标题 + 副标题 + 强调线)
|
||||||
|
├─ 生活 / 爱车 视图切换(插件开启「前台爱车模块」时出现)
|
||||||
|
├─ 到期提醒横幅(生活:重要日期;爱车:保险 / 年检 / 保养)
|
||||||
|
├─ 生活视图
|
||||||
|
│ ├─ 重要日期(便签卡片:倒计时、日期、农历标记、关联的人)
|
||||||
|
│ └─ 牵挂的人(大头贴、昵称、关系、脱敏生日与倒计时)
|
||||||
|
└─ 爱车视图
|
||||||
|
├─ 我的爱车(封面、分类品牌、脱敏车牌、到期徽章、车主/驾驶人)
|
||||||
|
└─ 车主与驾驶人 / 其他重要的人
|
||||||
|
```
|
||||||
|
|
||||||
|
## 交互
|
||||||
|
|
||||||
|
- **视图切换**:点击「生活 / 爱车」即时切换,地址栏记忆 `?view=car`
|
||||||
|
- **相册**:点击车辆封面全屏浏览相册(只读),`Esc` 或点击空白处关闭;未上传照片时给出轻提示
|
||||||
|
- **忽略提醒**:横幅右侧 ✕ 表示「本次到期周期内不再提示」,记录在浏览器本地,到期日变化后自动恢复
|
||||||
|
|
||||||
|
## 模板覆盖机制
|
||||||
|
|
||||||
|
插件通过 Halo 的 `TemplateNameResolver` 解析模板名:
|
||||||
|
|
||||||
|
1. 主题存在 `templates/important-dates.html` → 使用主题模板
|
||||||
|
2. 否则回退插件自带默认模板(会用主题的 `layout.html` 页面布局契约)
|
||||||
|
|
||||||
|
删除主题内的 `important-dates.html` 即可回到插件默认样式;插件不会写入或修改任何主题文件。
|
||||||
|
|
||||||
|
## 可用模板变量
|
||||||
|
|
||||||
|
| 变量 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| `title` | 页面标题(插件默认「记得」) |
|
||||||
|
| `dates` / `people` | 重要日期、人员列表 |
|
||||||
|
| `reminders` | 即将到来的重要日期(提醒横幅) |
|
||||||
|
| `cars` / `carEvents` | 座驾列表、即将到期的座驾事项 |
|
||||||
|
| `carPeople` / `otherPeople` / `personCars` | 车主驾驶人、其他人员、人员与车辆关系 |
|
||||||
|
| `showCarSection` / `carSkinEnabled` / `showAvatar` / `showImportantTag` | 各展示开关 |
|
||||||
|
| `allowDismiss` | 是否允许逐条忽略提醒 |
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
`PersonVo` 没有 `gender` 字段,自定义模板时不要写 `${p.gender}`,否则会触发 SpEL `EL1008E` 导致页面 500。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 相关页面
|
||||||
|
|
||||||
|
- [记得配置](/config/important-dates) — 页面标题与副标题
|
||||||
|
- [扩展页面](/pages/extended) — 其它插件扩展页面
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
const js = `(function(){var h=document.querySelector(".hero");if(!h)return "No .hero element";var s=getComputedStyle(h);return JSON.stringify({width:s.width,marginLeft:s.marginLeft,marginTop:s.marginTop,paddingTop:s.paddingTop})})()`;
|
|
||||||
console.log(Buffer.from(js).toString('base64'));
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
common.home=Home
|
|
||||||
common.search=Search
|
|
||||||
common.themeSwitch=Switch Theme
|
|
||||||
common.menu=Menu
|
|
||||||
common.close=Close
|
|
||||||
common.toc=Table of Contents
|
|
||||||
common.openToc=Open Table of Contents
|
|
||||||
common.closeToc=Close Table of Contents
|
|
||||||
common.newer=Newer
|
|
||||||
common.older=Older
|
|
||||||
common.loading=Loading...
|
|
||||||
common.allLoaded=All posts loaded
|
|
||||||
common.allMomentsLoaded=All moments loaded
|
|
||||||
common.noPosts=No posts yet.
|
|
||||||
common.postCount=Total {0} posts
|
|
||||||
common.categoryCount=Total {0} categories
|
|
||||||
common.tagCount=Total {0} tags
|
|
||||||
common.readMore=Read more
|
|
||||||
common.words=words
|
|
||||||
common.readTime=min read
|
|
||||||
common.views=views
|
|
||||||
common.like=Like
|
|
||||||
common.pagination=Pagination
|
|
||||||
common.toggleMenu=Toggle menu
|
|
||||||
common.all=All
|
|
||||||
common.noPhotos=No photos yet.
|
|
||||||
common.noPhotosInGroup=No photos in this group.
|
|
||||||
common.pluginError=Plugin loading error, please check plugin status.
|
|
||||||
common.installPlugin=This feature requires the {0} plugin, <a href="{1}">go to the marketplace to install</a>
|
|
||||||
common.featured=Featured
|
|
||||||
common.latestPosts=Latest Posts
|
|
||||||
common.timeline=Timeline
|
|
||||||
common.viewAll=View all
|
|
||||||
|
|
||||||
navbar.search=Search
|
|
||||||
navbar.themeSwitch=Switch Theme
|
|
||||||
|
|
||||||
hero.pinned=Pinned
|
|
||||||
hero.posts=Posts
|
|
||||||
|
|
||||||
article.previousPost=Previous
|
|
||||||
article.nextPost=Next
|
|
||||||
article.toc=Table of Contents
|
|
||||||
article.wordCount=words
|
|
||||||
article.readTime=min read
|
|
||||||
article.views=views
|
|
||||||
article.relatedPosts=Related Posts
|
|
||||||
|
|
||||||
share.title=Share
|
|
||||||
share.qq=QQ
|
|
||||||
share.wechat=WeChat
|
|
||||||
share.weibo=Weibo
|
|
||||||
share.twitter=X (Twitter)
|
|
||||||
share.facebook=Facebook
|
|
||||||
share.telegram=Telegram
|
|
||||||
share.copyLink=Copy Link
|
|
||||||
share.copied=Copied!
|
|
||||||
share.qrcode=QR Code
|
|
||||||
share.wechatQR=Scan to Share
|
|
||||||
share.wechatHint=Open WeChat and scan the QR code to share
|
|
||||||
|
|
||||||
archives.title=Archives
|
|
||||||
|
|
||||||
categories.title=Categories
|
|
||||||
|
|
||||||
tags.title=Tags
|
|
||||||
|
|
||||||
links.title=Friends
|
|
||||||
links.subtitle=Mountains and rivers, a journey of a thousand miles
|
|
||||||
|
|
||||||
moments.title=Moments
|
|
||||||
moments.noMoments=No moments yet, go record your mood \U0001f319
|
|
||||||
|
|
||||||
photos.title=Gallery
|
|
||||||
|
|
||||||
friends.title=Friends Circle
|
|
||||||
|
|
||||||
equipment.title=Equipment
|
|
||||||
|
|
||||||
steam.title=Steam
|
|
||||||
steam.homepage=Steam Homepage
|
|
||||||
steam.level=Level
|
|
||||||
steam.ownedGames=Owned Games
|
|
||||||
steam.totalPlaytime=Total Playtime
|
|
||||||
steam.recentPlaytime=Recent 2 Weeks
|
|
||||||
steam.badges=Badges
|
|
||||||
steam.hours=hours
|
|
||||||
steam.recentlyPlayed=Recently Played
|
|
||||||
steam.gameLibrary=Game Library
|
|
||||||
steam.lastPlayed=Last played {0}
|
|
||||||
|
|
||||||
douban.title=Douban
|
|
||||||
douban.wish=Wish
|
|
||||||
douban.done=Done
|
|
||||||
douban.wishRead=Wish
|
|
||||||
douban.reading=Reading
|
|
||||||
douban.listened=Listened
|
|
||||||
douban.wishPlay=Wish
|
|
||||||
douban.played=Played
|
|
||||||
douban.wishListen=Wish
|
|
||||||
douban.listening=Listening
|
|
||||||
douban.playing=Playing
|
|
||||||
douban.myRating=My:
|
|
||||||
|
|
||||||
messageboard.title=Message Board
|
|
||||||
messageboard.subtitle=Leave your footprints here \U0001f319
|
|
||||||
|
|
||||||
footer.poweredBy=Powered by Halo
|
|
||||||
|
|
||||||
error.404.title=Page Not Found
|
|
||||||
error.404.desc=The page you are looking for seems to have wandered off this island
|
|
||||||
error.404.home=Back to Home
|
|
||||||
error.404.back=Go Back
|
|
||||||
error.500.title=Server Error
|
|
||||||
error.500.desc=The island encountered some issues, please try again later
|
|
||||||
error.500.home=Back to Home
|
|
||||||
error.500.back=Go Back
|
|
||||||
|
|
||||||
sidebar.authorInfo=About Author
|
|
||||||
sidebar.latestPosts=Latest Posts
|
|
||||||
sidebar.recommendedPosts=Recommended
|
|
||||||
sidebar.timeProgress=Time Progress
|
|
||||||
sidebar.dayProgress=Day
|
|
||||||
sidebar.monthProgress=Month
|
|
||||||
sidebar.yearProgress=Year
|
|
||||||
sidebar.categories=Categories
|
|
||||||
sidebar.tags=Tags
|
|
||||||
|
|
||||||
comment.style=Warm
|
|
||||||
comment.showAvatar=Show Avatar
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
common.home=Home
|
|
||||||
common.search=Search
|
|
||||||
common.themeSwitch=Switch Theme
|
|
||||||
common.menu=Menu
|
|
||||||
common.close=Close
|
|
||||||
common.toc=Table of Contents
|
|
||||||
common.openToc=Open Table of Contents
|
|
||||||
common.closeToc=Close Table of Contents
|
|
||||||
common.newer=Newer
|
|
||||||
common.older=Older
|
|
||||||
common.loading=Loading...
|
|
||||||
common.allLoaded=All posts loaded
|
|
||||||
common.noPosts=No posts yet.
|
|
||||||
common.postCount=Total {0} posts
|
|
||||||
common.readMore=Read more
|
|
||||||
common.words=words
|
|
||||||
common.readTime=min read
|
|
||||||
common.views=views
|
|
||||||
common.like=Like
|
|
||||||
common.pagination=Pagination
|
|
||||||
common.toggleMenu=Toggle menu
|
|
||||||
common.allMomentsLoaded=All moments loaded
|
|
||||||
common.categoryCount=Total {0} categories
|
|
||||||
common.tagCount=Total {0} tags
|
|
||||||
common.all=All
|
|
||||||
common.noPhotos=No photos yet.
|
|
||||||
common.noPhotosInGroup=No photos in this group.
|
|
||||||
common.pluginError=Plugin loading error, please check plugin status.
|
|
||||||
common.installPlugin=This feature requires the {0} plugin, <a href="{1}">go to the marketplace to install</a>
|
|
||||||
common.featured=Featured
|
|
||||||
common.latestPosts=Latest Posts
|
|
||||||
common.timeline=Timeline
|
|
||||||
common.viewAll=View all
|
|
||||||
|
|
||||||
navbar.search=Search
|
|
||||||
navbar.themeSwitch=Switch Theme
|
|
||||||
|
|
||||||
hero.pinned=Pinned
|
|
||||||
hero.posts=Posts
|
|
||||||
|
|
||||||
article.previousPost=Previous
|
|
||||||
article.nextPost=Next
|
|
||||||
article.toc=Table of Contents
|
|
||||||
article.wordCount=words
|
|
||||||
article.readTime=min read
|
|
||||||
article.views=views
|
|
||||||
article.relatedPosts=Related Posts
|
|
||||||
|
|
||||||
share.title=Share
|
|
||||||
share.qq=QQ
|
|
||||||
share.wechat=WeChat
|
|
||||||
share.weibo=Weibo
|
|
||||||
share.twitter=X (Twitter)
|
|
||||||
share.facebook=Facebook
|
|
||||||
share.telegram=Telegram
|
|
||||||
share.copyLink=Copy Link
|
|
||||||
share.copied=Copied!
|
|
||||||
share.qrcode=QR Code
|
|
||||||
share.wechatQR=Scan to Share
|
|
||||||
share.wechatHint=Open WeChat and scan the QR code to share
|
|
||||||
|
|
||||||
archives.title=Archives
|
|
||||||
|
|
||||||
categories.title=Categories
|
|
||||||
|
|
||||||
tags.title=Tags
|
|
||||||
|
|
||||||
links.title=Friends
|
|
||||||
links.subtitle=Mountains and rivers, a journey of a thousand miles
|
|
||||||
|
|
||||||
moments.title=Moments
|
|
||||||
moments.noMoments=No moments yet, go record your mood \U0001f319
|
|
||||||
|
|
||||||
photos.title=Gallery
|
|
||||||
|
|
||||||
friends.title=Friends Circle
|
|
||||||
|
|
||||||
equipment.title=Equipment
|
|
||||||
|
|
||||||
steam.title=Steam
|
|
||||||
steam.homepage=Steam Homepage
|
|
||||||
steam.level=Level
|
|
||||||
steam.ownedGames=Owned Games
|
|
||||||
steam.totalPlaytime=Total Playtime
|
|
||||||
steam.recentPlaytime=Recent 2 Weeks
|
|
||||||
steam.badges=Badges
|
|
||||||
steam.hours=hours
|
|
||||||
steam.recentlyPlayed=Recently Played
|
|
||||||
steam.gameLibrary=Game Library
|
|
||||||
steam.lastPlayed=Last played {0}
|
|
||||||
|
|
||||||
douban.title=Douban
|
|
||||||
douban.wish=Wish
|
|
||||||
douban.done=Done
|
|
||||||
douban.wishRead=Wish
|
|
||||||
douban.reading=Reading
|
|
||||||
douban.listened=Listened
|
|
||||||
douban.wishPlay=Wish
|
|
||||||
douban.played=Played
|
|
||||||
douban.wishListen=Wish
|
|
||||||
douban.listening=Listening
|
|
||||||
douban.playing=Playing
|
|
||||||
douban.myRating=My:
|
|
||||||
|
|
||||||
messageboard.title=Message Board
|
|
||||||
messageboard.subtitle=Leave your footprints here \U0001f319
|
|
||||||
|
|
||||||
footer.poweredBy=Powered by Halo
|
|
||||||
|
|
||||||
error.404.title=Page Not Found
|
|
||||||
error.404.desc=The page you are looking for seems to have wandered off this island
|
|
||||||
error.404.home=Back to Home
|
|
||||||
error.404.back=Go Back
|
|
||||||
error.500.title=Server Error
|
|
||||||
error.500.desc=The island encountered some issues, please try again later
|
|
||||||
error.500.home=Back to Home
|
|
||||||
error.500.back=Go Back
|
|
||||||
|
|
||||||
sidebar.authorInfo=About Author
|
|
||||||
sidebar.latestPosts=Latest Posts
|
|
||||||
sidebar.recommendedPosts=Recommended
|
|
||||||
sidebar.timeProgress=Time Progress
|
|
||||||
sidebar.dayProgress=Day
|
|
||||||
sidebar.monthProgress=Month
|
|
||||||
sidebar.yearProgress=Year
|
|
||||||
sidebar.categories=Categories
|
|
||||||
sidebar.tags=Tags
|
|
||||||
|
|
||||||
comment.style=Warm
|
|
||||||
comment.showAvatar=Show Avatar
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
common.home=\u9996\u9875
|
|
||||||
common.search=\u641c\u7d22
|
|
||||||
common.themeSwitch=\u5207\u6362\u4e3b\u9898
|
|
||||||
common.menu=\u83dc\u5355
|
|
||||||
common.close=\u5173\u95ed
|
|
||||||
common.toc=\u76ee\u5f55
|
|
||||||
common.openToc=\u6253\u5f00\u76ee\u5f55
|
|
||||||
common.closeToc=\u5173\u95ed\u76ee\u5f55
|
|
||||||
common.newer=\u8f83\u65b0
|
|
||||||
common.older=\u8f83\u65e7
|
|
||||||
common.loading=\u52a0\u8f7d\u4e2d...
|
|
||||||
common.allLoaded=\u5df2\u52a0\u8f7d\u5168\u90e8\u6587\u7ae0
|
|
||||||
common.noPosts=\u6682\u65e0\u6587\u7ae0
|
|
||||||
common.postCount=\u5171 {0} \u7bc7\u6587\u7ae0
|
|
||||||
common.readMore=\u9605\u8bfb\u66f4\u591a
|
|
||||||
common.words=\u5b57
|
|
||||||
common.readTime=\u5206\u949f
|
|
||||||
common.views=\u9605\u8bfb
|
|
||||||
common.like=\u70b9\u8d5e
|
|
||||||
common.pagination=\u5206\u9875
|
|
||||||
common.toggleMenu=\u5207\u6362\u83dc\u5355
|
|
||||||
common.allMomentsLoaded=\u5df2\u52a0\u8f7d\u5168\u90e8\u77ac\u95f4
|
|
||||||
common.categoryCount=\u5171 {0} \u4e2a\u5206\u7c7b
|
|
||||||
common.tagCount=\u5171 {0} \u4e2a\u6807\u7b7e
|
|
||||||
common.all=\u5168\u90e8
|
|
||||||
common.noPhotos=\u6682\u65e0\u56fe\u7247
|
|
||||||
common.noPhotosInGroup=\u8be5\u5206\u7ec4\u6682\u65e0\u56fe\u7247
|
|
||||||
common.pluginError=\u63d2\u4ef6\u52a0\u8f7d\u5f02\u5e38\uff0c\u8bf7\u68c0\u67e5\u63d2\u4ef6\u72b6\u6001
|
|
||||||
common.installPlugin=\u8be5\u529f\u80fd\u9700\u8981\u5b89\u88c5\u300c{0}\u300d\u63d2\u4ef6\uff0c<a href="{1}">\u524d\u5f80\u5e94\u7528\u5e02\u573a\u5b89\u88c5</a>
|
|
||||||
common.featured=\u7cbe\u9009
|
|
||||||
common.latestPosts=\u6700\u65b0\u6587\u7ae0
|
|
||||||
common.timeline=\u65f6\u95f4\u7ebf
|
|
||||||
common.viewAll=\u67e5\u770b\u5168\u90e8
|
|
||||||
|
|
||||||
navbar.search=\u641c\u7d22
|
|
||||||
navbar.themeSwitch=\u5207\u6362\u4e3b\u9898
|
|
||||||
|
|
||||||
hero.pinned=\u7f6e\u9876
|
|
||||||
hero.posts=\u6587\u7ae0
|
|
||||||
|
|
||||||
article.previousPost=\u4e0a\u4e00\u7bc7
|
|
||||||
article.nextPost=\u4e0b\u4e00\u7bc7
|
|
||||||
article.toc=\u76ee\u5f55
|
|
||||||
article.wordCount=\u5b57
|
|
||||||
article.readTime=\u5206\u949f
|
|
||||||
article.views=\u9605\u8bfb
|
|
||||||
article.relatedPosts=\u76f8\u5173\u63a8\u8350
|
|
||||||
|
|
||||||
share.title=\u5206\u4eab
|
|
||||||
share.qq=QQ
|
|
||||||
share.wechat=\u5fae\u4fe1
|
|
||||||
share.weibo=\u5fae\u535a
|
|
||||||
share.twitter=X
|
|
||||||
share.facebook=Facebook
|
|
||||||
share.telegram=Telegram
|
|
||||||
share.copyLink=\u590d\u5236\u94fe\u63a5
|
|
||||||
share.copied=\u5df2\u590d\u5236
|
|
||||||
share.qrcode=\u4e8c\u7ef4\u7801
|
|
||||||
share.wechatQR=\u5fae\u4fe1\u626b\u7801\u5206\u4eab
|
|
||||||
share.wechatHint=\u6253\u5f00\u5fae\u4fe1\uff0c\u626b\u63cf\u4e8c\u7ef4\u7801\u5373\u53ef\u5206\u4eab
|
|
||||||
|
|
||||||
archives.title=\u5f52\u6863
|
|
||||||
|
|
||||||
categories.title=\u5206\u7c7b
|
|
||||||
|
|
||||||
tags.title=\u6807\u7b7e
|
|
||||||
|
|
||||||
links.title=\u53cb\u94fe
|
|
||||||
links.subtitle=\u5c71\u6c34\u4e00\u7a0b\uff0c\u4e09\u751f\u6709\u5e78
|
|
||||||
|
|
||||||
moments.title=\u52a8\u6001
|
|
||||||
moments.noMoments=\u8fd8\u6ca1\u6709\u77ac\u95f4\uff0c\u5feb\u53bb\u8bb0\u5f55\u5f53\u4e0b\u7684\u5fc3\u60c5\u5427 \U0001f319
|
|
||||||
|
|
||||||
photos.title=\u76f8\u518c
|
|
||||||
|
|
||||||
friends.title=\u53cb\u4eba\u5708
|
|
||||||
|
|
||||||
equipment.title=\u88c5\u5907
|
|
||||||
|
|
||||||
steam.title=Steam
|
|
||||||
steam.homepage=Steam \u4e3b\u9875
|
|
||||||
steam.level=\u7b49\u7ea7
|
|
||||||
steam.ownedGames=\u62e5\u6709\u6e38\u620f
|
|
||||||
steam.totalPlaytime=\u603b\u6e38\u73a9\u65f6\u957f
|
|
||||||
steam.recentPlaytime=\u8fd1\u4e24\u5468
|
|
||||||
steam.badges=\u5fbd\u7ae0
|
|
||||||
steam.hours=\u5c0f\u65f6
|
|
||||||
steam.recentlyPlayed=\u6700\u8fd1\u6e38\u73a9
|
|
||||||
steam.gameLibrary=\u6e38\u620f\u5e93
|
|
||||||
steam.lastPlayed=\u6700\u540e\u6e38\u73a9 {0}
|
|
||||||
|
|
||||||
douban.title=\u8c46\u74e3
|
|
||||||
douban.wish=\u60f3\u770b
|
|
||||||
douban.done=\u770b\u8fc7
|
|
||||||
douban.wishRead=\u60f3\u8bfb
|
|
||||||
douban.reading=\u5728\u8bfb
|
|
||||||
douban.listened=\u542c\u8fc7
|
|
||||||
douban.wishPlay=\u60f3\u73a9
|
|
||||||
douban.played=\u73a9\u8fc7
|
|
||||||
douban.wishListen=\u60f3\u542c
|
|
||||||
douban.listening=\u5728\u542c
|
|
||||||
douban.playing=\u5728\u73a9
|
|
||||||
douban.myRating=\u6211\u7684\uff1a
|
|
||||||
|
|
||||||
messageboard.title=\u7559\u8a00\u677f
|
|
||||||
messageboard.subtitle=\u5728\u8fd9\u91cc\u7559\u4e0b\u4f60\u7684\u8db3\u8ff9 \U0001f319
|
|
||||||
|
|
||||||
footer.poweredBy=Powered by Halo
|
|
||||||
|
|
||||||
error.404.title=\u9875\u9762\u672a\u627e\u5230
|
|
||||||
error.404.desc=\u4f60\u5bfb\u627e\u7684\u9875\u9762\u4f3c\u4e4e\u5df2\u7ecf\u8ff7\u8def\u4e86
|
|
||||||
error.404.home=\u8fd4\u56de\u9996\u9875
|
|
||||||
error.404.back=\u8fd4\u56de\u4e0a\u9875
|
|
||||||
error.500.title=\u670d\u52a1\u5668\u9519\u8bef
|
|
||||||
error.500.desc=\u5c0f\u5c9b\u9047\u5230\u4e86\u4e00\u4e9b\u95ee\u9898\uff0c\u8bf7\u7a0d\u540e\u518d\u8bd5
|
|
||||||
error.500.home=\u8fd4\u56de\u9996\u9875
|
|
||||||
error.500.back=\u8fd4\u56de\u4e0a\u9875
|
|
||||||
|
|
||||||
sidebar.authorInfo=\u535a\u4e3b\u4fe1\u606f
|
|
||||||
sidebar.latestPosts=\u6700\u65b0\u6587\u7ae0
|
|
||||||
sidebar.recommendedPosts=\u63a8\u8350\u6587\u7ae0
|
|
||||||
sidebar.timeProgress=\u65f6\u5149\u8fdb\u5ea6
|
|
||||||
sidebar.dayProgress=\u65e5
|
|
||||||
sidebar.monthProgress=\u6708
|
|
||||||
sidebar.yearProgress=\u5e74
|
|
||||||
sidebar.categories=\u5206\u7c7b
|
|
||||||
sidebar.tags=\u6807\u7b7e
|
|
||||||
|
|
||||||
comment.style=\u6e29\u6696
|
|
||||||
comment.showAvatar=\u663e\u793a\u5934\u50cf
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
common.home=\u9996\u9801
|
|
||||||
common.search=\u641c\u5c0b
|
|
||||||
common.themeSwitch=\u5207\u63db\u4e3b\u984c
|
|
||||||
common.menu=\u9078\u55ae
|
|
||||||
common.close=\u95dc\u9589
|
|
||||||
common.toc=\u76ee\u9304
|
|
||||||
common.openToc=\u6253\u958b\u76ee\u9304
|
|
||||||
common.closeToc=\u95dc\u9589\u76ee\u9304
|
|
||||||
common.newer=\u8f03\u65b0
|
|
||||||
common.older=\u8f03\u820a
|
|
||||||
common.loading=\u8f09\u5165\u4e2d...
|
|
||||||
common.allLoaded=\u5df2\u8f09\u5165\u5168\u90e8\u6587\u7ae0
|
|
||||||
common.noPosts=\u66ab\u7121\u6587\u7ae0
|
|
||||||
common.postCount=\u5171 {0} \u7bc7\u6587\u7ae0
|
|
||||||
common.readMore=\u95b1\u8b80\u66f4\u591a
|
|
||||||
common.words=\u5b57
|
|
||||||
common.readTime=\u5206\u9418
|
|
||||||
common.views=\u95b1\u8b80
|
|
||||||
common.like=\u6309\u8b9a
|
|
||||||
common.pagination=\u5206\u9801
|
|
||||||
common.toggleMenu=\u5207\u63db\u9078\u55ae
|
|
||||||
common.allMomentsLoaded=\u5df2\u8f09\u5165\u5168\u90e8\u77ac\u9593
|
|
||||||
common.categoryCount=\u5171 {0} \u500b\u5206\u985e
|
|
||||||
common.tagCount=\u5171 {0} \u500b\u6a19\u7c64
|
|
||||||
common.all=\u5168\u90e8
|
|
||||||
common.noPhotos=\u66ab\u7121\u5716\u7247
|
|
||||||
common.noPhotosInGroup=\u8a72\u5206\u7d44\u66ab\u7121\u5716\u7247
|
|
||||||
common.pluginError=\u5916\u639b\u7a0b\u5f0f\u8f09\u5165\u7570\u5e38\uff0c\u8acb\u6aa2\u67e5\u5916\u639b\u7a0b\u5f0f\u72c0\u614b
|
|
||||||
common.installPlugin=\u8a72\u529f\u80fd\u9700\u8981\u5b89\u88dd\u300c{0}\u300d\u5916\u639b\u7a0b\u5f0f\uff0c<a href="{1}">\u524d\u5f80\u61c9\u7528\u5e02\u5834\u5b89\u88dd</a>
|
|
||||||
common.featured=\u7cbe\u9078
|
|
||||||
common.latestPosts=\u6700\u65b0\u6587\u7ae0
|
|
||||||
common.timeline=\u6642\u9593\u7dda
|
|
||||||
common.viewAll=\u67e5\u770b\u5168\u90e8
|
|
||||||
|
|
||||||
navbar.search=\u641c\u5c0b
|
|
||||||
navbar.themeSwitch=\u5207\u63db\u4e3b\u984c
|
|
||||||
|
|
||||||
hero.pinned=\u7f6e\u9802
|
|
||||||
hero.posts=\u6587\u7ae0
|
|
||||||
|
|
||||||
article.previousPost=\u4e0a\u4e00\u7bc7
|
|
||||||
article.nextPost=\u4e0b\u4e00\u7bc7
|
|
||||||
article.toc=\u76ee\u9304
|
|
||||||
article.wordCount=\u5b57
|
|
||||||
article.readTime=\u5206\u9418
|
|
||||||
article.views=\u95b1\u8b80
|
|
||||||
article.relatedPosts=\u76f8\u95dc\u63a8\u85a6
|
|
||||||
|
|
||||||
share.title=\u5206\u4eab
|
|
||||||
share.qq=QQ
|
|
||||||
share.wechat=\u5fae\u4fe1
|
|
||||||
share.weibo=\u5fae\u535a
|
|
||||||
share.twitter=X
|
|
||||||
share.facebook=Facebook
|
|
||||||
share.telegram=Telegram
|
|
||||||
share.copyLink=\u8907\u88fd\u9023\u7d50
|
|
||||||
share.copied=\u5df2\u8907\u88fd
|
|
||||||
share.qrcode=\u4e8c\u7dad\u78bc
|
|
||||||
share.wechatQR=\u5fae\u4fe1\u6383\u78bc\u5206\u4eab
|
|
||||||
share.wechatHint=\u6253\u958b\u5fae\u4fe1\uff0c\u6383\u63cf\u4e8c\u7dad\u78bc\u5373\u53ef\u5206\u4eab
|
|
||||||
|
|
||||||
archives.title=\u6b78\u6a94
|
|
||||||
|
|
||||||
categories.title=\u5206\u985e
|
|
||||||
|
|
||||||
tags.title=\u6a19\u7c64
|
|
||||||
|
|
||||||
links.title=\u53cb\u93c8
|
|
||||||
links.subtitle=\u5c71\u6c34\u4e00\u7a0b\uff0c\u4e09\u751f\u6709\u5e78
|
|
||||||
|
|
||||||
moments.title=\u52d5\u614b
|
|
||||||
moments.noMoments=\u9084\u6c92\u6709\u77ac\u9593\uff0c\u5feb\u53bb\u8a18\u9304\u7576\u4e0b\u7684\u5fc3\u60c5\u5427 \U0001f319
|
|
||||||
|
|
||||||
photos.title=\u76f8\u7c3f
|
|
||||||
|
|
||||||
friends.title=\u53cb\u4eba\u5708
|
|
||||||
|
|
||||||
equipment.title=\u88dd\u5099
|
|
||||||
|
|
||||||
steam.title=Steam
|
|
||||||
steam.homepage=Steam \u4e3b\u9801
|
|
||||||
steam.level=\u7b49\u7d1a
|
|
||||||
steam.ownedGames=\u64c1\u6709\u904a\u6232
|
|
||||||
steam.totalPlaytime=\u7e3d\u904a\u73a9\u6642\u9577
|
|
||||||
steam.recentPlaytime=\u8fd1\u5169\u9031
|
|
||||||
steam.badges=\u5fbd\u7ae0
|
|
||||||
steam.hours=\u5c0f\u6642
|
|
||||||
steam.recentlyPlayed=\u6700\u8fd1\u904a\u73a9
|
|
||||||
steam.gameLibrary=\u904a\u6232\u5eab
|
|
||||||
steam.lastPlayed=\u6700\u5f8c\u904a\u73a9 {0}
|
|
||||||
|
|
||||||
douban.title=\u8c46\u74e3
|
|
||||||
douban.wish=\u60f3\u770b
|
|
||||||
douban.done=\u770b\u904e
|
|
||||||
douban.wishRead=\u60f3\u8b80
|
|
||||||
douban.reading=\u5728\u8b80
|
|
||||||
douban.listened=\u807d\u904e
|
|
||||||
douban.wishPlay=\u60f3\u73a9
|
|
||||||
douban.played=\u73a9\u904e
|
|
||||||
douban.wishListen=\u60f3\u807d
|
|
||||||
douban.listening=\u5728\u807d
|
|
||||||
douban.playing=\u5728\u73a9
|
|
||||||
douban.myRating=\u6211\u7684\uff1a
|
|
||||||
|
|
||||||
messageboard.title=\u7559\u8a00\u677f
|
|
||||||
messageboard.subtitle=\u5728\u9019\u88cf\u7559\u4e0b\u4f60\u7684\u8db3\u8de1 \U0001f319
|
|
||||||
|
|
||||||
footer.poweredBy=Powered by Halo
|
|
||||||
|
|
||||||
error.404.title=\u9801\u9762\u672a\u627e\u5230
|
|
||||||
error.404.desc=\u4f60\u5c0b\u627e\u7684\u9801\u9762\u4f3c\u4e4e\u5df2\u7d93\u8ff7\u8def\u4e86
|
|
||||||
error.404.home=\u8fd4\u56de\u9996\u9801
|
|
||||||
error.404.back=\u8fd4\u56de\u4e0a\u9801
|
|
||||||
error.500.title=\u4f3a\u670d\u5668\u932f\u8aa4
|
|
||||||
error.500.desc=\u5c0f\u5cf6\u9047\u5230\u4e86\u4e00\u4e9b\u554f\u984c\uff0c\u8acb\u7a0d\u5f8c\u518d\u8a66
|
|
||||||
error.500.home=\u8fd4\u56de\u9996\u9801
|
|
||||||
error.500.back=\u8fd4\u56de\u4e0a\u9801
|
|
||||||
|
|
||||||
sidebar.authorInfo=\u535a\u4e3b\u8cc7\u8a0a
|
|
||||||
sidebar.latestPosts=\u6700\u65b0\u6587\u7ae0
|
|
||||||
sidebar.recommendedPosts=\u63a8\u85a6\u6587\u7ae0
|
|
||||||
sidebar.timeProgress=\u6642\u5149\u9032\u5ea6
|
|
||||||
sidebar.dayProgress=\u65e5
|
|
||||||
sidebar.monthProgress=\u6708
|
|
||||||
sidebar.yearProgress=\u5e74
|
|
||||||
sidebar.categories=\u5206\u985e
|
|
||||||
sidebar.tags=\u6a19\u7c64
|
|
||||||
|
|
||||||
comment.style=\u6eab\u6696
|
|
||||||
comment.showAvatar=\u986f\u793a\u982d\u50cf
|
|
||||||
+4
-13
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.1.1",
|
"version": "1.2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.12.0"
|
"node": ">=22.12.0"
|
||||||
@@ -23,24 +23,15 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"pnpm": {
|
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"@parcel/watcher",
|
|
||||||
"sharp"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/vue": "^6.0.1",
|
|
||||||
"astro": "^6.1.4",
|
"astro": "^6.1.4",
|
||||||
"lightgallery": "^2.9.0",
|
"lightgallery": "^2.9.0"
|
||||||
"vue": "^3.5.32"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/mingcute": "^1.2.7",
|
"@halo-dev/theme-package-cli": "^1.1.1",
|
||||||
"nodemon": "^3.1.14",
|
"nodemon": "^3.1.14",
|
||||||
"prettier": "3.8.1",
|
"prettier": "3.8.1",
|
||||||
"prettier-plugin-astro": "0.14.1",
|
"prettier-plugin-astro": "0.14.1",
|
||||||
"sass": "^1.87.0",
|
"sass": "^1.87.0"
|
||||||
"unplugin-icons": "^23.0.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+682
-1061
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
allowBuilds:
|
allowBuilds:
|
||||||
'@parcel/watcher': true
|
"@parcel/watcher": true
|
||||||
esbuild: true
|
esbuild: true
|
||||||
sharp: true
|
sharp: true
|
||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
|
|||||||
@@ -1,455 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="zh-cn">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>字体分包构建报告</title>
|
|
||||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://fastly.jsdelivr.net/npm/vant@4.9.15/lib/index.css"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
href="https://unpkg.com/vue3-easy-data-table@1.5.47/dist/style.css"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<link rel="stylesheet" href="./result.css" />
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
font-size: 18px;
|
|
||||||
background-color: #edf0f3;
|
|
||||||
--easy-table-border: 0;
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
padding: 20px;
|
|
||||||
margin-top: var(--van-nav-bar-height);
|
|
||||||
margin-bottom: var(--van-tabbar-height);
|
|
||||||
}
|
|
||||||
.light-blue {
|
|
||||||
--easy-table-body-row-font-color: #427cb7;
|
|
||||||
}
|
|
||||||
.light-red {
|
|
||||||
--easy-table-body-row-font-color: #b74273;
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
color: gray;
|
|
||||||
margin: 1rem;
|
|
||||||
}
|
|
||||||
.scroller {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user {
|
|
||||||
height: 32%;
|
|
||||||
padding: 0 12px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app">
|
|
||||||
<van-nav-bar
|
|
||||||
:title="reporter.state?.css?.family +' 分包报告'"
|
|
||||||
right-text="Github"
|
|
||||||
@click-right="toGithub"
|
|
||||||
style="position: fixed; width: 100%; top: 0"
|
|
||||||
></van-nav-bar>
|
|
||||||
<div v-if="reporter.isLoading">加载中</div>
|
|
||||||
<div v-if="reporter.isError">加载错误</div>
|
|
||||||
|
|
||||||
<main :style="style">
|
|
||||||
<van-cell-group inset title="样式控制">
|
|
||||||
<van-field
|
|
||||||
v-model="fontSize"
|
|
||||||
label="字体大小"
|
|
||||||
placeholder="请输入字体大小"
|
|
||||||
></van-field>
|
|
||||||
<van-field
|
|
||||||
v-model="fontWeight"
|
|
||||||
label="字体重量"
|
|
||||||
placeholder="请输入字重"
|
|
||||||
></van-field>
|
|
||||||
</van-cell-group>
|
|
||||||
<!-- 汇总信息 -->
|
|
||||||
<display-chart
|
|
||||||
v-if="reporter.isOk() && activePage === 0"
|
|
||||||
:reporter="reporter.state"
|
|
||||||
></display-chart>
|
|
||||||
<!-- 字体信息表格 -->
|
|
||||||
<name-table
|
|
||||||
v-if="reporter.isOk() && activePage === 1"
|
|
||||||
:reporter="reporter.state"
|
|
||||||
></name-table>
|
|
||||||
<!-- 单包查询 -->
|
|
||||||
<pkg-list
|
|
||||||
v-if="reporter.isOk() && activePage === 2"
|
|
||||||
:reporter="reporter.state"
|
|
||||||
></pkg-list>
|
|
||||||
<div class="hint">
|
|
||||||
<a href="https://chinese-font.netlify.app">中文网字计划</a>
|
|
||||||
<span> cn-font-split 生成 </span>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- 底部导航栏 -->
|
|
||||||
<van-tabbar v-model="activePage">
|
|
||||||
<van-tabbar-item icon="home-o">汇总</van-tabbar-item>
|
|
||||||
<van-tabbar-item icon="home-o">字体信息</van-tabbar-item>
|
|
||||||
<van-tabbar-item icon="search">分包</van-tabbar-item>
|
|
||||||
</van-tabbar>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
<script src="https://unpkg.com/vue@3.4.33"></script>
|
|
||||||
<script src="https://unpkg.com/vant@4/lib/vant.min.js"></script>
|
|
||||||
<script src="https://unpkg.com/vue3-easy-data-table@1.5.47/dist/vue3-easy-data-table.umd.js"></script>
|
|
||||||
<script src="https://unpkg.com/echarts@5.5.1"></script>
|
|
||||||
<script src="https://unpkg.com/vue-echarts@7.0.3"></script>
|
|
||||||
<script src="https://unpkg.com/protobufjs@7.4.0/dist/protobuf.min.js"></script>
|
|
||||||
<script>
|
|
||||||
const { createApp } = Vue;
|
|
||||||
const app = createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
reporter: PromiseToState(loadReport),
|
|
||||||
activePage: 0,
|
|
||||||
fontWeight: '',
|
|
||||||
fontSize: 16,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
toGithub() {
|
|
||||||
window.open('https://github.com/KonghaYao/cn-font-split');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
const state = await this.reporter.refetch();
|
|
||||||
this.fontWeight = state.css.weight;
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
style() {
|
|
||||||
const size = this.fontSize < 12 ? 12 : this.fontSize;
|
|
||||||
return (
|
|
||||||
`font-family: '${this.reporter.state?.css?.family}';` +
|
|
||||||
`font-weight: ${this.fontWeight};` +
|
|
||||||
`--van-cell-font-size: ${size}px;`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 封装Promise为状态对象 **/
|
|
||||||
function PromiseToState(fn) {
|
|
||||||
return {
|
|
||||||
state: {},
|
|
||||||
isOk() {
|
|
||||||
return !this.isLoading && !this.isError && this.state;
|
|
||||||
},
|
|
||||||
isLoading: false,
|
|
||||||
isError: false,
|
|
||||||
async refetch() {
|
|
||||||
this.isLoading = true;
|
|
||||||
this.isError = false;
|
|
||||||
try {
|
|
||||||
this.state = await fn();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
this.isError = true;
|
|
||||||
}
|
|
||||||
this.isLoading = false;
|
|
||||||
return this.state;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 载入构建出来的二进制报告文件 **/
|
|
||||||
async function loadReport() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
protobuf.load('./index.proto', (err, root) => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
fetch('./reporter.bin')
|
|
||||||
.then((res) => res.arrayBuffer())
|
|
||||||
.then((buf) => {
|
|
||||||
const OutputReport = root.lookup('OutputReport');
|
|
||||||
const data = OutputReport.decode(
|
|
||||||
new Uint8Array(buf),
|
|
||||||
);
|
|
||||||
console.log(data);
|
|
||||||
resolve(data);
|
|
||||||
})
|
|
||||||
.catch(reject);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
globalThis.app = app;
|
|
||||||
app.use(vant);
|
|
||||||
app.component('v-chart', VueECharts);
|
|
||||||
app.component('easy-data-table', window['vue3-easy-data-table']);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- 汇总数据 -->
|
|
||||||
<template id="display-chart">
|
|
||||||
<van-cell-group inset title="构建信息">
|
|
||||||
<van-cell title="构建工具" value="cn-font-split"></van-cell>
|
|
||||||
<van-cell title="构建版本" :value="reporter.version"></van-cell>
|
|
||||||
<van-cell title="构建平台" :value="reporter.platform"></van-cell>
|
|
||||||
</van-cell-group>
|
|
||||||
<van-cell-group inset title="构建详情">
|
|
||||||
<div style="height: 300px; width: 400px">
|
|
||||||
<v-chart :option="pieOptions"></v-chart>
|
|
||||||
</div>
|
|
||||||
<div style="height: 400px; width: 100%">
|
|
||||||
<v-chart :option="option"></v-chart>
|
|
||||||
</div>
|
|
||||||
</van-cell-group>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
const tooltip = {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'cross',
|
|
||||||
crossStyle: {
|
|
||||||
color: '#999',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
app.component('display-chart', {
|
|
||||||
template: '#display-chart',
|
|
||||||
props: {
|
|
||||||
reporter: Object,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
option() {
|
|
||||||
const data = this.reporter.subsetDetail ?? [];
|
|
||||||
return {
|
|
||||||
title: {
|
|
||||||
text: '构建耗时',
|
|
||||||
x: 'center',
|
|
||||||
},
|
|
||||||
xAxis: { type: 'category' },
|
|
||||||
tooltip,
|
|
||||||
yAxis: [
|
|
||||||
{
|
|
||||||
type: 'value',
|
|
||||||
|
|
||||||
name: 'time / ms',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'value',
|
|
||||||
name: 'bytes / KB',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'time',
|
|
||||||
type: 'bar',
|
|
||||||
data: data.map((i) => i.duration),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bytes',
|
|
||||||
type: 'line',
|
|
||||||
data: data.map((i) =>
|
|
||||||
(i.bytes / 1024).toFixed(2),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
pieOptions() {
|
|
||||||
const message = this.reporter.bundleMessage;
|
|
||||||
if (!message) return {};
|
|
||||||
return {
|
|
||||||
title: {
|
|
||||||
text: '构建大小对比',
|
|
||||||
x: 'center',
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'pie',
|
|
||||||
radius: ['45%', '60%'],
|
|
||||||
label: {
|
|
||||||
formatter(b, c) {
|
|
||||||
return `${b.name}\n${(
|
|
||||||
b.value /
|
|
||||||
(1024 * 1024)
|
|
||||||
).toFixed(2)}MB`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
value: message.bundledBytes,
|
|
||||||
name: 'bundled',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: message.originBytes,
|
|
||||||
name: 'origin',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'pie',
|
|
||||||
radius: [0, '30%'],
|
|
||||||
label: {
|
|
||||||
formatter(b, c) {
|
|
||||||
return `${b.name}\n${b.value}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
value: message.bundledSize,
|
|
||||||
name: 'bundled',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: message.originSize,
|
|
||||||
name: 'origin',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- 字体信息表格 -->
|
|
||||||
<template id="name-table">
|
|
||||||
<van-cell-group inset title="CSS 信息">
|
|
||||||
<van-cell
|
|
||||||
title="font-family"
|
|
||||||
:value="reporter.css?.family"
|
|
||||||
></van-cell>
|
|
||||||
<van-cell
|
|
||||||
title="font-weight"
|
|
||||||
:value="reporter.css?.weight"
|
|
||||||
></van-cell>
|
|
||||||
<van-cell
|
|
||||||
title="font-style"
|
|
||||||
:value="reporter.css?.style"
|
|
||||||
></van-cell>
|
|
||||||
<van-cell
|
|
||||||
title="font-display"
|
|
||||||
:value="reporter.css?.display"
|
|
||||||
></van-cell>
|
|
||||||
</van-cell-group>
|
|
||||||
<van-cell-group inset title="字体信息">
|
|
||||||
<easy-data-table
|
|
||||||
:headers="columns"
|
|
||||||
:items="reporter.nameTable??[]"
|
|
||||||
:rows-per-page="1000"
|
|
||||||
:body-row-class-name="useColor"
|
|
||||||
></easy-data-table>
|
|
||||||
</van-cell-group>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
app.component('name-table', {
|
|
||||||
template: '#name-table',
|
|
||||||
props: {
|
|
||||||
reporter: Object,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
color: {
|
|
||||||
Windows: 'light-blue',
|
|
||||||
Macintosh: 'light-red',
|
|
||||||
},
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
text: '语言',
|
|
||||||
value: 'language',
|
|
||||||
sortable: true,
|
|
||||||
|
|
||||||
minWidth: '36px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '平台',
|
|
||||||
value: 'platform',
|
|
||||||
sortable: true,
|
|
||||||
|
|
||||||
minWidth: '20px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '名称',
|
|
||||||
value: 'name',
|
|
||||||
sortable: true,
|
|
||||||
minWidth: '50px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '值',
|
|
||||||
value: 'value',
|
|
||||||
sortable: true,
|
|
||||||
width: '200px',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
useColor(item) {
|
|
||||||
return this.color[item.platform];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- 每个包的数据 -->
|
|
||||||
<template id="pkg-list">
|
|
||||||
<van-cell-group inset>
|
|
||||||
<van-field
|
|
||||||
v-model="search"
|
|
||||||
label="搜索"
|
|
||||||
placeholder="请输入关键字"
|
|
||||||
></van-field>
|
|
||||||
</van-cell-group>
|
|
||||||
|
|
||||||
<van-cell-group inset title="字体分包详情">
|
|
||||||
<v-list
|
|
||||||
ref="list"
|
|
||||||
:items="items"
|
|
||||||
:first-render="10"
|
|
||||||
style="height: 80vh"
|
|
||||||
>
|
|
||||||
<template #default="{ item, index }">
|
|
||||||
<div style="padding: 0 1rem">
|
|
||||||
<h2 :style="{ width: item.width }">
|
|
||||||
ITEM: {{ index }} - {{ item.hash }}
|
|
||||||
</h2>
|
|
||||||
<span> {{String.fromCodePoint(...item.chars)}} </span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</v-list>
|
|
||||||
</van-cell-group>
|
|
||||||
</template>
|
|
||||||
<script src="https://unpkg.com/@virtual-list/vue/dist/v3/index.iife.js"></script>
|
|
||||||
<script>
|
|
||||||
app.component('v-list', virtualListVue);
|
|
||||||
app.component('pkg-list', {
|
|
||||||
template: '#pkg-list',
|
|
||||||
props: {
|
|
||||||
reporter: Object,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
activeNames: [],
|
|
||||||
search: '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {},
|
|
||||||
computed: {
|
|
||||||
items() {
|
|
||||||
const list = this.reporter.subsetDetail ?? [];
|
|
||||||
if (this.search) {
|
|
||||||
return list.filter((i) => i.hash.includes(this.search));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
app.mount('#app');
|
|
||||||
</script>
|
|
||||||
</html>
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package api_interface;
|
|
||||||
|
|
||||||
message InputTemplate {
|
|
||||||
bytes input = 1; // ttf/woff2 文件的 buffer
|
|
||||||
optional string out_dir = 2; // 切割后放置文件的文件夹
|
|
||||||
|
|
||||||
// ====== 构建产物配置 ======
|
|
||||||
|
|
||||||
optional CssProperties css = 5; // CSS 配置
|
|
||||||
optional string target_type = 6; // 目标类型
|
|
||||||
repeated bytes subsets = 7; // 子集
|
|
||||||
optional int32 chunk_size = 9; // 包大小
|
|
||||||
optional float chunk_size_tolerance = 10; // 包大小容差
|
|
||||||
optional int32 max_allow_subsets_count = 11; // 最大允许子集数量
|
|
||||||
|
|
||||||
optional bool test_html = 13; // 是否生成测试 HTML
|
|
||||||
optional bool reporter = 14; // 是否生成 reporter.bin
|
|
||||||
optional PreviewImage preview_image = 15; // 预览图像
|
|
||||||
|
|
||||||
optional string rename_output_font = 18; // 重命名输出字体
|
|
||||||
optional string build_mode = 20; // TODO 构建模式
|
|
||||||
|
|
||||||
// ====== 预分包优化项配置 =======
|
|
||||||
|
|
||||||
optional bool language_areas = 8; // 是否进行语言区域优化
|
|
||||||
optional bool multi_threads = 21; // TODO 是否使用多线程
|
|
||||||
optional bool font_feature = 22; // 是否启用字体特性
|
|
||||||
optional bool reduce_mins = 23; // 是否减少最小分包,
|
|
||||||
optional bool auto_subset = 24; // 是否自动子集化
|
|
||||||
optional bool subset_remain_chars = 25; // 是否自动添加没有声明的字符
|
|
||||||
|
|
||||||
// CSS 属性配置
|
|
||||||
message CssProperties {
|
|
||||||
optional string font_family = 1; // 字体家族名称
|
|
||||||
optional string font_weight = 2; // 字体粗细
|
|
||||||
optional string font_style = 3; // 字体样式
|
|
||||||
optional string font_display = 4; // 字体显示方式
|
|
||||||
repeated string local_family = 5; // 本地字体家族名称
|
|
||||||
repeated PolyfillType polyfill = 6; // Polyfill 类型
|
|
||||||
|
|
||||||
// 注释属性
|
|
||||||
|
|
||||||
optional bool comment_base = 11; // 基础注释
|
|
||||||
optional bool comment_name_table = 12; // 名称表注释
|
|
||||||
optional bool comment_unicodes = 13; // Unicode 注释
|
|
||||||
optional bool compress = 8; // 是否压缩
|
|
||||||
optional string file_name = 9; // 文件名
|
|
||||||
}
|
|
||||||
|
|
||||||
// Polyfill 类型
|
|
||||||
message PolyfillType {
|
|
||||||
string name = 1; // 名称
|
|
||||||
string format = 2; // 格式
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预览图像
|
|
||||||
message PreviewImage {
|
|
||||||
string text = 1; // 会显示在 svg 中的文本
|
|
||||||
string name = 2; // svg 文件的名称
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EventName {
|
|
||||||
UNSPECIFIED = 0;
|
|
||||||
OUTPUT_DATA = 1; // "output_data"
|
|
||||||
END = 2; // "end"
|
|
||||||
}
|
|
||||||
|
|
||||||
message EventMessage {
|
|
||||||
EventName event = 1;
|
|
||||||
string message = 2;
|
|
||||||
optional bytes data = 3;
|
|
||||||
}
|
|
||||||
message MultiMessages {
|
|
||||||
repeated EventMessage messages = 1;
|
|
||||||
}
|
|
||||||
message OutputReport {
|
|
||||||
string version = 1; // 版本号
|
|
||||||
Css css = 2; // CSS相关信息,可以直接用
|
|
||||||
string platform = 3; // 平台信息,rust 的构建平台
|
|
||||||
|
|
||||||
BundleMessage bundle_message = 24; // 构建消息
|
|
||||||
repeated NameTable name_table = 25; // name 表信息
|
|
||||||
repeated SubsetDetail subset_detail = 26; // 子集详情
|
|
||||||
|
|
||||||
message NameTable {
|
|
||||||
string platform = 1; // 平台
|
|
||||||
string language = 2; // 语言
|
|
||||||
string name = 3; // 名称
|
|
||||||
string value = 4; // 值
|
|
||||||
}
|
|
||||||
|
|
||||||
message SubsetDetail {
|
|
||||||
uint32 id = 1; // ID
|
|
||||||
string hash = 2; // 哈希值
|
|
||||||
string file_name = 6; // 文件名称
|
|
||||||
uint32 bytes = 3; // 字节数
|
|
||||||
repeated uint32 chars = 4; // 字符数组
|
|
||||||
uint32 duration = 5; // 构建时间
|
|
||||||
}
|
|
||||||
|
|
||||||
message BundleMessage {
|
|
||||||
uint32 origin_size = 1; // 原始大小
|
|
||||||
uint32 bundled_size = 2; // 产物大小
|
|
||||||
uint32 origin_bytes = 3; // 原始字节数
|
|
||||||
uint32 bundled_bytes = 4; // 产物字节数
|
|
||||||
}
|
|
||||||
|
|
||||||
message Css {
|
|
||||||
string family = 1; // 字体家族
|
|
||||||
string style = 2; // 样式
|
|
||||||
string weight = 3; // 字重
|
|
||||||
string display = 4; // 显示方式
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
@@ -126,6 +126,5 @@
|
|||||||
<a class="wi-error__back" href="javascript:history.back()">返回上页</a>
|
<a class="wi-error__back" href="javascript:history.back()">返回上页</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>(function(){var lang=navigator.language||'';var locale=lang.startsWith('zh-TW')||lang.startsWith('zh-HK')?'zh_TW':lang.startsWith('zh')?'zh_CN':'en';var i18n={zh_CN:{title:'页面走丢了',desc:'你寻找的页面似乎不在这个小岛上',home:'回到首页',back:'返回上页',pageTitle:'页面走丢了'},zh_TW:{title:'頁面走丟了',desc:'你尋找的頁面似乎不在這個小島上',home:'回到首頁',back:'返回上頁',pageTitle:'頁面走丟了'},en:{title:'Page Not Found',desc:'The page you are looking for seems to have wandered off this island',home:'Back to Home',back:'Go Back',pageTitle:'Page Not Found'}};var t=i18n[locale];var el=document.querySelector('.wi-error__title');if(el)el.textContent=t.title;var desc=document.querySelector('.wi-error__desc');if(desc)desc.textContent=t.desc;var home=document.querySelector('.wi-error__home');if(home)home.textContent=t.home;var back=document.querySelector('.wi-error__back');if(back)back.textContent=t.back;document.title=t.pageTitle+' - WarmIsland';})();</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
@@ -126,6 +126,5 @@
|
|||||||
<a class="wi-error__back" href="javascript:history.back()">返回上页</a>
|
<a class="wi-error__back" href="javascript:history.back()">返回上页</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>(function(){var lang=navigator.language||'';var locale=lang.startsWith('zh-TW')||lang.startsWith('zh-HK')?'zh_TW':lang.startsWith('zh')?'zh_CN':'en';var i18n={zh_CN:{title:'服务器开小差了',desc:'小岛遇到了一些问题,稍后再来看看吧',home:'回到首页',back:'返回上页',pageTitle:'服务器开小差了'},zh_TW:{title:'伺服器開小差了',desc:'小島遇到了一些問題,稍後再來看看吧',home:'回到首頁',back:'返回上頁',pageTitle:'伺服器開小差了'},en:{title:'Server Error',desc:'The island encountered some issues, please try again later',home:'Back to Home',back:'Go Back',pageTitle:'Server Error'}};var t=i18n[locale];var el=document.querySelector('.wi-error__title');if(el)el.textContent=t.title;var desc=document.querySelector('.wi-error__desc');if(desc)desc.textContent=t.desc;var home=document.querySelector('.wi-error__home');if(home)home.textContent=t.home;var back=document.querySelector('.wi-error__back');if(back)back.textContent=t.back;document.title=t.pageTitle+' - WarmIsland';})();</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
<ul class="wi-post-feed" role="list">
|
|
||||||
<li th:each="post : ${posts.items}" class="wi-post-feed__item">
|
|
||||||
<a class="wi-post-feed__link" th:href="@{${post.status.permalink}}">
|
|
||||||
<div class="wi-post-feed__image-wrap" th:unless="${#strings.isEmpty(post.spec.cover)}">
|
|
||||||
<img
|
|
||||||
class="wi-post-feed__image"
|
|
||||||
th:src="${post.spec.cover}"
|
|
||||||
th:srcset="|${thumbnail.gen(post.spec.cover, 's')} 400w,
|
|
||||||
${thumbnail.gen(post.spec.cover, 'm')} 800w,
|
|
||||||
${thumbnail.gen(post.spec.cover, 'l')} 1200w|"
|
|
||||||
sizes="(max-width: 768px) 100vw, 400px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="wi-post-feed__body">
|
|
||||||
<p class="wi-post-feed__date" th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"></p>
|
|
||||||
<h2 class="wi-post-feed__title" th:text="${post.spec.title}"></h2>
|
|
||||||
<p class="wi-post-feed__excerpt" th:text="${post.status.excerpt}"></p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
+359
-15
@@ -28,20 +28,63 @@ spec:
|
|||||||
- $formkit: text
|
- $formkit: text
|
||||||
name: label_archives_title
|
name: label_archives_title
|
||||||
label: 归档页标题
|
label: 归档页标题
|
||||||
|
- group: welcome_popup
|
||||||
|
label: 欢迎弹窗
|
||||||
|
formSchema:
|
||||||
|
- $formkit: switch
|
||||||
|
name: welcome_popup_enabled
|
||||||
|
id: welcome_popup_enabled
|
||||||
|
label: 启用欢迎弹窗
|
||||||
|
value: false
|
||||||
|
help: 首页显示"欢迎来自 XX·XX·XX 的朋友"的浮动提示弹窗,基于访客 IP 地理定位
|
||||||
- $formkit: select
|
- $formkit: select
|
||||||
name: language
|
name: welcome_popup_position
|
||||||
label: 语言
|
id: welcome_popup_position
|
||||||
|
label: 弹窗位置
|
||||||
options:
|
options:
|
||||||
- label: 跟随浏览器
|
- label: 顶端漂窗(顶部居中)
|
||||||
value: auto
|
value: top-center
|
||||||
- label: 简体中文
|
- label: 右上
|
||||||
value: zh_CN
|
value: top-right
|
||||||
- label: 繁體中文
|
- label: 左上
|
||||||
value: zh_TW
|
value: top-left
|
||||||
- label: English
|
- label: 右下
|
||||||
value: en
|
value: bottom-right
|
||||||
value: auto
|
- label: 左下
|
||||||
help: 设置主题界面语言,选择"跟随浏览器"将根据浏览器语言自动匹配
|
value: bottom-left
|
||||||
|
value: top-center
|
||||||
|
if: "$get(welcome_popup_enabled).value === true"
|
||||||
|
- $formkit: number
|
||||||
|
name: welcome_popup_duration
|
||||||
|
id: welcome_popup_duration
|
||||||
|
label: 自动消失时间(秒)
|
||||||
|
value: 5
|
||||||
|
min: 3
|
||||||
|
max: 60
|
||||||
|
step: 1
|
||||||
|
help: 弹窗自动消失的秒数,范围 3-60
|
||||||
|
if: "$get(welcome_popup_enabled).value === true"
|
||||||
|
- $formkit: url
|
||||||
|
name: welcome_popup_api
|
||||||
|
id: welcome_popup_api
|
||||||
|
label: IP 定位主 API
|
||||||
|
value: "https://myip.ipip.net/json"
|
||||||
|
help: 返回 JSON 的 IP 定位主接口,默认 IPIP;留空使用默认值。支持 IPIP/腾讯/ipapi.co 等常见格式
|
||||||
|
if: "$get(welcome_popup_enabled).value === true"
|
||||||
|
- $formkit: url
|
||||||
|
name: welcome_popup_fallback_api
|
||||||
|
id: welcome_popup_fallback_api
|
||||||
|
label: IP 定位备选 API
|
||||||
|
value: "https://r.inews.qq.com/api/ip2city?otype=json"
|
||||||
|
help: 主接口失败时尝试的备选接口,默认腾讯 ip2city;留空则不使用备选
|
||||||
|
if: "$get(welcome_popup_enabled).value === true"
|
||||||
|
- $formkit: switch
|
||||||
|
name: welcome_popup_show_once
|
||||||
|
id: welcome_popup_show_once
|
||||||
|
label: 同会话仅显示一次
|
||||||
|
value: true
|
||||||
|
help: 开启后同一浏览器会话内不再重复弹出;关闭则每次访问首页都显示
|
||||||
|
if: "$get(welcome_popup_enabled).value === true"
|
||||||
- group: style
|
- group: style
|
||||||
label: 总体样式
|
label: 总体样式
|
||||||
formSchema:
|
formSchema:
|
||||||
@@ -56,10 +99,38 @@ spec:
|
|||||||
- label: 深色
|
- label: 深色
|
||||||
value: dark
|
value: dark
|
||||||
value: system
|
value: system
|
||||||
|
- $formkit: select
|
||||||
|
name: color_palette
|
||||||
|
label: 主题配色
|
||||||
|
options:
|
||||||
|
- label: 暖屿 · 赤陶(默认)
|
||||||
|
value: warm-island
|
||||||
|
- label: 抹茶
|
||||||
|
value: matcha
|
||||||
|
- label: 黛蓝
|
||||||
|
value: indigo
|
||||||
|
- label: 樱粉
|
||||||
|
value: sakura
|
||||||
|
- label: 暮紫
|
||||||
|
value: violet
|
||||||
|
- label: 海盐
|
||||||
|
value: sea
|
||||||
|
- label: 琥珀
|
||||||
|
value: amber
|
||||||
|
- label: 梅酒
|
||||||
|
value: plum
|
||||||
|
- label: 石墨
|
||||||
|
value: graphite
|
||||||
|
- label: 可拿铁
|
||||||
|
value: cocoa
|
||||||
|
- label: 天青
|
||||||
|
value: azure
|
||||||
|
value: warm-island
|
||||||
|
help: 整套官方预调配色(含深浅两套色板);下方的强调色留空时跟随所选配色,填写后将覆盖配色中的强调色
|
||||||
- $formkit: color
|
- $formkit: color
|
||||||
name: accent_color
|
name: accent_color
|
||||||
label: 强调色
|
label: 强调色
|
||||||
value: "#d4764e"
|
help: 自定义强调色;留空使用「主题配色」自带的强调色
|
||||||
- $formkit: select
|
- $formkit: select
|
||||||
name: border_radius
|
name: border_radius
|
||||||
label: 圆角风格
|
label: 圆角风格
|
||||||
@@ -99,9 +170,36 @@ spec:
|
|||||||
label: 滚动渐入
|
label: 滚动渐入
|
||||||
value: true
|
value: true
|
||||||
- $formkit: switch
|
- $formkit: switch
|
||||||
name: animation_cursor_glow
|
name: click_effect_enabled
|
||||||
label: 光感跟随
|
id: click_effect_enabled
|
||||||
|
label: 鼠标点击效果
|
||||||
value: false
|
value: false
|
||||||
|
help: 在页面上点击鼠标时触发的视觉特效
|
||||||
|
- $formkit: select
|
||||||
|
name: click_effect_type
|
||||||
|
id: click_effect_type
|
||||||
|
label: 点击效果类型
|
||||||
|
value: values
|
||||||
|
if: "$get(click_effect_enabled).value === true"
|
||||||
|
options:
|
||||||
|
- label: 关闭
|
||||||
|
value: off
|
||||||
|
- label: 社会主义核心价值观
|
||||||
|
value: values
|
||||||
|
- label: 随机彩色爱心
|
||||||
|
value: hearts
|
||||||
|
- label: 烟花
|
||||||
|
value: fireworks
|
||||||
|
- label: 烟花波纹
|
||||||
|
value: ripples
|
||||||
|
- label: 粒子
|
||||||
|
value: particles
|
||||||
|
- label: 星星
|
||||||
|
value: stars
|
||||||
|
- label: 星星拖尾
|
||||||
|
value: star-trail
|
||||||
|
- label: 粒子拖尾
|
||||||
|
value: particle-trail
|
||||||
- group: navbar
|
- group: navbar
|
||||||
label: 导航栏
|
label: 导航栏
|
||||||
formSchema:
|
formSchema:
|
||||||
@@ -116,6 +214,35 @@ spec:
|
|||||||
- label: Float
|
- label: Float
|
||||||
value: float
|
value: float
|
||||||
value: glass
|
value: glass
|
||||||
|
- $formkit: select
|
||||||
|
name: navbar_brand_type
|
||||||
|
id: navbar_brand_type
|
||||||
|
label: 左侧显示
|
||||||
|
options:
|
||||||
|
- label: 站点名称
|
||||||
|
value: title
|
||||||
|
- label: Logo
|
||||||
|
value: logo
|
||||||
|
value: title
|
||||||
|
- $formkit: attachment
|
||||||
|
name: navbar_logo
|
||||||
|
id: navbar_logo
|
||||||
|
label: 导航栏 Logo
|
||||||
|
help: 选择"Logo"模式后在此上传图片;留空则使用基础设置中的自定义 Logo
|
||||||
|
if: "$get(navbar_brand_type).value === 'logo'"
|
||||||
|
- $formkit: select
|
||||||
|
name: navbar_logo_radius
|
||||||
|
id: navbar_logo_radius
|
||||||
|
label: Logo 圆角
|
||||||
|
options:
|
||||||
|
- label: 直角
|
||||||
|
value: square
|
||||||
|
- label: 圆角
|
||||||
|
value: rounded
|
||||||
|
- label: 圆形
|
||||||
|
value: circle
|
||||||
|
value: rounded
|
||||||
|
if: "$get(navbar_brand_type).value === 'logo'"
|
||||||
- $formkit: switch
|
- $formkit: switch
|
||||||
name: navbar_show_search
|
name: navbar_show_search
|
||||||
label: 显示搜索按钮
|
label: 显示搜索按钮
|
||||||
@@ -188,9 +315,79 @@ spec:
|
|||||||
label: 自定义描述文案
|
label: 自定义描述文案
|
||||||
key: hero_custom_description
|
key: hero_custom_description
|
||||||
if: "$get(hero_description_mode).value === 'custom'"
|
if: "$get(hero_description_mode).value === 'custom'"
|
||||||
|
- $formkit: switch
|
||||||
|
name: hero_hitokoto_auto_refresh
|
||||||
|
id: hero_hitokoto_auto_refresh
|
||||||
|
label: 一言自动刷新
|
||||||
|
value: false
|
||||||
|
help: 开启后按设定间隔自动刷新一言内容
|
||||||
|
- $formkit: select
|
||||||
|
name: hero_hitokoto_refresh_interval
|
||||||
|
label: 一言刷新间隔
|
||||||
|
if: "$get(hero_hitokoto_auto_refresh).value === true"
|
||||||
|
options:
|
||||||
|
- label: 5 秒
|
||||||
|
value: 5000
|
||||||
|
- label: 10 秒
|
||||||
|
value: 10000
|
||||||
|
- label: 15 秒
|
||||||
|
value: 15000
|
||||||
|
- label: 20 秒
|
||||||
|
value: 20000
|
||||||
|
- label: 25 秒
|
||||||
|
value: 25000
|
||||||
|
- label: 30 秒
|
||||||
|
value: 30000
|
||||||
|
value: 10000
|
||||||
- $formkit: attachment
|
- $formkit: attachment
|
||||||
name: hero_background_image
|
name: hero_background_image
|
||||||
label: 背景图片
|
label: 背景图片
|
||||||
|
- $formkit: attachment
|
||||||
|
name: hero_background_image_dark
|
||||||
|
label: 深色模式背景图片
|
||||||
|
help: 深色模式下显示的背景图;留空则深色模式也使用上方背景图
|
||||||
|
- $formkit: color
|
||||||
|
name: hero_title_color_light
|
||||||
|
label: 浅色模式大标题颜色
|
||||||
|
help: Hero 大标题文字颜色(浅色模式);留空使用主题默认
|
||||||
|
- $formkit: color
|
||||||
|
name: hero_title_color_dark
|
||||||
|
label: 深色模式大标题颜色
|
||||||
|
help: Hero 大标题文字颜色(深色模式);留空使用主题默认
|
||||||
|
- $formkit: color
|
||||||
|
name: hero_subtitle_color_light
|
||||||
|
label: 浅色模式副标题颜色
|
||||||
|
help: Hero 副标题文字颜色(浅色模式);留空使用主题默认
|
||||||
|
- $formkit: color
|
||||||
|
name: hero_subtitle_color_dark
|
||||||
|
label: 深色模式副标题颜色
|
||||||
|
help: Hero 副标题文字颜色(深色模式);留空使用主题默认
|
||||||
|
- $formkit: color
|
||||||
|
name: hero_quote_color_light
|
||||||
|
label: 浅色模式一言颜色
|
||||||
|
help: 一言(描述文案来源为"一言"时)的文字颜色(浅色模式);留空使用主题默认
|
||||||
|
- $formkit: color
|
||||||
|
name: hero_quote_color_dark
|
||||||
|
label: 深色模式一言颜色
|
||||||
|
help: 一言的文字颜色(深色模式);留空使用主题默认
|
||||||
|
- $formkit: select
|
||||||
|
name: hero_text_effect
|
||||||
|
label: 文字显示效果
|
||||||
|
options:
|
||||||
|
- label: 无效果(默认)
|
||||||
|
value: none
|
||||||
|
- label: 阴影
|
||||||
|
value: shadow
|
||||||
|
- label: 光晕
|
||||||
|
value: glow
|
||||||
|
- label: 半透明遮罩层
|
||||||
|
value: scrim
|
||||||
|
- label: 渐变遮罩
|
||||||
|
value: gradient
|
||||||
|
- label: 容器(半透明边框圆角矩形)
|
||||||
|
value: frame
|
||||||
|
value: none
|
||||||
|
help: 为 Hero 文字(标题/副标题/一言)叠加的辅助可读效果;默认无效果。遮罩类与容器类会在文字背后垫一层衬底并增加留白
|
||||||
- group: home
|
- group: home
|
||||||
label: 首页
|
label: 首页
|
||||||
formSchema:
|
formSchema:
|
||||||
@@ -304,6 +501,16 @@ spec:
|
|||||||
- label: Cold
|
- label: Cold
|
||||||
value: cold
|
value: cold
|
||||||
value: warm
|
value: warm
|
||||||
|
- $formkit: switch
|
||||||
|
name: article_extra_spacing
|
||||||
|
label: 中英文混排自动加空格
|
||||||
|
value: true
|
||||||
|
help: 依赖「API 扩展包」插件,在服务端为正文自动添加中英文间距;插件未安装时自动跳过
|
||||||
|
- $formkit: switch
|
||||||
|
name: article_extra_highlight
|
||||||
|
label: 使用服务端代码高亮
|
||||||
|
value: false
|
||||||
|
help: 依赖「API 扩展包」全量版(含 JS 运行时),启用后由服务端渲染代码高亮并替代主题自带样式;如环境不支持请勿开启
|
||||||
- $formkit: select
|
- $formkit: select
|
||||||
name: article_toc_expand
|
name: article_toc_expand
|
||||||
label: 目录展开模式
|
label: 目录展开模式
|
||||||
@@ -371,6 +578,10 @@ spec:
|
|||||||
- label: Masonry
|
- label: Masonry
|
||||||
value: masonry
|
value: masonry
|
||||||
value: cards
|
value: cards
|
||||||
|
- $formkit: attachment
|
||||||
|
name: moments_cover
|
||||||
|
label: 顶部封面图
|
||||||
|
help: 朋友圈样式顶部的封面图片,建议尺寸 1500×500
|
||||||
- $formkit: select
|
- $formkit: select
|
||||||
name: moments_loading_mode
|
name: moments_loading_mode
|
||||||
label: 加载方式
|
label: 加载方式
|
||||||
@@ -418,6 +629,11 @@ spec:
|
|||||||
- group: links
|
- group: links
|
||||||
label: 友情链接
|
label: 友情链接
|
||||||
formSchema:
|
formSchema:
|
||||||
|
- $formkit: switch
|
||||||
|
name: links_apply_form_enabled
|
||||||
|
label: 显示友链申请表单
|
||||||
|
value: true
|
||||||
|
help: 在友链页底部展示访客申请表单;需链接管理插件开启「友链申请」功能,未开启时表单自动隐藏
|
||||||
- $formkit: text
|
- $formkit: text
|
||||||
name: links_page_title
|
name: links_page_title
|
||||||
label: 页面标题
|
label: 页面标题
|
||||||
@@ -432,12 +648,88 @@ spec:
|
|||||||
- label: Cards
|
- label: Cards
|
||||||
value: cards
|
value: cards
|
||||||
value: cards
|
value: cards
|
||||||
|
- $formkit: switch
|
||||||
|
name: links_show_comment
|
||||||
|
label: 显示评论
|
||||||
|
value: false
|
||||||
|
help: 默认关闭评论功能,开启后在友情链接页面底部显示评论区
|
||||||
|
- $formkit: switch
|
||||||
|
name: links_random_order
|
||||||
|
label: 随机排列
|
||||||
|
value: false
|
||||||
|
help: 开启后友情链接卡片随机排列顺序
|
||||||
|
- $formkit: switch
|
||||||
|
name: links_random_portal
|
||||||
|
label: 随机传送门按钮
|
||||||
|
value: true
|
||||||
|
help: 开启后页面右上角显示"随机传送"按钮,点击随机访问一位博友
|
||||||
|
- group: subscriptions
|
||||||
|
label: 订阅
|
||||||
|
formSchema:
|
||||||
|
- $formkit: text
|
||||||
|
name: subscriptions_page_title
|
||||||
|
label: 页面标题
|
||||||
|
placeholder: "订阅"
|
||||||
|
- $formkit: text
|
||||||
|
name: subscriptions_subtitle
|
||||||
|
label: 页面副标题
|
||||||
|
placeholder: "博友们的最新文章"
|
||||||
|
- $formkit: number
|
||||||
|
name: subscriptions_page_size
|
||||||
|
label: 每页显示数量
|
||||||
|
value: 20
|
||||||
|
min: 1
|
||||||
|
max: 100
|
||||||
|
help: 单页展示的订阅动态条数,范围 1-100
|
||||||
|
- $formkit: select
|
||||||
|
name: subscriptions_loading_mode
|
||||||
|
label: 加载方式
|
||||||
|
options:
|
||||||
|
- label: 分页
|
||||||
|
value: pagination
|
||||||
|
- label: 下滑加载
|
||||||
|
value: infinite_scroll
|
||||||
|
value: pagination
|
||||||
|
- $formkit: select
|
||||||
|
name: subscriptions_layout
|
||||||
|
label: 布局样式
|
||||||
|
options:
|
||||||
|
- label: 列表卡片
|
||||||
|
value: list
|
||||||
|
- label: 网格卡片
|
||||||
|
value: grid
|
||||||
|
value: list
|
||||||
|
help: 网格卡片布局一行显示3个,内容区域更宽
|
||||||
- group: friends
|
- group: friends
|
||||||
label: 朋友圈
|
label: 朋友圈
|
||||||
formSchema:
|
formSchema:
|
||||||
- $formkit: text
|
- $formkit: text
|
||||||
name: friends_page_title
|
name: friends_page_title
|
||||||
label: 页面标题
|
label: 页面标题
|
||||||
|
- group: weread
|
||||||
|
label: 微信读书
|
||||||
|
formSchema:
|
||||||
|
- $formkit: text
|
||||||
|
name: weread_page_title
|
||||||
|
label: 页面标题
|
||||||
|
placeholder: "书架"
|
||||||
|
help: 页面顶部标题与浏览器标签页标题;插件自带的标题区会自动隐藏以避免重复
|
||||||
|
- $formkit: text
|
||||||
|
name: weread_page_subtitle
|
||||||
|
label: 页面副标题
|
||||||
|
placeholder: "来自微信读书的同步藏书"
|
||||||
|
- group: important_dates
|
||||||
|
label: 记得
|
||||||
|
formSchema:
|
||||||
|
- $formkit: text
|
||||||
|
name: idates_page_title
|
||||||
|
label: 页面标题
|
||||||
|
placeholder: "记得"
|
||||||
|
help: 对应「记得」插件的前台页面 /important-dates;留空时使用插件自带的标题
|
||||||
|
- $formkit: text
|
||||||
|
name: idates_page_subtitle
|
||||||
|
label: 页面副标题
|
||||||
|
placeholder: "记着重要的日子、惦记的人,还有陪你跑的爱车"
|
||||||
- group: messageboard
|
- group: messageboard
|
||||||
label: 留言板
|
label: 留言板
|
||||||
formSchema:
|
formSchema:
|
||||||
@@ -469,9 +761,61 @@ spec:
|
|||||||
- $formkit: text
|
- $formkit: text
|
||||||
name: footer_copyright
|
name: footer_copyright
|
||||||
label: 版权信息(留空则使用默认格式 © 年份 站点标题)
|
label: 版权信息(留空则使用默认格式 © 年份 站点标题)
|
||||||
|
- $formkit: switch
|
||||||
|
name: footer_show_stats
|
||||||
|
id: footer_show_stats
|
||||||
|
label: 显示全站统计
|
||||||
|
value: true
|
||||||
|
help: 在页脚展示文章数、分类数、评论数、访问量等站点统计数据
|
||||||
|
- $formkit: checkbox
|
||||||
|
name: footer_stats_items
|
||||||
|
id: footer_stats_items
|
||||||
|
label: 统计项
|
||||||
|
if: "$get(footer_show_stats).value !== false"
|
||||||
|
options:
|
||||||
|
- label: 文章数
|
||||||
|
value: post
|
||||||
|
- label: 分类数
|
||||||
|
value: category
|
||||||
|
- label: 评论数
|
||||||
|
value: comment
|
||||||
|
- label: 访问量
|
||||||
|
value: visit
|
||||||
|
- label: 点赞数
|
||||||
|
value: upvote
|
||||||
|
- label: 总字数
|
||||||
|
value: word
|
||||||
|
value:
|
||||||
|
- post
|
||||||
|
- category
|
||||||
|
- comment
|
||||||
|
- visit
|
||||||
|
help: 总字数依赖「API 扩展包」插件,未安装时自动隐藏该项
|
||||||
|
- $formkit: switch
|
||||||
|
name: footer_show_icp
|
||||||
|
label: 显示 ICP 备案号
|
||||||
|
value: false
|
||||||
|
help: 开启后页脚显示 ICP 备案链接
|
||||||
- $formkit: text
|
- $formkit: text
|
||||||
name: footer_icp
|
name: footer_icp
|
||||||
label: ICP 备案号
|
label: ICP 备案号
|
||||||
|
help: 工信部ICP备案号,填写后页脚显示备案链接(指向 beian.miit.gov.cn)
|
||||||
|
if: "$get(footer_show_icp).value === true"
|
||||||
|
- $formkit: switch
|
||||||
|
name: footer_show_mps
|
||||||
|
label: 显示公网安备号
|
||||||
|
value: false
|
||||||
|
help: 开启后页脚显示公网安备图标和链接
|
||||||
|
- $formkit: text
|
||||||
|
name: footer_mps
|
||||||
|
label: 公网安备号
|
||||||
|
help: 公安部联网备案号,填写后页脚显示备案图标和链接(指向 beian.mps.gov.cn)
|
||||||
|
if: "$get(footer_show_mps).value === true"
|
||||||
|
- $formkit: text
|
||||||
|
name: footer_mps_url
|
||||||
|
label: 公网安备跳转链接
|
||||||
|
help: 留空时默认指向 https://beian.mps.gov.cn/ ,可填写其他备案查询链接
|
||||||
|
if: "$get(footer_show_mps).value === true"
|
||||||
- $formkit: repeater
|
- $formkit: repeater
|
||||||
name: footer_socials
|
name: footer_socials
|
||||||
label: 社交链接
|
label: 社交链接
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
<div id="wi-click-effect" th:if="${theme.config?.style?.click_effect_enabled == true}" th:attr="data-effect=${theme.config?.style?.click_effect_type}">
|
||||||
|
|
||||||
|
<script is:inline th:inline="none">
|
||||||
|
(function () {
|
||||||
|
var container = document.getElementById("wi-click-effect");
|
||||||
|
if (!container) return;
|
||||||
|
var effect = container.getAttribute("data-effect") || "off";
|
||||||
|
if (effect === "off" || !effect) return;
|
||||||
|
|
||||||
|
/* 社会主义核心价值观 */
|
||||||
|
var CORE_VALUES = [
|
||||||
|
"富强", "民主", "文明", "和谐",
|
||||||
|
"自由", "平等", "公正", "法治",
|
||||||
|
"爱国", "敬业", "诚信", "友善"
|
||||||
|
];
|
||||||
|
|
||||||
|
var RANDOM_COLORS = [
|
||||||
|
"#ff6b6b", "#f06595", "#cc5de8", "#845ef7",
|
||||||
|
"#5c7cfa", "#339af0", "#22b8cf", "#20c997",
|
||||||
|
"#51cf66", "#94d82d", "#fcc419", "#ff922b"
|
||||||
|
];
|
||||||
|
|
||||||
|
/* 创建单个 DOM 元素并加入舞台 */
|
||||||
|
function spawn(el, x, y) {
|
||||||
|
el.style.left = x + "px";
|
||||||
|
el.style.top = y + "px";
|
||||||
|
container.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 1. 社会主义核心价值观 ========== */
|
||||||
|
function spawnValueText(x, y) {
|
||||||
|
var el = document.createElement("span");
|
||||||
|
el.className = "wi-click-fx wi-click-fx--text";
|
||||||
|
el.textContent = CORE_VALUES[Math.floor(Math.random() * CORE_VALUES.length)];
|
||||||
|
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||||
|
spawn(el, x, y);
|
||||||
|
setTimeout(function () {
|
||||||
|
if (el.parentNode) el.parentNode.removeChild(el);
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 2. 随机彩色爱心 ========== */
|
||||||
|
function spawnHeart(x, y) {
|
||||||
|
var el = document.createElement("span");
|
||||||
|
el.className = "wi-click-fx wi-click-fx--heart";
|
||||||
|
el.innerHTML = "❤";
|
||||||
|
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||||
|
var dx = (Math.random() - 0.5) * 80;
|
||||||
|
spawn(el, x, y);
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
el.style.transform = "translate(" + dx + "px, -80px) scale(0.2)";
|
||||||
|
el.style.opacity = "0";
|
||||||
|
});
|
||||||
|
setTimeout(function () {
|
||||||
|
if (el.parentNode) el.parentNode.removeChild(el);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 3. 烟花 ========== */
|
||||||
|
function spawnFirework(x, y) {
|
||||||
|
var count = 16;
|
||||||
|
var color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
var el = document.createElement("span");
|
||||||
|
el.className = "wi-click-fx wi-click-fx--spark";
|
||||||
|
el.style.background = color;
|
||||||
|
el.style.boxShadow = "0 0 6px " + color;
|
||||||
|
var angle = (Math.PI * 2 * i) / count;
|
||||||
|
var dist = 40 + Math.random() * 30;
|
||||||
|
var dx = Math.cos(angle) * dist;
|
||||||
|
var dy = Math.sin(angle) * dist;
|
||||||
|
spawn(el, x, y);
|
||||||
|
requestAnimationFrame(function (e, tx, ty) {
|
||||||
|
return function () {
|
||||||
|
e.style.transform = "translate(" + tx + "px, " + ty + "px) scale(0)";
|
||||||
|
e.style.opacity = "0";
|
||||||
|
};
|
||||||
|
}(el, dx, dy));
|
||||||
|
(function (e) {
|
||||||
|
setTimeout(function () {
|
||||||
|
if (e.parentNode) e.parentNode.removeChild(e);
|
||||||
|
}, 800);
|
||||||
|
})(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 4. 烟花波纹 ========== */
|
||||||
|
function spawnRipple(x, y) {
|
||||||
|
var color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||||
|
for (var r = 0; r < 2; r++) {
|
||||||
|
var el = document.createElement("span");
|
||||||
|
el.className = "wi-click-fx wi-click-fx--ripple";
|
||||||
|
el.style.borderColor = color;
|
||||||
|
spawn(el, x, y);
|
||||||
|
(function (e, delay) {
|
||||||
|
setTimeout(function () {
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
e.style.transform = "translate(-50%, -50%) scale(4)";
|
||||||
|
e.style.opacity = "0";
|
||||||
|
});
|
||||||
|
}, delay);
|
||||||
|
setTimeout(function () {
|
||||||
|
if (e.parentNode) e.parentNode.removeChild(e);
|
||||||
|
}, delay + 700);
|
||||||
|
})(el, r * 150);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 5. 粒子 ========== */
|
||||||
|
function spawnParticles(x, y, n) {
|
||||||
|
n = n || 8;
|
||||||
|
for (var i = 0; i < n; i++) {
|
||||||
|
var el = document.createElement("span");
|
||||||
|
el.className = "wi-click-fx wi-click-fx--particle";
|
||||||
|
el.style.background = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||||
|
var dx = (Math.random() - 0.5) * 60;
|
||||||
|
var dy = (Math.random() - 0.5) * 60 - 20;
|
||||||
|
spawn(el, x, y);
|
||||||
|
requestAnimationFrame(function (e, tx, ty) {
|
||||||
|
return function () {
|
||||||
|
e.style.transform = "translate(" + tx + "px, " + ty + "px) scale(0)";
|
||||||
|
e.style.opacity = "0";
|
||||||
|
};
|
||||||
|
}(el, dx, dy));
|
||||||
|
(function (e) {
|
||||||
|
setTimeout(function () {
|
||||||
|
if (e.parentNode) e.parentNode.removeChild(e);
|
||||||
|
}, 700);
|
||||||
|
})(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 6. 星星 ========== */
|
||||||
|
function spawnStar(x, y) {
|
||||||
|
var el = document.createElement("span");
|
||||||
|
el.className = "wi-click-fx wi-click-fx--star";
|
||||||
|
el.innerHTML = "★";
|
||||||
|
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||||
|
var dx = (Math.random() - 0.5) * 50;
|
||||||
|
var dy = (Math.random() - 0.5) * 50 - 30;
|
||||||
|
spawn(el, x, y);
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
el.style.transform = "translate(" + dx + "px, " + dy + "px) scale(1.6)";
|
||||||
|
el.style.opacity = "0";
|
||||||
|
});
|
||||||
|
setTimeout(function () {
|
||||||
|
if (el.parentNode) el.parentNode.removeChild(el);
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 点击分发 */
|
||||||
|
var clickHandlers = {
|
||||||
|
"values": spawnValueText,
|
||||||
|
"hearts": spawnHeart,
|
||||||
|
"fireworks": spawnFirework,
|
||||||
|
"ripples": spawnRipple,
|
||||||
|
"particles": function (x, y) { spawnParticles(x, y, 10); },
|
||||||
|
"stars": spawnStar
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 拖尾效果 */
|
||||||
|
var trailHandlers = {
|
||||||
|
"star-trail": function (x, y) {
|
||||||
|
var el = document.createElement("span");
|
||||||
|
el.className = "wi-click-fx wi-click-fx--trail-star";
|
||||||
|
el.innerHTML = "★";
|
||||||
|
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||||
|
spawn(el, x, y);
|
||||||
|
setTimeout(function () {
|
||||||
|
if (el.parentNode) el.parentNode.removeChild(el);
|
||||||
|
}, 600);
|
||||||
|
},
|
||||||
|
"particle-trail": function (x, y) {
|
||||||
|
var el = document.createElement("span");
|
||||||
|
el.className = "wi-click-fx wi-click-fx--trail-particle";
|
||||||
|
el.style.background = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||||
|
spawn(el, x, y);
|
||||||
|
setTimeout(function () {
|
||||||
|
if (el.parentNode) el.parentNode.removeChild(el);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var isTrail = effect.indexOf("trail") !== -1;
|
||||||
|
|
||||||
|
if (isTrail) {
|
||||||
|
var handler = trailHandlers[effect];
|
||||||
|
if (!handler) return;
|
||||||
|
var lastX = 0, lastY = 0;
|
||||||
|
var throttle = false;
|
||||||
|
document.addEventListener("mousemove", function (e) {
|
||||||
|
lastX = e.clientX;
|
||||||
|
lastY = e.clientY;
|
||||||
|
if (throttle) return;
|
||||||
|
throttle = true;
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
throttle = false;
|
||||||
|
handler(lastX, lastY);
|
||||||
|
});
|
||||||
|
}, { passive: true });
|
||||||
|
} else {
|
||||||
|
var handler = clickHandlers[effect];
|
||||||
|
if (!handler) return;
|
||||||
|
document.addEventListener("click", function (e) {
|
||||||
|
handler(e.clientX, e.clientY);
|
||||||
|
}, { passive: true });
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
.wi-click-fx{position:fixed;pointer-events:none;will-change:transform,opacity
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-click-fx--text{font-size:18px;font-weight:700;transform:translate(-50%,-50%);opacity:0;animation:wi-fx-text 1.2s ease-out forwards
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-click-fx--heart{font-size:20px;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform 1s cubic-bezier(.16,1,.3,1),opacity 1s ease-out
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-click-fx--spark{width:6px;height:6px;border-radius:50%;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform .8s cubic-bezier(.16,1,.3,1),opacity .8s ease-out
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-click-fx--ripple{width:30px;height:30px;border:2px solid;border-radius:50%;transform:translate(-50%,-50%) scale(.5);opacity:1;transition:transform .7s ease-out,opacity .7s ease-out
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-click-fx--particle{width:8px;height:8px;border-radius:50%;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform .7s cubic-bezier(.16,1,.3,1),opacity .7s ease-out
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-click-fx--star{font-size:16px;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform .8s cubic-bezier(.16,1,.3,1),opacity .8s ease-out
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-click-fx--trail-star{font-size:12px;transform:translate(-50%,-50%) scale(1);opacity:.8;transition:opacity .6s ease-out,transform .6s ease-out
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-click-fx--trail-particle{width:6px;height:6px;border-radius:50%;transform:translate(-50%,-50%) scale(1);opacity:.8;transition:opacity .5s ease-out,transform .5s ease-out
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wi-fx-text{0%{opacity:0;transform:translate(-50%,-50%) scale(.5)}20%{opacity:1;transform:translate(-50%,-80%) scale(1.1)}to{opacity:0;transform:translate(-50%,-120%) scale(.9)}}
|
||||||
|
</style>
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
---
|
|
||||||
---
|
|
||||||
|
|
||||||
<div class="wi-cursor-glow" id="wi-cursor-glow"></div>
|
|
||||||
|
|
||||||
<script is:inline>
|
|
||||||
(function(){
|
|
||||||
var glowEl = document.getElementById("wi-cursor-glow");
|
|
||||||
if (!glowEl) return;
|
|
||||||
var rafId = 0;
|
|
||||||
document.addEventListener("mousemove", function(e) {
|
|
||||||
if (rafId) return;
|
|
||||||
rafId = requestAnimationFrame(function() {
|
|
||||||
glowEl.style.transform = "translate(calc(" + e.clientX + "px - 50%), calc(" + e.clientY + "px - 50%))";
|
|
||||||
glowEl.classList.add("wi-cursor-glow--visible");
|
|
||||||
rafId = 0;
|
|
||||||
});
|
|
||||||
}, { passive: true });
|
|
||||||
document.addEventListener("mouseleave", function() {
|
|
||||||
glowEl.classList.remove("wi-cursor-glow--visible");
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.wi-cursor-glow {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 400px;
|
|
||||||
height: 400px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: radial-gradient(
|
|
||||||
circle,
|
|
||||||
rgba(212, 118, 78, 0.06) 0%,
|
|
||||||
transparent 70%
|
|
||||||
);
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 9999;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
will-change: transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-cursor-glow--visible {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .wi-cursor-glow {
|
|
||||||
background: radial-gradient(
|
|
||||||
circle,
|
|
||||||
rgba(232, 149, 95, 0.04) 0%,
|
|
||||||
transparent 70%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
>
|
>
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<div class="wi-section__header">
|
<div class="wi-section__header">
|
||||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_featured_title ?: #messages.msg('common.featured')}">精选</h2>
|
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_featured_title ?: '精选'}">精选</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-featured">
|
<div class="wi-featured">
|
||||||
<a
|
<a
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<span
|
<span
|
||||||
class="wi-featured__pinned"
|
class="wi-featured__pinned"
|
||||||
th:if="${post.spec.pinned}"
|
th:if="${post.spec.pinned}"
|
||||||
th:text="#{hero.pinned}">Pinned</span>
|
th:text="'置顶'">置顶</span>
|
||||||
</div>
|
</div>
|
||||||
<h3
|
<h3
|
||||||
class="wi-featured__title"
|
class="wi-featured__title"
|
||||||
|
|||||||
+107
-6
@@ -1,19 +1,42 @@
|
|||||||
---
|
---
|
||||||
const THEME_VERSION = "1.0.0";
|
import { readFileSync } from "node:fs";
|
||||||
const today = new Date();
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
|
// 版本号直接读取 theme.yaml,避免与包版本不同步
|
||||||
|
function readThemeVersion() {
|
||||||
|
try {
|
||||||
|
const yaml = readFileSync(resolve(process.cwd(), "theme.yaml"), "utf-8");
|
||||||
|
return yaml.match(/^\s*version:\s*"?([^"\s]+)"?/m)?.[1] ?? "";
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const THEME_VERSION = readThemeVersion();
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer th:with="showPowered=${theme.config?.footer?.footer_show_powered != false}, showTheme=${theme.config?.footer?.footer_show_theme != false}, hideRight=${showPowered == false and showTheme == false}">
|
<footer
|
||||||
|
th:with="showPowered=${theme.config?.footer?.footer_show_powered != false}, showTheme=${theme.config?.footer?.footer_show_theme != false}, hideRight=${showPowered == false and showTheme == false}, icpText=${theme.config?.footer?.footer_icp}, mpsText=${theme.config?.footer?.footer_mps}, showIcp=${theme.config?.footer?.footer_show_icp == true and not #strings.isEmpty(#strings.trim(icpText ?: ''))}, showMps=${theme.config?.footer?.footer_show_mps == true and not #strings.isEmpty(#strings.trim(mpsText ?: ''))}"
|
||||||
|
>
|
||||||
<div class="footer-inner">
|
<div class="footer-inner">
|
||||||
<div class="footer-bottom" th:classappend="${hideRight} ? 'footer-bottom--center' : ''">
|
<div class="footer-bottom" th:classappend="${hideRight} ? 'footer-bottom--center' : ''">
|
||||||
<div class="footer-bottom__left">
|
<div class="footer-bottom__left">
|
||||||
<span class="footer-copyright" th:if="${theme.config?.footer?.footer_copyright}" th:text="${theme.config?.footer?.footer_copyright}"></span>
|
<span class="footer-copyright" th:if="${theme.config?.footer?.footer_copyright}" th:text="${theme.config?.footer?.footer_copyright}"></span>
|
||||||
<span class="footer-copyright" th:unless="${theme.config?.footer?.footer_copyright}">
|
<span class="footer-copyright" th:unless="${theme.config?.footer?.footer_copyright}">
|
||||||
© {today.getFullYear()}
|
© <th:block th:text="${#dates.format(#dates.createNow(), 'yyyy')}">2026</th:block>
|
||||||
<th:block th:text="${theme.config?.basic?.owner_name ?: site.title}"></th:block>
|
<th:block th:text="${theme.config?.basic?.owner_name ?: site.title}"></th:block>
|
||||||
</span>
|
</span>
|
||||||
<span class="footer-icp" th:if="${theme.config?.footer?.footer_icp}">
|
<span class="footer-icp" th:if="${showIcp}">
|
||||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer" th:text="${theme.config?.footer?.footer_icp}"></a>
|
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer" th:text="${#strings.trim(icpText)}"></a>
|
||||||
|
</span>
|
||||||
|
<span class="footer-mps" th:if="${showMps}" th:with="mpsUrl=${theme.config?.footer?.footer_mps_url ?: 'https://beian.mps.gov.cn/'}">
|
||||||
|
<a th:href="${mpsUrl}" target="_blank" rel="noopener noreferrer">
|
||||||
|
<svg class="footer-mps__icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M12 2L2 7v6c0 5.5 3.8 10.7 10 12 6.2-1.3 10-6.5 10-12V7l-10-5z"></path>
|
||||||
|
<path d="M9.5 12.5l2 2 3.5-4" stroke="#fff" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
</svg>
|
||||||
|
<span th:text="${#strings.trim(mpsText)}"></span>
|
||||||
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-socials" th:if="${theme.config?.footer?.footer_socials}">
|
<div class="footer-socials" th:if="${theme.config?.footer?.footer_socials}">
|
||||||
@@ -34,6 +57,39 @@ const today = new Date();
|
|||||||
<span th:if="${showTheme}">WarmIsland v{THEME_VERSION}</span>
|
<span th:if="${showTheme}">WarmIsland v{THEME_VERSION}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="footer-stats"
|
||||||
|
th:if="${theme.config?.footer?.footer_show_stats != false}"
|
||||||
|
th:with="stats=${siteStatsFinder.getStats()}, items=${theme.config?.footer?.footer_stats_items ?: {'post','category','comment','visit'}}"
|
||||||
|
>
|
||||||
|
<span class="footer-stat" th:if="${#lists.contains(items, 'post')}">
|
||||||
|
<span class="footer-stat__value" th:text="${stats.post ?: 0}">0</span>
|
||||||
|
<span class="footer-stat__label">文章</span>
|
||||||
|
</span>
|
||||||
|
<span class="footer-stat" th:if="${#lists.contains(items, 'category')}">
|
||||||
|
<span class="footer-stat__value" th:text="${stats.category ?: 0}">0</span>
|
||||||
|
<span class="footer-stat__label">分类</span>
|
||||||
|
</span>
|
||||||
|
<span class="footer-stat" th:if="${#lists.contains(items, 'comment')}">
|
||||||
|
<span class="footer-stat__value" th:text="${stats.comment ?: 0}">0</span>
|
||||||
|
<span class="footer-stat__label">评论</span>
|
||||||
|
</span>
|
||||||
|
<span class="footer-stat" th:if="${#lists.contains(items, 'visit')}">
|
||||||
|
<span class="footer-stat__value" th:text="${stats.visit ?: 0}">0</span>
|
||||||
|
<span class="footer-stat__label">访问</span>
|
||||||
|
</span>
|
||||||
|
<span class="footer-stat" th:if="${#lists.contains(items, 'upvote')}">
|
||||||
|
<span class="footer-stat__value" th:text="${stats.upvote ?: 0}">0</span>
|
||||||
|
<span class="footer-stat__label">点赞</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="footer-stat"
|
||||||
|
th:if="${#lists.contains(items, 'word') and pluginFinder.available('extra-api')}"
|
||||||
|
>
|
||||||
|
<span class="footer-stat__value" th:text="${extraApiStatsFinder.getPostWordCount()}">0</span>
|
||||||
|
<span class="footer-stat__label">字数</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div th:if="${theme.config?.footer?.footer_custom_html}" th:utext="${theme.config?.footer?.footer_custom_html}" class="footer-custom"></div>
|
<div th:if="${theme.config?.footer?.footer_custom_html}" th:utext="${theme.config?.footer?.footer_custom_html}" class="footer-custom"></div>
|
||||||
<div class="footer-injected">
|
<div class="footer-injected">
|
||||||
<halo:footer />
|
<halo:footer />
|
||||||
@@ -104,6 +160,51 @@ const today = new Date();
|
|||||||
color: var(--ink-2);
|
color: var(--ink-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer-mps a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
color: var(--ink-3);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-mps a:hover {
|
||||||
|
color: var(--ink-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-mps__icon {
|
||||||
|
flex: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-stats {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem 1.25rem;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--ink-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-stat {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-stat__value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ink-2);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-stat__label {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.footer-socials {
|
.footer-socials {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
---
|
---
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<style is:inline th:if="${theme.config?.hero?.hero_title_color_light != null and theme.config?.hero?.hero_title_color_light != ''}" th:utext="${' :root { --hero-title-text: ' + theme.config?.hero?.hero_title_color_light + '; } '}">/*hero-title-light*/</style>
|
||||||
|
<style is:inline th:if="${theme.config?.hero?.hero_title_color_dark != null and theme.config?.hero?.hero_title_color_dark != ''}" th:utext="${' html.dark { --hero-title-text: ' + theme.config?.hero?.hero_title_color_dark + '; } '}">/*hero-title-dark*/</style>
|
||||||
|
<style is:inline th:if="${theme.config?.hero?.hero_subtitle_color_light != null and theme.config?.hero?.hero_subtitle_color_light != ''}" th:utext="${' :root { --hero-subtitle-text: ' + theme.config?.hero?.hero_subtitle_color_light + '; } '}">/*hero-subtitle-light*/</style>
|
||||||
|
<style is:inline th:if="${theme.config?.hero?.hero_subtitle_color_dark != null and theme.config?.hero?.hero_subtitle_color_dark != ''}" th:utext="${' html.dark { --hero-subtitle-text: ' + theme.config?.hero?.hero_subtitle_color_dark + '; } '}">/*hero-subtitle-dark*/</style>
|
||||||
|
<style is:inline th:if="${theme.config?.hero?.hero_quote_color_light != null and theme.config?.hero?.hero_quote_color_light != ''}" th:utext="${' :root { --hero-quote-text: ' + theme.config?.hero?.hero_quote_color_light + '; } '}">/*hero-quote-light*/</style>
|
||||||
|
<style is:inline th:if="${theme.config?.hero?.hero_quote_color_dark != null and theme.config?.hero?.hero_quote_color_dark != ''}" th:utext="${' html.dark { --hero-quote-text: ' + theme.config?.hero?.hero_quote_color_dark + '; } '}">/*hero-quote-dark*/</style>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
class="hero"
|
class="hero"
|
||||||
id="wi-hero"
|
id="wi-hero"
|
||||||
th:if="${theme.config?.hero?.hero_enabled != false}"
|
th:if="${theme.config?.hero?.hero_enabled != false}"
|
||||||
th:classappend="${theme.config?.animation?.animation_breath == false} ? 'hero--no-breath'"
|
th:classappend="${(theme.config?.animation?.animation_breath == false ? 'hero--no-breath ' : '') + 'hero--fx-' + (theme.config?.hero?.hero_text_effect ?: 'none')}"
|
||||||
>
|
>
|
||||||
<div class="hero__bg">
|
<div class="hero__bg">
|
||||||
<div class="hero__orb hero__orb--1"></div>
|
<div class="hero__orb hero__orb--1"></div>
|
||||||
@@ -13,9 +20,24 @@
|
|||||||
<div class="hero__orb hero__orb--3"></div>
|
<div class="hero__orb hero__orb--3"></div>
|
||||||
<div
|
<div
|
||||||
class="hero__bg-image"
|
class="hero__bg-image"
|
||||||
th:if="${theme.config?.hero?.hero_background_image}"
|
th:if="${!#strings.isEmpty(theme.config?.hero?.hero_background_image) and #strings.isEmpty(theme.config?.hero?.hero_background_image_dark)}"
|
||||||
th:style="'background-image: url(' + ${theme.config?.hero?.hero_background_image} + ')'"
|
th:style="'background-image: url(' + ${theme.config?.hero?.hero_background_image} + ')'"
|
||||||
></div>
|
></div>
|
||||||
|
<div
|
||||||
|
class="hero__bg-image"
|
||||||
|
th:if="${#strings.isEmpty(theme.config?.hero?.hero_background_image) and !#strings.isEmpty(theme.config?.hero?.hero_background_image_dark)}"
|
||||||
|
th:style="'background-image: url(' + ${theme.config?.hero?.hero_background_image_dark} + ')'"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="hero__bg-image hero__bg-image--light"
|
||||||
|
th:if="${!#strings.isEmpty(theme.config?.hero?.hero_background_image) and !#strings.isEmpty(theme.config?.hero?.hero_background_image_dark)}"
|
||||||
|
th:style="'background-image: url(' + ${theme.config?.hero?.hero_background_image} + ')'"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="hero__bg-image hero__bg-image--dark"
|
||||||
|
th:if="${!#strings.isEmpty(theme.config?.hero?.hero_background_image) and !#strings.isEmpty(theme.config?.hero?.hero_background_image_dark)}"
|
||||||
|
th:style="'background-image: url(' + ${theme.config?.hero?.hero_background_image_dark} + ')'"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hero__blur-overlay" id="wi-hero-blur-overlay"></div>
|
<div class="hero__blur-overlay" id="wi-hero-blur-overlay"></div>
|
||||||
@@ -37,7 +59,7 @@
|
|||||||
class="hero__description"
|
class="hero__description"
|
||||||
th:if="${theme.config?.hero?.hero_description_mode == 'hitokoto'}"
|
th:if="${theme.config?.hero?.hero_description_mode == 'hitokoto'}"
|
||||||
id="wi-hitokoto"
|
id="wi-hitokoto"
|
||||||
th:attr="data-api=${theme.config?.hero?.hero_hitokoto_api ?: 'https://v1.hitokoto.cn/'},data-categories=${theme.config?.hero?.hero_hitokoto_categories}"
|
th:attr="data-api=${theme.config?.hero?.hero_hitokoto_api ?: 'https://v1.hitokoto.cn/'},data-categories=${theme.config?.hero?.hero_hitokoto_categories},data-auto-refresh=${theme.config?.hero?.hero_hitokoto_auto_refresh == true ? 'true' : 'false'},data-refresh-interval=${theme.config?.hero?.hero_hitokoto_refresh_interval ?: 10000}"
|
||||||
></p>
|
></p>
|
||||||
<p
|
<p
|
||||||
class="hero__description"
|
class="hero__description"
|
||||||
@@ -46,7 +68,12 @@
|
|||||||
></p>
|
></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hero__scroll-indicator" id="wi-hero-scroll-indicator" aria-hidden="true">
|
<button
|
||||||
|
class="hero__scroll-indicator"
|
||||||
|
id="wi-hero-scroll-indicator"
|
||||||
|
type="button"
|
||||||
|
aria-label="向下浏览"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
@@ -56,53 +83,113 @@
|
|||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<polyline points="6 9 12 15 18 9"></polyline>
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
|
/* Hero 下滑箭头:平滑滚动到首屏内容区 */
|
||||||
|
(function () {
|
||||||
|
var indicator = document.getElementById("wi-hero-scroll-indicator");
|
||||||
|
if (!indicator) return;
|
||||||
|
|
||||||
|
indicator.addEventListener("click", function () {
|
||||||
|
var content = document.querySelector(".wi-home-content");
|
||||||
|
var hero = document.getElementById("wi-hero");
|
||||||
|
var top;
|
||||||
|
|
||||||
|
if (content) {
|
||||||
|
/* 与首页滚动吸附逻辑保持一致:内容区顶部对齐视口顶部,完全盖住固定背景的 Hero */
|
||||||
|
top = content.getBoundingClientRect().top + window.pageYOffset;
|
||||||
|
} else if (hero) {
|
||||||
|
top = hero.offsetTop + hero.offsetHeight;
|
||||||
|
} else {
|
||||||
|
top = window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reduce =
|
||||||
|
window.matchMedia &&
|
||||||
|
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||||
|
|
||||||
|
window.scrollTo({
|
||||||
|
top: Math.max(0, top),
|
||||||
|
behavior: reduce ? "auto" : "smooth",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
var el = document.getElementById("wi-hitokoto");
|
var el = document.getElementById("wi-hitokoto");
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
var api = el.getAttribute("data-api") || "https://v1.hitokoto.cn/";
|
var api = el.getAttribute("data-api") || "https://v1.hitokoto.cn/";
|
||||||
var categories = el.getAttribute("data-categories");
|
var categories = el.getAttribute("data-categories");
|
||||||
var url = api + "?encode=json";
|
var autoRefresh = el.getAttribute("data-auto-refresh") === "true";
|
||||||
|
var refreshInterval = parseInt(el.getAttribute("data-refresh-interval"), 10) || 10000;
|
||||||
|
|
||||||
if (categories) {
|
function buildUrl() {
|
||||||
try {
|
var url = api + "?encode=json";
|
||||||
var cats = JSON.parse(categories);
|
if (categories) {
|
||||||
if (Array.isArray(cats)) {
|
try {
|
||||||
cats.forEach(function (c) {
|
var cats = JSON.parse(categories);
|
||||||
url += "&c=" + encodeURIComponent(c);
|
if (Array.isArray(cats)) {
|
||||||
});
|
cats.forEach(function (c) {
|
||||||
}
|
url += "&c=" + encodeURIComponent(c);
|
||||||
} catch (e) {}
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
el.style.opacity = "0.5";
|
function load(initial) {
|
||||||
el.textContent = "\u2026";
|
if (initial) {
|
||||||
|
el.style.opacity = "0.5";
|
||||||
|
el.textContent = "\u2026";
|
||||||
|
}
|
||||||
|
|
||||||
fetch(url)
|
return fetch(buildUrl())
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
el.style.opacity = "1";
|
var text = data.hitokoto || "";
|
||||||
var text = data.hitokoto || "";
|
var from = data.from || data.from_who || "";
|
||||||
var from = data.from || data.from_who || "";
|
if (from) {
|
||||||
if (from) {
|
text += " \u2014\u2014 " + from;
|
||||||
text += " \u2014\u2014 " + from;
|
}
|
||||||
}
|
|
||||||
el.textContent = text;
|
if (initial) {
|
||||||
})
|
el.style.opacity = "1";
|
||||||
.catch(function () {
|
el.textContent = text;
|
||||||
el.style.opacity = "1";
|
} else {
|
||||||
el.textContent = "";
|
/* 自动刷新:淡出 → 换文案 → 淡入,避免生硬跳变 */
|
||||||
});
|
el.style.opacity = "0";
|
||||||
|
setTimeout(function () {
|
||||||
|
el.textContent = text;
|
||||||
|
el.style.opacity = "1";
|
||||||
|
}, 280);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
// 失败时恢复可见:首次为空,刷新时保留原文案
|
||||||
|
el.style.opacity = "1";
|
||||||
|
if (initial) el.textContent = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
load(true);
|
||||||
|
|
||||||
|
// 自动刷新:仅在开启且间隔不小于 3 秒时启用,避免请求过于频繁
|
||||||
|
if (autoRefresh && refreshInterval >= 3000) {
|
||||||
|
setInterval(function () {
|
||||||
|
load(false);
|
||||||
|
}, refreshInterval);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -196,7 +283,17 @@
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
opacity: 0.15;
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深色模式背景图切换:未配置双图时不生效,保持单一背景图 */
|
||||||
|
.hero__bg-image--dark,
|
||||||
|
html.dark .hero__bg-image--light {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .hero__bg-image--dark {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero__content {
|
.hero__content {
|
||||||
@@ -216,36 +313,134 @@
|
|||||||
font-size: clamp(3rem, 8vw, 6rem);
|
font-size: clamp(3rem, 8vw, 6rem);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: -0.03em;
|
letter-spacing: -0.03em;
|
||||||
color: var(--ink);
|
color: var(--hero-title-text, var(--ink));
|
||||||
line-height: var(--leading-tight);
|
line-height: var(--leading-tight);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero__subtitle {
|
.hero__subtitle {
|
||||||
font-family: var(--font-body);
|
font-family: var(--font-body);
|
||||||
font-size: var(--text-xl);
|
font-size: var(--text-xl);
|
||||||
color: var(--ink-2);
|
color: var(--hero-subtitle-text, var(--ink-2));
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
line-height: var(--leading-normal);
|
line-height: var(--leading-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero__description {
|
.hero__description {
|
||||||
font-size: var(--text-lg);
|
font-size: var(--text-lg);
|
||||||
color: var(--ink-2);
|
color: var(--hero-quote-text, var(--ink-2));
|
||||||
line-height: var(--leading-relaxed);
|
line-height: var(--leading-relaxed);
|
||||||
max-width: 560px;
|
max-width: 560px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 文字显示效果(后台「Hero 首屏 → 文字显示效果」,默认 none 无任何附加效果) ===== */
|
||||||
|
.hero--fx-shadow .hero__title,
|
||||||
|
.hero--fx-shadow .hero__subtitle,
|
||||||
|
.hero--fx-shadow .hero__description {
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.22), 0 2px 10px rgba(0, 0, 0, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .hero--fx-shadow .hero__title,
|
||||||
|
html.dark .hero--fx-shadow .hero__subtitle,
|
||||||
|
html.dark .hero--fx-shadow .hero__description {
|
||||||
|
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6), 0 2px 12px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero--fx-glow .hero__title,
|
||||||
|
.hero--fx-glow .hero__subtitle,
|
||||||
|
.hero--fx-glow .hero__description {
|
||||||
|
text-shadow: 0 0 10px rgba(255, 255, 255, 0.85), 0 0 26px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .hero--fx-glow .hero__title,
|
||||||
|
html.dark .hero--fx-glow .hero__subtitle,
|
||||||
|
html.dark .hero--fx-glow .hero__description {
|
||||||
|
text-shadow: 0 0 8px rgba(255, 255, 255, 0.45), 0 0 24px rgba(255, 255, 255, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 半透明遮罩层:文字背后垫一块毛玻璃衬底 */
|
||||||
|
.hero--fx-scrim .hero__content {
|
||||||
|
background: rgba(255, 255, 255, 0.45);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: var(--space-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .hero--fx-scrim .hero__content {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 渐变遮罩:斜向渐变衬底 */
|
||||||
|
.hero--fx-gradient .hero__content {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(255, 255, 255, 0.55) 0%,
|
||||||
|
rgba(255, 255, 255, 0.18) 100%
|
||||||
|
);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: var(--space-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .hero--fx-gradient .hero__content {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(0, 0, 0, 0.5) 0%,
|
||||||
|
rgba(0, 0, 0, 0.16) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 容器:半透明边框圆角矩形 + 更大留白,把文字"装"进设计好的容器里 */
|
||||||
|
.hero--fx-frame .hero__content {
|
||||||
|
border: 1px solid color-mix(in srgb, var(--ink) 22%, transparent);
|
||||||
|
background: color-mix(in srgb, var(--bg) 30%, transparent);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: var(--space-2xl) var(--space-3xl);
|
||||||
|
gap: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .hero--fx-frame .hero__content {
|
||||||
|
border-color: color-mix(in srgb, #ffffff 32%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero--fx-frame .hero__title {
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero--fx-frame .hero__subtitle {
|
||||||
|
letter-spacing: 0.22em;
|
||||||
|
}
|
||||||
|
|
||||||
.hero__scroll-indicator {
|
.hero__scroll-indicator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 2rem;
|
bottom: 2rem;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: transparent;
|
||||||
color: var(--ink-3);
|
color: var(--ink-3);
|
||||||
|
cursor: pointer;
|
||||||
animation: scrollHint 2s ease-in-out infinite;
|
animation: scrollHint 2s ease-in-out infinite;
|
||||||
transition: opacity 0.4s ease;
|
transition: opacity 0.4s ease, color 0.2s ease, background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__scroll-indicator:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__scroll-indicator:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero--no-breath .hero__orb {
|
.hero--no-breath .hero__orb {
|
||||||
@@ -310,6 +505,16 @@
|
|||||||
gap: var(--space-md);
|
gap: var(--space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hero--fx-scrim .hero__content,
|
||||||
|
.hero--fx-gradient .hero__content {
|
||||||
|
padding: var(--space-xl) var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero--fx-frame .hero__content {
|
||||||
|
padding: var(--space-xl) var(--space-lg);
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
.hero__description {
|
.hero__description {
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<div class="wi-section__header">
|
<div class="wi-section__header">
|
||||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_latest_title ?: #messages.msg('common.latestPosts')}">Latest Posts</h2>
|
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_latest_title ?: '最新文章'}">最新文章</h2>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="wi-latest"
|
class="wi-latest"
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
th:if="${posts.hasPrevious()}"
|
th:if="${posts.hasPrevious()}"
|
||||||
th:href="@{${posts.prevUrl}}"
|
th:href="@{${posts.prevUrl}}"
|
||||||
class="wi-pagination__prev"
|
class="wi-pagination__prev"
|
||||||
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
|
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||||
>← Newer</a>
|
>← Newer</a>
|
||||||
<span
|
<span
|
||||||
class="wi-pagination__info"
|
class="wi-pagination__info"
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
th:if="${posts.hasNext()}"
|
th:if="${posts.hasNext()}"
|
||||||
th:href="@{${posts.nextUrl}}"
|
th:href="@{${posts.nextUrl}}"
|
||||||
class="wi-pagination__next"
|
class="wi-pagination__next"
|
||||||
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
|
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||||
>Older →</a>
|
>Older →</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
>
|
>
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<div class="wi-section__header">
|
<div class="wi-section__header">
|
||||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_message_wall_title ?: #messages.msg('messageboard.title')}">留言墙</h2>
|
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_message_wall_title ?: '留言板'}">留言墙</h2>
|
||||||
<span class="wi-section__accent"></span>
|
<span class="wi-section__accent"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,16 @@
|
|||||||
class="wi-mobile-menu__link"
|
class="wi-mobile-menu__link"
|
||||||
th:data-href="${menuItem.status.href}"
|
th:data-href="${menuItem.status.href}"
|
||||||
></a>
|
></a>
|
||||||
|
<th:block th:if="${not #lists.isEmpty(menuItem.children)}">
|
||||||
|
<a
|
||||||
|
th:each="child : ${menuItem.children}"
|
||||||
|
th:href="@{${child.status.href}}"
|
||||||
|
th:target="${child.spec?.target?.value == '_blank'} ? '_blank' : '_self'"
|
||||||
|
th:text="${'| ' + child.status.displayName}"
|
||||||
|
class="wi-mobile-menu__link wi-mobile-menu__link--sub"
|
||||||
|
th:data-href="${child.status.href}"
|
||||||
|
></a>
|
||||||
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
@@ -35,19 +45,19 @@
|
|||||||
onclick="SearchWidget.open()"
|
onclick="SearchWidget.open()"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||||
<span th:text="${theme.config?.basic?.label_search ?: #messages.msg('common.search')}">Search</span>
|
<span th:text="${theme.config?.basic?.label_search ?: '搜索'}">搜索</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="wi-mobile-menu__action-btn wi-mobile-menu__theme-btn" type="button" aria-label="Toggle theme">
|
<button class="wi-mobile-menu__action-btn wi-mobile-menu__theme-btn" type="button" aria-label="Toggle theme">
|
||||||
<svg class="wi-mobile-menu__icon-moon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
|
<svg class="wi-mobile-menu__icon-moon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
|
||||||
<svg class="wi-mobile-menu__icon-sun" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
|
<svg class="wi-mobile-menu__icon-sun" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
|
||||||
<span class="wi-mobile-menu__theme-label" th:text="${theme.config?.basic?.label_theme_switch ?: #messages.msg('common.themeSwitch')}">Switch Theme</span>
|
<span class="wi-mobile-menu__theme-label" th:text="${theme.config?.basic?.label_theme_switch ?: '切换主题'}">切换主题</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
const menu = document.getElementById("wi-mobile-menu");
|
const menu = document.getElementById("wi-mobile-menu");
|
||||||
const backdrop = menu?.querySelector(".wi-mobile-menu__backdrop");
|
const backdrop = menu?.querySelector(".wi-mobile-menu__backdrop");
|
||||||
const closeBtn = menu?.querySelector(".wi-mobile-menu__close");
|
const closeBtn = menu?.querySelector(".wi-mobile-menu__close");
|
||||||
@@ -213,6 +223,14 @@
|
|||||||
background var(--duration-fast) var(--ease-out-quart);
|
background var(--duration-fast) var(--ease-out-quart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 二级菜单项:缩进 + 弱化,与一级项区分 */
|
||||||
|
.wi-mobile-menu__link--sub {
|
||||||
|
padding-left: 2rem;
|
||||||
|
font-size: var(--text-base);
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--ink-3);
|
||||||
|
}
|
||||||
|
|
||||||
.wi-mobile-menu__link:hover {
|
.wi-mobile-menu__link:hover {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
background: var(--bg-raised);
|
background: var(--bg-raised);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const pageConfig = findPageBySlug("moments");
|
|||||||
<th:block th:if="${pluginFinder.available('PluginMoments')}">
|
<th:block th:if="${pluginFinder.available('PluginMoments')}">
|
||||||
<section class="wi-moments-section">
|
<section class="wi-moments-section">
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<h2 class="wi-moments-section__title" th:text="${pageConfig?.title ?: theme.config?.moments?.moments_title ?: #messages.msg('moments.title')}">瞬间</h2>
|
<h2 class="wi-moments-section__title" th:text="${pageConfig?.title ?: theme.config?.moments?.moments_title ?: '动态'}">瞬间</h2>
|
||||||
<div
|
<div
|
||||||
class="wi-moments-section__list"
|
class="wi-moments-section__list"
|
||||||
th:attr="data-style=${theme.config?.moments?.moments_style ?: 'timeline'}"
|
th:attr="data-style=${theme.config?.moments?.moments_style ?: 'timeline'}"
|
||||||
@@ -29,7 +29,7 @@ const pageConfig = findPageBySlug("moments");
|
|||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
<a th:href="@{'/moments'}" class="wi-moments-section__more">
|
<a th:href="@{'/moments'}" class="wi-moments-section__more">
|
||||||
<span th:text="${theme.config?.home?.home_label_view_all ?: #messages.msg('common.viewAll')}">查看全部</span>
|
<span th:text="${theme.config?.home?.home_label_view_all ?: '查看全部'}">查看全部</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
+133
-16
@@ -4,18 +4,22 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
|||||||
|
|
||||||
<header class="wi-navbar-wrapper" id="wi-navbar" th:classappend="${theme.config?.navbar?.navbar_style == 'minimal'} ? 'wi-navbar--minimal' : (${theme.config?.navbar?.navbar_style == 'float'} ? 'wi-navbar--float' : 'wi-navbar--glass')">
|
<header class="wi-navbar-wrapper" id="wi-navbar" th:classappend="${theme.config?.navbar?.navbar_style == 'minimal'} ? 'wi-navbar--minimal' : (${theme.config?.navbar?.navbar_style == 'float'} ? 'wi-navbar--float' : 'wi-navbar--glass')">
|
||||||
<div class="wi-navbar__capsule">
|
<div class="wi-navbar__capsule">
|
||||||
<a class="wi-navbar__brand" th:href="@{/}">
|
<a
|
||||||
|
class="wi-navbar__brand"
|
||||||
|
th:href="@{/}"
|
||||||
|
th:with="brandType=${theme.config?.navbar?.navbar_brand_type}, logoUrl=${!#strings.isEmpty(theme.config?.navbar?.navbar_logo) ? theme.config?.navbar?.navbar_logo : theme.config?.basic?.logo}, logoRadius=${theme.config?.navbar?.navbar_logo_radius}"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
th:if="${theme.config?.basic?.logo}"
|
th:if="${brandType == 'logo' and !#strings.isEmpty(logoUrl)}"
|
||||||
th:src="${theme.config?.basic?.logo}"
|
th:src="${logoUrl}"
|
||||||
alt=""
|
alt=""
|
||||||
class="wi-navbar__logo"
|
th:class="${'wi-navbar__logo wi-navbar__logo--' + (logoRadius != null ? logoRadius : 'rounded')}"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
onerror="this.style.display='none';this.nextElementSibling&&this.nextElementSibling.classList.add('wi-navbar__title')"
|
onerror="this.style.display='none'"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
th:if="${#strings.isEmpty(theme.config?.basic?.logo)}"
|
th:if="${brandType != 'logo' or #strings.isEmpty(logoUrl)}"
|
||||||
class="wi-navbar__title"
|
class="wi-navbar__title"
|
||||||
th:text="${site.title}"
|
th:text="${site.title}"
|
||||||
></span>
|
></span>
|
||||||
@@ -28,15 +32,34 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
|||||||
<th:block th:if="${menuFinder != null}">
|
<th:block th:if="${menuFinder != null}">
|
||||||
<th:block th:with="menu = ${menuFinder.getPrimary()}">
|
<th:block th:with="menu = ${menuFinder.getPrimary()}">
|
||||||
<th:block th:if="${menu != null and menu.menuItems != null and not #lists.isEmpty(menu.menuItems)}">
|
<th:block th:if="${menu != null and menu.menuItems != null and not #lists.isEmpty(menu.menuItems)}">
|
||||||
<a
|
<div
|
||||||
th:each="menuItem : ${menu.menuItems}"
|
th:each="menuItem : ${menu.menuItems}"
|
||||||
th:href="@{${menuItem.status.href}}"
|
class="wi-navbar__item"
|
||||||
th:target="${menuItem.spec?.target?.value == '_blank'} ? '_blank' : '_self'"
|
th:classappend="${not #lists.isEmpty(menuItem.children)} ? 'wi-navbar__item--has-children'"
|
||||||
th:text="${menuItem.status.displayName}"
|
|
||||||
class="wi-navbar__link"
|
|
||||||
th:data-href="${menuItem.status.href}"
|
|
||||||
>
|
>
|
||||||
</a>
|
<a
|
||||||
|
th:href="@{${menuItem.status.href}}"
|
||||||
|
th:target="${menuItem.spec?.target?.value == '_blank'} ? '_blank' : '_self'"
|
||||||
|
th:text="${menuItem.status.displayName}"
|
||||||
|
class="wi-navbar__link"
|
||||||
|
th:data-href="${menuItem.status.href}"
|
||||||
|
th:aria-haspopup="${not #lists.isEmpty(menuItem.children)} ? 'true' : null"
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
<div
|
||||||
|
class="wi-navbar__submenu"
|
||||||
|
th:if="${not #lists.isEmpty(menuItem.children)}"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
th:each="child : ${menuItem.children}"
|
||||||
|
th:href="@{${child.status.href}}"
|
||||||
|
th:target="${child.spec?.target?.value == '_blank'} ? '_blank' : '_self'"
|
||||||
|
th:text="${child.status.displayName}"
|
||||||
|
class="wi-navbar__submenu-link"
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
@@ -47,14 +70,14 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
|||||||
th:if="${pluginFinder.available('PluginSearchWidget') and theme.config?.navbar?.navbar_show_search != false}"
|
th:if="${pluginFinder.available('PluginSearchWidget') and theme.config?.navbar?.navbar_show_search != false}"
|
||||||
class="wi-navbar__action-btn wi-navbar__search-btn"
|
class="wi-navbar__action-btn wi-navbar__search-btn"
|
||||||
type="button"
|
type="button"
|
||||||
th:aria-label="${theme.config?.basic?.label_search ?: #messages.msg('common.search')}"
|
th:aria-label="${theme.config?.basic?.label_search ?: '搜索'}"
|
||||||
onclick="SearchWidget.open()"
|
onclick="SearchWidget.open()"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<th:block th:if="${theme.config?.navbar?.navbar_show_theme_switch != false}">
|
<th:block th:if="${theme.config?.navbar?.navbar_show_theme_switch != false}">
|
||||||
<span th:attr="data-label=${theme.config?.basic?.label_theme_switch ?: #messages.msg('common.themeSwitch')}" style="display:inline-flex">
|
<span th:attr="data-label=${theme.config?.basic?.label_theme_switch ?: '切换主题'}" style="display:inline-flex">
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
</span>
|
</span>
|
||||||
</th:block>
|
</th:block>
|
||||||
@@ -70,7 +93,7 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
const navbar = document.getElementById("wi-navbar");
|
const navbar = document.getElementById("wi-navbar");
|
||||||
let lastScrollY = window.scrollY;
|
let lastScrollY = window.scrollY;
|
||||||
let ticking = false;
|
let ticking = false;
|
||||||
@@ -218,6 +241,18 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wi-navbar__logo--square {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-navbar__logo--rounded {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-navbar__logo--circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.wi-navbar__title {
|
.wi-navbar__title {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -241,6 +276,88 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 一级菜单项容器(含子菜单时作为下拉的定位锚点) */
|
||||||
|
.wi-navbar__item {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 二级下拉:纯 CSS 悬停/聚焦展开,无 JS 开销 */
|
||||||
|
.wi-navbar__submenu {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) translateY(6px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 168px;
|
||||||
|
padding: 6px;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
border-radius: var(--radius-md, 12px);
|
||||||
|
background: var(--bg-raised);
|
||||||
|
border: 1px solid var(--rule);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.18s ease, transform 0.18s var(--ease-out-quart),
|
||||||
|
visibility 0.18s;
|
||||||
|
z-index: 101;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 悬停桥:避免鼠标从一级移到二级途中经过空隙导致菜单关闭 */
|
||||||
|
.wi-navbar__item--has-children::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 100%;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-navbar__item--has-children:hover .wi-navbar__submenu,
|
||||||
|
.wi-navbar__item--has-children:focus-within .wi-navbar__submenu {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-navbar__submenu-link {
|
||||||
|
display: block;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--ink-2);
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: color 0.15s ease, background 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-navbar__submenu-link:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
background: color-mix(in srgb, var(--accent) 8%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 有子菜单的一级项带下拉小箭头 */
|
||||||
|
.wi-navbar__item--has-children > .wi-navbar__link::after {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
margin-left: 0.3rem;
|
||||||
|
margin-top: -2px;
|
||||||
|
border-right: 1.5px solid currentColor;
|
||||||
|
border-bottom: 1.5px solid currentColor;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: transform 0.18s var(--ease-out-quart);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-navbar__item--has-children:hover > .wi-navbar__link::after {
|
||||||
|
transform: rotate(225deg);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.wi-navbar__link {
|
.wi-navbar__link {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const pageConfig = findPageBySlug("photos");
|
|||||||
<th:block th:if="${pluginFinder.available('PluginPhotos')}">
|
<th:block th:if="${pluginFinder.available('PluginPhotos')}">
|
||||||
<section class="wi-photos-section">
|
<section class="wi-photos-section">
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<h2 class="wi-photos-section__title" th:text="${pageConfig?.title ?: theme.config?.photos?.photos_title ?: #messages.msg('photos.title')}">图库</h2>
|
<h2 class="wi-photos-section__title" th:text="${pageConfig?.title ?: theme.config?.photos?.photos_title ?: '相册'}">图库</h2>
|
||||||
<div
|
<div
|
||||||
class="wi-photos-section__grid"
|
class="wi-photos-section__grid"
|
||||||
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
||||||
@@ -37,7 +37,7 @@ const pageConfig = findPageBySlug("photos");
|
|||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
<a th:href="@{'/photos'}" class="wi-photos-section__more">
|
<a th:href="@{'/photos'}" class="wi-photos-section__more">
|
||||||
<span th:text="${theme.config?.home?.home_label_view_all ?: #messages.msg('common.viewAll')}">查看全部</span>
|
<span th:text="${theme.config?.home?.home_label_view_all ?: '查看全部'}">查看全部</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -28,10 +28,18 @@ const { variant = "default" } = Astro.props;
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="post-card__content">
|
<div class="post-card__content">
|
||||||
<time
|
<div class="post-card__meta">
|
||||||
class="post-card__date"
|
<time
|
||||||
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
|
class="post-card__date"
|
||||||
></time>
|
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
|
||||||
|
></time>
|
||||||
|
<span
|
||||||
|
class="post-card__words"
|
||||||
|
th:if="${theme.config?.home?.home_card_show_wordcount == true and pluginFinder.available('extra-api')}"
|
||||||
|
th:with="words=${extraApiStatsFinder.getPostWordCount({name: post.metadata.name})}"
|
||||||
|
th:text="${words > 0 ? words + ' 字' : ''}"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
<h3 class="post-card__title" th:text="${post.spec.title}"></h3>
|
<h3 class="post-card__title" th:text="${post.spec.title}"></h3>
|
||||||
<p
|
<p
|
||||||
th:if="${variant != 'compact'}"
|
th:if="${variant != 'compact'}"
|
||||||
@@ -110,6 +118,20 @@ const { variant = "default" } = Astro.props;
|
|||||||
line-height: var(--leading-none);
|
line-height: var(--leading-none);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-card__meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card__words {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
color: var(--ink-3);
|
||||||
|
line-height: var(--leading-none);
|
||||||
|
padding-left: 0.6rem;
|
||||||
|
border-left: 1px solid var(--rule);
|
||||||
|
}
|
||||||
|
|
||||||
.post-card__title {
|
.post-card__title {
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
---
|
---
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
(function(){
|
(function(){
|
||||||
var enabled = !document.documentElement.classList.contains("wi-no-animations");
|
var enabled = !document.documentElement.classList.contains("wi-no-animations");
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, onUnmounted } from "vue";
|
|
||||||
|
|
||||||
function handleKeydown(e: KeyboardEvent) {
|
|
||||||
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
||||||
e.preventDefault();
|
|
||||||
const searchWidget = (window as any).SearchWidget;
|
|
||||||
if (searchWidget && typeof searchWidget.open === "function") {
|
|
||||||
searchWidget.open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
document.addEventListener("keydown", handleKeydown);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.removeEventListener("keydown", handleKeydown);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<span class="wi-search-hint" aria-hidden="true">
|
|
||||||
<kbd class="wi-search-hint__key">⌘</kbd>
|
|
||||||
<kbd class="wi-search-hint__key">K</kbd>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.wi-search-hint {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
pointer-events: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-search-hint__key {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
padding: 0 5px;
|
|
||||||
font-family: var(--font-body);
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1;
|
|
||||||
color: var(--ink-3);
|
|
||||||
background: var(--bg-raised);
|
|
||||||
border: 1px solid var(--rule);
|
|
||||||
border-radius: 6px;
|
|
||||||
box-shadow: 0 1px 0 var(--rule);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
(function(){
|
(function(){
|
||||||
var btn = document.getElementById("wi-theme-toggle");
|
var btn = document.getElementById("wi-theme-toggle");
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
@@ -57,8 +57,9 @@
|
|||||||
document.documentElement.classList.toggle("dark", isDark);
|
document.documentElement.classList.toggle("dark", isDark);
|
||||||
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 bgPair = window.WI_PALETTE_BG || { light: "#faf7f2", dark: "#1e1c1a" };
|
||||||
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 ? bgPair.dark : bgPair.light);
|
||||||
updateIcons();
|
updateIcons();
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
document.documentElement.classList.remove("wi-theme-transition");
|
document.documentElement.classList.remove("wi-theme-transition");
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
>
|
>
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<div class="wi-section__header">
|
<div class="wi-section__header">
|
||||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_timeline_title ?: #messages.msg('common.timeline')}">时间线</h2>
|
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_timeline_title ?: '时间线'}">时间线</h2>
|
||||||
<span class="wi-section__accent"></span>
|
<span class="wi-section__accent"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,443 @@
|
|||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
<section
|
||||||
|
class="wi-welcome-popup"
|
||||||
|
id="wi-welcome-popup"
|
||||||
|
th:if="${theme.config?.welcome_popup?.welcome_popup_enabled == true}"
|
||||||
|
th:attr="data-position=${theme.config?.welcome_popup?.welcome_popup_position ?: 'top-center'},data-duration=${theme.config?.welcome_popup?.welcome_popup_duration ?: 5},data-api=${theme.config?.welcome_popup?.welcome_popup_api},data-fallback-api=${theme.config?.welcome_popup?.welcome_popup_fallback_api},data-show-once=${theme.config?.welcome_popup?.welcome_popup_show_once != false ? 'true' : 'false'}"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-atomic="true"
|
||||||
|
>
|
||||||
|
<div class="wi-welcome-popup__inner">
|
||||||
|
<div class="wi-welcome-popup__icon" aria-hidden="true">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"></path>
|
||||||
|
<circle cx="12" cy="10" r="3"></circle>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="wi-welcome-popup__content" id="wi-welcome-popup-text">加载中…</div>
|
||||||
|
<button class="wi-welcome-popup__close" type="button" aria-label="关闭">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M18 6 6 18"></path>
|
||||||
|
<path d="m6 6 12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="wi-welcome-popup__progress" id="wi-welcome-popup-progress" aria-hidden="true"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script is:inline th:inline="none">
|
||||||
|
(function () {
|
||||||
|
var popup = document.getElementById("wi-welcome-popup");
|
||||||
|
if (!popup) return;
|
||||||
|
|
||||||
|
/* 同会话已显示过则不再弹出(可通过"同会话仅显示一次"开关关闭) */
|
||||||
|
var sessionKey = "wi-welcome-shown";
|
||||||
|
var showOnce = popup.getAttribute("data-show-once") !== "false";
|
||||||
|
try {
|
||||||
|
if (showOnce && sessionStorage.getItem(sessionKey)) {
|
||||||
|
popup.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
var textEl = document.getElementById("wi-welcome-popup-text");
|
||||||
|
var closeBtn = popup.querySelector(".wi-welcome-popup__close");
|
||||||
|
var progress = document.getElementById("wi-welcome-popup-progress");
|
||||||
|
var duration = parseInt(popup.getAttribute("data-duration"), 10) || 5;
|
||||||
|
var api = popup.getAttribute("data-api") || "https://myip.ipip.net/json";
|
||||||
|
var fallbackApi = popup.getAttribute("data-fallback-api") || "https://r.inews.qq.com/api/ip2city?otype=json";
|
||||||
|
|
||||||
|
var hideTimer = null;
|
||||||
|
var progressRaf = null;
|
||||||
|
var progressStart = 0;
|
||||||
|
var isHidden = false;
|
||||||
|
|
||||||
|
function hidePopup() {
|
||||||
|
if (isHidden) return;
|
||||||
|
isHidden = true;
|
||||||
|
popup.classList.remove("wi-welcome-popup--visible");
|
||||||
|
popup.classList.add("wi-welcome-popup--leaving");
|
||||||
|
if (hideTimer) {
|
||||||
|
clearTimeout(hideTimer);
|
||||||
|
hideTimer = null;
|
||||||
|
}
|
||||||
|
if (progressRaf) {
|
||||||
|
cancelAnimationFrame(progressRaf);
|
||||||
|
progressRaf = null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (showOnce) sessionStorage.setItem(sessionKey, "1");
|
||||||
|
} catch (e) {}
|
||||||
|
setTimeout(function () {
|
||||||
|
popup.style.display = "none";
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAutoHide() {
|
||||||
|
progressStart = performance.now();
|
||||||
|
var step = function (now) {
|
||||||
|
var elapsed = (now - progressStart) / 1000;
|
||||||
|
var ratio = Math.min(elapsed / duration, 1);
|
||||||
|
if (progress) {
|
||||||
|
progress.style.transform = "scaleX(" + (1 - ratio) + ")";
|
||||||
|
}
|
||||||
|
if (ratio < 1 && !isHidden) {
|
||||||
|
progressRaf = requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
progressRaf = requestAnimationFrame(step);
|
||||||
|
hideTimer = setTimeout(hidePopup, duration * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMessage(location) {
|
||||||
|
var loc = (location || "").trim();
|
||||||
|
if (!loc) loc = "未知星球";
|
||||||
|
if (textEl) {
|
||||||
|
textEl.textContent = "欢迎来自 " + loc + " 的朋友";
|
||||||
|
}
|
||||||
|
popup.classList.add("wi-welcome-popup--visible");
|
||||||
|
startAutoHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchLocation() {
|
||||||
|
if (textEl) {
|
||||||
|
textEl.textContent = "加载中…";
|
||||||
|
}
|
||||||
|
popup.classList.add("wi-welcome-popup--visible");
|
||||||
|
|
||||||
|
/* 按顺序尝试主 API -> 备选 API */
|
||||||
|
var endpoints = [];
|
||||||
|
if (api) endpoints.push(api);
|
||||||
|
if (fallbackApi && fallbackApi !== api) endpoints.push(fallbackApi);
|
||||||
|
|
||||||
|
if (endpoints.length === 0) {
|
||||||
|
renderMessage("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = 0;
|
||||||
|
|
||||||
|
function tryNext() {
|
||||||
|
if (idx >= endpoints.length) {
|
||||||
|
renderMessage("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var url = endpoints[idx++];
|
||||||
|
fetchSingle(url, 6000)
|
||||||
|
.then(function (location) {
|
||||||
|
if (location) {
|
||||||
|
renderMessage(location);
|
||||||
|
} else {
|
||||||
|
tryNext();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
tryNext();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tryNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchSingle(url, timeoutMs) {
|
||||||
|
var controller = null;
|
||||||
|
var timeoutId = null;
|
||||||
|
|
||||||
|
if (window.AbortController) {
|
||||||
|
controller = new AbortController();
|
||||||
|
timeoutId = setTimeout(function () {
|
||||||
|
controller.abort();
|
||||||
|
}, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(url, controller ? { signal: controller.signal } : {})
|
||||||
|
.then(function (res) {
|
||||||
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then(function (data) {
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
return parseLocation(data);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 解析多种 IP API 的返回格式:
|
||||||
|
- IPIP: {"ret":"ok","data":{"location":["中国","河北","张家口","","联通"]}}
|
||||||
|
- 腾讯 ip2city: {"ret":0,"province":"河北","city":"张家口",...}
|
||||||
|
- ipapi.co / ip-api.com: {"country":"China","regionName":"Hebei","city":"Zhangjiakou"}
|
||||||
|
- 太平洋: {"pro":"广东省","city":"广州市",...} */
|
||||||
|
function parseLocation(data) {
|
||||||
|
if (!data) return "";
|
||||||
|
|
||||||
|
/* IPIP: data.data.location 数组 */
|
||||||
|
if (data.data && Array.isArray(data.data.location)) {
|
||||||
|
var loc = data.data.location
|
||||||
|
.filter(function (s) {
|
||||||
|
return s && String(s).trim();
|
||||||
|
})
|
||||||
|
.map(function (s) {
|
||||||
|
return String(s).trim();
|
||||||
|
});
|
||||||
|
/* 去掉末尾的运营商字段(如"联通""电信""移动") */
|
||||||
|
var ispKeywords = ["联通", "电信", "移动", "铁通", "广电", "教育网", "长城"];
|
||||||
|
while (loc.length > 1 && ispKeywords.indexOf(loc[loc.length - 1]) !== -1) {
|
||||||
|
loc.pop();
|
||||||
|
}
|
||||||
|
return loc.length ? loc.join("·") : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var country = data.country || data.country_name || data.nation || "";
|
||||||
|
var region = data.regionName || data.region || data.province || data.pro || "";
|
||||||
|
var city = data.city || data.cityName || "";
|
||||||
|
var district = data.district || data.area || data.info3 || "";
|
||||||
|
|
||||||
|
/* 短英文国家代码(如 CN)舍弃,避免显示无意义的两字母 */
|
||||||
|
if (country && country.length <= 3 && !/[一-龥]/.test(country)) {
|
||||||
|
country = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = [];
|
||||||
|
if (country) parts.push(country);
|
||||||
|
if (region && region !== country) parts.push(region);
|
||||||
|
if (city && city !== region) parts.push(city);
|
||||||
|
if (district && district !== city) parts.push(district);
|
||||||
|
|
||||||
|
return parts.length ? parts.join("·") : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 交互:悬停/触摸暂停倒计时,离开恢复 */
|
||||||
|
function pauseTimer() {
|
||||||
|
if (hideTimer) {
|
||||||
|
clearTimeout(hideTimer);
|
||||||
|
hideTimer = null;
|
||||||
|
}
|
||||||
|
if (progressRaf) {
|
||||||
|
cancelAnimationFrame(progressRaf);
|
||||||
|
progressRaf = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resumeTimer() {
|
||||||
|
var remaining = duration;
|
||||||
|
if (progress) {
|
||||||
|
var matrix = progress.style.transform;
|
||||||
|
var match = matrix && matrix.match(/scaleX\(([\d.]+)\)/);
|
||||||
|
if (match) {
|
||||||
|
var ratio = parseFloat(match[1]);
|
||||||
|
remaining = duration * ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progressStart = performance.now() - (duration - remaining) * 1000;
|
||||||
|
var step = function (now) {
|
||||||
|
var elapsed = (now - progressStart) / 1000;
|
||||||
|
var ratio = Math.min(elapsed / duration, 1);
|
||||||
|
if (progress) {
|
||||||
|
progress.style.transform = "scaleX(" + (1 - ratio) + ")";
|
||||||
|
}
|
||||||
|
if (ratio < 1 && !isHidden) {
|
||||||
|
progressRaf = requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
progressRaf = requestAnimationFrame(step);
|
||||||
|
hideTimer = setTimeout(hidePopup, remaining * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.addEventListener("mouseenter", pauseTimer);
|
||||||
|
popup.addEventListener("mouseleave", resumeTimer);
|
||||||
|
popup.addEventListener("touchstart", pauseTimer, { passive: true });
|
||||||
|
popup.addEventListener("touchend", resumeTimer, { passive: true });
|
||||||
|
|
||||||
|
if (closeBtn) closeBtn.addEventListener("click", hidePopup);
|
||||||
|
|
||||||
|
/* 延迟 800ms 显示,避免与首屏渲染争抢资源 */
|
||||||
|
setTimeout(fetchLocation, 800);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wi-welcome-popup {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9999;
|
||||||
|
max-width: min(380px, calc(100vw - 32px));
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-12px);
|
||||||
|
transition: opacity 0.4s var(--ease-out-expo), transform 0.4s var(--ease-out-expo);
|
||||||
|
will-change: opacity, transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup[data-position="top-left"] {
|
||||||
|
top: 88px;
|
||||||
|
left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup[data-position="top-right"] {
|
||||||
|
top: 88px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup[data-position="bottom-left"] {
|
||||||
|
bottom: 24px;
|
||||||
|
left: 24px;
|
||||||
|
transform: translateY(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup[data-position="bottom-right"] {
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
transform: translateY(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup[data-position="top-center"] {
|
||||||
|
top: 88px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%) translateY(-12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup--visible {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup--visible[data-position="top-left"],
|
||||||
|
.wi-welcome-popup--visible[data-position="top-right"],
|
||||||
|
.wi-welcome-popup--visible[data-position="bottom-left"],
|
||||||
|
.wi-welcome-popup--visible[data-position="bottom-right"] {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup--visible[data-position="top-center"] {
|
||||||
|
transform: translate(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup--leaving {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup--leaving[data-position^="top"] {
|
||||||
|
transform: translateY(-12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup--leaving[data-position="bottom-left"],
|
||||||
|
.wi-welcome-popup--leaving[data-position="bottom-right"] {
|
||||||
|
transform: translateY(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup--leaving[data-position="top-center"] {
|
||||||
|
transform: translate(-50%) translateY(-12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup__inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: color-mix(in srgb, var(--bg-raised) 92%, transparent);
|
||||||
|
backdrop-filter: blur(20px) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||||
|
border: 1px solid var(--rule);
|
||||||
|
box-shadow: 0 8px 24px -8px #0000001f, 0 2px 8px -2px #0000000f;
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup__icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup__icon svg {
|
||||||
|
animation: wi-welcome-popup-pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup__content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup__close {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--ink-3);
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.2s ease, background 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup__close:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background: color-mix(in srgb, var(--ink-3) 12%, transparent);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup__progress {
|
||||||
|
position: absolute;
|
||||||
|
left: 14px;
|
||||||
|
right: 14px;
|
||||||
|
bottom: 4px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 1px;
|
||||||
|
background: color-mix(in srgb, var(--accent) 30%, transparent);
|
||||||
|
transform-origin: left center;
|
||||||
|
transform: scaleX(1);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-welcome-popup--visible .wi-welcome-popup__progress {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wi-welcome-popup-pulse {
|
||||||
|
0%,
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.wi-welcome-popup[data-position] {
|
||||||
|
max-width: calc(100vw - 24px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.wi-welcome-popup,
|
||||||
|
.wi-welcome-popup--visible,
|
||||||
|
.wi-welcome-popup--leaving,
|
||||||
|
.wi-welcome-popup__icon svg {
|
||||||
|
transition: opacity 0.2s ease !important;
|
||||||
|
transform: none !important;
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+50
-46
@@ -3,27 +3,37 @@ import "../styles/main.scss";
|
|||||||
import Navbar from "../components/Navbar.astro";
|
import Navbar from "../components/Navbar.astro";
|
||||||
import Footer from "../components/Footer.astro";
|
import Footer from "../components/Footer.astro";
|
||||||
import ScrollReveal from "../components/ScrollReveal.astro";
|
import ScrollReveal from "../components/ScrollReveal.astro";
|
||||||
import CursorGlow from "../components/CursorGlow.astro";
|
|
||||||
import MobileMenu from "../components/MobileMenu.astro";
|
import MobileMenu from "../components/MobileMenu.astro";
|
||||||
|
import ClickEffect from "../components/ClickEffect.astro";
|
||||||
interface Props {
|
interface Props {
|
||||||
pageTitle?: string;
|
pageTitle?: string;
|
||||||
contentClass?: string;
|
contentClass?: string;
|
||||||
wide?: boolean;
|
wide?: boolean;
|
||||||
home?: boolean;
|
home?: boolean;
|
||||||
|
/** 渲染为 Halo 页面布局契约(templates/layout.html 的 html(head, content) 片段) */
|
||||||
|
layoutFragment?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { pageTitle, contentClass, wide, home } = Astro.props;
|
const { pageTitle, contentClass, wide, home, layoutFragment = false } = Astro.props;
|
||||||
|
const fragmentAttr = layoutFragment ? "html (head, content)" : undefined;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html class={home ? 'wi-home-snap' : ''} style="overflow-x:hidden" th:lang="${#locale.toLanguageTag()}" th:classappend="${theme.config?.animation?.animation_enabled == false} ? 'wi-no-animations'" th:attr="data-lang=${theme.config?.basic?.language}">
|
<html
|
||||||
|
lang="zh-CN"
|
||||||
|
class={home ? 'wi-home-snap' : ''}
|
||||||
|
style="overflow-x:hidden"
|
||||||
|
th:classappend="${theme.config?.animation?.animation_enabled == false} ? 'wi-no-animations'"
|
||||||
|
th:attr="data-palette=${theme.config?.style?.color_palette ?: 'warm-island'}"
|
||||||
|
xmlns:th={layoutFragment ? "https://www.thymeleaf.org" : undefined}
|
||||||
|
th:fragment={fragmentAttr}
|
||||||
|
>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#faf7f2" />
|
<meta name="theme-color" content="#faf7f2" />
|
||||||
<meta th:if="${theme.config?.basic?.site_description}" name="description" th:content="${theme.config?.basic?.site_description}" />
|
<meta th:if="${theme.config?.basic?.site_description}" name="description" th:content="${theme.config?.basic?.site_description}" />
|
||||||
<script is:inline th:attr="data-scheme=${theme.config?.style?.color_scheme}">
|
<script is:inline th:inline="none" th:attr="data-scheme=${theme.config?.style?.color_scheme}">
|
||||||
(function () {
|
(function () {
|
||||||
var stored = localStorage.getItem("wi-theme");
|
var stored = localStorage.getItem("wi-theme");
|
||||||
var prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
var prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
@@ -46,14 +56,30 @@ const { pageTitle, contentClass, wide, home } = Astro.props;
|
|||||||
if (isDark) {
|
if (isDark) {
|
||||||
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"]');
|
|
||||||
if (tc) tc.setAttribute("content", "#1a1614");
|
|
||||||
} 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");
|
||||||
var tc = document.querySelector('meta[name="theme-color"]');
|
|
||||||
if (tc) tc.setAttribute("content", "#faf7f2");
|
|
||||||
}
|
}
|
||||||
|
/* 浏览器地址栏颜色跟随当前配色(含主题配色系统) */
|
||||||
|
var paletteBg = {
|
||||||
|
"warm-island": ["#faf7f2", "#1e1c1a"],
|
||||||
|
"matcha": ["#f6f8f1", "#1b1f1a"],
|
||||||
|
"indigo": ["#f5f7fa", "#191d23"],
|
||||||
|
"sakura": ["#faf5f6", "#201b1c"],
|
||||||
|
"violet": ["#f7f5fa", "#1c1a21"],
|
||||||
|
"sea": ["#f3f8f7", "#181f1e"],
|
||||||
|
"amber": ["#faf7ef", "#201d16"],
|
||||||
|
"plum": ["#f9f4f5", "#1f1a1b"],
|
||||||
|
"graphite": ["#f7f7f6", "#1b1b1a"],
|
||||||
|
"cocoa": ["#f8f4f0", "#1e1a17"],
|
||||||
|
"azure": ["#f4f8fb", "#172026"]
|
||||||
|
};
|
||||||
|
var pal = document.documentElement.getAttribute("data-palette");
|
||||||
|
var pair = (pal && paletteBg[pal]) || paletteBg["warm-island"];
|
||||||
|
var tc = document.querySelector('meta[name="theme-color"]');
|
||||||
|
if (tc) tc.setAttribute("content", isDark ? pair[1] : pair[0]);
|
||||||
|
/* 供 ThemeSwitcher 切换深浅色时取当前配色的背景色 */
|
||||||
|
window.WI_PALETTE_BG = { light: pair[0], dark: pair[1] };
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<link
|
<link
|
||||||
@@ -73,47 +99,31 @@ const { pageTitle, contentClass, wide, home } = Astro.props;
|
|||||||
<style is:inline th:if="${theme.config?.style?.layout_container_width == 'medium' or theme.config?.style?.layout_container_width == null}" th:utext="${'.wi-content-wrap { max-width: 800px; } .wi-content-wrap--wide { max-width: 1200px; } .wi-main--wide { max-width: 1400px; }'}">/*width*/</style>
|
<style is:inline th:if="${theme.config?.style?.layout_container_width == 'medium' or theme.config?.style?.layout_container_width == null}" th:utext="${'.wi-content-wrap { max-width: 800px; } .wi-content-wrap--wide { max-width: 1200px; } .wi-main--wide { max-width: 1400px; }'}">/*width*/</style>
|
||||||
<style is:inline th:if="${theme.config?.style?.layout_container_width == 'wide'}" th:utext="${'.wi-content-wrap { max-width: 900px; } .wi-content-wrap--wide { max-width: 1400px; } .wi-main--wide { max-width: 1600px; }'}">/*width*/</style>
|
<style is:inline th:if="${theme.config?.style?.layout_container_width == 'wide'}" th:utext="${'.wi-content-wrap { max-width: 900px; } .wi-content-wrap--wide { max-width: 1400px; } .wi-main--wide { max-width: 1600px; }'}">/*width*/</style>
|
||||||
<style is:inline th:if="${theme.config?.style?.custom_css}" th:utext="${theme.config?.style?.custom_css}">/*custom*/</style>
|
<style is:inline th:if="${theme.config?.style?.custom_css}" th:utext="${theme.config?.style?.custom_css}">/*custom*/</style>
|
||||||
|
{
|
||||||
|
layoutFragment && (
|
||||||
|
<title th:if="${head == null}" th:text="${site.title}"></title>
|
||||||
|
<th:block th:if="${head != null}">
|
||||||
|
<th:block th:replace="${head}" />
|
||||||
|
</th:block>
|
||||||
|
)
|
||||||
|
}
|
||||||
<slot name="head" />
|
<slot name="head" />
|
||||||
</head>
|
</head>
|
||||||
<body class="wi-body">
|
<body class="wi-body">
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
window.addEventListener("unhandledrejection", function (e) {
|
window.addEventListener("unhandledrejection", function (e) {
|
||||||
if (e.reason && e.reason.message && e.reason.message.indexOf("Could not establish connection") !== -1) {
|
if (e.reason && e.reason.message && e.reason.message.indexOf("Could not establish connection") !== -1) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script is:inline>
|
|
||||||
(function(){
|
|
||||||
try{
|
|
||||||
var lang=document.documentElement.getAttribute("data-lang");
|
|
||||||
if(!lang||lang==="auto"||lang==="null"||lang===""){
|
|
||||||
var cookies=document.cookie.split(";");
|
|
||||||
for(var i=0;i<cookies.length;i++){
|
|
||||||
var c=cookies[i].trim();
|
|
||||||
if(c.indexOf("language=")===0){
|
|
||||||
document.cookie="language=;path=/;max-age=0;SameSite=Lax";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var tag=lang.replace(/_/g,"-");
|
|
||||||
var cookies=document.cookie.split(";");
|
|
||||||
var current="";
|
|
||||||
for(var i=0;i<cookies.length;i++){
|
|
||||||
var c=cookies[i].trim();
|
|
||||||
if(c.indexOf("language=")===0){current=c.substring(9);break;}
|
|
||||||
}
|
|
||||||
if(current!==tag){
|
|
||||||
document.cookie="language="+tag+";path=/;max-age=31536000;SameSite=Lax";
|
|
||||||
}
|
|
||||||
}catch(e){}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main class:list={["wi-main", { "wi-main--wide": wide || home, "wi-main--home": home }]}>
|
<main class:list={["wi-main", { "wi-main--wide": wide || home, "wi-main--home": home }]}>
|
||||||
{home ? (
|
{layoutFragment ? (
|
||||||
|
<div class:list={["wi-content-wrap", { "wi-content-wrap--wide": wide }]}>
|
||||||
|
<th:block th:replace="${content}" />
|
||||||
|
</div>
|
||||||
|
) : home ? (
|
||||||
<slot />
|
<slot />
|
||||||
) : (
|
) : (
|
||||||
<div class:list={["wi-content-wrap", { "wi-content-wrap--wide": wide }]}>
|
<div class:list={["wi-content-wrap", { "wi-content-wrap--wide": wide }]}>
|
||||||
@@ -124,7 +134,7 @@ const { pageTitle, contentClass, wide, home } = Astro.props;
|
|||||||
<Footer />
|
<Footer />
|
||||||
<MobileMenu />
|
<MobileMenu />
|
||||||
<ScrollReveal th:if="${theme.config?.animation?.animation_enabled != false and theme.config?.animation?.animation_scroll_reveal != false}" />
|
<ScrollReveal th:if="${theme.config?.animation?.animation_enabled != false and theme.config?.animation?.animation_scroll_reveal != false}" />
|
||||||
<CursorGlow th:if="${theme.config?.animation?.animation_enabled != false and theme.config?.animation?.animation_cursor_glow == true}" />
|
<ClickEffect />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -169,10 +179,4 @@ html.wi-no-animations .wi-reveal {
|
|||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-post__content img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Fragment slot="head">
|
<Fragment slot="head">
|
||||||
<title th:text="|${theme.config?.basic?.label_archives_title ?: #messages.msg('archives.title')} - ${site.title}|"></title>
|
<title th:text="|${theme.config?.basic?.label_archives_title ?: '归档'} - ${site.title}|"></title>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
<div class="wi-archives">
|
<div class="wi-archives">
|
||||||
<header class="wi-archives__header">
|
<header class="wi-archives__header">
|
||||||
<h1 class="wi-archives__title" th:text="${theme.config?.basic?.label_archives_title ?: #messages.msg('archives.title')}">归档</h1>
|
<h1 class="wi-archives__title" th:text="${theme.config?.basic?.label_archives_title ?: '归档'}">归档</h1>
|
||||||
<span class="wi-page-accent"></span>
|
<span class="wi-page-accent"></span>
|
||||||
<p class="wi-archives__subtitle" th:text="${#strings.replace(#messages.msg('common.postCount'), '{0}', #strings.toString(archives.total))}"></p>
|
<p class="wi-archives__subtitle" th:text="|共 ${archives.total} 篇文章|">共 0 篇文章</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="wi-archives__timeline" aria-label="Archive timeline">
|
<section class="wi-archives__timeline" aria-label="Archive timeline">
|
||||||
@@ -57,7 +57,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<div
|
<div
|
||||||
th:if="${archives.total == 0}"
|
th:if="${archives.total == 0}"
|
||||||
class="wi-archives__empty"
|
class="wi-archives__empty"
|
||||||
th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
|
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章'}"
|
||||||
>No posts yet.</div>
|
>No posts yet.</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
th:if="${archives.hasPrevious()}"
|
th:if="${archives.hasPrevious()}"
|
||||||
th:href="@{${archives.prevUrl}}"
|
th:href="@{${archives.prevUrl}}"
|
||||||
class="wi-archives__page-link wi-archives__page-link--prev"
|
class="wi-archives__page-link wi-archives__page-link--prev"
|
||||||
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
|
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||||
>← Newer</a>
|
>← Newer</a>
|
||||||
<span
|
<span
|
||||||
class="wi-archives__page-info"
|
class="wi-archives__page-info"
|
||||||
@@ -80,7 +80,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
th:if="${archives.hasNext()}"
|
th:if="${archives.hasNext()}"
|
||||||
th:href="@{${archives.nextUrl}}"
|
th:href="@{${archives.nextUrl}}"
|
||||||
class="wi-archives__page-link wi-archives__page-link--next"
|
class="wi-archives__page-link wi-archives__page-link--next"
|
||||||
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
|
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||||
>Older →</a>
|
>Older →</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Fragment slot="head">
|
<Fragment slot="head">
|
||||||
<title th:text="|#{categories.title} - ${site.title}|"></title>
|
<title th:text="|分类 - ${site.title}|"></title>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
<div class="wi-categories">
|
<div class="wi-categories">
|
||||||
<header class="wi-categories__header">
|
<header class="wi-categories__header">
|
||||||
<h1 class="wi-categories__title" th:text="#{categories.title}">分类</h1>
|
<h1 class="wi-categories__title" th:text="'分类'">分类</h1>
|
||||||
<span class="wi-page-accent"></span>
|
<span class="wi-page-accent"></span>
|
||||||
<p class="wi-categories__subtitle" th:text="${#messages.msg('common.categoryCount', categories.size())}"></p>
|
<p class="wi-categories__subtitle" th:text="|共 ${categories.size()} 个分类|">共 0 个分类</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<nav class="wi-categories__list" aria-label="Category list">
|
<nav class="wi-categories__list" aria-label="Category list">
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
th:text="${category.spec.displayName}"
|
th:text="${category.spec.displayName}"
|
||||||
></h1>
|
></h1>
|
||||||
<span class="wi-page-accent"></span>
|
<span class="wi-page-accent"></span>
|
||||||
<p class="wi-category__subtitle" th:text="#{common.postCount(${posts.total})}"></p>
|
<p class="wi-category__subtitle" th:text="|共 ${posts.total} 篇文章|">共 0 篇文章</p>
|
||||||
<p
|
<p
|
||||||
th:if="${not #strings.isEmpty(category.spec.description)}"
|
th:if="${not #strings.isEmpty(category.spec.description)}"
|
||||||
class="wi-category__description"
|
class="wi-category__description"
|
||||||
@@ -58,7 +58,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<div
|
<div
|
||||||
th:if="${posts.total == 0}"
|
th:if="${posts.total == 0}"
|
||||||
class="wi-category__empty"
|
class="wi-category__empty"
|
||||||
th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
|
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章'}"
|
||||||
>No posts yet.</div>
|
>No posts yet.</div>
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
@@ -70,7 +70,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
th:if="${posts.hasPrevious()}"
|
th:if="${posts.hasPrevious()}"
|
||||||
th:href="@{${posts.prevUrl}}"
|
th:href="@{${posts.prevUrl}}"
|
||||||
class="wi-category__page-link wi-category__page-link--prev"
|
class="wi-category__page-link wi-category__page-link--prev"
|
||||||
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
|
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||||
>← Newer</a>
|
>← Newer</a>
|
||||||
<span
|
<span
|
||||||
class="wi-category__page-info"
|
class="wi-category__page-info"
|
||||||
@@ -80,7 +80,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
th:if="${posts.hasNext()}"
|
th:if="${posts.hasNext()}"
|
||||||
th:href="@{${posts.nextUrl}}"
|
th:href="@{${posts.nextUrl}}"
|
||||||
class="wi-category__page-link wi-category__page-link--next"
|
class="wi-category__page-link wi-category__page-link--next"
|
||||||
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
|
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||||
>Older →</a>
|
>Older →</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+33
-33
@@ -10,7 +10,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<section class="wi-douban">
|
<section class="wi-douban">
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<div class="wi-douban__header">
|
<div class="wi-douban__header">
|
||||||
<h1 class="wi-douban__title" th:text="${title ?: #messages.msg('douban.title')}">Douban</h1>
|
<h1 class="wi-douban__title" th:text="${title ?: '豆瓣'}">豆瓣</h1>
|
||||||
<span class="wi-page-accent"></span>
|
<span class="wi-page-accent"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -32,63 +32,63 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type=${currentType},status='wish')}"
|
th:href="@{/douban(type=${currentType},status='wish')}"
|
||||||
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.wish}">Wish</a>
|
th:text="'想看'">想看</a>
|
||||||
<a
|
<a
|
||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type=${currentType},status='done')}"
|
th:href="@{/douban(type=${currentType},status='done')}"
|
||||||
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.done}">Done</a>
|
th:text="'看过'">看过</a>
|
||||||
</th:block>
|
</th:block>
|
||||||
<th:block th:if="${currentType == 'book'}">
|
<th:block th:if="${currentType == 'book'}">
|
||||||
<a
|
<a
|
||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type='book',status='wish')}"
|
th:href="@{/douban(type='book',status='wish')}"
|
||||||
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.wishRead}">Wish</a>
|
th:text="'想读'">想读</a>
|
||||||
<a
|
<a
|
||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type='book',status='doing')}"
|
th:href="@{/douban(type='book',status='doing')}"
|
||||||
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.reading}">Reading</a>
|
th:text="'在读'">在读</a>
|
||||||
<a
|
<a
|
||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type='book',status='done')}"
|
th:href="@{/douban(type='book',status='done')}"
|
||||||
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.done}">Done</a>
|
th:text="'看过'">看过</a>
|
||||||
</th:block>
|
</th:block>
|
||||||
<th:block th:if="${currentType == 'music'}">
|
<th:block th:if="${currentType == 'music'}">
|
||||||
<a
|
<a
|
||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type='music',status='wish')}"
|
th:href="@{/douban(type='music',status='wish')}"
|
||||||
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.wishListen}">Wish</a>
|
th:text="'想听'">想听</a>
|
||||||
<a
|
<a
|
||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type='music',status='doing')}"
|
th:href="@{/douban(type='music',status='doing')}"
|
||||||
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.listening}">Listening</a>
|
th:text="'在听'">在听</a>
|
||||||
<a
|
<a
|
||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type='music',status='done')}"
|
th:href="@{/douban(type='music',status='done')}"
|
||||||
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.listened}">Listened</a>
|
th:text="'听过'">听过</a>
|
||||||
</th:block>
|
</th:block>
|
||||||
<th:block th:if="${currentType == 'game'}">
|
<th:block th:if="${currentType == 'game'}">
|
||||||
<a
|
<a
|
||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type='game',status='wish')}"
|
th:href="@{/douban(type='game',status='wish')}"
|
||||||
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.wishPlay}">Wish</a>
|
th:text="'想玩'">想玩</a>
|
||||||
<a
|
<a
|
||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type='game',status='doing')}"
|
th:href="@{/douban(type='game',status='doing')}"
|
||||||
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.playing}">Playing</a>
|
th:text="'在玩'">在玩</a>
|
||||||
<a
|
<a
|
||||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||||
th:href="@{/douban(type='game',status='done')}"
|
th:href="@{/douban(type='game',status='done')}"
|
||||||
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
||||||
th:text="#{douban.played}">Played</a>
|
th:text="'玩过'">玩过</a>
|
||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -119,68 +119,68 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<article
|
<article
|
||||||
class="wi-douban__card"
|
class="wi-douban__card"
|
||||||
th:each="item : ${doubans.items}"
|
th:each="item : ${doubans.items}"
|
||||||
th:if="${!hasGenre or #sets.contains(item.spec?.genres, currentGenre)}"
|
th:if="${!hasGenre or #sets.contains(item.genres, currentGenre)}"
|
||||||
th:attr="data-type=${currentType}"
|
th:attr="data-type=${currentType}"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="wi-douban__card-link"
|
class="wi-douban__card-link"
|
||||||
th:if="${!#strings.isEmpty(item.spec?.link)}"
|
th:if="${!#strings.isEmpty(item.link)}"
|
||||||
th:href="${item.spec?.link}"
|
th:href="${item.link}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></a>
|
></a>
|
||||||
<div class="wi-douban__poster" th:if="${!#strings.isEmpty(item.spec?.poster)}">
|
<div class="wi-douban__poster" th:if="${!#strings.isEmpty(item.poster)}">
|
||||||
<img
|
<img
|
||||||
th:src="${item.spec?.poster}"
|
th:src="${item.poster}"
|
||||||
th:alt="${item.spec?.name}"
|
th:alt="${item.name}"
|
||||||
class="wi-douban__poster-img"
|
class="wi-douban__poster-img"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-douban__poster wi-douban__poster--placeholder" th:if="${#strings.isEmpty(item.spec?.poster)}">
|
<div class="wi-douban__poster wi-douban__poster--placeholder" th:if="${#strings.isEmpty(item.poster)}">
|
||||||
<span th:text="${#strings.substring(item.spec?.name ?: '', 0, 1)}"></span>
|
<span th:text="${#strings.substring(item.name ?: '', 0, 1)}"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-douban__body">
|
<div class="wi-douban__body">
|
||||||
<h3 class="wi-douban__name" th:text="${item.spec?.name}"></h3>
|
<h3 class="wi-douban__name" th:text="${item.name}"></h3>
|
||||||
<p
|
<p
|
||||||
class="wi-douban__subtitle"
|
class="wi-douban__subtitle"
|
||||||
th:if="${!#strings.isEmpty(item.spec?.cardSubtitle)}"
|
th:if="${!#strings.isEmpty(item.cardSubtitle)}"
|
||||||
th:text="${item.spec?.cardSubtitle}"
|
th:text="${item.cardSubtitle}"
|
||||||
></p>
|
></p>
|
||||||
<div class="wi-douban__meta">
|
<div class="wi-douban__meta">
|
||||||
<span
|
<span
|
||||||
class="wi-douban__score"
|
class="wi-douban__score"
|
||||||
th:if="${!#strings.isEmpty(item.spec?.score) and item.spec?.score != '0'}"
|
th:if="${!#strings.isEmpty(item.score) and item.score != '0'}"
|
||||||
>
|
>
|
||||||
<svg class="wi-douban__star" viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
<svg class="wi-douban__star" viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
||||||
<th:block th:text="${item.spec?.score}"></th:block>
|
<th:block th:text="${item.score}"></th:block>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="wi-douban__my-score"
|
class="wi-douban__my-score"
|
||||||
th:if="${!#strings.isEmpty(item.faves?.score)}"
|
th:if="${!#strings.isEmpty(item.favesScore)}"
|
||||||
>
|
>
|
||||||
<span th:text="#{douban.myRating}">My:</span><th:block th:text="${item.faves?.score}"></th:block>
|
<span th:text="'我的:'">My:</span><th:block th:text="${item.favesScore}"></th:block>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="wi-douban__year"
|
class="wi-douban__year"
|
||||||
th:if="${!#strings.isEmpty(item.spec?.year)}"
|
th:if="${!#strings.isEmpty(item.year)}"
|
||||||
th:text="${item.spec?.year}"
|
th:text="${item.year}"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-douban__tags" th:if="${item.spec?.genres != null and !#sets.isEmpty(item.spec?.genres)}">
|
<div class="wi-douban__tags" th:if="${item.genres != null and !#sets.isEmpty(item.genres)}">
|
||||||
<span
|
<span
|
||||||
class="wi-douban__tag"
|
class="wi-douban__tag"
|
||||||
th:each="genre : ${item.spec?.genres}"
|
th:each="genre : ${item.genres}"
|
||||||
th:text="${genre}"
|
th:text="${genre}"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<p
|
<p
|
||||||
class="wi-douban__remark"
|
class="wi-douban__remark"
|
||||||
th:if="${!#strings.isEmpty(item.faves?.remark)}"
|
th:if="${!#strings.isEmpty(item.favesRemark)}"
|
||||||
th:text="${item.faves?.remark}"
|
th:text="${item.favesRemark}"
|
||||||
></p>
|
></p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
|
|
||||||
<Layout wide>
|
<Layout wide>
|
||||||
<Fragment slot="head">
|
<Fragment slot="head">
|
||||||
<title th:text="|${theme.config?.friends?.friends_page_title ?: #messages.msg('friends.title')} - ${site.title}|"></title>
|
<title th:text="|${theme.config?.friends?.friends_page_title ?: '友人圈'} - ${site.title}|"></title>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
@@ -15,7 +15,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<div class="wi-friends-page__header">
|
<div class="wi-friends-page__header">
|
||||||
<h1
|
<h1
|
||||||
class="wi-friends-page__title"
|
class="wi-friends-page__title"
|
||||||
th:text="${theme.config?.friends?.friends_page_title ?: #messages.msg('friends.title')}"
|
th:text="${theme.config?.friends?.friends_page_title ?: '友人圈'}"
|
||||||
>
|
>
|
||||||
Friends Circle
|
Friends Circle
|
||||||
</h1>
|
</h1>
|
||||||
@@ -87,7 +87,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
th:if="${friends.hasPrevious()}"
|
th:if="${friends.hasPrevious()}"
|
||||||
th:href="@{${friends.prevUrl}}"
|
th:href="@{${friends.prevUrl}}"
|
||||||
class="wi-friends-page__page-link"
|
class="wi-friends-page__page-link"
|
||||||
th:text="#{common.newer}"
|
th:text="'较新'"
|
||||||
>
|
>
|
||||||
← Newer
|
← Newer
|
||||||
</a>
|
</a>
|
||||||
@@ -99,7 +99,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
th:if="${friends.hasNext()}"
|
th:if="${friends.hasNext()}"
|
||||||
th:href="@{${friends.nextUrl}}"
|
th:href="@{${friends.nextUrl}}"
|
||||||
class="wi-friends-page__page-link"
|
class="wi-friends-page__page-link"
|
||||||
th:text="#{common.older}"
|
th:text="'较旧'"
|
||||||
>
|
>
|
||||||
Older →
|
Older →
|
||||||
</a>
|
</a>
|
||||||
@@ -113,11 +113,11 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
>
|
>
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<div class="wi-friends-page__header">
|
<div class="wi-friends-page__header">
|
||||||
<h1 class="wi-friends-page__title" th:text="#{friends.title}">Friends Circle</h1>
|
<h1 class="wi-friends-page__title" th:text="'友人圈'">友人圈</h1>
|
||||||
<span class="wi-page-accent"></span>
|
<span class="wi-page-accent"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-friends-page__empty">
|
<div class="wi-friends-page__empty">
|
||||||
<p th:utext="#{common.installPlugin('Friends Circle', 'https://www.halo.run/store/apps/app-friends')}">This feature requires the Friends Circle plugin</p>
|
<p>该功能需要安装「朋友圈」插件,<a href="https://www.halo.run/store/apps/app-yISsV" target="_blank" rel="noopener noreferrer">前往应用市场安装</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+23
-15
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
import HeroSection from "../components/HeroSection.astro";
|
import HeroSection from "../components/HeroSection.astro";
|
||||||
|
import WelcomePopup from "../components/WelcomePopup.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout home>
|
<Layout home>
|
||||||
@@ -8,12 +9,13 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
<title th:text="${site.title}"></title>
|
<title th:text="${site.title}"></title>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
|
<WelcomePopup />
|
||||||
|
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="wi-home-content"
|
class="wi-home-content"
|
||||||
th:classappend="${theme.config?.hero?.hero_enabled != false} ? 'wi-home-content--with-hero'"
|
th:classappend="${theme.config?.hero?.hero_enabled != false} ? 'wi-home-content--with-hero'"
|
||||||
th:attr="data-i18n-all-loaded=#{common.allLoaded},data-i18n-loading=#{common.loading},data-i18n-words=#{common.words},data-i18n-read-time=#{common.readTime},data-i18n-views=#{common.views}"
|
|
||||||
>
|
>
|
||||||
<section
|
<section
|
||||||
class="wi-home-pinned"
|
class="wi-home-pinned"
|
||||||
@@ -21,7 +23,7 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
>
|
>
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<div class="wi-section__header">
|
<div class="wi-section__header">
|
||||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_pinned_title ?: #messages.msg('hero.pinned')}">置顶</h2>
|
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_pinned_title ?: '置顶'}">置顶</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-pinned">
|
<div class="wi-pinned">
|
||||||
<a
|
<a
|
||||||
@@ -66,7 +68,7 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
<section class="wi-home-posts" id="wi-home-posts">
|
<section class="wi-home-posts" id="wi-home-posts">
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<div class="wi-section__header">
|
<div class="wi-section__header">
|
||||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_posts_title ?: #messages.msg('hero.posts')}">文章</h2>
|
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_posts_title ?: '文章'}">文章</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-flow" id="wi-post-grid">
|
<div class="wi-flow" id="wi-post-grid">
|
||||||
<article
|
<article
|
||||||
@@ -125,11 +127,16 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
<div class="wi-flow__stats">
|
<div class="wi-flow__stats">
|
||||||
|
<span
|
||||||
|
class="wi-flow__stat"
|
||||||
|
th:if="${theme.config?.home?.home_card_show_wordcount == true and pluginFinder.available('extra-api')}"
|
||||||
|
th:text="${extraApiStatsFinder.getPostWordCount({name: post.metadata.name}) + ' 字'}"
|
||||||
|
></span>
|
||||||
<span
|
<span
|
||||||
class="wi-flow__stat"
|
class="wi-flow__stat"
|
||||||
th:if="${theme.config?.home?.home_card_show_visit ?: true}"
|
th:if="${theme.config?.home?.home_card_show_visit ?: true}"
|
||||||
>
|
>
|
||||||
<th:block th:text="${post.stats?.visit ?: 0}"></th:block> <th:block th:text="#{common.views}">阅读</th:block>
|
<th:block th:text="${post.stats?.visit ?: 0}"></th:block> <th:block th:text="'阅读'">阅读</th:block>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,7 +160,7 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
th:if="${posts.hasPrevious()}"
|
th:if="${posts.hasPrevious()}"
|
||||||
th:href="@{${posts.prevUrl}}"
|
th:href="@{${posts.prevUrl}}"
|
||||||
class="wi-pagination__prev"
|
class="wi-pagination__prev"
|
||||||
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
|
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||||
>← 较新</a>
|
>← 较新</a>
|
||||||
<span
|
<span
|
||||||
class="wi-pagination__info"
|
class="wi-pagination__info"
|
||||||
@@ -163,18 +170,18 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
th:if="${posts.hasNext()}"
|
th:if="${posts.hasNext()}"
|
||||||
th:href="@{${posts.nextUrl}}"
|
th:href="@{${posts.nextUrl}}"
|
||||||
class="wi-pagination__next"
|
class="wi-pagination__next"
|
||||||
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
|
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||||
>较旧 →</a>
|
>较旧 →</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
th:if="${posts.hasNext() and theme.config?.home?.home_post_loading == 'infinite_scroll'}"
|
th:if="${posts.hasNext() and theme.config?.home?.home_post_loading == 'infinite_scroll'}"
|
||||||
id="wi-infinite-scroll-sentinel"
|
id="wi-infinite-scroll-sentinel"
|
||||||
th:attr="data-next-url=${posts.nextUrl}, data-all-loaded-text=${theme.config?.home?.home_label_all_loaded ?: #messages.msg('common.allLoaded')}"
|
th:attr="data-next-url=${posts.nextUrl}, data-all-loaded-text=${theme.config?.home?.home_label_all_loaded ?: '已加载全部文章'}"
|
||||||
>
|
>
|
||||||
<div class="wi-infinite-scroll__loader">
|
<div class="wi-infinite-scroll__loader">
|
||||||
<div class="wi-infinite-scroll__spinner"></div>
|
<div class="wi-infinite-scroll__spinner"></div>
|
||||||
<span th:text="${theme.config?.home?.home_label_loading ?: #messages.msg('common.loading')}">加载中...</span>
|
<span th:text="${theme.config?.home?.home_label_loading ?: '加载中...'}">加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -182,23 +189,22 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
th:if="${!posts.hasNext() and posts.totalPages gt 1 and theme.config?.home?.home_post_loading == 'infinite_scroll'}"
|
th:if="${!posts.hasNext() and posts.totalPages gt 1 and theme.config?.home?.home_post_loading == 'infinite_scroll'}"
|
||||||
class="wi-infinite-scroll__end"
|
class="wi-infinite-scroll__end"
|
||||||
>
|
>
|
||||||
<span th:text="${theme.config?.home?.home_label_all_loaded ?: #messages.msg('common.allLoaded')}">已加载全部文章</span>
|
<span th:text="${theme.config?.home?.home_label_all_loaded ?: '已加载全部文章'}">已加载全部文章</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
(function () {
|
(function () {
|
||||||
var sentinel = document.getElementById("wi-infinite-scroll-sentinel");
|
var sentinel = document.getElementById("wi-infinite-scroll-sentinel");
|
||||||
if (!sentinel) return;
|
if (!sentinel) return;
|
||||||
|
|
||||||
var grid = document.getElementById("wi-post-grid");
|
var grid = document.getElementById("wi-post-grid");
|
||||||
var i18nEl = document.querySelector(".wi-home-content");
|
|
||||||
var loading = false;
|
var loading = false;
|
||||||
var nextUrl = sentinel.getAttribute("data-next-url");
|
var nextUrl = sentinel.getAttribute("data-next-url");
|
||||||
var allLoadedText = sentinel.getAttribute("data-all-loaded-text") || (i18nEl && i18nEl.getAttribute("data-i18n-all-loaded")) || "All posts loaded";
|
var allLoadedText = sentinel.getAttribute("data-all-loaded-text") || "已加载全部文章";
|
||||||
|
|
||||||
var observer = new IntersectionObserver(
|
var observer = new IntersectionObserver(
|
||||||
function (entries) {
|
function (entries) {
|
||||||
@@ -240,7 +246,9 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
sentinel.style.display = "none";
|
sentinel.style.display = "none";
|
||||||
var endMsg = document.createElement("div");
|
var endMsg = document.createElement("div");
|
||||||
endMsg.className = "wi-infinite-scroll__end";
|
endMsg.className = "wi-infinite-scroll__end";
|
||||||
endMsg.innerHTML = "<span>" + allLoadedText + "</span>";
|
var endText = document.createElement("span");
|
||||||
|
endText.textContent = allLoadedText;
|
||||||
|
endMsg.appendChild(endText);
|
||||||
sentinel.parentNode.appendChild(endMsg);
|
sentinel.parentNode.appendChild(endMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +268,7 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
(function () {
|
(function () {
|
||||||
var hero = document.getElementById("wi-hero");
|
var hero = document.getElementById("wi-hero");
|
||||||
var blurOverlay = document.getElementById("wi-hero-blur-overlay");
|
var blurOverlay = document.getElementById("wi-hero-blur-overlay");
|
||||||
@@ -304,7 +312,7 @@ import HeroSection from "../components/HeroSection.astro";
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
(function () {
|
(function () {
|
||||||
var hero = document.getElementById("wi-hero");
|
var hero = document.getElementById("wi-hero");
|
||||||
var content = document.querySelector(".wi-home-content--with-hero");
|
var content = document.querySelector(".wi-home-content--with-hero");
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
页面布局契约(Halo 2.26+)
|
||||||
|
供插件前台页面通过 ~{layout :: html(head = ..., content = ...)} 复用主题外观,
|
||||||
|
避免这些页面回退到 Halo 内置的 fallback 布局导致风格不一致。
|
||||||
|
|
||||||
|
本文件只负责声明契约:实际结构、配色、导航、页脚均由 Layout 组件提供,
|
||||||
|
因此修改主题外观时无需同步改这里。
|
||||||
|
-->
|
||||||
|
<Layout layoutFragment wide />
|
||||||
+414
-17
@@ -4,24 +4,39 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
|
|
||||||
<Layout wide>
|
<Layout wide>
|
||||||
<Fragment slot="head">
|
<Fragment slot="head">
|
||||||
<title th:text="|${theme.config?.links?.links_page_title ?: #messages.msg('links.title')} - ${site.title}|"></title>
|
<title th:text="|${theme.config?.links?.links_page_title ?: '友链'} - ${site.title}|"></title>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
class="wi-links"
|
class="wi-links"
|
||||||
th:if="${pluginFinder.available('PluginLinks')}"
|
th:if="${pluginFinder.available('PluginLinks')}"
|
||||||
th:attr="data-style=${theme.config?.links?.links_style ?: 'cards'}"
|
th:attr="data-style=${theme.config?.links?.links_style ?: 'cards'},data-random=${theme.config?.links?.links_random_order == true ? 'true' : 'false'}"
|
||||||
>
|
>
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<div class="wi-links__header">
|
<div class="wi-links__header">
|
||||||
<h1
|
<h1
|
||||||
class="wi-links__title"
|
class="wi-links__title"
|
||||||
th:text="${theme.config?.links?.links_page_title ?: #messages.msg('links.title')}"
|
th:text="${theme.config?.links?.links_page_title ?: '友链'}"
|
||||||
>
|
>
|
||||||
友情链接
|
友情链接
|
||||||
</h1>
|
</h1>
|
||||||
<p class="wi-links__subtitle" th:text="#{links.subtitle}">Mountains and rivers, a journey of a thousand miles</p>
|
<p class="wi-links__subtitle" th:text="'山水一程,三生有幸'">山水一程,三生有幸</p>
|
||||||
<span class="wi-page-accent"></span>
|
<span class="wi-page-accent"></span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="wi-links__portal"
|
||||||
|
id="wi-links-portal"
|
||||||
|
th:if="${theme.config?.links?.links_random_portal != false}"
|
||||||
|
title="随机传送"
|
||||||
|
aria-label="随机传送"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||||
|
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
||||||
|
<polyline points="3.27 6.96 12 12.01 20.73 6.96" transform="translate(0 8)"></polyline>
|
||||||
|
</svg>
|
||||||
|
<span class="wi-links__portal-text">随机传送</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<th:block th:each="group : ${groups}">
|
<th:block th:each="group : ${groups}">
|
||||||
@@ -38,23 +53,38 @@ 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}"
|
||||||
>
|
>
|
||||||
<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'}"
|
||||||
|
th:title="'站点访问正常'"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
<span
|
||||||
|
class="wi-links__check wi-links__check--bad"
|
||||||
|
th:if="${accessState == 'INACCESSIBLE'}"
|
||||||
|
th:title="'站点访问异常'"
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
<div class="wi-links__info">
|
<div class="wi-links__info">
|
||||||
<span
|
<span
|
||||||
class="wi-links__name"
|
class="wi-links__name"
|
||||||
@@ -71,8 +101,167 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
</a>
|
</a>
|
||||||
</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
|
||||||
|
class="wi-links__apply"
|
||||||
|
th:if="${theme.config?.links?.links_apply_form_enabled != false}"
|
||||||
|
th:with="appliedList=${param?.applied},
|
||||||
|
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>
|
||||||
|
<p
|
||||||
|
class="wi-links__apply-result"
|
||||||
|
th:if="${applied != null}"
|
||||||
|
th:classappend="${applied == 'success'} ? 'wi-links-check-ok' : 'wi-links-check-error'"
|
||||||
|
th:text="${applyMsg != null ? applyMsg : '申请已提交,等待站长审核 🌙'}"
|
||||||
|
role="status"
|
||||||
|
></p>
|
||||||
|
|
||||||
|
<th:block th:unless="${applied == 'disabled'}">
|
||||||
|
<form
|
||||||
|
class="wi-links__apply-form"
|
||||||
|
method="post"
|
||||||
|
th:action="@{/links/apply/submit}"
|
||||||
|
>
|
||||||
|
<input type="hidden" name="_csrf" th:value="${csrfToken}" />
|
||||||
|
<div class="wi-links__apply-row">
|
||||||
|
<label class="wi-links__apply-field">
|
||||||
|
<span th:text="'站点名称 *'">站点名称 *</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="displayName"
|
||||||
|
required
|
||||||
|
maxlength="50"
|
||||||
|
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">
|
||||||
|
<label class="wi-links__apply-field">
|
||||||
|
<span th:text="'图标地址'">图标地址</span>
|
||||||
|
<input type="url" name="logo" placeholder="https://" />
|
||||||
|
</label>
|
||||||
|
<label class="wi-links__apply-field">
|
||||||
|
<span th:text="'联系邮箱'">联系邮箱</span>
|
||||||
|
<input type="email" name="email" placeholder="用于通知审核结果" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label class="wi-links__apply-field">
|
||||||
|
<span th:text="'站点描述'">站点描述</span>
|
||||||
|
<input type="text" name="description" maxlength="120" placeholder="一句话介绍你的站点" />
|
||||||
|
</label>
|
||||||
|
<label class="wi-links__apply-field">
|
||||||
|
<span th:text="'反链页面'">反链页面</span>
|
||||||
|
<input type="url" name="backlink" placeholder="https://" />
|
||||||
|
</label>
|
||||||
|
<label class="wi-links__apply-field">
|
||||||
|
<span th:text="'RSS / Atom 地址(每行一个)'">RSS / Atom 地址(每行一个)</span>
|
||||||
|
<textarea name="feedUrls" rows="2" placeholder="https://"></textarea>
|
||||||
|
</label>
|
||||||
|
<div class="wi-links__apply-row">
|
||||||
|
<label class="wi-links__apply-field">
|
||||||
|
<span th:text="'验证码 *'">验证码 *</span>
|
||||||
|
<input type="text" name="captchaCode" required maxlength="8" autocomplete="off" placeholder="图中五位字符" />
|
||||||
|
</label>
|
||||||
|
<div class="wi-links__apply-captcha">
|
||||||
|
<img
|
||||||
|
class="wi-links__apply-captcha-img"
|
||||||
|
th:src="@{/links/apply/captcha}"
|
||||||
|
alt="验证码"
|
||||||
|
width="120"
|
||||||
|
height="44"
|
||||||
|
loading="lazy"
|
||||||
|
title="点击刷新验证码"
|
||||||
|
onclick="this.src=this.src.split('?')[0]+'?ts='+Date.now()"
|
||||||
|
onerror="var c=this.closest('.wi-links__apply');if(c)c.classList.add('wi-links__apply--unavailable')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="wi-links__apply-submit"
|
||||||
|
type="submit"
|
||||||
|
th:text="'提交申请'"
|
||||||
|
>
|
||||||
|
提交申请
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</th:block>
|
||||||
|
<p
|
||||||
|
class="wi-links__apply-unavailable-tip"
|
||||||
|
th:text="'友链申请暂未开放。'"
|
||||||
|
hidden
|
||||||
|
></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wi-links__comment" th:if="${theme.config?.links?.links_show_comment == true}">
|
||||||
|
<halo:comment group="plugin.halo.run" kind="Plugin" name="PluginLinks"></halo:comment>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<script is:inline th:inline="none">
|
||||||
|
(function () {
|
||||||
|
var section = document.querySelector(".wi-links");
|
||||||
|
if (!section) return;
|
||||||
|
|
||||||
|
var cards = Array.prototype.slice.call(section.querySelectorAll(".wi-links__card"));
|
||||||
|
if (!cards.length) return;
|
||||||
|
|
||||||
|
// 随机排列:Fisher-Yates 打乱每个分组内的卡片顺序
|
||||||
|
if (section.getAttribute("data-random") === "true") {
|
||||||
|
Array.prototype.forEach.call(section.querySelectorAll(".wi-links__grid"), function (grid) {
|
||||||
|
for (var i = grid.children.length; i >= 0; i--) {
|
||||||
|
var j = Math.floor(Math.random() * i);
|
||||||
|
grid.appendChild(grid.children[j]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 随机传送:从当前页所有友链中随机打开一个
|
||||||
|
var portal = document.getElementById("wi-links-portal");
|
||||||
|
if (portal) {
|
||||||
|
portal.addEventListener("click", function () {
|
||||||
|
var links = Array.prototype.slice
|
||||||
|
.call(section.querySelectorAll(".wi-links__card"))
|
||||||
|
.map(function (a) {
|
||||||
|
return a.getAttribute("href");
|
||||||
|
})
|
||||||
|
.filter(function (h) {
|
||||||
|
return h && h !== "#";
|
||||||
|
});
|
||||||
|
if (!links.length) return;
|
||||||
|
var target = links[Math.floor(Math.random() * links.length)];
|
||||||
|
window.open(target, "_blank", "noopener");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -85,6 +274,186 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
margin-bottom: var(--space-3xl);
|
margin-bottom: var(--space-3xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wi-links__portal {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.375rem;
|
||||||
|
padding: 0.6rem 1.1rem;
|
||||||
|
min-height: 44px;
|
||||||
|
margin-top: var(--space-md);
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--accent),
|
||||||
|
color-mix(in srgb, var(--accent) 70%, #000)
|
||||||
|
);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 30%, transparent);
|
||||||
|
transition: transform var(--duration-fast) var(--ease-out-quart),
|
||||||
|
box-shadow var(--duration-fast) var(--ease-out-quart),
|
||||||
|
opacity var(--duration-fast) var(--ease-out-quart);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__portal:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 40%, transparent);
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__portal:active {
|
||||||
|
transform: translateY(0) scale(0.97);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__portal svg {
|
||||||
|
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__portal:hover svg {
|
||||||
|
transform: rotate(180deg) scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__comment {
|
||||||
|
margin-top: var(--space-3xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 友链申请表单 ===== */
|
||||||
|
.wi-links__apply {
|
||||||
|
margin-top: var(--space-3xl);
|
||||||
|
padding: var(--space-xl);
|
||||||
|
border: 1px solid var(--rule);
|
||||||
|
border-radius: var(--radius-lg, 16px);
|
||||||
|
background: var(--bg-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--text-xl);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--ink);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-result {
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--accent);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-result--ok,
|
||||||
|
.wi-links-check-ok {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-result--error,
|
||||||
|
.wi-links-check-error {
|
||||||
|
color: #e6162d;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .wi-links__apply-result--error,
|
||||||
|
html.dark .wi-links-check-error {
|
||||||
|
color: #ff6b6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--ink-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-field input,
|
||||||
|
.wi-links__apply-field textarea {
|
||||||
|
padding: 0.6rem 0.8rem;
|
||||||
|
border: 1px solid var(--rule);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--ink);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-field input:focus,
|
||||||
|
.wi-links__apply-field textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-captcha {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-captcha-img {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--rule);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-submit {
|
||||||
|
align-self: center;
|
||||||
|
padding: 0.65rem 2.2rem;
|
||||||
|
min-height: 44px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: inherit;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 70%, #000));
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform var(--duration-fast) var(--ease-out-quart), opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-submit:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply-submit:active {
|
||||||
|
transform: translateY(0) scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 插件未开放申请时(验证码 404)自动隐藏表单并提示 */
|
||||||
|
.wi-links__apply--unavailable .wi-links__apply-form {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-links__apply--unavailable .wi-links__apply-unavailable-tip {
|
||||||
|
display: block !important;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--ink-3);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.wi-links__apply-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.wi-links__title {
|
.wi-links__title {
|
||||||
font-size: var(--text-4xl);
|
font-size: var(--text-4xl);
|
||||||
margin-bottom: var(--space-xs);
|
margin-bottom: var(--space-xs);
|
||||||
@@ -153,8 +522,9 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
/* 注意:这里绝不能加 overflow:hidden —— 悬浮时内层卡片会上移 4px 并投射大范围
|
||||||
|
阴影,裁切会把阴影切出一条"断开的光带"、卡片顶部被削平(v1.2.0 修复) */
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-links__card-glow {
|
.wi-links__card-glow {
|
||||||
@@ -198,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;
|
||||||
@@ -208,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);
|
||||||
}
|
}
|
||||||
|
|||||||
+129
-32
@@ -5,13 +5,22 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Fragment slot="head">
|
<Fragment slot="head">
|
||||||
<title th:text="|${theme.config?.moments?.moments_page_title ?: #messages.msg('moments.title')} - ${site.title}|"></title>
|
<title th:text="|${theme.config?.moments?.moments_page_title ?: '动态'} - ${site.title}|"></title>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
<section class="wi-moments-page" th:attr="data-i18n-all-moments-loaded=#{common.allMomentsLoaded}">
|
<section class="wi-moments-page">
|
||||||
|
<div class="wi-moments-page__hero" th:if="${theme.config?.moments?.moments_cover}">
|
||||||
|
<img
|
||||||
|
class="wi-moments-page__cover"
|
||||||
|
th:src="${theme.config?.moments?.moments_cover}"
|
||||||
|
alt=""
|
||||||
|
loading="eager"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="wi-container">
|
<div class="wi-container">
|
||||||
<div class="wi-moments-page__header">
|
<div class="wi-moments-page__header" th:unless="${theme.config?.moments?.moments_cover}">
|
||||||
<h1 class="wi-moments-page__title" th:text="${theme.config?.moments?.moments_page_title ?: #messages.msg('moments.title')}">瞬间</h1>
|
<h1 class="wi-moments-page__title" th:text="${theme.config?.moments?.moments_page_title ?: '动态'}">瞬间</h1>
|
||||||
<span class="wi-page-accent"></span>
|
<span class="wi-page-accent"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -22,20 +31,42 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
<div class="wi-moments-page__cards" th:if="${theme.config?.moments?.moments_style == 'cards' or theme.config?.moments?.moments_style == null or theme.config?.moments?.moments_style == 'timeline'}">
|
<div class="wi-moments-page__cards" th:if="${theme.config?.moments?.moments_style == 'cards' or theme.config?.moments?.moments_style == null or theme.config?.moments?.moments_style == 'timeline'}">
|
||||||
<th:block th:if="${momentsResult != null and not #lists.isEmpty(momentsResult.items)}">
|
<th:block th:if="${momentsResult != null and not #lists.isEmpty(momentsResult.items)}">
|
||||||
<div class="wi-moments-page__card-item" th:each="moment : ${momentsResult.items}">
|
<div class="wi-moments-page__card-item" th:each="moment : ${momentsResult.items}">
|
||||||
<div class="wi-moments-page__card-text" th:utext="${moment.spec?.content?.html ?: moment.spec?.content?.raw ?: moment.spec?.content}"></div>
|
<div class="wi-moments-page__card-layout">
|
||||||
<div class="wi-moments-page__card-media" th:if="${moment.spec.content.medium != null and !#lists.isEmpty(moment.spec.content.medium)}">
|
<img
|
||||||
<th:block th:each="media : ${moment.spec.content.medium}">
|
th:if="${moment.owner != null and !#strings.isEmpty(moment.owner.avatar)}"
|
||||||
<img
|
class="wi-moments-page__avatar"
|
||||||
th:if="${media.type.name == 'PHOTO'}"
|
th:src="${moment.owner.avatar}"
|
||||||
th:src="${media.url}"
|
th:alt="''"
|
||||||
th:alt="''"
|
loading="lazy"
|
||||||
class="wi-moments-page__card-image"
|
decoding="async"
|
||||||
loading="lazy"
|
/>
|
||||||
decoding="async"
|
<div
|
||||||
/>
|
th:unless="${moment.owner != null and !#strings.isEmpty(moment.owner.avatar)}"
|
||||||
</th:block>
|
class="wi-moments-page__avatar wi-moments-page__avatar--fallback"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
<div class="wi-moments-page__card-footer">
|
>
|
||||||
|
<span th:text="${#strings.substring(site.title, 0, 1)}"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wi-moments-page__main">
|
||||||
|
<div
|
||||||
|
class="wi-moments-page__name"
|
||||||
|
th:text="${moment.owner != null and !#strings.isEmpty(moment.owner.displayName) ? moment.owner.displayName : site.title}"
|
||||||
|
></div>
|
||||||
|
<div class="wi-moments-page__card-text" th:utext="${moment.spec?.content?.html ?: moment.spec?.content?.raw ?: moment.spec?.content}"></div>
|
||||||
|
<div class="wi-moments-page__card-media" th:if="${moment.spec.content.medium != null and !#lists.isEmpty(moment.spec.content.medium)}">
|
||||||
|
<th:block th:each="media : ${moment.spec.content.medium}">
|
||||||
|
<img
|
||||||
|
th:if="${media.type.name == 'PHOTO'}"
|
||||||
|
th:src="${media.url}"
|
||||||
|
th:alt="''"
|
||||||
|
class="wi-moments-page__card-image"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
</th:block>
|
||||||
|
</div>
|
||||||
|
<div class="wi-moments-page__card-footer">
|
||||||
<time
|
<time
|
||||||
class="wi-moments-page__card-date"
|
class="wi-moments-page__card-date"
|
||||||
th:text="${#dates.format(moment.spec?.releaseTime ?: moment.metadata?.creationTimestamp, 'yyyy-MM-dd HH:mm')}"
|
th:text="${#dates.format(moment.spec?.releaseTime ?: moment.metadata?.creationTimestamp, 'yyyy-MM-dd HH:mm')}"
|
||||||
@@ -66,11 +97,13 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
th:attr="name=${moment.metadata.name}"
|
th:attr="name=${moment.metadata.name}"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div> <!-- /__main -->
|
||||||
|
</div> <!-- /__card-layout -->
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
|
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
|
||||||
<div class="wi-moments-page__empty">
|
<div class="wi-moments-page__empty">
|
||||||
<p th:text="#{moments.noMoments}">No moments yet</p>
|
<p th:text="'还没有瞬间,快去记录当下的心情吧 🌙'">还没有瞬间,快去记录当下的心情吧 🌙</p>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,7 +159,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
</th:block>
|
</th:block>
|
||||||
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
|
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
|
||||||
<div class="wi-moments-page__empty">
|
<div class="wi-moments-page__empty">
|
||||||
<p th:text="#{moments.noMoments}">No moments yet</p>
|
<p th:text="'还没有瞬间,快去记录当下的心情吧 🌙'">还没有瞬间,快去记录当下的心情吧 🌙</p>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,13 +174,13 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
class="wi-moments-page__page-btn"
|
class="wi-moments-page__page-btn"
|
||||||
th:if="${momentsResult.hasPrevious()}"
|
th:if="${momentsResult.hasPrevious()}"
|
||||||
th:href="${momentsResult.prevUrl}"
|
th:href="${momentsResult.prevUrl}"
|
||||||
th:text="#{common.newer}">Newer</a>
|
th:text="'较新'">较新</a>
|
||||||
<span class="wi-moments-page__page-spacer" th:if="${momentsResult.hasPrevious() and momentsResult.hasNext()}"></span>
|
<span class="wi-moments-page__page-spacer" th:if="${momentsResult.hasPrevious() and momentsResult.hasNext()}"></span>
|
||||||
<a
|
<a
|
||||||
class="wi-moments-page__page-btn"
|
class="wi-moments-page__page-btn"
|
||||||
th:if="${momentsResult.hasNext()}"
|
th:if="${momentsResult.hasNext()}"
|
||||||
th:href="${momentsResult.nextUrl}"
|
th:href="${momentsResult.nextUrl}"
|
||||||
th:text="#{common.older}">Older</a>
|
th:text="'较旧'">较旧</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -157,7 +190,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
></div>
|
></div>
|
||||||
<th:block th:if="${momentFinder == null}">
|
<th:block th:if="${momentFinder == null}">
|
||||||
<div class="wi-moments-page__empty">
|
<div class="wi-moments-page__empty">
|
||||||
<p th:text="#{common.pluginError}">Plugin error</p>
|
<p th:text="'插件加载异常,请检查插件状态'">插件加载异常,请检查插件状态</p>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,7 +198,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
|
|
||||||
<th:block th:unless="${pluginFinder.available('PluginMoments')}">
|
<th:block th:unless="${pluginFinder.available('PluginMoments')}">
|
||||||
<div class="wi-moments-page__empty">
|
<div class="wi-moments-page__empty">
|
||||||
<p th:utext="#{common.installPlugin('Moments', 'https://www.halo.run/store/apps/app-hqbe')}">This feature requires the Moments plugin</p>
|
<p>该功能需要安装「瞬间」插件,<a href="https://www.halo.run/store/apps/app-SnwWD" target="_blank" rel="noopener noreferrer">前往应用市场安装</a></p>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,7 +207,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
<LightGallery />
|
<LightGallery />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
(function () {
|
(function () {
|
||||||
document.addEventListener("click", function (e) {
|
document.addEventListener("click", function (e) {
|
||||||
var likeBtn = e.target.closest(".wi-moments-page__like-btn");
|
var likeBtn = e.target.closest(".wi-moments-page__like-btn");
|
||||||
@@ -350,9 +383,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
sentinel.style.display = "none";
|
sentinel.style.display = "none";
|
||||||
var endMsg = document.createElement("div");
|
var endMsg = document.createElement("div");
|
||||||
endMsg.className = "wi-moments-page__empty";
|
endMsg.className = "wi-moments-page__empty";
|
||||||
var momentsSection = document.querySelector(".wi-moments-page");
|
endMsg.innerHTML = "<p>已加载全部瞬间</p>";
|
||||||
var allMomentsText = momentsSection ? momentsSection.getAttribute("data-i18n-all-moments-loaded") : "All moments loaded";
|
|
||||||
endMsg.innerHTML = "<p>" + allMomentsText + "</p>";
|
|
||||||
sentinel.parentNode.appendChild(endMsg);
|
sentinel.parentNode.appendChild(endMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,6 +407,23 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
padding-block: 1rem;
|
padding-block: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wi-moments-page__hero {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: clamp(220px, 30vw, 380px);
|
||||||
|
/* 顶图与首条瞬间的卡片边框不能贴死:留出与页面头部一致的呼吸间距 */
|
||||||
|
margin-bottom: var(--space-2xl);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--bg-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-moments-page__cover {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.wi-moments-page__header {
|
.wi-moments-page__header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: var(--space-3xl);
|
margin-bottom: var(--space-3xl);
|
||||||
@@ -505,6 +553,43 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 朋友圈式布局:头像在左,昵称/正文/九宫格在右 */
|
||||||
|
.wi-moments-page__card-layout {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-md);
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-moments-page__avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 10px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-moments-page__avatar--fallback {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-moments-page__main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-moments-page__name {
|
||||||
|
font-size: var(--text-base);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent);
|
||||||
|
margin-bottom: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
.wi-moments-page__card-text {
|
.wi-moments-page__card-text {
|
||||||
font-size: var(--text-base);
|
font-size: var(--text-base);
|
||||||
line-height: var(--leading-relaxed);
|
line-height: var(--leading-relaxed);
|
||||||
@@ -546,8 +631,15 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-moments-page__card-media:has(.wi-moments-page__card-image:nth-child(4)) {
|
/* 微信九宫格规则:4 张时 2x2 排布 */
|
||||||
grid-template-columns: repeat(3, 1fr);
|
.wi-moments-page__card-media:has(.wi-moments-page__card-image:nth-child(4)):not(:has(.wi-moments-page__card-image:nth-child(5))) {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
max-width: 72%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 三列九宫格限制宽度,避免大屏下图片单元过大 */
|
||||||
|
.wi-moments-page__card-media:has(.wi-moments-page__card-image:nth-child(3)) {
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-moments-page__card-footer {
|
.wi-moments-page__card-footer {
|
||||||
@@ -625,8 +717,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 {
|
||||||
@@ -678,6 +771,10 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
.wi-moments-page__hero {
|
||||||
|
height: clamp(180px, 36vw, 260px);
|
||||||
|
}
|
||||||
|
|
||||||
.wi-moments-page__masonry {
|
.wi-moments-page__masonry {
|
||||||
columns: 1;
|
columns: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,21 +92,6 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-page__body :is(h1, h2, h3, h4, h5, h6) {
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
margin-top: 2em;
|
|
||||||
margin-bottom: 0.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-page__body p {
|
|
||||||
margin-block-end: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-page__body img {
|
|
||||||
border-radius: 12px;
|
|
||||||
margin-block: var(--space-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-page__body a {
|
.wi-page__body a {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-decoration-thickness: 1px;
|
text-decoration-thickness: 1px;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<Layout>
|
<Layout>
|
||||||
<Fragment slot="head">
|
<Fragment slot="head">
|
||||||
<title
|
<title
|
||||||
th:text="|${theme.config?.messageboard?.messageboard_title ?: #messages.msg('messageboard.title')} - ${site.title}|"
|
th:text="|${theme.config?.messageboard?.messageboard_title ?: '留言板'} - ${site.title}|"
|
||||||
></title>
|
></title>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
@@ -14,12 +14,12 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<span class="wi-messageboard__deco">"</span>
|
<span class="wi-messageboard__deco">"</span>
|
||||||
<h1
|
<h1
|
||||||
class="wi-messageboard__title"
|
class="wi-messageboard__title"
|
||||||
th:text="${theme.config?.messageboard?.messageboard_title ?: #messages.msg('messageboard.title')}"
|
th:text="${theme.config?.messageboard?.messageboard_title ?: '留言板'}"
|
||||||
></h1>
|
></h1>
|
||||||
<span class="wi-page-accent"></span>
|
<span class="wi-page-accent"></span>
|
||||||
<p
|
<p
|
||||||
class="wi-messageboard__subtitle"
|
class="wi-messageboard__subtitle"
|
||||||
th:text="${theme.config?.messageboard?.messageboard_subtitle ?: #messages.msg('messageboard.subtitle')}"
|
th:text="${theme.config?.messageboard?.messageboard_subtitle ?: '在这里留下你的足迹 🌙'}"
|
||||||
></p>
|
></p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
|
|
||||||
<Layout wide>
|
<Layout wide>
|
||||||
<Fragment slot="head">
|
<Fragment slot="head">
|
||||||
<title th:text="|${title ?: (theme.config?.photos?.photos_page_title ?: #messages.msg('photos.title'))} - ${site.title}|"></title>
|
<title th:text="|${title ?: (theme.config?.photos?.photos_page_title ?: '相册')} - ${site.title}|"></title>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
<section class="wi-photos-page">
|
<section class="wi-photos-page">
|
||||||
@@ -13,7 +13,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
<div class="wi-photos-page__header">
|
<div class="wi-photos-page__header">
|
||||||
<h1
|
<h1
|
||||||
class="wi-photos-page__title"
|
class="wi-photos-page__title"
|
||||||
th:text="${theme.config?.photos?.photos_page_title ?: #messages.msg('photos.title')}"
|
th:text="${theme.config?.photos?.photos_page_title ?: '相册'}"
|
||||||
>
|
>
|
||||||
Gallery
|
Gallery
|
||||||
</h1>
|
</h1>
|
||||||
@@ -30,7 +30,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
th:href="@{${photoUrl.list()}}"
|
th:href="@{${photoUrl.list()}}"
|
||||||
class="wi-photos-page__group-link"
|
class="wi-photos-page__group-link"
|
||||||
th:classappend="${#strings.isEmpty(currentGroup)} ? 'wi-photos-page__group-link--active' : ''"
|
th:classappend="${#strings.isEmpty(currentGroup)} ? 'wi-photos-page__group-link--active' : ''"
|
||||||
th:text="#{common.all}"
|
th:text="'全部'"
|
||||||
>
|
>
|
||||||
All
|
All
|
||||||
</a>
|
</a>
|
||||||
@@ -49,7 +49,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
<div class="wi-photos-page__all-groups">
|
<div class="wi-photos-page__all-groups">
|
||||||
<th:block th:if="${showUngrouped and photos != null and !#lists.isEmpty(photos.items)}">
|
<th:block th:if="${showUngrouped and photos != null and !#lists.isEmpty(photos.items)}">
|
||||||
<div class="wi-photos-page__section">
|
<div class="wi-photos-page__section">
|
||||||
<h2 class="wi-photos-page__section-title" th:text="#{common.all}">All</h2>
|
<h2 class="wi-photos-page__section-title" th:text="'全部'">全部</h2>
|
||||||
<div
|
<div
|
||||||
class="wi-photos-page__grid"
|
class="wi-photos-page__grid"
|
||||||
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
||||||
@@ -241,7 +241,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-photos-page__empty" th:if="${#lists.isEmpty(group.photos)}">
|
<div class="wi-photos-page__empty" th:if="${#lists.isEmpty(group.photos)}">
|
||||||
<p th:text="#{common.noPhotosInGroup}">No photos in this group.</p>
|
<p th:text="'该分组暂无图片'">该分组暂无图片</p>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
@@ -249,20 +249,20 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<div class="wi-photos-page__empty" th:if="${#lists.isEmpty(groups) and !(showUngrouped and photos != null and !#lists.isEmpty(photos.items))}">
|
<div class="wi-photos-page__empty" th:if="${#lists.isEmpty(groups) and !(showUngrouped and photos != null and !#lists.isEmpty(photos.items))}">
|
||||||
<p th:text="#{common.noPhotos}">No photos yet.</p>
|
<p th:text="'暂无图片'">暂无图片</p>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<th:block th:if="${groups == null}">
|
<th:block th:if="${groups == null}">
|
||||||
<div class="wi-photos-page__empty">
|
<div class="wi-photos-page__empty">
|
||||||
<p th:text="#{common.pluginError}">Plugin error</p>
|
<p th:text="'插件加载异常,请检查插件状态'">插件加载异常,请检查插件状态</p>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<th:block th:unless="${pluginFinder.available('PluginPhotos')}">
|
<th:block th:unless="${pluginFinder.available('PluginPhotos')}">
|
||||||
<div class="wi-photos-page__empty">
|
<div class="wi-photos-page__empty">
|
||||||
<p th:utext="#{common.installPlugin('Gallery', 'https://www.halo.run/store/apps/app-PgHgw')}">This feature requires the Gallery plugin</p>
|
<p>该功能需要安装「相册」插件,<a href="https://www.halo.run/store/apps/app-BmQJW" target="_blank" rel="noopener noreferrer">前往应用市场安装</a></p>
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
@@ -547,7 +547,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
(function () {
|
(function () {
|
||||||
var rafPending = false;
|
var rafPending = false;
|
||||||
|
|
||||||
|
|||||||
+143
-249
@@ -38,7 +38,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
|
|
||||||
<div class="wi-reading-progress" id="wi-reading-progress"></div>
|
<div class="wi-reading-progress" id="wi-reading-progress"></div>
|
||||||
|
|
||||||
<article class="wi-post" th:attr="data-i18n-words=#{common.words},data-i18n-read-time=#{common.readTime}">
|
<article class="wi-post">
|
||||||
<header class="wi-post__header">
|
<header class="wi-post__header">
|
||||||
<div
|
<div
|
||||||
th:if="${theme.config?.article?.article_show_cover != false and !#strings.isEmpty(post.spec.cover)}"
|
th:if="${theme.config?.article?.article_show_cover != false and !#strings.isEmpty(post.spec.cover)}"
|
||||||
@@ -111,12 +111,14 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
th:if="${theme.config?.article?.article_show_word_count ?: true}"
|
th:if="${theme.config?.article?.article_show_word_count ?: true}"
|
||||||
class="wi-post__stat-item"
|
class="wi-post__stat-item"
|
||||||
id="wi-word-count"
|
id="wi-word-count"
|
||||||
>-- words</span>
|
th:with="serverWords=${pluginFinder.available('extra-api') ? extraApiStatsFinder.getPostWordCount({name: post.metadata.name}) : 0}"
|
||||||
|
th:text="${serverWords > 0 ? serverWords + ' 字' : ''}"
|
||||||
|
></span>
|
||||||
<span
|
<span
|
||||||
th:if="${theme.config?.article?.article_show_visit_count ?: true}"
|
th:if="${theme.config?.article?.article_show_visit_count ?: true}"
|
||||||
class="wi-post__stat-item"
|
class="wi-post__stat-item"
|
||||||
>
|
>
|
||||||
<th:block th:text="${post.stats?.visit ?: 0}"></th:block> <th:block th:text="#{common.views}"></th:block>
|
<th:block th:text="${post.stats?.visit ?: 0}"></th:block> <th:block th:text="'阅读'"></th:block>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
th:if="${theme.config?.article?.article_show_read_time ?: false}"
|
th:if="${theme.config?.article?.article_show_read_time ?: false}"
|
||||||
@@ -131,7 +133,10 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
<div
|
<div
|
||||||
class="wi-post__body"
|
class="wi-post__body"
|
||||||
id="wi-post-body"
|
id="wi-post-body"
|
||||||
th:utext="${post.content.content}"
|
th:with="hasExtra=${pluginFinder.available('extra-api')},
|
||||||
|
spaced=${hasExtra and (theme.config?.article?.article_extra_spacing ?: true) ? extraApiRenderFinder.applySpacingInHtml(post.content.content) : post.content.content},
|
||||||
|
rendered=${hasExtra and (theme.config?.article?.article_extra_highlight ?: false) and extraApiPluginInfoFinder.isFullVersion() ? extraApiJsRenderFinder.highlightCodeInHtml(spaced) : spaced}"
|
||||||
|
th:utext="${rendered}"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<aside
|
<aside
|
||||||
@@ -141,7 +146,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
th:attr="data-position=${theme.config?.article?.article_toc_position ?: 'left'},data-expand=${theme.config?.article?.article_toc_expand ?: 'active'}"
|
th:attr="data-position=${theme.config?.article?.article_toc_position ?: 'left'},data-expand=${theme.config?.article?.article_toc_expand ?: 'active'}"
|
||||||
>
|
>
|
||||||
<div class="wi-toc__header">
|
<div class="wi-toc__header">
|
||||||
<span class="wi-toc__title" th:text="#{article.toc}">目录</span>
|
<span class="wi-toc__title" th:text="'目录'">目录</span>
|
||||||
</div>
|
</div>
|
||||||
<nav class="wi-toc__nav" id="wi-toc-nav"></nav>
|
<nav class="wi-toc__nav" id="wi-toc-nav"></nav>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -158,7 +163,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wi-post__share" th:if="${theme.config?.article?.article_show_share != false}" th:with="shareUrl=${post.status.permalink}, shareTitle=${post.spec.title}, shareChannels=${theme.config?.article?.article_share_channels ?: 'qq,wechat,weibo'}">
|
<div class="wi-post__share" th:if="${theme.config?.article?.article_show_share != false}" th:with="shareUrl=${post.status.permalink}, shareTitle=${post.spec.title}, shareChannels=${theme.config?.article?.article_share_channels ?: 'qq,wechat,weibo'}">
|
||||||
<span class="wi-post__share-label" th:text="${theme.config?.article?.article_share_title ?: #messages.msg('share.title')}">分享</span>
|
<span class="wi-post__share-label" th:text="${theme.config?.article?.article_share_title ?: '分享'}">分享</span>
|
||||||
<div class="wi-post__share-buttons">
|
<div class="wi-post__share-buttons">
|
||||||
<button th:if="${#strings.contains(shareChannels, 'qq')}" class="wi-post__share-btn wi-post__share-btn--qq" type="button" th:attr="data-url=${shareUrl},data-title=${shareTitle}" title="QQ">
|
<button th:if="${#strings.contains(shareChannels, 'qq')}" class="wi-post__share-btn wi-post__share-btn--qq" type="button" th:attr="data-url=${shareUrl},data-title=${shareTitle}" title="QQ">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="18" height="18" fill="currentColor"><!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M530.1 484.4C518.6 485.8 485.2 431.7 485.2 431.7C485.2 463 469.1 503.9 434.2 533.5C451 538.7 489 552.7 480 567.9C472.7 580.2 354.5 575.8 320.4 571.9C286.3 575.7 168.1 580.2 160.8 567.9C151.8 552.7 189.7 538.7 206.6 533.5C171.7 504 155.5 463.1 155.5 431.7C155.5 431.7 122.2 485.8 110.6 484.4C105.2 483.8 98.2 454.8 119.9 384.7C130.2 351.7 141.9 324.2 160 278.9C156.9 162 205.2 63.9 320.3 63.9C434 63.9 483.5 160 480.6 278.9C498.7 324.1 510.5 351.8 520.7 384.7C542.5 454.8 535.4 483.8 530 484.4z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="18" height="18" fill="currentColor"><!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M530.1 484.4C518.6 485.8 485.2 431.7 485.2 431.7C485.2 463 469.1 503.9 434.2 533.5C451 538.7 489 552.7 480 567.9C472.7 580.2 354.5 575.8 320.4 571.9C286.3 575.7 168.1 580.2 160.8 567.9C151.8 552.7 189.7 538.7 206.6 533.5C171.7 504 155.5 463.1 155.5 431.7C155.5 431.7 122.2 485.8 110.6 484.4C105.2 483.8 98.2 454.8 119.9 384.7C130.2 351.7 141.9 324.2 160 278.9C156.9 162 205.2 63.9 320.3 63.9C434 63.9 483.5 160 480.6 278.9C498.7 324.1 510.5 351.8 520.7 384.7C542.5 454.8 535.4 483.8 530 484.4z"/></svg>
|
||||||
@@ -189,9 +194,9 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
<button class="wi-wechat-qr__close" type="button" aria-label="Close">
|
<button class="wi-wechat-qr__close" type="button" aria-label="Close">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="wi-wechat-qr__title" th:text="#{share.wechatQR ?: '微信扫码分享'}">微信扫码分享</div>
|
<div class="wi-wechat-qr__title" th:text="'微信扫码分享'">微信扫码分享</div>
|
||||||
<canvas class="wi-wechat-qr__canvas" id="wi-wechat-qr-canvas"></canvas>
|
<canvas class="wi-wechat-qr__canvas" id="wi-wechat-qr-canvas"></canvas>
|
||||||
<div class="wi-wechat-qr__hint" th:text="#{share.wechatHint ?: '打开微信,扫描二维码即可分享'}">打开微信,扫描二维码即可分享</div>
|
<div class="wi-wechat-qr__hint" th:text="'打开微信,扫描二维码即可分享'">打开微信,扫描二维码即可分享</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -205,7 +210,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
th:href="@{${cursor.previous.status.permalink}}"
|
th:href="@{${cursor.previous.status.permalink}}"
|
||||||
class="wi-post__nav-link wi-post__nav-link--prev"
|
class="wi-post__nav-link wi-post__nav-link--prev"
|
||||||
>
|
>
|
||||||
<span class="wi-post__nav-label" th:text="#{article.previousPost}">上一篇</span>
|
<span class="wi-post__nav-label" th:text="'上一篇'">上一篇</span>
|
||||||
<span
|
<span
|
||||||
class="wi-post__nav-title"
|
class="wi-post__nav-title"
|
||||||
th:text="${cursor.previous.spec.title}"
|
th:text="${cursor.previous.spec.title}"
|
||||||
@@ -220,7 +225,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
th:href="@{${cursor.next.status.permalink}}"
|
th:href="@{${cursor.next.status.permalink}}"
|
||||||
class="wi-post__nav-link wi-post__nav-link--next"
|
class="wi-post__nav-link wi-post__nav-link--next"
|
||||||
>
|
>
|
||||||
<span class="wi-post__nav-label" th:text="#{article.nextPost}">下一篇</span>
|
<span class="wi-post__nav-label" th:text="'下一篇'">下一篇</span>
|
||||||
<span
|
<span
|
||||||
class="wi-post__nav-title"
|
class="wi-post__nav-title"
|
||||||
th:text="${cursor.next.spec.title}"
|
th:text="${cursor.next.spec.title}"
|
||||||
@@ -236,7 +241,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
|
|
||||||
<th:block th:if="${theme.config?.article?.article_show_related == true}">
|
<th:block th:if="${theme.config?.article?.article_show_related == true}">
|
||||||
<section class="wi-post__related" th:with="relatedCat=${post.categories != null and !post.categories.isEmpty()} ? ${post.categories[0].metadata.name} : null, relatedPosts=${relatedCat != null} ? ${postFinder.list({size: 3, categoryName: relatedCat})} : ${postFinder.list({size: 3})}">
|
<section class="wi-post__related" th:with="relatedCat=${post.categories != null and !post.categories.isEmpty()} ? ${post.categories[0].metadata.name} : null, relatedPosts=${relatedCat != null} ? ${postFinder.list({size: 3, categoryName: relatedCat})} : ${postFinder.list({size: 3})}">
|
||||||
<h2 class="wi-post__related-title" th:text="${theme.config?.article?.article_related_title ?: #messages.msg('article.relatedPosts')}">相关推荐</h2>
|
<h2 class="wi-post__related-title" th:text="${theme.config?.article?.article_related_title ?: '相关推荐'}">相关推荐</h2>
|
||||||
<div class="wi-post__related-grid">
|
<div class="wi-post__related-grid">
|
||||||
<a th:each="rp : ${relatedPosts.items}" th:if="${rp.metadata.name != post.metadata.name}" th:href="@{${rp.status.permalink}}" class="wi-post__related-card">
|
<a th:each="rp : ${relatedPosts.items}" th:if="${rp.metadata.name != post.metadata.name}" th:href="@{${rp.status.permalink}}" class="wi-post__related-card">
|
||||||
<div class="wi-post__related-cover" th:if="${!#strings.isEmpty(rp.spec.cover)}">
|
<div class="wi-post__related-cover" th:if="${!#strings.isEmpty(rp.spec.cover)}">
|
||||||
@@ -266,7 +271,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
class="wi-toc-fab"
|
class="wi-toc-fab"
|
||||||
id="wi-toc-fab"
|
id="wi-toc-fab"
|
||||||
type="button"
|
type="button"
|
||||||
th:aria-label="#{common.openToc}"
|
th:aria-label="'打开目录'"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/></svg>
|
||||||
</button>
|
</button>
|
||||||
@@ -279,8 +284,8 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
<div class="wi-toc-drawer__overlay"></div>
|
<div class="wi-toc-drawer__overlay"></div>
|
||||||
<div class="wi-toc-drawer__panel">
|
<div class="wi-toc-drawer__panel">
|
||||||
<div class="wi-toc-drawer__header">
|
<div class="wi-toc-drawer__header">
|
||||||
<span class="wi-toc-drawer__title" th:text="#{article.toc}">目录</span>
|
<span class="wi-toc-drawer__title" th:text="'目录'">目录</span>
|
||||||
<button class="wi-toc-drawer__close" type="button" th:aria-label="#{common.closeToc}">
|
<button class="wi-toc-drawer__close" type="button" th:aria-label="'关闭目录'">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -305,7 +310,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
|
|
||||||
<script is:inline defer src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
|
<script is:inline defer src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
|
||||||
<script is:inline defer src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
<script is:inline defer src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
(function () {
|
(function () {
|
||||||
if (typeof Prism !== "undefined") {
|
if (typeof Prism !== "undefined") {
|
||||||
Prism.plugins.autoloader.languages_path = "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/";
|
Prism.plugins.autoloader.languages_path = "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/";
|
||||||
@@ -321,7 +326,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline th:inline="none">
|
||||||
(function () {
|
(function () {
|
||||||
var progressBar = document.getElementById("wi-reading-progress");
|
var progressBar = document.getElementById("wi-reading-progress");
|
||||||
if (progressBar) {
|
if (progressBar) {
|
||||||
@@ -347,6 +352,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
var body = document.getElementById("wi-post-body");
|
var body = document.getElementById("wi-post-body");
|
||||||
if (!body) return;
|
if (!body) return;
|
||||||
|
|
||||||
|
/* 图片:补齐懒加载属性 + 加载前的模糊占位 */
|
||||||
body.querySelectorAll("img").forEach(function(img) {
|
body.querySelectorAll("img").forEach(function(img) {
|
||||||
if (!img.hasAttribute("loading")) {
|
if (!img.hasAttribute("loading")) {
|
||||||
img.setAttribute("loading", "lazy");
|
img.setAttribute("loading", "lazy");
|
||||||
@@ -354,21 +360,63 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
if (!img.hasAttribute("decoding")) {
|
if (!img.hasAttribute("decoding")) {
|
||||||
img.setAttribute("decoding", "async");
|
img.setAttribute("decoding", "async");
|
||||||
}
|
}
|
||||||
|
if (img.complete && img.naturalWidth > 0) {
|
||||||
|
img.classList.add("wi-media-loaded");
|
||||||
|
} else {
|
||||||
|
img.classList.add("wi-media-loading");
|
||||||
|
img.addEventListener("load", function () {
|
||||||
|
img.classList.remove("wi-media-loading");
|
||||||
|
img.classList.add("wi-media-loaded");
|
||||||
|
});
|
||||||
|
img.addEventListener("error", function () {
|
||||||
|
img.classList.remove("wi-media-loading");
|
||||||
|
img.classList.add("wi-media-loaded");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* 嵌入媒体(B站 / 腾讯视频 / YouTube 等 iframe):
|
||||||
|
按原始 width/height 生成 aspect-ratio 容器,保证居中且自适应不形变 */
|
||||||
|
body.querySelectorAll("iframe").forEach(function(frame) {
|
||||||
|
if (frame.closest(".wi-embed")) return;
|
||||||
|
var w = parseInt(frame.getAttribute("width"), 10);
|
||||||
|
var h = parseInt(frame.getAttribute("height"), 10);
|
||||||
|
var wrap = document.createElement("div");
|
||||||
|
wrap.className = "wi-embed";
|
||||||
|
if (w > 0 && h > 0) {
|
||||||
|
wrap.style.aspectRatio = w + " / " + h;
|
||||||
|
} else {
|
||||||
|
wrap.style.aspectRatio = "16 / 9";
|
||||||
|
}
|
||||||
|
frame.parentNode.insertBefore(wrap, frame);
|
||||||
|
wrap.appendChild(frame);
|
||||||
|
frame.removeAttribute("width");
|
||||||
|
frame.removeAttribute("height");
|
||||||
});
|
});
|
||||||
|
|
||||||
var showWordCount = document.getElementById("wi-word-count");
|
var showWordCount = document.getElementById("wi-word-count");
|
||||||
var showReadTime = document.getElementById("wi-read-time");
|
var showReadTime = document.getElementById("wi-read-time");
|
||||||
var articleEl = document.querySelector(".wi-post");
|
var wordsLabel = "字";
|
||||||
var wordsLabel = articleEl ? articleEl.getAttribute("data-i18n-words") : "words";
|
var readTimeLabel = "分钟";
|
||||||
var readTimeLabel = articleEl ? articleEl.getAttribute("data-i18n-read-time") : "min read";
|
|
||||||
|
|
||||||
if (showWordCount || showReadTime) {
|
if (showWordCount || showReadTime) {
|
||||||
var text = (body.textContent || body.innerText || "").trim();
|
var text = (body.textContent || body.innerText || "").trim();
|
||||||
var charCount = text.replace(/\s/g, "").length;
|
var charCount = text.replace(/\s/g, "").length;
|
||||||
var readMinutes = Math.max(1, Math.ceil(charCount / 500));
|
var readMinutes = Math.max(1, Math.ceil(charCount / 500));
|
||||||
|
|
||||||
|
/* 服务端已统计字数时(API 扩展包插件)直接复用,避免重复计算 */
|
||||||
if (showWordCount) {
|
if (showWordCount) {
|
||||||
showWordCount.textContent = charCount + " " + wordsLabel;
|
if (showWordCount.textContent.trim()) {
|
||||||
|
var servered = parseInt(showWordCount.textContent.replace(/\D/g, ""), 10);
|
||||||
|
if (servered > 0) {
|
||||||
|
charCount = servered;
|
||||||
|
readMinutes = Math.max(1, Math.ceil(charCount / 500));
|
||||||
|
} else {
|
||||||
|
showWordCount.textContent = charCount + " " + wordsLabel;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showWordCount.textContent = charCount + " " + wordsLabel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (showReadTime) {
|
if (showReadTime) {
|
||||||
showReadTime.textContent = readMinutes + " " + readTimeLabel;
|
showReadTime.textContent = readMinutes + " " + readTimeLabel;
|
||||||
@@ -727,20 +775,6 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var lazyImgs = document.querySelectorAll(".wi-post__content img[loading='lazy']");
|
|
||||||
lazyImgs.forEach(function (img) {
|
|
||||||
if (img.complete && img.naturalWidth > 0) {
|
|
||||||
img.setAttribute("data-blur", "loaded");
|
|
||||||
} else {
|
|
||||||
img.setAttribute("data-blur", "true");
|
|
||||||
img.addEventListener("load", function () {
|
|
||||||
img.setAttribute("data-blur", "loaded");
|
|
||||||
});
|
|
||||||
img.addEventListener("error", function () {
|
|
||||||
img.setAttribute("data-blur", "loaded");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</Layout>
|
</Layout>
|
||||||
@@ -863,120 +897,20 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-post__body * {
|
/* 正文本身及内部元素样式统一放在全局样式 src/styles/_content.scss,
|
||||||
max-width: 100%;
|
因为正文由 Thymeleaf 在运行时注入,不会带 Astro 的 data-astro-cid 作用域属性,
|
||||||
}
|
写在组件 scoped 样式里对正文内容完全无效。 */
|
||||||
|
|
||||||
.wi-post__body :is(pre, code, table) {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body pre {
|
|
||||||
margin-block: 1.5em;
|
|
||||||
padding: 1.25em;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid var(--rule);
|
|
||||||
background: var(--bg-raised);
|
|
||||||
font-size: 0.875em;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body pre code {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body code {
|
|
||||||
background: var(--bg-raised);
|
|
||||||
padding: 0.15em 0.4em;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.875em;
|
|
||||||
border: 1px solid var(--rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body :is(h1, h2, h3, h4, h5, h6) {
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
margin-top: 1.8em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
scroll-margin-top: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body h2 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-top: 2em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body h3 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body .ai-summary,
|
|
||||||
.wi-post__body [class*="ai-summary"],
|
|
||||||
.wi-post__body [class*="ai-abstract"],
|
|
||||||
.wi-post__body .abstract,
|
|
||||||
.wi-post__body [class*="likcc-article-summary"] {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body p {
|
|
||||||
margin-block-end: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body img {
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-block: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__content img {
|
|
||||||
transition: filter 0.4s ease, opacity 0.4s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__content img[data-blur="true"] {
|
|
||||||
filter: blur(20px);
|
|
||||||
opacity: 0.6;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__content img[data-blur="loaded"] {
|
|
||||||
filter: blur(0);
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body a {
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-thickness: 1px;
|
|
||||||
text-underline-offset: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body ul,
|
|
||||||
.wi-post__body ol {
|
|
||||||
padding-inline-start: 1.5em;
|
|
||||||
margin-block-end: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body li {
|
|
||||||
margin-block-end: 0.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body blockquote {
|
|
||||||
margin-block: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-toc {
|
.wi-toc {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -1135,12 +1069,6 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc-drawer__nav {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__like {
|
.wi-post__like {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -1387,10 +1315,35 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
.wi-post__nav-link {
|
.wi-post__nav-link {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.3rem;
|
gap: 0.35rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
max-width: 45%;
|
max-width: 48%;
|
||||||
transition: color var(--duration-fast) var(--ease-out-quart);
|
padding: 0.85rem 1.1rem;
|
||||||
|
border-radius: var(--radius-md, 12px);
|
||||||
|
background: transparent;
|
||||||
|
transition: background 0.2s ease, transform 0.2s var(--ease-out-quart);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-post__nav-link:hover {
|
||||||
|
background: color-mix(in srgb, var(--accent) 7%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-post__nav-link--prev:hover {
|
||||||
|
transform: translateX(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-post__nav-link--next:hover {
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-post__nav-label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
color: var(--ink-3);
|
||||||
|
letter-spacing: var(--tracking-wider);
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-post__nav-link--next {
|
.wi-post__nav-link--next {
|
||||||
@@ -1398,15 +1351,32 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wi-post__nav-link--next .wi-post__nav-label {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.wi-post__nav-link--empty {
|
.wi-post__nav-link--empty {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-post__nav-label {
|
.wi-post__nav-link--prev .wi-post__nav-label::before {
|
||||||
font-size: var(--text-xs);
|
content: "←";
|
||||||
color: var(--ink-3);
|
font-size: 0.9em;
|
||||||
letter-spacing: var(--tracking-wider);
|
transition: transform 0.2s var(--ease-out-quart);
|
||||||
text-transform: uppercase;
|
}
|
||||||
|
|
||||||
|
.wi-post__nav-link--next .wi-post__nav-label::after {
|
||||||
|
content: "→";
|
||||||
|
font-size: 0.9em;
|
||||||
|
transition: transform 0.2s var(--ease-out-quart);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-post__nav-link--prev:hover .wi-post__nav-label::before {
|
||||||
|
transform: translateX(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-post__nav-link--next:hover .wi-post__nav-label::after {
|
||||||
|
transform: translateX(2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-post__nav-title {
|
.wi-post__nav-title {
|
||||||
@@ -1540,6 +1510,7 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
.wi-post__nav-link {
|
.wi-post__nav-link {
|
||||||
max-width: 48%;
|
max-width: 48%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
padding: 0.6rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-post__nav-title {
|
.wi-post__nav-title {
|
||||||
@@ -1558,51 +1529,10 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
line-height: 1.82;
|
line-height: 1.82;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-post__body p {
|
|
||||||
margin-block-end: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body h2 {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body h3 {
|
|
||||||
font-size: 1.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body h4 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body pre {
|
|
||||||
font-size: 0.9em;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body code {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style is:global>
|
<style is:global>
|
||||||
.wi-post__body figure {
|
|
||||||
margin-block: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-post__body figcaption {
|
|
||||||
color: #545164;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-bottom: 1.2em;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .wi-post__body figcaption {
|
|
||||||
color: #8a8494;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-toc__group {
|
.wi-toc__group {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -1615,36 +1545,38 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__group--active .wi-toc__sub {
|
.wi-toc__group--active .wi-toc__sub {
|
||||||
max-height: 600px;
|
max-height: 1600px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__group--expanded .wi-toc__sub {
|
.wi-toc__group--expanded .wi-toc__sub {
|
||||||
max-height: 600px;
|
max-height: 1600px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc[data-expand="all"] .wi-toc__sub {
|
.wi-toc[data-expand="all"] .wi-toc__sub {
|
||||||
max-height: 600px;
|
max-height: 1600px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__link {
|
.wi-toc__link {
|
||||||
display: block;
|
display: -webkit-box;
|
||||||
padding: 4px 0;
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 5px 8px;
|
||||||
font-size: var(--text-xs);
|
font-size: var(--text-xs);
|
||||||
line-height: var(--leading-snug);
|
line-height: var(--leading-snug);
|
||||||
color: var(--ink-3);
|
color: var(--ink-3);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
border-left: 2px solid transparent;
|
border-left: 2px solid transparent;
|
||||||
|
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__link:hover {
|
.wi-toc__link:hover {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
|
background: color-mix(in srgb, var(--accent) 5%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__link--active {
|
.wi-toc__link--active {
|
||||||
@@ -1655,56 +1587,23 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__link--group {
|
.wi-toc__link--group {
|
||||||
padding-left: 0;
|
padding-left: 8px;
|
||||||
padding-right: 4px;
|
padding-right: 8px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
|
color: var(--ink-2);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__link--group::before {
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
width: 4px;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--ink-3);
|
|
||||||
margin-right: 6px;
|
|
||||||
vertical-align: middle;
|
|
||||||
transition: background 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-toc__link--group::after {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
border-right: 1.5px solid var(--ink-3);
|
|
||||||
border-bottom: 1.5px solid var(--ink-3);
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
margin-left: 6px;
|
|
||||||
vertical-align: middle;
|
|
||||||
transition: transform 0.2s ease, border-color 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-toc__group--expanded > .wi-toc__link--group::after,
|
|
||||||
.wi-toc__group--active > .wi-toc__link--group::after {
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-toc__link--active.wi-toc__link--group::before {
|
|
||||||
background: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wi-toc__link--h3 {
|
.wi-toc__link--h3 {
|
||||||
padding-left: 16px;
|
padding-left: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__link--h4 {
|
.wi-toc__link--h4 {
|
||||||
padding-left: 32px;
|
padding-left: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__link--h5 {
|
.wi-toc__link--h5 {
|
||||||
padding-left: 48px;
|
padding-left: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__link--h6 {
|
.wi-toc__link--h6 {
|
||||||
@@ -1715,11 +1614,6 @@ import LightGallery from "../components/LightGallery.astro";
|
|||||||
border-left-color: var(--accent);
|
border-left-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wi-toc__sub .wi-toc__link--h3.wi-toc__link--active { padding-left: 14px; }
|
|
||||||
.wi-toc__sub .wi-toc__link--h4.wi-toc__link--active { padding-left: 30px; }
|
|
||||||
.wi-toc__sub .wi-toc__link--h5.wi-toc__link--active { padding-left: 46px; }
|
|
||||||
.wi-toc__sub .wi-toc__link--h6.wi-toc__link--active { padding-left: 62px; }
|
|
||||||
|
|
||||||
.wi-toc-drawer__nav .wi-toc__link {
|
.wi-toc-drawer__nav .wi-toc__link {
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-sm);
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
|
|||||||
+13
-13
@@ -55,35 +55,35 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
th:href="${profile.summary?.profileUrl}"
|
th:href="${profile.summary?.profileUrl}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
th:text="#{steam.homepage}">Steam Homepage →</a>
|
th:text="'Steam 主页'">Steam Homepage →</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-steam__level" th:if="${profile.steamLevel != null}">
|
<div class="wi-steam__level" th:if="${profile.steamLevel != null}">
|
||||||
<span class="wi-steam__level-num" th:text="${profile.steamLevel}"></span>
|
<span class="wi-steam__level-num" th:text="${profile.steamLevel}"></span>
|
||||||
<span class="wi-steam__level-label" th:text="#{steam.level}">Level</span>
|
<span class="wi-steam__level-label" th:text="'等级'">等级</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wi-steam__stats" th:if="${stats != null}">
|
<div class="wi-steam__stats" th:if="${stats != null}">
|
||||||
<div class="wi-steam__stat-card">
|
<div class="wi-steam__stat-card">
|
||||||
<span class="wi-steam__stat-value" th:text="${stats.totalGames} ?: 0">0</span>
|
<span class="wi-steam__stat-value" th:text="${stats.totalGames} ?: 0">0</span>
|
||||||
<span class="wi-steam__stat-label" th:text="#{steam.ownedGames}">Owned Games</span>
|
<span class="wi-steam__stat-label" th:text="'拥有游戏'">拥有游戏</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-steam__stat-card">
|
<div class="wi-steam__stat-card">
|
||||||
<span class="wi-steam__stat-value" th:text="${stats.totalPlaytimeFormatted} ?: '0 hours'">0 hours</span>
|
<span class="wi-steam__stat-value" th:text="${stats.totalPlaytimeFormatted} ?: '0 hours'">0 hours</span>
|
||||||
<span class="wi-steam__stat-label" th:text="#{steam.totalPlaytime}">Total Playtime</span>
|
<span class="wi-steam__stat-label" th:text="'总游玩时长'">总游玩时长</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-steam__stat-card">
|
<div class="wi-steam__stat-card">
|
||||||
<span class="wi-steam__stat-value" th:text="${stats.recentPlaytimeFormatted} ?: '0 hours'">0 hours</span>
|
<span class="wi-steam__stat-value" th:text="${stats.recentPlaytimeFormatted} ?: '0 hours'">0 hours</span>
|
||||||
<span class="wi-steam__stat-label" th:text="#{steam.recentPlaytime}">Recent 2 Weeks</span>
|
<span class="wi-steam__stat-label" th:text="'近两周'">近两周</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="wi-steam__stat-card">
|
<div class="wi-steam__stat-card">
|
||||||
<span class="wi-steam__stat-value" th:text="${badges != null and badges.totalBadges != null ? badges.totalBadges : 0}">0</span>
|
<span class="wi-steam__stat-value" th:text="${badges != null and badges.totalBadges != null ? badges.totalBadges : 0}">0</span>
|
||||||
<span class="wi-steam__stat-label" th:text="#{steam.badges}">Badges</span>
|
<span class="wi-steam__stat-label" th:text="'徽章'">徽章</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wi-steam__section" th:if="${recentGames != null and !recentGames.isEmpty()}">
|
<div class="wi-steam__section" th:if="${recentGames != null and !recentGames.isEmpty()}">
|
||||||
<h3 class="wi-steam__section-title" th:text="#{steam.recentlyPlayed}">Recently Played</h3>
|
<h3 class="wi-steam__section-title" th:text="'最近游玩'">最近游玩</h3>
|
||||||
<div class="wi-steam__recent-scroll">
|
<div class="wi-steam__recent-scroll">
|
||||||
<article class="wi-steam__recent-card" th:each="game : ${recentGames}">
|
<article class="wi-steam__recent-card" th:each="game : ${recentGames}">
|
||||||
<a
|
<a
|
||||||
@@ -107,7 +107,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<div class="wi-steam__recent-body">
|
<div class="wi-steam__recent-body">
|
||||||
<h4 class="wi-steam__recent-name" th:text="${game.name}"></h4>
|
<h4 class="wi-steam__recent-name" th:text="${game.name}"></h4>
|
||||||
<div class="wi-steam__recent-meta">
|
<div class="wi-steam__recent-meta">
|
||||||
<span class="wi-steam__recent-time" th:text="${game.playtime2WeeksFormatted} ?: #{steam.hours}"></span>
|
<span class="wi-steam__recent-time" th:text="${game.playtime2WeeksFormatted} ?: '小时'"></span>
|
||||||
<span
|
<span
|
||||||
class="wi-steam__recent-achievements"
|
class="wi-steam__recent-achievements"
|
||||||
th:if="${game.achievementProgressText != null}"
|
th:if="${game.achievementProgressText != null}"
|
||||||
@@ -122,7 +122,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<div class="wi-steam__section">
|
<div class="wi-steam__section">
|
||||||
<h3 class="wi-steam__section-title" th:text="#{steam.gameLibrary}">Game Library</h3>
|
<h3 class="wi-steam__section-title" th:text="'游戏库'">游戏库</h3>
|
||||||
<div class="wi-steam__games-grid">
|
<div class="wi-steam__games-grid">
|
||||||
<article class="wi-steam__game-card" th:each="game : ${games.items}">
|
<article class="wi-steam__game-card" th:each="game : ${games.items}">
|
||||||
<a
|
<a
|
||||||
@@ -146,11 +146,11 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<div class="wi-steam__game-body">
|
<div class="wi-steam__game-body">
|
||||||
<h4 class="wi-steam__game-name" th:text="${game.name}"></h4>
|
<h4 class="wi-steam__game-name" th:text="${game.name}"></h4>
|
||||||
<div class="wi-steam__game-meta">
|
<div class="wi-steam__game-meta">
|
||||||
<span class="wi-steam__game-time" th:text="${game.playtimeFormatted} ?: #{steam.hours}"></span>
|
<span class="wi-steam__game-time" th:text="${game.playtimeFormatted} ?: '小时'"></span>
|
||||||
<span
|
<span
|
||||||
class="wi-steam__game-last"
|
class="wi-steam__game-last"
|
||||||
th:if="${!#strings.isEmpty(game.lastPlayedFormatted)}"
|
th:if="${!#strings.isEmpty(game.lastPlayedFormatted)}"
|
||||||
th:text="#{steam.lastPlayed(${game.lastPlayedFormatted})}"
|
th:text="|最后游玩 ${game.lastPlayedFormatted}|"
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,13 +166,13 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
class="wi-steam__page-btn"
|
class="wi-steam__page-btn"
|
||||||
th:if="${games.hasPrevious()}"
|
th:if="${games.hasPrevious()}"
|
||||||
th:href="${games.prevUrl}"
|
th:href="${games.prevUrl}"
|
||||||
th:text="#{common.newer}">Previous</a>
|
th:text="'较新'">较新</a>
|
||||||
<span class="wi-steam__page-spacer" th:if="${games.hasPrevious() and games.hasNext()}"></span>
|
<span class="wi-steam__page-spacer" th:if="${games.hasPrevious() and games.hasNext()}"></span>
|
||||||
<a
|
<a
|
||||||
class="wi-steam__page-btn"
|
class="wi-steam__page-btn"
|
||||||
th:if="${games.hasNext()}"
|
th:if="${games.hasNext()}"
|
||||||
th:href="${games.nextUrl}"
|
th:href="${games.nextUrl}"
|
||||||
th:text="#{common.older}">Next</a>
|
th:text="'较旧'">较旧</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
+4
-4
@@ -11,7 +11,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<header class="wi-tag__header">
|
<header class="wi-tag__header">
|
||||||
<h1 class="wi-tag__title" th:text="${tag.spec.displayName}"></h1>
|
<h1 class="wi-tag__title" th:text="${tag.spec.displayName}"></h1>
|
||||||
<span class="wi-page-accent"></span>
|
<span class="wi-page-accent"></span>
|
||||||
<p class="wi-tag__subtitle" th:text="#{common.postCount(${posts.total})}"></p>
|
<p class="wi-tag__subtitle" th:text="|共 ${posts.total} 篇文章|">共 0 篇文章</p>
|
||||||
<p
|
<p
|
||||||
th:if="${not #strings.isEmpty(tag.spec.description)}"
|
th:if="${not #strings.isEmpty(tag.spec.description)}"
|
||||||
class="wi-tag__description"
|
class="wi-tag__description"
|
||||||
@@ -55,7 +55,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
<div
|
<div
|
||||||
th:if="${posts.total == 0}"
|
th:if="${posts.total == 0}"
|
||||||
class="wi-tag__empty"
|
class="wi-tag__empty"
|
||||||
th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
|
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章'}"
|
||||||
>No posts yet.</div>
|
>No posts yet.</div>
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
@@ -67,7 +67,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
th:if="${posts.hasPrevious()}"
|
th:if="${posts.hasPrevious()}"
|
||||||
th:href="@{${posts.prevUrl}}"
|
th:href="@{${posts.prevUrl}}"
|
||||||
class="wi-tag__page-link wi-tag__page-link--prev"
|
class="wi-tag__page-link wi-tag__page-link--prev"
|
||||||
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
|
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||||
>← Newer</a>
|
>← Newer</a>
|
||||||
<span
|
<span
|
||||||
class="wi-tag__page-info"
|
class="wi-tag__page-info"
|
||||||
@@ -77,7 +77,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
th:if="${posts.hasNext()}"
|
th:if="${posts.hasNext()}"
|
||||||
th:href="@{${posts.nextUrl}}"
|
th:href="@{${posts.nextUrl}}"
|
||||||
class="wi-tag__page-link wi-tag__page-link--next"
|
class="wi-tag__page-link wi-tag__page-link--next"
|
||||||
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
|
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||||
>Older →</a>
|
>Older →</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Fragment slot="head">
|
<Fragment slot="head">
|
||||||
<title th:text="|#{tags.title} - ${site.title}|"></title>
|
<title th:text="|标签 - ${site.title}|"></title>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
||||||
<div class="wi-tags">
|
<div class="wi-tags">
|
||||||
<header class="wi-tags__header">
|
<header class="wi-tags__header">
|
||||||
<h1 class="wi-tags__title" th:text="#{tags.title}">标签</h1>
|
<h1 class="wi-tags__title" th:text="'标签'">标签</h1>
|
||||||
<span class="wi-page-accent"></span>
|
<span class="wi-page-accent"></span>
|
||||||
<p class="wi-tags__subtitle" th:text="${#messages.msg('common.tagCount', tags.size())}"></p>
|
<p class="wi-tags__subtitle" th:text="|共 ${tags.size()} 个标签|">共 0 个标签</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="wi-tags__cloud" role="list" aria-label="Tag list">
|
<div class="wi-tags__cloud" role="list" aria-label="Tag list">
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout wide>
|
||||||
|
<Fragment slot="head">
|
||||||
|
<title th:text="|${theme.config?.weread?.weread_page_title ?: '书架'} - ${site.title}|"></title>
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
<section class="wi-weread">
|
||||||
|
<div class="wi-container">
|
||||||
|
<div class="wi-weread__header">
|
||||||
|
<h1
|
||||||
|
class="wi-weread__title"
|
||||||
|
th:text="${theme.config?.weread?.weread_page_title ?: '书架'}"
|
||||||
|
>
|
||||||
|
书架
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
class="wi-weread__subtitle"
|
||||||
|
th:if="${theme.config?.weread?.weread_page_subtitle}"
|
||||||
|
th:text="${theme.config?.weread?.weread_page_subtitle}"
|
||||||
|
>
|
||||||
|
来自微信读书的同步藏书
|
||||||
|
</p>
|
||||||
|
<span class="wi-page-accent"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<th:block th:if="${pluginFinder.available('halo-weread-plugin')}">
|
||||||
|
<div
|
||||||
|
class="wi-weread__shelf"
|
||||||
|
id="weread-shelf-wrapper"
|
||||||
|
th:attr="data-endpoint=@{/halo-weread-plugin/shelf-html}"
|
||||||
|
>
|
||||||
|
<p class="wi-weread__loading">正在展开书架…</p>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="wi-weread__empty"
|
||||||
|
th:unless="${pluginFinder.available('halo-weread-plugin')}"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
该功能需要安装「微信读书(WeRead)信息展示」插件,<a
|
||||||
|
href="https://github.com/yeora26/halo-weread-plugin"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>前往插件仓库了解</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script is:inline th:inline="none">
|
||||||
|
(function () {
|
||||||
|
var container = document.getElementById("weread-shelf-wrapper");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
var endpoint =
|
||||||
|
container.getAttribute("data-endpoint") ||
|
||||||
|
"/halo-weread-plugin/shelf-html";
|
||||||
|
|
||||||
|
fetch(endpoint)
|
||||||
|
.then(function (res) {
|
||||||
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||||
|
return res.text();
|
||||||
|
})
|
||||||
|
.then(function (html) {
|
||||||
|
container.innerHTML = html;
|
||||||
|
|
||||||
|
/* 笔记弹窗是 position:fixed,但 innerHTML 会把它留在 .wi-main 内,
|
||||||
|
而 .wi-main 有自己的层叠上下文(z-index:1),导致弹窗被导航栏
|
||||||
|
(z-index:100)和页脚遮挡。移到 body 下即可恢复正常层级。 */
|
||||||
|
var modal = document.getElementById("wrNotesModal");
|
||||||
|
if (modal && modal.parentNode !== document.body) {
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* innerHTML 插入的脚本不会执行,需要重建后才会运行 */
|
||||||
|
var oldScripts = container.querySelectorAll("script");
|
||||||
|
Array.prototype.forEach.call(oldScripts, function (oldScript) {
|
||||||
|
var script = document.createElement("script");
|
||||||
|
script.textContent = oldScript.textContent;
|
||||||
|
oldScript.parentNode.replaceChild(script, oldScript);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
container.innerHTML =
|
||||||
|
'<p class="wi-weread__loading">书架暂时无法展开,请稍后再试。</p>';
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 头部结构与订阅/图库/友链/记得等页面保持一致:居中标题 + 副标题 + 强调线 */
|
||||||
|
.wi-weread {
|
||||||
|
padding-block: 1rem 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: var(--space-3xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__title {
|
||||||
|
font-size: var(--text-4xl);
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__subtitle {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--ink-3);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
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 {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__loading {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--ink-3);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
padding: var(--space-2xl) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-2xl) var(--space-lg);
|
||||||
|
border: 1px dashed var(--rule);
|
||||||
|
border-radius: var(--radius-lg, 16px);
|
||||||
|
color: var(--ink-3);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__empty a {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__empty a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 插件返回的书架内容(.wr-shelf)适配 ===== */
|
||||||
|
|
||||||
|
/* 插件自带的标题区与主题头部重复,隐藏后由主题统一呈现 */
|
||||||
|
.wi-weread__shelf :global(.wr-head-title) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 插件容器自带 clamp(40px,6vw,72px) 的顶部内边距,主题已提供边距,收紧以消除大片空白 */
|
||||||
|
.wi-weread__shelf :global(.wr-shelf) {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统计数据水平居中,紧贴主题标题区(标题 + 强调线)下方 */
|
||||||
|
.wi-weread__shelf :global(.wr-head) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__shelf :global(.wr-head-stats) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__shelf :global(.wr-head-stats span) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__shelf :global(img) {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-weread__shelf :global(a) {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 暗黑模式:插件内容为苹果风格浅色配色,深色下需要接管 ===== */
|
||||||
|
|
||||||
|
/* 覆盖插件内部变量(按其命名约定 --wr-*,未命中也无副作用)。
|
||||||
|
--wr-line 比全站分隔线略亮:年份分组等装饰线在深色底上需要保证可见 */
|
||||||
|
html.dark .wi-weread__shelf :global(.wr-shelf) {
|
||||||
|
--wr-ink: var(--ink);
|
||||||
|
--wr-muted: var(--ink-2);
|
||||||
|
--wr-bg: var(--bg-raised);
|
||||||
|
--wr-line: #4a443c;
|
||||||
|
--wr-fill: color-mix(in srgb, var(--ink) 8%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* :where() 零特异性兜底:只接管插件未显式着色的文字,不会破坏其自身样式 */
|
||||||
|
html.dark .wi-weread__shelf :global(:where(h2, h3, strong)) {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .wi-weread__shelf :global(:where(p, span, small, i, em, li)) {
|
||||||
|
color: var(--ink-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 笔记弹窗:运行时被移到 body 下,脱离了 .wr-shelf 的变量作用域,
|
||||||
|
需要独立适配(选择器整体 :global,否则 scoped 属性匹配不到注入节点) ===== */
|
||||||
|
:global(html.dark .wr-notes-modal) {
|
||||||
|
--wr-ink: var(--ink);
|
||||||
|
--wr-muted: var(--ink-2);
|
||||||
|
--wr-bg: var(--bg-raised);
|
||||||
|
--wr-line: #4a443c;
|
||||||
|
--wr-fill: color-mix(in srgb, var(--ink) 8%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark .wr-notes-dialog) {
|
||||||
|
background: var(--bg-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark .wr-notes-close) {
|
||||||
|
background: color-mix(in srgb, var(--ink) 10%, transparent);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark .wr-notes-modal :where(h3, h4, strong)) {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark .wr-notes-modal :where(p, span, small, em, li)) {
|
||||||
|
color: var(--ink-2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+31
-26
@@ -1,7 +1,9 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg: #faf7f2;
|
--bg: #faf7f2;
|
||||||
--bg-raised: #f3ede5;
|
--bg-raised: #f3ede5;
|
||||||
--bg-overlay: rgba(250, 247, 242, 0.85);
|
/* 派生变量基于 --bg / --rule 动态混合:调色板只需覆盖核心变量,
|
||||||
|
玻璃拟态、遮罩层等会自动跟随当前配色(暖屿默认时与旧值完全一致) */
|
||||||
|
--bg-overlay: color-mix(in srgb, var(--bg) 85%, transparent);
|
||||||
--ink: #2c2420;
|
--ink: #2c2420;
|
||||||
--ink-2: #7a6e64;
|
--ink-2: #7a6e64;
|
||||||
--ink-3: #b5a99e;
|
--ink-3: #b5a99e;
|
||||||
@@ -12,32 +14,35 @@
|
|||||||
--mist-pink: #f0e4de;
|
--mist-pink: #f0e4de;
|
||||||
--sea-salt: #e8e0d6;
|
--sea-salt: #e8e0d6;
|
||||||
--caramel: #8b6f5e;
|
--caramel: #8b6f5e;
|
||||||
--glass-bg: rgba(250, 247, 242, 0.72);
|
--glass-bg: color-mix(in srgb, var(--bg) 72%, transparent);
|
||||||
--glass-border: rgba(232, 224, 214, 0.5);
|
--glass-border: color-mix(in srgb, var(--rule) 50%, transparent);
|
||||||
--shadow-sm: 0 1px 3px rgba(44, 36, 32, 0.06);
|
/* 阴影色由墨色动态混合,保证换配色后阴影色调一致 */
|
||||||
--shadow-md: 0 4px 16px rgba(44, 36, 32, 0.08);
|
--shadow-sm: 0 1px 3px color-mix(in srgb, var(--ink) 6%, transparent);
|
||||||
--shadow-lg: 0 8px 32px rgba(44, 36, 32, 0.12);
|
--shadow-md: 0 4px 16px color-mix(in srgb, var(--ink) 8%, transparent);
|
||||||
--shadow-glow: 0 0 40px rgba(212, 118, 78, 0.15);
|
--shadow-lg: 0 8px 32px color-mix(in srgb, var(--ink) 12%, transparent);
|
||||||
|
--shadow-glow: 0 0 40px color-mix(in srgb, var(--accent) 15%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark {
|
html.dark {
|
||||||
--bg: #1a1614;
|
/* 暗色基准:偏中性、只保留一丝暖意,避免大面积黄褐底色带来的闷感 */
|
||||||
--bg-raised: #242018;
|
--bg: #1e1c1a;
|
||||||
--bg-overlay: rgba(26, 22, 20, 0.85);
|
--bg-raised: #292623;
|
||||||
--ink: #ede6de;
|
--bg-overlay: color-mix(in srgb, var(--bg) 85%, transparent);
|
||||||
--ink-2: #9a8e84;
|
--ink: #e9e4dd;
|
||||||
--ink-3: #5e544a;
|
--ink-2: #aaa096;
|
||||||
--rule: #2e2822;
|
/* 深色背景下人眼对低对比更敏感,次要文字与分隔线比浅色模式略提亮 */
|
||||||
--accent: #e8955f;
|
--ink-3: #7c7268;
|
||||||
--accent-hover: #f0a872;
|
--rule: #3b362f;
|
||||||
--accent-bg: #2a1e16;
|
--accent: #e89a64;
|
||||||
--mist-pink: #2e2420;
|
--accent-hover: #f2ad79;
|
||||||
--sea-salt: #2e2822;
|
--accent-bg: #2c231b;
|
||||||
--caramel: #a08878;
|
--mist-pink: #2e2823;
|
||||||
--glass-bg: rgba(26, 22, 20, 0.72);
|
--sea-salt: #302b26;
|
||||||
--glass-border: rgba(46, 40, 34, 0.5);
|
--caramel: #a38b7b;
|
||||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.2);
|
--glass-bg: color-mix(in srgb, var(--bg) 72%, transparent);
|
||||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.25);
|
--glass-border: color-mix(in srgb, var(--rule) 50%, transparent);
|
||||||
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.35);
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.24);
|
||||||
--shadow-glow: 0 0 40px rgba(232, 149, 95, 0.12);
|
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||||
|
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||||
|
--shadow-glow: 0 0 40px rgba(232, 154, 100, 0.12);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,331 @@
|
|||||||
|
@use 'variables' as *;
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
正文排版(文章页 .wi-post__body / 单页 .wi-page__body 共用)
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
目标:
|
||||||
|
1. 标题层级清晰、字号克制,移动端逐级收敛(而不是反向放大)
|
||||||
|
2. 图片 / 视频 / 嵌入 iframe / figure 一律水平居中且不破坏响应式
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/* 正文容器:运行时由 Thymeleaf 注入内容,因此这里的规则必须是全局的,
|
||||||
|
不能写进组件的 scoped <style>(那样不会命中注入的内容)。 */
|
||||||
|
.wi-post__body,
|
||||||
|
.wi-page__body,
|
||||||
|
.wi-messageboard__body {
|
||||||
|
/* ---------- 标题层级 ---------- */
|
||||||
|
:is(h1, h2, h3, h4, h5, h6) {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: var(--ink);
|
||||||
|
scroll-margin-top: 88px;
|
||||||
|
text-wrap: balance;
|
||||||
|
/* 覆盖全局标题装饰(左侧竖线 / 圆点),正文内保持干净 */
|
||||||
|
padding-left: 0;
|
||||||
|
border-left: 0;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(1.6rem, 1.4rem + 1vw, 2rem);
|
||||||
|
margin-top: 2em;
|
||||||
|
margin-bottom: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: clamp(1.42rem, 1.3rem + 0.6vw, 1.68rem);
|
||||||
|
margin-top: 1.9em;
|
||||||
|
margin-bottom: 0.65em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: clamp(1.24rem, 1.16rem + 0.4vw, 1.4rem);
|
||||||
|
margin-top: 1.7em;
|
||||||
|
margin-bottom: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: clamp(1.1rem, 1.05rem + 0.25vw, 1.2rem);
|
||||||
|
margin-top: 1.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.04rem;
|
||||||
|
margin-top: 1.4em;
|
||||||
|
margin-bottom: 0.45em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 0.98rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ink-3);
|
||||||
|
margin-top: 1.4em;
|
||||||
|
margin-bottom: 0.45em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题紧跟在另一个标题后时去掉多余上间距 */
|
||||||
|
:is(h1, h2, h3, h4, h5, h6) + :is(h1, h2, h3, h4, h5, h6) {
|
||||||
|
margin-top: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 段落内首个元素不需要上间距 */
|
||||||
|
> :is(h1, h2, h3, h4, h5, h6):first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-block-end: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 代码块 / 引用 / 列表 ---------- */
|
||||||
|
|
||||||
|
* {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(pre, code, table) {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin-block: 1.5em;
|
||||||
|
padding: 1.25em;
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
border: 1px solid var(--rule);
|
||||||
|
background: var(--bg-raised);
|
||||||
|
font-size: 0.875em;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: var(--bg-raised);
|
||||||
|
padding: 0.15em 0.4em;
|
||||||
|
border-radius: $border-radius-sm;
|
||||||
|
font-size: 0.875em;
|
||||||
|
border: 1px solid var(--rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin-block: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(ul, ol) {
|
||||||
|
padding-inline-start: 1.5em;
|
||||||
|
margin-block-end: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-block-end: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
text-underline-offset: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 第三方摘要插件容器 */
|
||||||
|
:is(.ai-summary, .abstract),
|
||||||
|
[class*='ai-summary'],
|
||||||
|
[class*='ai-abstract'],
|
||||||
|
[class*='likcc-article-summary'] {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 媒体居中 ---------- */
|
||||||
|
|
||||||
|
/* 图片 / 视频:块级 + 自动外边距实现居中,max-width 保证不溢出容器 */
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
margin-block: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 行内小图标 / 表情不受居中规则影响 */
|
||||||
|
img[role='presentation'],
|
||||||
|
img[aria-hidden='true'],
|
||||||
|
:is(.emoji, .icon, .avatar, .badge) {
|
||||||
|
display: inline;
|
||||||
|
margin-inline: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编辑器可能输出 align="center|left|right" 或 text-align 样式,保持一致居中 */
|
||||||
|
[align='center'],
|
||||||
|
[style*='text-align:center'],
|
||||||
|
[style*='text-align: center'] {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-inline: auto;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-inline: auto;
|
||||||
|
margin-block: 1.5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure :is(img, video) {
|
||||||
|
margin-block: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption {
|
||||||
|
color: var(--ink-3);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lightgallery 注入的 <a> 包裹层:收缩到图片宽度并居中 */
|
||||||
|
.wi-lightgallery-item {
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-inline: auto;
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-lightgallery-item > img {
|
||||||
|
margin-block: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式嵌入容器(由脚本按 width/height 生成 aspect-ratio) */
|
||||||
|
.wi-embed {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-inline: auto;
|
||||||
|
margin-block: 1.5em;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: $border-radius-md;
|
||||||
|
background: var(--bg-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-embed > :is(iframe, video, embed, object) {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图片懒加载占位模糊 */
|
||||||
|
.wi-media-loading {
|
||||||
|
filter: blur(20px);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wi-media-loaded {
|
||||||
|
filter: blur(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端:标题逐级收敛,正文行高放宽 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.wi-post__body,
|
||||||
|
.wi-page__body,
|
||||||
|
.wi-messageboard__body {
|
||||||
|
h1 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.28rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-block-end: 1.35em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
margin-block: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 窄屏图片允许撑满内容宽度,避免留白过大 */
|
||||||
|
img,
|
||||||
|
video,
|
||||||
|
.wi-lightgallery-item,
|
||||||
|
figure {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.wi-post__body,
|
||||||
|
.wi-page__body,
|
||||||
|
.wi-messageboard__body {
|
||||||
|
h1 {
|
||||||
|
font-size: 1.32rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.22rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.12rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
/*
|
||||||
|
* 主题配色系统:官方预设调色板
|
||||||
|
* ---------------------------------------------------------------
|
||||||
|
* 每个[调色板]覆盖核心色彩变量(浅色 html[data-palette="x"] /
|
||||||
|
* 深色 html[data-palette="x"].dark 两套)。
|
||||||
|
* 派生变量(--bg-overlay、--glass-*、阴影色等)在 _colors.scss 中
|
||||||
|
* 由 --bg / --rule / --ink / --accent 动态混合而成,会自动跟随。
|
||||||
|
* data-palette 由 layout 头部根据后台「总体样式 → 主题配色」输出;
|
||||||
|
* 默认值 warm-island(暖屿·赤陶)即 _colors.scss 的基准值,无需覆盖。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 抹茶 */
|
||||||
|
html[data-palette="matcha"] {
|
||||||
|
--bg: #f6f8f1;
|
||||||
|
--bg-raised: #ecf1e3;
|
||||||
|
--ink: #26302a;
|
||||||
|
--ink-2: #6d7a6e;
|
||||||
|
--ink-3: #a8b3a4;
|
||||||
|
--rule: #dde5d4;
|
||||||
|
--accent: #6f9d52;
|
||||||
|
--accent-hover: #5d8943;
|
||||||
|
--accent-bg: #ecf3e2;
|
||||||
|
--mist-pink: #e7eddc;
|
||||||
|
--sea-salt: #dfe8d3;
|
||||||
|
--caramel: #7d8a68;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-palette="matcha"].dark {
|
||||||
|
--bg: #1b1f1a;
|
||||||
|
--bg-raised: #252b22;
|
||||||
|
--ink: #e6e9e0;
|
||||||
|
--ink-2: #a3ab9c;
|
||||||
|
--ink-3: #767e70;
|
||||||
|
--rule: #363d33;
|
||||||
|
--accent: #9cc47f;
|
||||||
|
--accent-hover: #aed493;
|
||||||
|
--accent-bg: #242c1f;
|
||||||
|
--mist-pink: #262d22;
|
||||||
|
--sea-salt: #282f24;
|
||||||
|
--caramel: #98a387;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 黛蓝 */
|
||||||
|
html[data-palette="indigo"] {
|
||||||
|
--bg: #f5f7fa;
|
||||||
|
--bg-raised: #eaeff5;
|
||||||
|
--ink: #232a33;
|
||||||
|
--ink-2: #66707d;
|
||||||
|
--ink-3: #a3adb8;
|
||||||
|
--rule: #dce2ea;
|
||||||
|
--accent: #4a6fa5;
|
||||||
|
--accent-hover: #3d5d8c;
|
||||||
|
--accent-bg: #e9eff8;
|
||||||
|
--mist-pink: #e2e8f0;
|
||||||
|
--sea-salt: #dbe4ee;
|
||||||
|
--caramel: #707c8e;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-palette="indigo"].dark {
|
||||||
|
--bg: #191d23;
|
||||||
|
--bg-raised: #222831;
|
||||||
|
--ink: #e4e7ec;
|
||||||
|
--ink-2: #9aa4b1;
|
||||||
|
--ink-3: #6d7683;
|
||||||
|
--rule: #323a45;
|
||||||
|
--accent: #7fa3d1;
|
||||||
|
--accent-hover: #93b4dc;
|
||||||
|
--accent-bg: #1f2632;
|
||||||
|
--mist-pink: #232830;
|
||||||
|
--sea-salt: #252b34;
|
||||||
|
--caramel: #8b95a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 樱粉 */
|
||||||
|
html[data-palette="sakura"] {
|
||||||
|
--bg: #faf5f6;
|
||||||
|
--bg-raised: #f3eaec;
|
||||||
|
--ink: #322a2c;
|
||||||
|
--ink-2: #7d6e72;
|
||||||
|
--ink-3: #b4a6aa;
|
||||||
|
--rule: #ecdfe2;
|
||||||
|
--accent: #d97387;
|
||||||
|
--accent-hover: #c85d72;
|
||||||
|
--accent-bg: #fbeef1;
|
||||||
|
--mist-pink: #f2e2e6;
|
||||||
|
--sea-salt: #eddfe3;
|
||||||
|
--caramel: #96757e;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-palette="sakura"].dark {
|
||||||
|
--bg: #201b1c;
|
||||||
|
--bg-raised: #2a2425;
|
||||||
|
--ink: #ece5e6;
|
||||||
|
--ink-2: #b0a4a7;
|
||||||
|
--ink-3: #7f7276;
|
||||||
|
--rule: #3c3436;
|
||||||
|
--accent: #e89aab;
|
||||||
|
--accent-hover: #f0aec0;
|
||||||
|
--accent-bg: #2d2225;
|
||||||
|
--mist-pink: #2f2729;
|
||||||
|
--sea-salt: #31282a;
|
||||||
|
--caramel: #a88d94;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暮紫 */
|
||||||
|
html[data-palette="violet"] {
|
||||||
|
--bg: #f7f5fa;
|
||||||
|
--bg-raised: #eeeaf5;
|
||||||
|
--ink: #2b2733;
|
||||||
|
--ink-2: #6f6a7c;
|
||||||
|
--ink-3: #aaa5b6;
|
||||||
|
--rule: #e2ddeb;
|
||||||
|
--accent: #8a6fc0;
|
||||||
|
--accent-hover: #7759ae;
|
||||||
|
--accent-bg: #f0eaf9;
|
||||||
|
--mist-pink: #e8e2f0;
|
||||||
|
--sea-salt: #e3dcee;
|
||||||
|
--caramel: #7f7590;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-palette="violet"].dark {
|
||||||
|
--bg: #1c1a21;
|
||||||
|
--bg-raised: #26222f;
|
||||||
|
--ink: #e8e5ee;
|
||||||
|
--ink-2: #a49eaf;
|
||||||
|
--ink-3: #746e80;
|
||||||
|
--rule: #373241;
|
||||||
|
--accent: #b49ae0;
|
||||||
|
--accent-hover: #c3ace8;
|
||||||
|
--accent-bg: #262033;
|
||||||
|
--mist-pink: #292434;
|
||||||
|
--sea-salt: #2b2536;
|
||||||
|
--caramel: #948caa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 海盐 */
|
||||||
|
html[data-palette="sea"] {
|
||||||
|
--bg: #f3f8f7;
|
||||||
|
--bg-raised: #e8f0ee;
|
||||||
|
--ink: #21302d;
|
||||||
|
--ink-2: #647872;
|
||||||
|
--ink-3: #9fb1ac;
|
||||||
|
--rule: #d9e6e3;
|
||||||
|
--accent: #3d9c8f;
|
||||||
|
--accent-hover: #2f8578;
|
||||||
|
--accent-bg: #e5f3f0;
|
||||||
|
--mist-pink: #dee9e6;
|
||||||
|
--sea-salt: #d6e5e1;
|
||||||
|
--caramel: #6c8680;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-palette="sea"].dark {
|
||||||
|
--bg: #181f1e;
|
||||||
|
--bg-raised: #212a28;
|
||||||
|
--ink: #e3eae8;
|
||||||
|
--ink-2: #9aaba6;
|
||||||
|
--ink-3: #6d7d79;
|
||||||
|
--rule: #2f3b39;
|
||||||
|
--accent: #6cc3b4;
|
||||||
|
--accent-hover: #82d0c2;
|
||||||
|
--accent-bg: #1d2a27;
|
||||||
|
--mist-pink: #222c2a;
|
||||||
|
--sea-salt: #242e2c;
|
||||||
|
--caramel: #869894;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 琥珀 */
|
||||||
|
html[data-palette="amber"] {
|
||||||
|
--bg: #faf7ef;
|
||||||
|
--bg-raised: #f3eddc;
|
||||||
|
--ink: #322b1e;
|
||||||
|
--ink-2: #7d7360;
|
||||||
|
--ink-3: #b5ab94;
|
||||||
|
--rule: #eae1cb;
|
||||||
|
--accent: #b97f1c;
|
||||||
|
--accent-hover: #9f6a0e;
|
||||||
|
--accent-bg: #f9efd8;
|
||||||
|
--mist-pink: #f0e8d2;
|
||||||
|
--sea-salt: #ebe2ca;
|
||||||
|
--caramel: #96835c;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-palette="amber"].dark {
|
||||||
|
--bg: #201d16;
|
||||||
|
--bg-raised: #2a261d;
|
||||||
|
--ink: #ebe6d9;
|
||||||
|
--ink-2: #b0a893;
|
||||||
|
--ink-3: #7f7866;
|
||||||
|
--rule: #3c3729;
|
||||||
|
--accent: #e0a83e;
|
||||||
|
--accent-hover: #ebba5c;
|
||||||
|
--accent-bg: #2c251a;
|
||||||
|
--mist-pink: #2f2a1e;
|
||||||
|
--sea-salt: #312c20;
|
||||||
|
--caramel: #a89a76;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 梅酒 */
|
||||||
|
html[data-palette="plum"] {
|
||||||
|
--bg: #f9f4f5;
|
||||||
|
--bg-raised: #f1e8ea;
|
||||||
|
--ink: #2f2427;
|
||||||
|
--ink-2: #796a6f;
|
||||||
|
--ink-3: #b1a2a7;
|
||||||
|
--rule: #e9dde0;
|
||||||
|
--accent: #a4506b;
|
||||||
|
--accent-hover: #8d3f59;
|
||||||
|
--accent-bg: #f7e9ee;
|
||||||
|
--mist-pink: #ecdee2;
|
||||||
|
--sea-salt: #e5d8db;
|
||||||
|
--caramel: #8d6f77;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-palette="plum"].dark {
|
||||||
|
--bg: #1f1a1b;
|
||||||
|
--bg-raised: #292223;
|
||||||
|
--ink: #eae2e4;
|
||||||
|
--ink-2: #ada0a4;
|
||||||
|
--ink-3: #7c6e73;
|
||||||
|
--rule: #3a3133;
|
||||||
|
--accent: #d98aa3;
|
||||||
|
--accent-hover: #e39fb5;
|
||||||
|
--accent-bg: #2b2023;
|
||||||
|
--mist-pink: #2d2527;
|
||||||
|
--sea-salt: #2f2729;
|
||||||
|
--caramel: #a08890;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 石墨 */
|
||||||
|
html[data-palette="graphite"] {
|
||||||
|
--bg: #f7f7f6;
|
||||||
|
--bg-raised: #eeedeb;
|
||||||
|
--ink: #262626;
|
||||||
|
--ink-2: #6e6e6c;
|
||||||
|
--ink-3: #ababa8;
|
||||||
|
--rule: #e0e0de;
|
||||||
|
--accent: #3f3f3e;
|
||||||
|
--accent-hover: #262626;
|
||||||
|
--accent-bg: #ececea;
|
||||||
|
--mist-pink: #e9e9e7;
|
||||||
|
--sea-salt: #e5e5e3;
|
||||||
|
--caramel: #7b7b78;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-palette="graphite"].dark {
|
||||||
|
--bg: #1b1b1a;
|
||||||
|
--bg-raised: #252524;
|
||||||
|
--ink: #e8e8e6;
|
||||||
|
--ink-2: #a5a5a2;
|
||||||
|
--ink-3: #757572;
|
||||||
|
--rule: #353534;
|
||||||
|
--accent: #c9c9c5;
|
||||||
|
--accent-hover: #d8d8d4;
|
||||||
|
--accent-bg: #262625;
|
||||||
|
--mist-pink: #2a2a29;
|
||||||
|
--sea-salt: #2c2c2b;
|
||||||
|
--caramel: #8f8f8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可拿铁 */
|
||||||
|
html[data-palette="cocoa"] {
|
||||||
|
--bg: #f8f4f0;
|
||||||
|
--bg-raised: #f0e8e1;
|
||||||
|
--ink: #2e2722;
|
||||||
|
--ink-2: #786c62;
|
||||||
|
--ink-3: #b3a89d;
|
||||||
|
--rule: #e7ddd3;
|
||||||
|
--accent: #9a6b4f;
|
||||||
|
--accent-hover: #835539;
|
||||||
|
--accent-bg: #f5e9e0;
|
||||||
|
--mist-pink: #ecdfd4;
|
||||||
|
--sea-salt: #e6d9cd;
|
||||||
|
--caramel: #8a6f5b;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-palette="cocoa"].dark {
|
||||||
|
--bg: #1e1a17;
|
||||||
|
--bg-raised: #282320;
|
||||||
|
--ink: #eae4dd;
|
||||||
|
--ink-2: #aca199;
|
||||||
|
--ink-3: #7b7169;
|
||||||
|
--rule: #3a332e;
|
||||||
|
--accent: #cd9878;
|
||||||
|
--accent-hover: #daa98c;
|
||||||
|
--accent-bg: #2a211c;
|
||||||
|
--mist-pink: #2d2721;
|
||||||
|
--sea-salt: #2f2923;
|
||||||
|
--caramel: #a08d7c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 天青 */
|
||||||
|
html[data-palette="azure"] {
|
||||||
|
--bg: #f4f8fb;
|
||||||
|
--bg-raised: #e9f1f7;
|
||||||
|
--ink: #223038;
|
||||||
|
--ink-2: #62737e;
|
||||||
|
--ink-3: #a0b0ba;
|
||||||
|
--rule: #dbe7ee;
|
||||||
|
--accent: #3b82c4;
|
||||||
|
--accent-hover: #2e6ba5;
|
||||||
|
--accent-bg: #e7f1f9;
|
||||||
|
--mist-pink: #e0ebf2;
|
||||||
|
--sea-salt: #d8e6ef;
|
||||||
|
--caramel: #6d8090;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-palette="azure"].dark {
|
||||||
|
--bg: #172026;
|
||||||
|
--bg-raised: #202a31;
|
||||||
|
--ink: #e3eaef;
|
||||||
|
--ink-2: #99a8b3;
|
||||||
|
--ink-3: #6b7a85;
|
||||||
|
--rule: #2e3a42;
|
||||||
|
--accent: #74b3e0;
|
||||||
|
--accent-hover: #88c1e8;
|
||||||
|
--accent-bg: #1d2830;
|
||||||
|
--mist-pink: #222c34;
|
||||||
|
--sea-salt: #243038;
|
||||||
|
--caramel: #8496a3;
|
||||||
|
}
|
||||||
@@ -39,19 +39,19 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: var(--text-4xl);
|
font-size: var(--text-3xl);
|
||||||
margin-bottom: var(--space-lg);
|
margin-bottom: var(--space-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: var(--text-3xl);
|
font-size: var(--text-2xl);
|
||||||
padding-left: var(--space-md);
|
padding-left: var(--space-md);
|
||||||
border-left: 3px solid var(--accent);
|
border-left: 3px solid var(--accent);
|
||||||
margin-bottom: var(--space-sm);
|
margin-bottom: var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-size: var(--text-2xl);
|
font-size: var(--text-xl);
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: var(--space-lg);
|
padding-left: var(--space-lg);
|
||||||
|
|
||||||
@@ -71,16 +71,16 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: var(--text-xl);
|
font-size: var(--text-md);
|
||||||
color: var(--ink-2);
|
color: var(--ink-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
font-size: var(--text-lg);
|
font-size: var(--text-base);
|
||||||
color: var(--ink-2);
|
color: var(--ink-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
h6 {
|
h6 {
|
||||||
font-size: var(--text-base);
|
font-size: var(--text-sm);
|
||||||
color: var(--ink-3);
|
color: var(--ink-3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,12 +1,15 @@
|
|||||||
@use 'variables' as *;
|
@use 'variables' as *;
|
||||||
@use 'mixins';
|
@use 'mixins';
|
||||||
@use 'colors';
|
@use 'colors';
|
||||||
|
@use 'palettes';
|
||||||
@use 'typography';
|
@use 'typography';
|
||||||
|
@use 'content';
|
||||||
@use 'spacing';
|
@use 'spacing';
|
||||||
@use 'animations';
|
@use 'animations';
|
||||||
@use 'comment';
|
@use 'comment';
|
||||||
@use 'search';
|
@use 'search';
|
||||||
@use 'lightgallery';
|
@use 'lightgallery';
|
||||||
|
@use 'widgets';
|
||||||
|
|
||||||
*,
|
*,
|
||||||
*::before,
|
*::before,
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
(function(){
|
|
||||||
return fetch('/apis/api.halo.run/v1alpha1/posts/5152aea5-c2e8-4717-8bba-2263d46e19d5/likes', {method: 'POST'})
|
|
||||||
.then(function(r){
|
|
||||||
return r.text().then(function(t){
|
|
||||||
return JSON.stringify({status: r.status, body: t.substring(0, 500)});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})()
|
|
||||||
+10
-2
@@ -20,8 +20,16 @@ spec:
|
|||||||
description: 留言板
|
description: 留言板
|
||||||
screenshot:
|
screenshot:
|
||||||
file: page_messageboard.html
|
file: page_messageboard.html
|
||||||
version: "1.1.1"
|
- name: Subscriptions
|
||||||
requires: ">=2.24.0"
|
description: 订阅页面(展示博友文章订阅动态,依赖链接管理插件)
|
||||||
|
screenshot:
|
||||||
|
file: page_subscriptions.html
|
||||||
|
- name: WeRead
|
||||||
|
description: 微信读书书架(依赖微信读书插件)
|
||||||
|
screenshot:
|
||||||
|
file: weread.html
|
||||||
|
version: "1.2.0"
|
||||||
|
requires: ">=2.26.0"
|
||||||
license:
|
license:
|
||||||
- name: "GPL-3.0"
|
- name: "GPL-3.0"
|
||||||
url: "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
url: "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||||
|
|||||||
+3
-4
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "astro/tsconfigs/strict",
|
"extends": "astro/tsconfigs/strict",
|
||||||
"include": [".astro/types.d.ts", "**/*"],
|
"include": [".astro/types.d.ts", "src/**/*"],
|
||||||
"exclude": ["dist"],
|
"exclude": ["dist", "templates", "node_modules", "docs"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve"
|
||||||
"types": ["unplugin-icons/types/vue"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
|
|
||||||
const pageCdpUrl = process.argv[2];
|
|
||||||
const filePath = process.argv[3];
|
|
||||||
|
|
||||||
async function uploadFile() {
|
|
||||||
const ws = new WebSocket(pageCdpUrl);
|
|
||||||
|
|
||||||
let msgId = 1;
|
|
||||||
const pending = new Map();
|
|
||||||
|
|
||||||
ws.addEventListener('message', function(event) {
|
|
||||||
const msg = JSON.parse(event.data);
|
|
||||||
if (msg.id && pending.has(msg.id)) {
|
|
||||||
const { resolve, reject } = pending.get(msg.id);
|
|
||||||
pending.delete(msg.id);
|
|
||||||
if (msg.error) reject(msg.error);
|
|
||||||
else resolve(msg.result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function send(method, params = {}) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const id = msgId++;
|
|
||||||
pending.set(id, { resolve, reject });
|
|
||||||
ws.send(JSON.stringify({ id, method, params }));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise(resolve => ws.addEventListener('open', resolve, { once: true }));
|
|
||||||
|
|
||||||
const docResult = await send('DOM.getDocument');
|
|
||||||
const { nodeId } = await send('DOM.querySelector', {
|
|
||||||
nodeId: docResult.root.nodeId,
|
|
||||||
selector: 'input[type="file"]'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!nodeId) {
|
|
||||||
console.error('File input not found');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const absolutePath = path.resolve(filePath);
|
|
||||||
console.log('Setting file:', absolutePath);
|
|
||||||
|
|
||||||
await send('DOM.setFileInputFiles', {
|
|
||||||
nodeId,
|
|
||||||
files: [absolutePath]
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('File upload triggered successfully');
|
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
||||||
ws.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadFile().catch(err => {
|
|
||||||
console.error('Error:', err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
const CDP_URL = 'ws://127.0.0.1:62529/devtools/browser/a36c1b63-8ff4-41d5-93fa-5c228657ef70';
|
|
||||||
const ZIP_FILE = 'c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\dist\\warm-island-1.0.0.zip';
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const ws = new WebSocket(CDP_URL);
|
|
||||||
let msgId = 1;
|
|
||||||
const pending = new Map();
|
|
||||||
|
|
||||||
ws.addEventListener('message', (event) => {
|
|
||||||
const msg = JSON.parse(event.data);
|
|
||||||
if (msg.id && pending.has(msg.id)) {
|
|
||||||
const { resolve, reject } = pending.get(msg.id);
|
|
||||||
pending.delete(msg.id);
|
|
||||||
if (msg.error) reject(msg.error);
|
|
||||||
else resolve(msg.result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const send = (method, params = {}, sessionId) => new Promise((resolve, reject) => {
|
|
||||||
const id = msgId++;
|
|
||||||
const msg = { id, method, params };
|
|
||||||
if (sessionId) msg.sessionId = sessionId;
|
|
||||||
pending.set(id, { resolve, reject });
|
|
||||||
ws.send(JSON.stringify(msg));
|
|
||||||
});
|
|
||||||
|
|
||||||
await new Promise(r => ws.addEventListener('open', r, { once: true }));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { targetInfos } = await send('Target.getTargets');
|
|
||||||
const pageTarget = targetInfos.find(t => t.type === 'page' && t.url.includes('localhost:8090'));
|
|
||||||
if (!pageTarget) { console.error('No page target found'); process.exit(1); }
|
|
||||||
|
|
||||||
const { sessionId } = await send('Target.attachToTarget', { targetId: pageTarget.targetId, flatten: true });
|
|
||||||
|
|
||||||
const doc = await send('DOM.getDocument', {}, sessionId);
|
|
||||||
const { nodeId } = await send('DOM.querySelector', { nodeId: doc.root.nodeId, selector: 'input[type="file"][accept=".zip"]' }, sessionId);
|
|
||||||
|
|
||||||
if (!nodeId) { console.error('File input not found'); process.exit(1); }
|
|
||||||
console.log('Found file input node:', nodeId);
|
|
||||||
|
|
||||||
await send('DOM.setFileInputFiles', { nodeId, files: [ZIP_FILE] }, sessionId);
|
|
||||||
console.log('File upload triggered successfully!');
|
|
||||||
|
|
||||||
ws.close();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error:', JSON.stringify(err));
|
|
||||||
ws.close();
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
Reference in New Issue
Block a user