增加i18n支持

This commit is contained in:
nxxy335top
2026-05-20 17:18:33 +08:00
parent 6cd8167b49
commit bab8380894
80 changed files with 1363 additions and 743 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# WarmIsland 暖屿 # WarmIsland 暖屿
具有独特气质的生活博客主题,适用于 [Halo CMS](https://github.com/halo-dev/halo) 暖如灯火,静如留白。暖屿以柔和的色调和舒缓的节奏回归内容本身,为记录与阅读留出一方安静从容的角落
> 本项目由 AI 开发完成,人工仅进行部分功能测试及问题找寻 > 本项目由 AI 开发完成,人工仅进行部分功能测试及问题找寻

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

-29
View File
@@ -1,29 +0,0 @@
(function(){
var result = {};
var hero = document.querySelector(".hero");
if (hero) {
var h1 = hero.querySelector("h1");
var subtitle = hero.querySelector(".hero-subtitle, .subtitle, h2, p");
result.heroTitle = h1 ? h1.textContent.trim() : "no h1 found";
result.heroSubtitle = subtitle ? subtitle.textContent.trim() : "no subtitle found";
result.heroHTML = hero.innerHTML.substring(0, 500);
} else {
result.hero = "not found";
}
var footer = document.querySelector("footer, .footer, #footer");
if (footer) {
result.footerText = footer.textContent.trim().substring(0, 300);
result.footerHeight = getComputedStyle(footer).height;
result.footerMarginBottom = getComputedStyle(footer).marginBottom;
result.footerPaddingBottom = getComputedStyle(footer).paddingBottom;
} else {
result.footer = "not found";
}
var body = document.body;
result.bodyMarginBottom = getComputedStyle(body).marginBottom;
result.bodyPaddingBottom = getComputedStyle(body).paddingBottom;
result.htmlMarginBottom = getComputedStyle(document.documentElement).marginBottom;
result.documentHeight = document.documentElement.scrollHeight;
result.windowHeight = window.innerHeight;
return JSON.stringify(result);
})()
-5
View File
@@ -1,5 +0,0 @@
(function(){
return fetch('/apis/api.console.halo.run/v1alpha1/configmaps/warm-island-config')
.then(r => r.text())
.then(data => data.substring(0, 3000));
})()
-5
View File
@@ -1,5 +0,0 @@
(function(){
return fetch('/apis/api.console.halo.run/v1alpha1/themes/warm-island/config')
.then(r => r.text())
.then(data => data.substring(0, 2000));
})()
-15
View File
@@ -1,15 +0,0 @@
(function(){
var selects = document.querySelectorAll('select');
var result = [];
selects.forEach(function(s, i) {
result.push({index: i, value: s.value, name: s.name, options: Array.from(s.options).map(function(o){return o.value + ':' + o.text})});
});
var inputs = document.querySelectorAll('input');
var inputResult = [];
inputs.forEach(function(inp, i) {
if (inp.type !== 'hidden') {
inputResult.push({index: i, type: inp.type, name: inp.name, value: inp.value});
}
});
return JSON.stringify({selects: result, inputs: inputResult.slice(0, 10)});
})()
-8
View File
@@ -1,8 +0,0 @@
(function(){
var header = document.querySelector('header');
if (!header) return JSON.stringify({header: "not found"});
return JSON.stringify({
className: header.className,
innerHTML: header.innerHTML.substring(0, 500)
});
})()
-13
View File
@@ -1,13 +0,0 @@
(function(){
var h = document.querySelector(".hero");
if (!h) return "No .hero element found";
var s = getComputedStyle(h);
return JSON.stringify({
width: s.width,
marginLeft: s.marginLeft,
marginTop: s.marginTop,
paddingTop: s.paddingTop,
left: s.left,
position: s.position
});
})()
-13
View File
@@ -1,13 +0,0 @@
(function(){
var likeBtn = document.querySelector('.wi-like-btn');
if (!likeBtn) return JSON.stringify({likeBtn: "not found"});
var likeContainer = likeBtn.closest('.wi-post__like');
return JSON.stringify({
btnClass: likeBtn.className,
btnDisabled: likeBtn.disabled,
containerClass: likeContainer ? likeContainer.className : "none",
text: likeContainer ? likeContainer.textContent.trim() : likeBtn.textContent.trim(),
liked: likeBtn.classList.contains('wi-like-btn--liked') || (likeContainer && likeContainer.classList.contains('wi-post__like--liked')),
svgFill: likeBtn.querySelector('svg') ? getComputedStyle(likeBtn.querySelector('svg')).fill : "none"
});
})()
-17
View File
@@ -1,17 +0,0 @@
(function(){
var likeBtn = document.querySelector('.like-btn, .wi-like, [class*="like"], [class*="heart"]');
if (!likeBtn) {
var buttons = document.querySelectorAll('button');
var result = [];
buttons.forEach(function(b, i) {
result.push({index: i, text: b.textContent.trim(), className: b.className, innerHTML: b.innerHTML.substring(0, 200)});
});
return JSON.stringify({likeBtn: "not found", buttons: result});
}
return JSON.stringify({
className: likeBtn.className,
text: likeBtn.textContent.trim(),
innerHTML: likeBtn.innerHTML.substring(0, 300),
ariaLabel: likeBtn.getAttribute('aria-label')
});
})()
-8
View File
@@ -1,8 +0,0 @@
(function(){
return fetch('/apis/api.console.halo.run/v1alpha1/themes/warm-island')
.then(r => r.json())
.then(data => JSON.stringify({
settingName: data.spec.settingName,
configMapName: data.spec.configMapName
}));
})()
-18
View File
@@ -1,18 +0,0 @@
(function(){
var nav = document.querySelector("nav, .navbar, .nav, header nav, .header-nav");
if (!nav) return JSON.stringify({nav: "not found"});
var s = getComputedStyle(nav);
var parent = nav.parentElement;
var ps = parent ? getComputedStyle(parent) : null;
return JSON.stringify({
navClass: nav.className,
navWidth: s.width,
navMargin: s.margin,
navBorderRadius: s.borderRadius,
navBorder: s.border,
navPadding: s.padding,
parentClass: parent ? parent.className : "none",
parentWidth: ps ? ps.width : "none",
parentBorderRadius: ps ? ps.borderRadius : "none"
});
})()
-8
View File
@@ -1,8 +0,0 @@
(function(){
var navbar = document.querySelector('.wi-navbar');
if (!navbar) return JSON.stringify({navbar: "not found"});
return JSON.stringify({
className: navbar.className,
allClasses: navbar.className
});
})()
-5
View File
@@ -1,5 +0,0 @@
(function(){
return fetch('/apis/api.console.halo.run/v1alpha1/themes/warm-island')
.then(r => r.text())
.then(data => data.substring(0, 2000));
})()
-5
View File
@@ -1,5 +0,0 @@
(function(){
return fetch('/apis/api.console.halo.run/v1alpha1/themes/warm-island/setting')
.then(r => r.text())
.then(data => data.substring(0, 1000));
})()
-34
View File
@@ -1,34 +0,0 @@
(function(){
var result = {};
var h1 = document.querySelector('h1');
var content = document.querySelector('.wi-post__content, .post-content, article, .content');
if (h1 && content) {
var h1Rect = h1.getBoundingClientRect();
var contentRect = content.getBoundingClientRect();
result.titleBottom = h1Rect.bottom;
result.contentTop = contentRect.top;
result.gap = contentRect.top - h1Rect.bottom;
} else {
result.h1Found = !!h1;
result.contentFound = !!content;
if (h1) {
var nextEl = h1.nextElementSibling;
result.nextElTag = nextEl ? nextEl.tagName : "none";
result.nextElClass = nextEl ? nextEl.className : "none";
var h1Rect = h1.getBoundingClientRect();
if (nextEl) {
var nextRect = nextEl.getBoundingClientRect();
result.gap = nextRect.top - h1Rect.bottom;
}
}
}
var footer = document.querySelector("footer, .footer, #footer");
if (footer) {
var footerRect = footer.getBoundingClientRect();
result.footerBottom = footerRect.bottom;
result.windowHeight = window.innerHeight;
result.spaceBelowFooter = window.innerHeight - footerRect.bottom;
result.docHeight = document.documentElement.scrollHeight;
}
return JSON.stringify(result);
})()
-13
View File
@@ -1,13 +0,0 @@
(function(){
return fetch('/apis/api.console.halo.run/v1alpha1/themes?sort=creationTimestamp%2Cdesc')
.then(r => r.json())
.then(data => {
var items = data.items || [];
var result = items.map(t => ({
name: t.metadata.name,
displayName: t.spec.displayName,
active: !!t.spec.activationTime
}));
return JSON.stringify(result);
});
})()
-14
View File
@@ -1,14 +0,0 @@
(function(){
return fetch('/apis/api.console.halo.run/v1alpha1/themes?sort=creationTimestamp%2Cdesc')
.then(r => r.json())
.then(data => {
var items = data.items || [];
var result = items.map(t => ({
name: t.metadata.name,
displayName: t.spec.displayName,
active: t.spec.activationTime,
allKeys: Object.keys(t.spec)
}));
return JSON.stringify(result);
});
})()
-21
View File
@@ -1,21 +0,0 @@
(function(){
var btn = document.querySelector('.wi-like-btn');
if (!btn) return JSON.stringify({error: "no btn"});
var origFetch = window.fetch;
var lastRequest = null;
window.fetch = function() {
lastRequest = {url: arguments[0], options: arguments[1]};
return origFetch.apply(this, arguments);
};
btn.click();
return new Promise(function(resolve){
setTimeout(function(){
resolve(JSON.stringify({
lastRequest: lastRequest,
btnClass: btn.className,
liked: btn.classList.contains('wi-like-btn--liked'),
text: btn.closest('.wi-post__like').textContent.trim()
}));
}, 3000);
});
})()
-16
View File
@@ -1,16 +0,0 @@
(function(){
var btn = document.querySelector('.wi-like-btn');
if (!btn) return "no btn";
btn.click();
return new Promise(function(resolve){
setTimeout(function(){
var container = btn.closest('.wi-post__like');
resolve(JSON.stringify({
btnClass: btn.className,
btnDisabled: btn.disabled,
text: container ? container.textContent.trim() : btn.textContent.trim(),
liked: btn.classList.contains('wi-like-btn--liked')
}));
}, 2000);
});
})()
+29 -5
View File
@@ -4,6 +4,8 @@
首次发布 🎉 首次发布 🎉
- 🗑️ 移除侧边栏功能
### 核心功能 ### 核心功能
- 🎨 暖色调设计语言,呼吸动效光球、毛玻璃导航栏 - 🎨 暖色调设计语言,呼吸动效光球、毛玻璃导航栏
@@ -11,6 +13,23 @@
- 📱 响应式布局,移动端专属菜单与交互体验 - 📱 响应式布局,移动端专属菜单与交互体验
- ✨ 滚动渐入动画、光感跟随效果 - ✨ 滚动渐入动画、光感跟随效果
### SEO 与结构化数据
- 🔍 Open Graph 元标签,优化社交媒体分享预览
- 📋 JSON-LD 结构化数据,提升搜索引擎理解与展示
### 国际化
- 🌐 i18n 国际化支持(简体中文、繁体中文、英语)
- 🔄 跟随浏览器语言自动匹配,也可手动指定
### 文章页增强
- 📑 目录展开模式选项(仅展开当前分组 / 全部展开)
- 📰 相关文章推荐,帮助读者发现更多内容
- 🔗 分享功能(微博 / X / Facebook / Telegram / 复制链接)
- 🖼️ 图片懒加载 blur-up 效果,优化加载体验
### 页面模板 ### 页面模板
- 🏠 Hero 首屏(一言 API / 自定义描述 / 背景图片) - 🏠 Hero 首屏(一言 API / 自定义描述 / 背景图片)
@@ -30,14 +49,19 @@
- 🎯 三种导航栏样式(Glass / Minimal / Float - 🎯 三种导航栏样式(Glass / Minimal / Float
- 🌓 主题切换按钮 - 🌓 主题切换按钮
### 性能与体验
- ⚡ preconnect / dns-prefetch 资源提示,优化外部资源加载
- 🖼️ 图片懒加载 blur-up 渐显效果
- 🔤 移除自定义字体和内置字体功能,使用系统默认字体栈,提升加载速度
### 错误页面
- 🐘 404 / 500 错误页增强(导航栏 + 页脚),方便访客返回
### 配置与自定义 ### 配置与自定义
- ⚙️ 丰富的主题设置面板(13 个配置分组) - ⚙️ 丰富的主题设置面板(13 个配置分组)
- 🎨 可自定义强调色、圆角风格、容器宽度 - 🎨 可自定义强调色、圆角风格、容器宽度
- 📝 自定义 CSS 支持 - 📝 自定义 CSS 支持
- 🔧 页脚社交链接和自定义 HTML - 🔧 页脚社交链接和自定义 HTML
### 错误页面
- 🐘 404 页面
- 🐘 500 页面
+56
View File
@@ -17,6 +17,11 @@
| `article_show_visit_count` | 开关 | `true` | 显示阅读量统计 | | `article_show_visit_count` | 开关 | `true` | 显示阅读量统计 |
| `article_show_read_time` | 开关 | `false` | 显示预计阅读时间 | | `article_show_read_time` | 开关 | `false` | 显示预计阅读时间 |
| `article_code_theme` | 下拉 | `warm` | 代码高亮主题:`warm` / `cold` | | `article_code_theme` | 下拉 | `warm` | 代码高亮主题:`warm` / `cold` |
| `article_toc_expand` | 下拉 | `active` | 目录展开模式:`active` / `all` |
| `article_show_related` | 开关 | `false` | 显示相关文章推荐 |
| `article_related_title` | 文本 | `相关推荐` | 相关文章标题 |
| `article_show_share` | 开关 | `true` | 显示分享按钮 |
| `article_share_title` | 文本 | `分享` | 分享区域标题 |
## 详细说明 ## 详细说明
@@ -98,6 +103,57 @@ article_code_theme: warm
代码高亮支持多种编程语言,包括 JavaScript、Python、Java、Go、Rust、YAML、JSON、Bash 等。语言检测基于代码块的语言标记自动完成。 代码高亮支持多种编程语言,包括 JavaScript、Python、Java、Go、Rust、YAML、JSON、Bash 等。语言检测基于代码块的语言标记自动完成。
::: :::
### 目录展开模式(article_toc_expand
控制文章目录的展开方式:
| 值 | 说明 |
| --- | --- |
| `active` | 仅展开当前阅读位置所在的分组,其余分组折叠(默认) |
| `all` | 全部展开,显示所有层级的目录项 |
::: tip
对于标题层级较多的长文,推荐使用 `active` 模式,可以让读者更聚焦于当前章节。对于短文或标题层级较少的文章,`all` 模式可以一次性展示完整目录结构。
:::
### 相关文章推荐(article_show_related
开启后,文章底部会显示相关文章推荐列表,帮助读者发现更多感兴趣的内容。相关文章基于 Halo 的随机推荐算法选取。
#### 相关文章标题(article_related_title
自定义相关文章推荐区域的标题文字,默认为「相关推荐」。仅在 `article_show_related` 开启时显示此配置项。
```yaml
article_show_related: true
article_related_title: "猜你喜欢"
```
### 分享功能(article_show_share
开启后,文章底部会显示分享按钮,支持以下社交平台:
| 平台 | 说明 |
| --- | --- |
| 微博 | 分享到新浪微博 |
| X (Twitter) | 分享到 X |
| Facebook | 分享到 Facebook |
| Telegram | 分享到 Telegram |
| 复制链接 | 复制文章链接到剪贴板 |
#### 分享区域标题(article_share_title
自定义分享区域的标题文字,默认为「分享」。仅在 `article_show_share` 开启时显示此配置项。
```yaml
article_show_share: true
article_share_title: "分享这篇文章"
```
::: tip
分享功能会自动携带文章标题和链接,无需手动配置分享内容。移动端会优先显示适合移动端的分享方式。
:::
## 阅读增强功能 ## 阅读增强功能
除了可配置的项目外,文章详情页还包含以下内置功能: 除了可配置的项目外,文章详情页还包含以下内置功能:
+16
View File
@@ -13,6 +13,7 @@
| `label_search` | 文本 | `搜索` | 搜索按钮的标签文案 | | `label_search` | 文本 | `搜索` | 搜索按钮的标签文案 |
| `label_theme_switch` | 文本 | `切换主题` | 主题切换按钮的标签文案 | | `label_theme_switch` | 文本 | `切换主题` | 主题切换按钮的标签文案 |
| `label_archives_title` | 文本 | `归档` | 归档页面的标题文案 | | `label_archives_title` | 文本 | `归档` | 归档页面的标题文案 |
| `language` | 下拉 | `auto` | 主题界面语言 |
## 详细说明 ## 详细说明
@@ -72,6 +73,21 @@ label_archives_title: "Archives"
标签文案的修改会即时生效,无需重启 Halo。如果使用多语言场景,可以配合 Halo 的多语言插件实现更完整的国际化。 标签文案的修改会即时生效,无需重启 Halo。如果使用多语言场景,可以配合 Halo 的多语言插件实现更完整的国际化。
::: :::
### 语言(language
设置主题界面的显示语言,影响导航栏、文章页、错误页等所有主题内置文案。提供以下选项:
| 值 | 说明 |
| --- | --- |
| `auto` | 跟随浏览器语言自动匹配(默认) |
| `zh_CN` | 简体中文 |
| `zh_TW` | 繁體中文 |
| `en` | English |
::: tip
选择 `auto` 时,主题会根据访客浏览器的语言设置自动匹配最接近的语言。如果浏览器语言不在支持列表中,将回退到简体中文。
:::
## 相关页面 ## 相关页面
- [样式设置](/config/style) — 调整配色、圆角、动画等视觉表现 - [样式设置](/config/style) — 调整配色、圆角、动画等视觉表现
-12
View File
@@ -10,7 +10,6 @@
| `accent_color` | 颜色 | `#d4764e` | 全局强调色 | | `accent_color` | 颜色 | `#d4764e` | 全局强调色 |
| `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` |
| `layout_sidebar` | 开关 | `false` | 启用侧边栏(开发中) |
| `animation_enabled` | 开关 | `true` | 全局动画总开关 | | `animation_enabled` | 开关 | `true` | 全局动画总开关 |
| `animation_breath` | 开关 | `true` | 呼吸动画效果 | | `animation_breath` | 开关 | `true` | 呼吸动画效果 |
| `animation_scroll_reveal` | 开关 | `true` | 滚动渐入动画 | | `animation_scroll_reveal` | 开关 | `true` | 滚动渐入动画 |
@@ -79,12 +78,6 @@ accent_color: "#8b5cf6" # 紫罗兰
| `medium` | 中等宽度,兼顾阅读体验与信息密度(默认) | | `medium` | 中等宽度,兼顾阅读体验与信息密度(默认) |
| `wide` | 宽容器,适合图片较多的站点或需要更大展示空间的场景 | | `wide` | 宽容器,适合图片较多的站点或需要更大展示空间的场景 |
### 侧边栏(layout_sidebar
::: danger
侧边栏功能目前仍在开发中,此选项暂不可用。开启后不会产生实际效果,请关注后续版本更新。
:::
### 动画效果 ### 动画效果
WarmIsland 提供了丰富的动画效果,所有动画都可以独立开关: WarmIsland 提供了丰富的动画效果,所有动画都可以独立开关:
@@ -118,11 +111,6 @@ WarmIsland 提供了丰富的动画效果,所有动画都可以独立开关:
在此处编写自定义 CSS 代码,会注入到全站所有页面的 `<style>` 标签中。可以用来覆盖主题默认样式或添加全新样式。 在此处编写自定义 CSS 代码,会注入到全站所有页面的 `<style>` 标签中。可以用来覆盖主题默认样式或添加全新样式。
```css ```css
/* 示例:修改正文字体 */
body {
font-family: "LXGW WenKai", sans-serif;
}
/* 示例:自定义卡片悬停效果 */ /* 示例:自定义卡片悬停效果 */
.post-card:hover { .post-card:hover {
transform: translateY(-4px); transform: translateY(-4px);
+7 -7
View File
@@ -104,18 +104,18 @@ else if (!stored && prefersDark) { isDark = true; }
| 变量 | 字体栈 | 用途 | | 变量 | 字体栈 | 用途 |
|------|--------|------| |------|--------|------|
| `--font-sans` | `'Noto Serif SC', 'Source Han Serif SC', Georgia, serif` | 标题、强调文字 | | `--font-sans` | `Georgia, 'Noto Serif SC', 'Source Han Serif SC', serif` | 标题、强调文字 |
| `--font-body` | `'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif` | 正文、UI 文字 | | `--font-body` | `-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans SC', sans-serif` | 正文、UI 文字 |
| `--font-mono` | `'JetBrains Mono', 'Fira Code', monospace` | 代码 | | `--font-mono` | `'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace` | 代码 |
### 设计思路 ### 设计思路
- **标题用衬线体**Noto Serif SC 赋予标题优雅的文艺气质 - **标题用衬线体**Georgia 赋予标题优雅的文艺气质,回退到系统中文字体
- **正文用无衬线体**Noto Sans SC 保证长文阅读的舒适度 - **正文用系统字体**使用各平台原生系统字体栈,确保最佳阅读体验与加载性能
- **代码用等宽体**JetBrains Mono 专为代码设计 - **代码用等宽体**优先使用 JetBrains Mono,回退到系统等宽字体
::: tip ::: tip
这些字体不会自动加载,需要在 Halo 后台或 HTML 中引入对应的字体 CSS。推荐使用 Google Fonts 或国内 CDN 镜像 主题使用系统默认字体栈,无需额外加载字体文件,可显著提升页面加载速度。如需使用自定义字体,可通过 [自定义 CSS](/customize/custom-css) 覆盖 `--font-sans``--font-body` 等变量
::: :::
## 间距系统 ## 间距系统
+1 -17
View File
@@ -1,6 +1,6 @@
# 快速开始 # 快速开始
WarmIsland(暖屿)是一款为 [Halo](https://halo.run) 打造的生活博客主题,具有独特的暖色调设计语言和丰富的功能模块 WarmIsland(暖屿)是一款为 [Halo](https://halo.run) 打造的博客主题。暖如灯火,静如留白——它以柔和的色调和舒缓的节奏回归内容本身,为记录与阅读留出一方安静从容的角落
## 主题特色 ## 主题特色
@@ -33,22 +33,6 @@ WarmIsland(暖屿)是一款为 [Halo](https://halo.run) 打造的生活博
4. 上传下载的 `.zip` 文件 4. 上传下载的 `.zip` 文件
5. 点击 **启用** 激活主题 5. 点击 **启用** 激活主题
### 方式三:Git 克隆安装
1. SSH 登录服务器,进入 Halo 主题目录:
```bash
cd ~/.halo2/themes
```
2. 克隆主题仓库:
```bash
git clone https://github.com/nxxy335top/halo-theme-WarmIsland.git warm-island
```
3. 在 Halo 后台 **主题** 管理页面中找到 WarmIsland,点击 **启用**
## 初始配置 ## 初始配置
安装并启用主题后,建议按以下顺序进行初始配置: 安装并启用主题后,建议按以下顺序进行初始配置:
+11 -2
View File
@@ -1,6 +1,6 @@
# WarmIsland 是什么? # WarmIsland 是什么?
**WarmIsland(暖屿)** 是一款为 [Halo](https://halo.run) 打造的生活博客主题。 **WarmIsland(暖屿)** 是一款为 [Halo](https://halo.run) 打造的博客主题。暖如灯火,静如留白——它以柔和的色调和舒缓的节奏回归内容本身,为记录与阅读留出一方安静从容的角落。
> 本主题由 AI 开发完成。 > 本主题由 AI 开发完成。
@@ -33,11 +33,14 @@
### 📝 阅读体验 ### 📝 阅读体验
- **文章目录** — 桌面端侧边悬浮 + 移动端底部抽屉 - **文章目录** — 桌面端侧边悬浮 + 移动端底部抽屉,支持展开模式选项
- **阅读进度条** — 实时显示阅读进度 - **阅读进度条** — 实时显示阅读进度
- **代码高亮** — Prism.js 支持,Warm / Cold 双主题 - **代码高亮** — Prism.js 支持,Warm / Cold 双主题
- **图片灯箱** — 点击放大,支持左右切换 - **图片灯箱** — 点击放大,支持左右切换
- **图片懒加载** — blur-up 渐显效果,优化加载体验
- **点赞功能** — 一键点赞,状态持久化 - **点赞功能** — 一键点赞,状态持久化
- **相关文章推荐** — 文章底部推荐相关内容
- **分享功能** — 微博 / X / Facebook / Telegram / 复制链接
### 📱 响应式 ### 📱 响应式
@@ -46,6 +49,12 @@
- 文章卡片自动切换为上下布局 - 文章卡片自动切换为上下布局
- 目录自动切换为悬浮按钮 + 抽屉 - 目录自动切换为悬浮按钮 + 抽屉
### 🔍 SEO 与国际化
- **Open Graph 元标签** — 优化社交媒体分享预览
- **JSON-LD 结构化数据** — 提升搜索引擎理解与展示
- **i18n 国际化** — 简体中文、繁体中文、英语,支持跟随浏览器自动匹配
### 🔌 插件生态 ### 🔌 插件生态
深度集成 Halo 插件,提供丰富的扩展页面: 深度集成 Halo 插件,提供丰富的扩展页面:
+1 -1
View File
@@ -4,7 +4,7 @@ layout: home
hero: hero:
name: "WarmIsland" name: "WarmIsland"
text: "暖屿主题文档" text: "暖屿主题文档"
tagline: 具有独特气质的生活博客主题,为 Halo 打造 tagline: 暖如灯火,静如留白。暖屿以柔和的色调和舒缓的节奏回归内容本身,为记录与阅读留出一方安静从容的角落。
actions: actions:
- theme: brand - theme: brand
text: 快速开始 text: 快速开始
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-all.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-all.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\click-like.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-cm.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-config.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-form.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-header.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-like.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\test-like-api.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\click-like-debug.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-like-state.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-names.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-nav.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-navbar-class.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-raw.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-setting.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-spacing.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-theme.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-3
View File
@@ -1,3 +0,0 @@
const fs = require('fs');
const js = fs.readFileSync('c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\check-theme2.js', 'utf8');
console.log(Buffer.from(js).toString('base64'));
-62
View File
@@ -1,62 +0,0 @@
{
"cookies": [
{
"name": "language",
"value": "zh-CN",
"domain": "localhost",
"path": "/",
"expires": -1.0,
"size": 13,
"httpOnly": false,
"secure": false,
"session": true,
"sameSite": "Lax"
},
{
"name": "XSRF-TOKEN",
"value": "01a0a3fe-aad4-414d-bc42-3a173c9bc0ab",
"domain": "localhost",
"path": "/",
"expires": -1.0,
"size": 46,
"httpOnly": true,
"secure": false,
"session": true
},
{
"name": "NID",
"value": "531=aZe-V6TNWj_kuxgiHTVkXjS6ptWqLuE0riVNGyXvxwnE9QdErkY4CznBbrF9lyQLZ9xDo0D8GSLbJHpi3JzQErmBm7pTVjoMnoOZUwYY3-9cwTfXq4tO0d8frwBCQFx7MHorS0Gf9zjpBLbpa0iQiht8sybQAlfsvHQWfyaBAz1dIW5jh9jHrb4ZREMKf5CCLFCBywI",
"domain": ".google.com",
"path": "/",
"expires": 1794719008.977387,
"size": 206,
"httpOnly": true,
"secure": false,
"session": false
},
{
"name": "SESSION",
"value": "84b72204-3b7b-4594-908f-5b1a8a3db254",
"domain": "localhost",
"path": "/",
"expires": -1.0,
"size": 43,
"httpOnly": true,
"secure": false,
"session": true
},
{
"name": "device_id",
"value": "63a8d881b6144d6e98511d7e647257bc",
"domain": "localhost",
"path": "/",
"expires": 1787547843.038663,
"size": 41,
"httpOnly": true,
"secure": false,
"session": false,
"sameSite": "Lax"
}
],
"origins": []
}
+126
View File
@@ -0,0 +1,126 @@
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.weibo=Weibo
share.twitter=X (Twitter)
share.facebook=Facebook
share.telegram=Telegram
share.copyLink=Copy Link
share.copied=Copied!
share.qrcode=QR Code
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
+126
View File
@@ -0,0 +1,126 @@
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.weibo=Weibo
share.twitter=X (Twitter)
share.facebook=Facebook
share.telegram=Telegram
share.copyLink=Copy Link
share.copied=Copied!
share.qrcode=QR Code
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
+126
View File
@@ -0,0 +1,126 @@
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.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
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
+126
View File
@@ -0,0 +1,126 @@
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.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
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
+1
View File
@@ -126,5 +126,6 @@
<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
View File
@@ -126,5 +126,6 @@
<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>
+44 -22
View File
@@ -22,15 +22,26 @@ spec:
- $formkit: text - $formkit: text
name: label_search name: label_search
label: 搜索按钮标签 label: 搜索按钮标签
value: 搜索
- $formkit: text - $formkit: text
name: label_theme_switch name: label_theme_switch
label: 主题切换标签 label: 主题切换标签
value: 切换主题
- $formkit: text - $formkit: text
name: label_archives_title name: label_archives_title
label: 归档页标题 label: 归档页标题
value: 归档 - $formkit: select
name: language
label: 语言
options:
- label: 跟随浏览器
value: auto
- label: 简体中文
value: zh_CN
- label: 繁體中文
value: zh_TW
- label: English
value: en
value: auto
help: 设置主题界面语言,选择"跟随浏览器"将根据浏览器语言自动匹配
- group: style - group: style
label: 总体样式 label: 总体样式
formSchema: formSchema:
@@ -75,11 +86,6 @@ spec:
- label: Wide - label: Wide
value: wide value: wide
value: medium value: medium
- $formkit: switch
name: layout_sidebar
label: 启用侧边栏
value: false
help: 侧边栏功能开发中,暂不可用
- $formkit: switch - $formkit: switch
name: animation_enabled name: animation_enabled
label: 启用动效 label: 启用动效
@@ -191,11 +197,9 @@ spec:
- $formkit: text - $formkit: text
name: home_pinned_title name: home_pinned_title
label: 置顶模块标题 label: 置顶模块标题
value: 置顶
- $formkit: text - $formkit: text
name: home_posts_title name: home_posts_title
label: 文章模块标题 label: 文章模块标题
value: 文章
- $formkit: switch - $formkit: switch
name: home_card_show_visit name: home_card_show_visit
label: 文章卡片显示阅读量 label: 文章卡片显示阅读量
@@ -228,27 +232,21 @@ spec:
- $formkit: text - $formkit: text
name: home_label_newer name: home_label_newer
label: 分页-较新标签 label: 分页-较新标签
value: 较新
- $formkit: text - $formkit: text
name: home_label_older name: home_label_older
label: 分页-较旧标签 label: 分页-较旧标签
value: 较旧
- $formkit: text - $formkit: text
name: home_label_loading name: home_label_loading
label: 无限滚动-加载中文案 label: 无限滚动-加载中文案
value: 加载中...
- $formkit: text - $formkit: text
name: home_label_all_loaded name: home_label_all_loaded
label: 无限滚动-全部加载文案 label: 无限滚动-全部加载文案
value: 已加载全部文章
- $formkit: text - $formkit: text
name: home_label_no_posts name: home_label_no_posts
label: 暂无文章文案 label: 暂无文章文案
value: 暂无文章。
- $formkit: text - $formkit: text
name: home_label_post_count name: home_label_post_count
label: 文章数量文案({total}为占位符) label: 文章数量文案({total}为占位符)
value: 共 {total} 篇文章
- group: article - group: article
label: 文章 label: 文章
formSchema: formSchema:
@@ -306,13 +304,42 @@ spec:
- label: Cold - label: Cold
value: cold value: cold
value: warm value: warm
- $formkit: select
name: article_toc_expand
label: 目录展开模式
options:
- label: 仅展开当前分组
value: active
- label: 全部展开
value: all
value: active
help: 控制文章目录的展开方式
- $formkit: switch
name: article_show_related
id: article_show_related
label: 显示相关文章推荐
value: false
- $formkit: text
name: article_related_title
key: article_related_title
label: 相关文章标题
if: "$get(article_show_related).value === true"
- $formkit: switch
name: article_show_share
id: article_show_share
label: 显示分享按钮
value: true
- $formkit: text
name: article_share_title
key: article_share_title
label: 分享区域标题
if: "$get(article_show_share).value !== false"
- group: moments - group: moments
label: 瞬间 label: 瞬间
formSchema: formSchema:
- $formkit: text - $formkit: text
name: moments_page_title name: moments_page_title
label: 页面标题 label: 页面标题
value: 瞬间
- $formkit: select - $formkit: select
name: moments_style name: moments_style
label: 展示样式 label: 展示样式
@@ -343,7 +370,6 @@ spec:
- $formkit: text - $formkit: text
name: photos_page_title name: photos_page_title
label: 页面标题 label: 页面标题
value: 图库
- $formkit: select - $formkit: select
name: photos_style name: photos_style
label: 展示样式 label: 展示样式
@@ -375,7 +401,6 @@ spec:
- $formkit: text - $formkit: text
name: links_page_title name: links_page_title
label: 页面标题 label: 页面标题
value: 友情链接
- $formkit: select - $formkit: select
name: links_style name: links_style
label: 展示样式 label: 展示样式
@@ -393,18 +418,15 @@ spec:
- $formkit: text - $formkit: text
name: friends_page_title name: friends_page_title
label: 页面标题 label: 页面标题
value: 朋友圈
- group: messageboard - group: messageboard
label: 留言板 label: 留言板
formSchema: formSchema:
- $formkit: text - $formkit: text
name: messageboard_title name: messageboard_title
label: 留言板标题 label: 留言板标题
value: 留言板
- $formkit: textarea - $formkit: textarea
name: messageboard_subtitle name: messageboard_subtitle
label: 留言板副标题 label: 留言板副标题
value: 在这里留下你的足迹吧 🌙
- group: comment - group: comment
label: 评论 label: 评论
formSchema: formSchema:
+2 -2
View File
@@ -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 ?: '精选'}">精选</h2> <h2 class="wi-section__title" th:text="${theme.config?.home?.home_featured_title ?: #messages.msg('common.featured')}">精选</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}"
>置顶</span> th:text="#{hero.pinned}">Pinned</span>
</div> </div>
<h3 <h3
class="wi-featured__title" class="wi-featured__title"
+5 -5
View File
@@ -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 ?: '最新文章'}">最新文章</h2> <h2 class="wi-section__title" th:text="${theme.config?.home?.home_latest_title ?: #messages.msg('common.latestPosts')}">Latest Posts</h2>
</div> </div>
<div <div
class="wi-latest" class="wi-latest"
@@ -58,8 +58,8 @@
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 ?: '较新'}" th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
>&larr; 较新</a> >&larr; Newer</a>
<span <span
class="wi-pagination__info" class="wi-pagination__info"
th:text="|${posts.page} / ${posts.totalPages}|" th:text="|${posts.page} / ${posts.totalPages}|"
@@ -68,8 +68,8 @@
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 ?: '较旧'}" th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
>较旧 &rarr;</a> >Older &rarr;</a>
</nav> </nav>
</div> </div>
</section> </section>
+1 -1
View File
@@ -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 ?: '留言墙'}">留言墙</h2> <h2 class="wi-section__title" th:text="${theme.config?.home?.home_message_wall_title ?: #messages.msg('messageboard.title')}">留言墙</h2>
<span class="wi-section__accent"></span> <span class="wi-section__accent"></span>
</div> </div>
+3 -3
View File
@@ -28,20 +28,20 @@
<div class="wi-mobile-menu__footer"> <div class="wi-mobile-menu__footer">
<button <button
th:if="${pluginFinder.available('PluginSearchWidget')}" th:if="${pluginFinder.available('PluginSearchWidget') and theme.config?.navbar?.navbar_show_search != false}"
class="wi-mobile-menu__action-btn" class="wi-mobile-menu__action-btn"
type="button" type="button"
aria-label="Search" aria-label="Search"
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 ?: '搜索'}">搜索</span> <span th:text="${theme.config?.basic?.label_search ?: #messages.msg('common.search')}">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 ?: '切换主题'}">切换主题</span> <span class="wi-mobile-menu__theme-label" th:text="${theme.config?.basic?.label_theme_switch ?: #messages.msg('common.themeSwitch')}">Switch Theme</span>
</button> </button>
</div> </div>
</div> </div>
+2 -2
View File
@@ -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 ?: '瞬间'}">瞬间</h2> <h2 class="wi-moments-section__title" th:text="${pageConfig?.title ?: theme.config?.moments?.moments_title ?: #messages.msg('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 ?: '查看全部'}">查看全部</span> <span th:text="${theme.config?.home?.home_label_view_all ?: #messages.msg('common.viewAll')}">查看全部</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>
+2 -2
View File
@@ -44,14 +44,14 @@ import ThemeSwitcher from "./ThemeSwitcher.vue";
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" class="wi-navbar__action-btn"
type="button" type="button"
th:aria-label="${theme.config?.basic?.label_search ?: '搜索'}" th:aria-label="${theme.config?.basic?.label_search ?: #messages.msg('common.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 ?: '切换主题'}" style="display:inline-flex"> <span th:attr="data-label=${theme.config?.basic?.label_theme_switch ?: #messages.msg('common.themeSwitch')}" style="display:inline-flex">
<ThemeSwitcher client:idle /> <ThemeSwitcher client:idle />
</span> </span>
</th:block> </th:block>
+2 -2
View File
@@ -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 ?: '图库'}">图库</h2> <h2 class="wi-photos-section__title" th:text="${pageConfig?.title ?: theme.config?.photos?.photos_title ?: #messages.msg('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 ?: '查看全部'}">查看全部</span> <span th:text="${theme.config?.home?.home_label_view_all ?: #messages.msg('common.viewAll')}">查看全部</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>
+1 -1
View File
@@ -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 ?: '时间线'}">时间线</h2> <h2 class="wi-section__title" th:text="${theme.config?.home?.home_timeline_title ?: #messages.msg('common.timeline')}">时间线</h2>
<span class="wi-section__accent"></span> <span class="wi-section__accent"></span>
</div> </div>
+46 -9
View File
@@ -17,7 +17,7 @@ const { pageTitle, contentClass, wide, home } = Astro.props;
--- ---
<!doctype html> <!doctype html>
<html lang="zh" th:lang="${theme.config?.basic?.language ?: #locale.language}" th:classappend="${theme.config?.animation?.animation_enabled == false} ? 'wi-no-animations'"> <html class={home ? 'wi-home-snap' : ''} th:lang="${#locale.toLanguageTag()}" th:classappend="${theme.config?.animation?.animation_enabled == false} ? 'wi-no-animations'" th:attr="data-lang=${theme.config?.basic?.language}">
<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" />
@@ -56,17 +56,47 @@ const { pageTitle, contentClass, wide, home } = Astro.props;
th:href="${theme.config?.basic?.favicon ?: '/favicon.ico'}" th:href="${theme.config?.basic?.favicon ?: '/favicon.ico'}"
/> />
<link rel="alternate" type="application/rss+xml" th:title="${site.title}" th:href="@{/feed.xml}" /> <link rel="alternate" type="application/rss+xml" th:title="${site.title}" th:href="@{/feed.xml}" />
<style th:if="${theme.config?.style?.accent_color}" th:utext="${':root { --accent: ' + theme.config?.style?.accent_color + '; --accent-hover: ' + theme.config?.style?.accent_color + '; }'}"></style> <link rel="preconnect" href="https://cdn.jsdelivr.net" />
<style th:if="${theme.config?.style?.border_radius == 'small'}" th:utext="${':root { --radius-sm: 4px; --radius-md: 8px; --radius-lg: 16px; }'}"></style> <link rel="dns-prefetch" href="https://cdn.jsdelivr.net" />
<style th:if="${theme.config?.style?.border_radius == 'medium' or theme.config?.style?.border_radius == null}" th:utext="${':root { --radius-sm: 8px; --radius-md: 12px; --radius-lg: 24px; }'}"></style> <style is:inline th:if="${theme.config?.style?.accent_color}" th:utext="${':root { --accent: ' + theme.config?.style?.accent_color + '; --accent-hover: ' + theme.config?.style?.accent_color + '; }'}">/*accent*/</style>
<style th:if="${theme.config?.style?.border_radius == 'large'}" th:utext="${':root { --radius-sm: 12px; --radius-md: 20px; --radius-lg: 36px; }'}"></style> <style is:inline th:if="${theme.config?.style?.border_radius == 'small'}" th:utext="${':root { --radius-sm: 4px; --radius-md: 8px; --radius-lg: 16px; }'}">/*radius*/</style>
<style th:if="${theme.config?.style?.layout_container_width == 'narrow'}" th:utext="${'.wi-content-wrap { max-width: 680px; } .wi-content-wrap--wide { max-width: 1000px; } .wi-main--wide { max-width: 1200px; }'}"></style> <style is:inline th:if="${theme.config?.style?.border_radius == 'medium' or theme.config?.style?.border_radius == null}" th:utext="${':root { --radius-sm: 8px; --radius-md: 12px; --radius-lg: 24px; }'}">/*radius*/</style>
<style 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; }'}"></style> <style is:inline th:if="${theme.config?.style?.border_radius == 'large'}" th:utext="${':root { --radius-sm: 12px; --radius-md: 20px; --radius-lg: 36px; }'}">/*radius*/</style>
<style 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; }'}"></style> <style is:inline th:if="${theme.config?.style?.layout_container_width == 'narrow'}" th:utext="${'.wi-content-wrap { max-width: 680px; } .wi-content-wrap--wide { max-width: 1000px; } .wi-main--wide { max-width: 1200px; }'}">/*width*/</style>
<style th:if="${theme.config?.style?.custom_css}" th:utext="${theme.config?.style?.custom_css}"></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?.custom_css}" th:utext="${theme.config?.style?.custom_css}">/*custom*/</style>
<slot name="head" /> <slot name="head" />
</head> </head>
<body class="wi-body"> <body class="wi-body">
<script is:inline>
(function(){
var lang=document.documentElement.getAttribute("data-lang");
if(lang&&lang!=="auto"){
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";
window.location.reload();
}
}else if(lang==="auto"){
var cookies2=document.cookie.split(";");
var hasCookie=false;
for(var j=0;j<cookies2.length;j++){
if(cookies2[j].trim().indexOf("language=")===0){hasCookie=true;break;}
}
if(hasCookie){
document.cookie="language=;path=/;max-age=0;SameSite=Lax";
window.location.reload();
}
}
})();
</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 ? ( {home ? (
@@ -124,4 +154,11 @@ html.wi-no-animations .wi-reveal {
transform: none; transform: none;
transition: none; transition: none;
} }
.wi-post__content img {
max-width: 100%;
height: auto;
}
</style> </style>
+9 -9
View File
@@ -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 ?: '归档'} - ${site.title}|"></title> <title th:text="|${theme.config?.basic?.label_archives_title ?: #messages.msg('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 ?: '归档'}">归档</h1> <h1 class="wi-archives__title" th:text="${theme.config?.basic?.label_archives_title ?: #messages.msg('archives.title')}">归档</h1>
<span class="wi-page-accent"></span> <span class="wi-page-accent"></span>
<p class="wi-archives__subtitle" th:text="${#strings.replace(theme.config?.home?.home_label_post_count ?: '共 {total} 篇文章', '{total}', #strings.toString(archives.total))}"></p> <p class="wi-archives__subtitle" th:text="${#strings.replace(#messages.msg('common.postCount'), '{0}', #strings.toString(archives.total))}"></p>
</header> </header>
<section class="wi-archives__timeline" aria-label="Archive timeline"> <section class="wi-archives__timeline" aria-label="Archive timeline">
@@ -57,8 +57,8 @@ 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 ?: '暂无文章。'}" th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
>暂无文章。</div> >No posts yet.</div>
</section> </section>
<nav <nav
@@ -70,8 +70,8 @@ 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 ?: '较新'}" th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
>&larr; 较新</a> >&larr; Newer</a>
<span <span
class="wi-archives__page-info" class="wi-archives__page-info"
th:text="|${archives.page} / ${archives.totalPages}|" th:text="|${archives.page} / ${archives.totalPages}|"
@@ -80,8 +80,8 @@ 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 ?: '较旧'}" th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
>较旧 &rarr;</a> >Older &rarr;</a>
</nav> </nav>
</div> </div>
</Layout> </Layout>
+3 -3
View File
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
<Layout> <Layout>
<Fragment slot="head"> <Fragment slot="head">
<title th:text="|分类 - ${site.title}|"></title> <title th:text="|#{categories.title} - ${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">分类</h1> <h1 class="wi-categories__title" th:text="#{categories.title}">分类</h1>
<span class="wi-page-accent"></span> <span class="wi-page-accent"></span>
<p class="wi-categories__subtitle" th:text="|共 ${categories.size} 个分类|"></p> <p class="wi-categories__subtitle" th:text="${#messages.msg('common.categoryCount', ${categories.size})}"></p>
</header> </header>
<nav class="wi-categories__list" aria-label="Category list"> <nav class="wi-categories__list" aria-label="Category list">
+135 -8
View File
@@ -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="|共 ${posts.total} 篇文章|"></p> <p class="wi-category__subtitle" th:text="#{common.postCount(${posts.total})}"></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"
@@ -22,13 +22,44 @@ import Layout from "../layouts/Layout.astro";
></p> ></p>
</header> </header>
<th:block th:replace="~{fragments/post-list}"></th:block> <ul class="wi-category__list" role="list">
<li
th:each="post : ${posts.items}"
class="wi-category__entry"
>
<a
class="wi-category__entry-link"
th:href="@{${post.status.permalink}}"
>
<div class="wi-category__entry-body">
<time
class="wi-category__entry-date"
th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"
datetime="${post.spec.publishTime}"
></time>
<h2 class="wi-category__entry-title" th:text="${post.spec.title}"></h2>
<p
class="wi-category__entry-excerpt"
th:text="${post.status.excerpt}"
></p>
</div>
<img
th:unless="${#strings.isEmpty(post.spec.cover)}"
class="wi-category__entry-image"
th:src="${post.spec.cover}"
alt=""
loading="lazy"
decoding="async"
/>
</a>
</li>
</ul>
<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 ?: '暂无文章。'}" th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
>暂无文章。</div> >No posts yet.</div>
<nav <nav
th:if="${posts.totalPages gt 1}" th:if="${posts.totalPages gt 1}"
@@ -39,8 +70,8 @@ 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 ?: '较新'}" th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
>&larr; 较新</a> >&larr; Newer</a>
<span <span
class="wi-category__page-info" class="wi-category__page-info"
th:text="|${posts.page} / ${posts.totalPages}|" th:text="|${posts.page} / ${posts.totalPages}|"
@@ -49,8 +80,8 @@ 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 ?: '较旧'}" th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
>较旧 &rarr;</a> >Older &rarr;</a>
</nav> </nav>
</div> </div>
</Layout> </Layout>
@@ -97,6 +128,90 @@ import Layout from "../layouts/Layout.astro";
line-height: var(--leading-normal); line-height: var(--leading-normal);
} }
.wi-category__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: var(--space-lg);
}
.wi-category__entry {
border-bottom: 1px dashed var(--rule);
transition: background var(--duration-fast) var(--ease-out-quart);
}
.wi-category__entry:last-child {
border-bottom: none;
}
.wi-category__entry:hover {
background: var(--bg-raised);
margin: 0 calc(-1 * var(--space-md));
padding-left: var(--space-md);
padding-right: var(--space-md);
border-radius: var(--radius-sm);
}
.wi-category__entry-link {
display: grid;
grid-template-columns: minmax(0, 1fr) 200px;
gap: var(--space-lg);
padding: var(--space-lg) 0;
text-decoration: none;
color: inherit;
align-items: start;
}
.wi-category__entry-body {
display: grid;
gap: 0.35rem;
}
.wi-category__entry-date {
color: var(--ink-3);
font-size: var(--text-sm);
font-variant-numeric: tabular-nums;
}
.wi-category__entry-title {
font-family: var(--font-sans);
font-size: var(--text-lg);
font-weight: 600;
color: var(--ink);
line-height: var(--leading-snug);
margin: 0.25rem 0;
transition: color var(--duration-fast) var(--ease-out-quart);
}
.wi-category__entry-link:hover .wi-category__entry-title {
color: var(--accent);
}
.wi-category__entry-excerpt {
margin: 0;
font-size: var(--text-sm);
color: var(--ink-2);
line-height: var(--leading-normal);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.wi-category__entry-image {
width: 200px;
height: 140px;
object-fit: cover;
border-radius: var(--radius-sm);
align-self: center;
transition: transform var(--duration-fast) var(--ease-out-quart);
}
.wi-category__entry:hover .wi-category__entry-image {
transform: scale(1.02);
}
.wi-category__empty { .wi-category__empty {
color: var(--ink-3); color: var(--ink-3);
font-size: var(--text-md); font-size: var(--text-md);
@@ -127,4 +242,16 @@ import Layout from "../layouts/Layout.astro";
.wi-category__page-info { .wi-category__page-info {
color: var(--ink-3); color: var(--ink-3);
} }
@media (max-width: 768px) {
.wi-category__entry-link {
grid-template-columns: 1fr;
}
.wi-category__entry-image {
width: 100%;
height: 180px;
order: -1;
}
}
</style> </style>
+25 -13
View File
@@ -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}">豆瓣</h1> <h1 class="wi-douban__title" th:text="${title ?: #messages.msg('douban.title')}">Douban</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' : ''"
>想看</a> th:text="#{douban.wish}">Wish</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' : ''"
>看过</a> th:text="#{douban.done}">Done</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' : ''"
>想读</a> th:text="#{douban.wishRead}">Wish</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' : ''"
>在读</a> th:text="#{douban.reading}">Reading</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' : ''"
>读过</a> th:text="#{douban.done}">Done</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' : ''"
>想听</a> th:text="#{douban.wishListen}">Wish</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' : ''"
>在听</a> th:text="#{douban.listening}">Listening</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' : ''"
>听过</a> th:text="#{douban.listened}">Listened</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' : ''"
>想玩</a> th:text="#{douban.wishPlay}">Wish</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' : ''"
>在玩</a> th:text="#{douban.playing}">Playing</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' : ''"
>玩过</a> th:text="#{douban.played}">Played</a>
</th:block> </th:block>
</div> </div>
@@ -120,6 +120,7 @@ import Layout from "../layouts/Layout.astro";
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.spec?.genres, currentGenre)}"
th:attr="data-type=${currentType}"
> >
<a <a
class="wi-douban__card-link" class="wi-douban__card-link"
@@ -161,7 +162,7 @@ import Layout from "../layouts/Layout.astro";
class="wi-douban__my-score" class="wi-douban__my-score"
th:if="${!#strings.isEmpty(item.faves?.score)}" th:if="${!#strings.isEmpty(item.faves?.score)}"
> >
我的:<th:block th:text="${item.faves?.score}"></th:block> <span th:text="#{douban.myRating}">My:</span><th:block th:text="${item.faves?.score}"></th:block>
</span> </span>
<span <span
class="wi-douban__year" class="wi-douban__year"
@@ -299,6 +300,12 @@ import Layout from "../layouts/Layout.astro";
background: var(--bg); background: var(--bg);
} }
.wi-douban__card[data-type="music"] .wi-douban__poster {
width: 80px;
height: 80px;
border-radius: 8px;
}
.wi-douban__poster-img { .wi-douban__poster-img {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -408,5 +415,10 @@ import Layout from "../layouts/Layout.astro";
width: 64px; width: 64px;
height: 90px; height: 90px;
} }
.wi-douban__card[data-type="music"] .wi-douban__poster {
width: 64px;
height: 64px;
}
} }
</style> </style>
-161
View File
@@ -1,161 +0,0 @@
---
import Navbar from "../components/Navbar.astro";
import MobileMenu from "../components/MobileMenu.astro";
import "../styles/main.scss";
---
<!doctype html>
<html lang="zh" th:lang="${theme.config?.basic?.language ?: #locale.language}" th:classappend="${theme.config?.animation?.animation_enabled == false} ? 'wi-no-animations'">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script is:inline th:attr="data-scheme=${theme.config?.style?.color_scheme}">
(function () {
var stored = localStorage.getItem("wi-theme");
var prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
var scheme = null;
if (document.currentScript && document.currentScript.getAttribute("data-scheme")) {
scheme = document.currentScript.getAttribute("data-scheme");
}
var isDark = false;
if (stored === "dark") { isDark = true; }
else if (stored === "light") { isDark = false; }
else if (scheme === "dark") { isDark = true; }
else if (scheme === "light") { isDark = false; }
else if (!stored && prefersDark) { isDark = true; }
if (isDark) {
document.documentElement.classList.add("dark");
document.documentElement.setAttribute("data-color-scheme", "dark");
} else {
document.documentElement.classList.remove("dark");
document.documentElement.setAttribute("data-color-scheme", "light");
}
})();
</script>
<title th:text="|${settings.title} - ${site.title}|"></title>
<link rel="stylesheet" type="text/css" th:href="@{/plugins/footprint/assets/static/css/footprint.css(version=${version})}" />
<link rel="stylesheet" type="text/css" th:href="@{/plugins/footprint/assets/static/font/result.css(version=${version})}" />
<script type="text/javascript" th:src="@{/plugins/footprint/assets/static/js/jquery-3.6.0.min.js}"></script>
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: /*[[${settings.gaoDeKey}]]*/''
}
</script>
<script type="text/javascript" th:src="|https://webapi.amap.com/maps?v=2.0&key=${settings.gaoDeKey}|"></script>
<script type="text/javascript" th:src="@{/plugins/footprint/assets/static/js/footprint.js(version=${version})}"></script>
<script th:inline="javascript">
window.FOOTPRINT_CONFIG = {
amapKey: /*[[${settings.gaoDeKey}]]*/'',
footprints: [],
title: /*[[${settings.title}]]*/ '足迹',
logoName: /*[[${settings.logoName}]]*/ '足迹',
describe: /*[[${settings.describe}]]*/ '描述',
hsla: /*[[${settings.hsla}]]*/ '200, 100%, 50%',
mapStyle: /*[[${settings.mapStyle}]]*/ 'amap://styles/normal'
};
</script>
</head>
<body id="footprint-page">
<Navbar />
<MobileMenu />
<div class="footprint-container">
<div id="footprint-map"></div>
<div class="logo-container">
<h1 class="footprint-logo" th:text="${settings.logoName}">足迹<span class="logo-version">2024</span></h1>
<p class="footprint-slogan" th:text="${settings.describe}">每一处足迹都充满了故事,那是对人生的思考和无限的风光。</p>
</div>
<div class="map-controls">
<div class="likcc-layer-selector">
<button class="likcc-layer-btn active" data-type="normal">
<svg class="likcc-layer-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<path d="M9 9h6v6H9z"></path>
</svg>
<span>标准图</span>
</button>
<button class="likcc-layer-btn" data-type="satellite">
<svg class="likcc-layer-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
</svg>
<span>卫星图</span>
</button>
</div>
<div class="likcc-feature-toggles">
<label class="likcc-toggle-item">
<input type="checkbox" data-type="road">
<div class="likcc-toggle-switch">
<div class="likcc-toggle-slider"></div>
</div>
<div class="likcc-toggle-content">
<svg class="likcc-toggle-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 7v10a4 4 0 004 4h10a4 4 0 004-4V7a4 4 0 00-4-4H7a4 4 0 00-4 4z"></path>
<path d="M9 12h6"></path>
</svg>
<span class="likcc-toggle-label">路网</span>
</div>
</label>
<label class="likcc-toggle-item">
<input type="checkbox" data-type="traffic">
<div class="likcc-toggle-switch">
<div class="likcc-toggle-slider"></div>
</div>
<div class="likcc-toggle-content">
<svg class="likcc-toggle-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"></path>
</svg>
<span class="likcc-toggle-label">路况</span>
</div>
</label>
</div>
<div class="likcc-zoom-controls">
<button class="likcc-zoom-btn" id="zoom-out">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="M21 21l-4.35-4.35"></path>
<line x1="8" y1="11" x2="14" y2="11"></line>
</svg>
</button>
<button class="likcc-zoom-btn" id="zoom-in">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="M21 21l-4.35-4.35"></path>
<line x1="11" y1="8" x2="11" y2="14"></line>
<line x1="8" y1="11" x2="14" y2="11"></line>
</svg>
</button>
</div>
<div class="likcc-scale-display">
<div class="likcc-scale-bar"></div>
<span class="likcc-scale-text" id="scale-text">1000 公里</span>
</div>
</div>
</div>
</body>
</html>
<style>
#footprint-page {
margin: 0;
padding: 0;
}
#footprint-page .footprint-container {
position: relative;
width: 100%;
height: calc(100vh - 72px);
margin-top: 72px;
}
#footprint-page #footprint-map {
width: 100%;
height: 100%;
}
</style>
+9 -7
View File
@@ -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 ?: '朋友圈'} - ${site.title}|"></title> <title th:text="|${theme.config?.friends?.friends_page_title ?: #messages.msg('friends.title')} - ${site.title}|"></title>
</Fragment> </Fragment>
<section <section
@@ -15,9 +15,9 @@ 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 ?: '朋友圈'}" th:text="${theme.config?.friends?.friends_page_title ?: #messages.msg('friends.title')}"
> >
朋友圈 Friends Circle
</h1> </h1>
<span class="wi-page-accent"></span> <span class="wi-page-accent"></span>
</div> </div>
@@ -87,8 +87,9 @@ 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}"
> >
← 上一页 &larr; Newer
</a> </a>
<span <span
class="wi-friends-page__page-info" class="wi-friends-page__page-info"
@@ -98,8 +99,9 @@ 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}"
> >
下一页 → Older &rarr;
</a> </a>
</div> </div>
</div> </div>
@@ -111,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">朋友圈</h1> <h1 class="wi-friends-page__title" th:text="#{friends.title}">Friends Circle</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>朋友圈功能需要安装「朋友圈」插件,<a href="https://www.halo.run/store/apps/app-friends">前往应用市场安装</a></p> <p th:utext="#{common.installPlugin('Friends Circle', 'https://www.halo.run/store/apps/app-friends')}">This feature requires the Friends Circle plugin</p>
</div> </div>
</div> </div>
</section> </section>
+76 -9
View File
@@ -13,6 +13,7 @@ import HeroSection from "../components/HeroSection.astro";
<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"
@@ -20,7 +21,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 ?: '置顶'}">置顶</h2> <h2 class="wi-section__title" th:text="${theme.config?.home?.home_pinned_title ?: #messages.msg('hero.pinned')}">置顶</h2>
</div> </div>
<div class="wi-pinned"> <div class="wi-pinned">
<a <a
@@ -65,7 +66,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 ?: '文章'}">文章</h2> <h2 class="wi-section__title" th:text="${theme.config?.home?.home_posts_title ?: #messages.msg('hero.posts')}">文章</h2>
</div> </div>
<div class="wi-flow" id="wi-post-grid"> <div class="wi-flow" id="wi-post-grid">
<article <article
@@ -128,7 +129,7 @@ import HeroSection from "../components/HeroSection.astro";
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="${post.stats?.visit ?: 0}"></th:block> <th:block th:text="#{common.views}">阅读</th:block>
</span> </span>
</div> </div>
</div> </div>
@@ -152,7 +153,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 ?: '较新'}" th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
>&larr; 较新</a> >&larr; 较新</a>
<span <span
class="wi-pagination__info" class="wi-pagination__info"
@@ -162,18 +163,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 ?: '较旧'}" th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
>较旧 &rarr;</a> >较旧 &rarr;</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 ?: '已加载全部文章'}" th:attr="data-next-url=${posts.nextUrl}, data-all-loaded-text=${theme.config?.home?.home_label_all_loaded ?: #messages.msg('common.allLoaded')}"
> >
<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 ?: '加载中...'}">加载中...</span> <span th:text="${theme.config?.home?.home_label_loading ?: #messages.msg('common.loading')}">加载中...</span>
</div> </div>
</div> </div>
@@ -181,7 +182,7 @@ 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 ?: '已加载全部文章'}">已加载全部文章</span> <span th:text="${theme.config?.home?.home_label_all_loaded ?: #messages.msg('common.allLoaded')}">已加载全部文章</span>
</div> </div>
</div> </div>
</section> </section>
@@ -194,9 +195,10 @@ import HeroSection from "../components/HeroSection.astro";
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") || "已加载全部文章"; var allLoadedText = sentinel.getAttribute("data-all-loaded-text") || (i18nEl && i18nEl.getAttribute("data-i18n-all-loaded")) || "All posts loaded";
var observer = new IntersectionObserver( var observer = new IntersectionObserver(
function (entries) { function (entries) {
@@ -302,6 +304,71 @@ import HeroSection from "../components/HeroSection.astro";
})(); })();
</script> </script>
<script is:inline>
(function () {
var hero = document.getElementById("wi-hero");
var content = document.querySelector(".wi-home-content--with-hero");
if (!hero || !content) return;
var isSnapping = false;
var heroHeight = window.innerHeight;
window.addEventListener("resize", function () {
heroHeight = window.innerHeight;
}, { passive: true });
function snapTo(targetY) {
if (isSnapping) return;
isSnapping = true;
window.scrollTo({ top: targetY, behavior: "smooth" });
var timer = setTimeout(function () {
isSnapping = false;
}, 800);
if ("onscrollend" in window) {
window.addEventListener(
"scrollend",
function () {
clearTimeout(timer);
isSnapping = false;
},
{ once: true }
);
}
}
window.addEventListener("wheel", function (e) {
if (isSnapping) {
e.preventDefault();
return;
}
var y = window.scrollY;
if (y < heroHeight && e.deltaY > 0) {
e.preventDefault();
snapTo(heroHeight);
} else if (y > 0 && y <= heroHeight && e.deltaY < 0) {
e.preventDefault();
snapTo(0);
}
}, { passive: false });
var touchStartY = 0;
window.addEventListener("touchstart", function (e) {
touchStartY = e.touches[0].clientY;
}, { passive: true });
window.addEventListener("touchend", function (e) {
if (isSnapping) return;
var diff = touchStartY - e.changedTouches[0].clientY;
var y = window.scrollY;
if (y < heroHeight && diff > 30) {
snapTo(heroHeight);
} else if (y > 0 && y <= heroHeight && diff < -30) {
snapTo(0);
}
}, { passive: true });
})();
</script>
<style> <style>
.wi-home-content { .wi-home-content {
position: relative; position: relative;
+5 -3
View File
@@ -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?.links?.links_page_title ?: '友情链接'} - ${site.title}|"></title> <title th:text="|${theme.config?.links?.links_page_title ?: #messages.msg('links.title')} - ${site.title}|"></title>
</Fragment> </Fragment>
<section <section
@@ -16,11 +16,11 @@ import Layout from "../layouts/Layout.astro";
<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 ?: '友情链接'}" th:text="${theme.config?.links?.links_page_title ?: #messages.msg('links.title')}"
> >
友情链接 友情链接
</h1> </h1>
<p class="wi-links__subtitle">山水一程,三生有幸</p> <p class="wi-links__subtitle" th:text="#{links.subtitle}">Mountains and rivers, a journey of a thousand miles</p>
<span class="wi-page-accent"></span> <span class="wi-page-accent"></span>
</div> </div>
@@ -154,6 +154,7 @@ import Layout from "../layouts/Layout.astro";
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
overflow: hidden; overflow: hidden;
display: flex;
} }
.wi-links__card-glow { .wi-links__card-glow {
@@ -184,6 +185,7 @@ import Layout from "../layouts/Layout.astro";
-webkit-backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
border: 1px solid var(--glass-border); border: 1px solid var(--glass-border);
border-radius: 16px; border-radius: 16px;
flex: 1;
transition: transition:
transform var(--duration-normal) var(--ease-out-expo), transform var(--duration-normal) var(--ease-out-expo),
box-shadow var(--duration-normal) var(--ease-out-expo), box-shadow var(--duration-normal) var(--ease-out-expo),
+13 -11
View File
@@ -5,13 +5,13 @@ import LightGallery from "../components/LightGallery.astro";
<Layout> <Layout>
<Fragment slot="head"> <Fragment slot="head">
<title th:text="|${theme.config?.moments?.moments_page_title ?: '瞬间'} - ${site.title}|"></title> <title th:text="|${theme.config?.moments?.moments_page_title ?: #messages.msg('moments.title')} - ${site.title}|"></title>
</Fragment> </Fragment>
<section class="wi-moments-page"> <section class="wi-moments-page" th:attr="data-i18n-all-moments-loaded=#{common.allMomentsLoaded}">
<div class="wi-container"> <div class="wi-container">
<div class="wi-moments-page__header"> <div class="wi-moments-page__header">
<h1 class="wi-moments-page__title" th:text="${theme.config?.moments?.moments_page_title ?: '瞬间'}">瞬间</h1> <h1 class="wi-moments-page__title" th:text="${theme.config?.moments?.moments_page_title ?: #messages.msg('moments.title')}">瞬间</h1>
<span class="wi-page-accent"></span> <span class="wi-page-accent"></span>
</div> </div>
@@ -77,7 +77,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>还没有瞬间,快去记录当下的心情吧 🌙</p> <p th:text="#{moments.noMoments}">No moments yet</p>
</div> </div>
</th:block> </th:block>
</div> </div>
@@ -133,7 +133,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>还没有瞬间,快去记录当下的心情吧 🌙</p> <p th:text="#{moments.noMoments}">No moments yet</p>
</div> </div>
</th:block> </th:block>
</div> </div>
@@ -189,7 +189,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>还没有瞬间,快去记录当下的心情吧 🌙</p> <p th:text="#{moments.noMoments}">No moments yet</p>
</div> </div>
</th:block> </th:block>
</div> </div>
@@ -204,13 +204,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}"
>← 较新</a> th:text="#{common.newer}">Newer</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}"
>较旧 →</a> th:text="#{common.older}">Older</a>
</nav> </nav>
<div <div
@@ -220,7 +220,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>瞬间插件加载异常,请检查插件状态</p> <p th:text="#{common.pluginError}">Plugin error</p>
</div> </div>
</th:block> </th:block>
</div> </div>
@@ -228,7 +228,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>瞬间功能需要安装「瞬间」插件,<a href="https://www.halo.run/store/apps/app-hqbe">前往应用市场安装</a></p> <p th:utext="#{common.installPlugin('Moments', 'https://www.halo.run/store/apps/app-hqbe')}">This feature requires the Moments plugin</p>
</div> </div>
</th:block> </th:block>
</div> </div>
@@ -414,7 +414,9 @@ 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";
endMsg.innerHTML = "<p>已加载全部瞬间</p>"; var momentsSection = document.querySelector(".wi-moments-page");
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);
} }
+3 -3
View File
@@ -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 ?: '留言板'} - ${site.title}|" th:text="|${theme.config?.messageboard?.messageboard_title ?: #messages.msg('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 ?: '留言板'}" th:text="${theme.config?.messageboard?.messageboard_title ?: #messages.msg('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}" th:text="${theme.config?.messageboard?.messageboard_subtitle ?: #messages.msg('messageboard.subtitle')}"
></p> ></p>
</header> </header>
+10 -14
View File
@@ -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 ?: '图库')} - ${site.title}|"></title> <title th:text="|${title ?: (theme.config?.photos?.photos_page_title ?: #messages.msg('photos.title'))} - ${site.title}|"></title>
</Fragment> </Fragment>
<section class="wi-photos-page"> <section class="wi-photos-page">
@@ -13,9 +13,9 @@ 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 ?: '图库'}" th:text="${theme.config?.photos?.photos_page_title ?: #messages.msg('photos.title')}"
> >
图库 Gallery
</h1> </h1>
<span class="wi-page-accent"></span> <span class="wi-page-accent"></span>
</div> </div>
@@ -30,8 +30,9 @@ 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}"
> >
全部 All
</a> </a>
<a <a
th:each="group : ${groups}" th:each="group : ${groups}"
@@ -48,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">全部</h2> <h2 class="wi-photos-page__section-title" th:text="#{common.all}">All</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}"
@@ -240,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>该分组暂无图片</p> <p th:text="#{common.noPhotosInGroup}">No photos in this group.</p>
</div> </div>
</th:block> </th:block>
</th:block> </th:block>
@@ -248,25 +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>暂无图片</p> <p th:text="#{common.noPhotos}">No photos yet.</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>图库插件加载异常,请检查插件状态</p> <p th:text="#{common.pluginError}">Plugin error</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> <p th:utext="#{common.installPlugin('Gallery', 'https://www.halo.run/store/apps/app-PgHgw')}">This feature requires the Gallery plugin</p>
图库功能需要安装「图库」插件,<a
href="https://www.halo.run/store/apps/app-PgHgw"
>前往应用市场安装</a
>
</p>
</div> </div>
</th:block> </th:block>
</div> </div>
+313 -16
View File
@@ -3,14 +3,42 @@ import Layout from "../layouts/Layout.astro";
import LightGallery from "../components/LightGallery.astro"; import LightGallery from "../components/LightGallery.astro";
--- ---
<Layout> <Layout wide={true}>
<Fragment slot="head"> <Fragment slot="head">
<title th:text="|${post.spec.title} - ${site.title}|"></title> <title th:text="|${post.spec.title} - ${site.title}|"></title>
<meta th:if="${post.spec.cover}" property="og:image" th:content="${post.spec.cover}" />
<meta property="og:title" th:content="${post.spec.title}" />
<meta property="og:type" content="article" />
<meta property="og:url" th:content="${post.status.permalink}" />
<meta th:if="${post.status.excerpt}" property="og:description" th:content="${post.status.excerpt}" />
<meta property="og:site_name" th:content="${site.title}" />
<meta name="twitter:card" content="summary_large_image" th:if="${post.spec.cover}" />
<meta name="twitter:card" content="summary" th:unless="${post.spec.cover}" />
<meta name="twitter:title" th:content="${post.spec.title}" />
<meta th:if="${post.spec.cover}" name="twitter:image" th:content="${post.spec.cover}" />
<meta th:if="${post.status.excerpt}" name="twitter:description" th:content="${post.status.excerpt}" />
<meta property="article:published_time" th:content="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}" />
<th:block th:if="${post.categories != null and !post.categories.isEmpty()}">
<meta property="article:section" th:content="${post.categories[0].spec.displayName}" />
</th:block>
<th:block th:if="${post.tags != null and !post.tags.isEmpty()}">
<meta th:each="tag : ${post.tags}" property="article:tag" th:content="${tag.spec.displayName}" />
</th:block>
<script type="application/ld+json" th:utext="${'{
&quot;@context&quot;: &quot;https://schema.org&quot;,
&quot;@type&quot;: &quot;Article&quot;,
&quot;headline&quot;: &quot;' + post.spec.title + '&quot;,
&quot;datePublished&quot;: &quot;' + #dates.format(post.spec.publishTime, &quot;yyyy-MM-dd&quot;) + '&quot;,
&quot;author&quot;: {
&quot;@type&quot;: &quot;Person&quot;,
&quot;name&quot;: &quot;' + (post.contributors != null and !post.contributors.isEmpty() ? post.contributors[0].displayName : site.title) + '&quot;
}' + (!#strings.isEmpty(post.spec.cover) ? ', &quot;image&quot;: &quot;' + post.spec.cover + '&quot;' : '') + (!#strings.isEmpty(post.status.excerpt) ? ', &quot;description&quot;: &quot;' + #strings.replace(post.status.excerpt, '&quot;', '\\&quot;') + '&quot;' : '') + '
}'}"></script>
</Fragment> </Fragment>
<div class="wi-reading-progress" id="wi-reading-progress"></div> <div class="wi-reading-progress" id="wi-reading-progress"></div>
<article class="wi-post"> <article class="wi-post" th:attr="data-i18n-words=#{common.words},data-i18n-read-time=#{common.readTime}">
<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)}"
@@ -82,18 +110,18 @@ 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"
>-- </span> >-- words</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="${post.stats?.visit ?: 0}"></th:block> <th:block th:text="#{common.views}"></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}"
class="wi-post__stat-item" class="wi-post__stat-item"
id="wi-read-time" id="wi-read-time"
>-- 分钟</span> >-- min read</span>
</span> </span>
</th:block> </th:block>
</div> </div>
@@ -109,10 +137,10 @@ import LightGallery from "../components/LightGallery.astro";
th:if="${theme.config?.article?.article_show_toc ?: true}" th:if="${theme.config?.article?.article_show_toc ?: true}"
class="wi-toc" class="wi-toc"
id="wi-toc" id="wi-toc"
th:attr="data-position=${theme.config?.article?.article_toc_position ?: 'left'}" 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">目录</span> <span class="wi-toc__title" th:text="#{article.toc}">目录</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>
@@ -128,6 +156,27 @@ import LightGallery from "../components/LightGallery.astro";
</button> </button>
</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}">
<span class="wi-post__share-label" th:text="${theme.config?.article?.article_share_title ?: #messages.msg('share.title')}">分享</span>
<div class="wi-post__share-buttons">
<a class="wi-post__share-btn wi-post__share-btn--weibo" th:href="'https://service.weibo.com/share/share.php?url=' + ${shareUrl} + '&title=' + ${shareTitle}" target="_blank" rel="noopener noreferrer" title="Weibo">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M10.098 20.323c-3.977.391-7.414-1.406-7.672-4.02-.259-2.609 2.759-5.047 6.74-5.441 3.979-.394 7.413 1.404 7.671 4.018.259 2.6-2.759 5.049-6.739 5.443zM9.05 17.219c-.384.616-1.208.884-1.829.602-.612-.279-.793-.991-.406-1.593.379-.595 1.176-.861 1.793-.583.631.283.822.997.442 1.574zm1.27-1.627c-.141.237-.449.353-.689.253-.236-.09-.307-.361-.164-.584.14-.227.44-.34.682-.246.24.085.318.352.171.577zm.176-2.719c-1.893-.494-4.033.45-4.857 2.118-.836 1.704-.026 3.591 1.886 4.21 1.983.642 4.318-.341 5.132-2.145.8-1.752-.145-3.681-2.161-4.183zM17.616 3.68c-.998-.27-1.652-.068-1.877.136-.225.207-.094.544.28.726.377.186.856.072 1.122-.258.268-.328.391-.545.475-.604zm2.471 1.947c-1.278-1.399-3.398-1.878-5.217-1.176-1.818.704-2.478 2.523-1.527 4.099.956 1.582 3.168 2.202 5.011 1.395 1.84-.804 2.977-2.899 1.733-4.318zM20.4 4.44c1.462 1.599.936 4.076-.878 5.478-1.816 1.402-4.556 1.402-6.128-.006-1.573-1.404-1.3-3.834.554-5.314 1.854-1.478 4.99-1.759 6.452-.158z"/></svg>
</a>
<a class="wi-post__share-btn wi-post__share-btn--twitter" th:href="'https://twitter.com/intent/tweet?url=' + ${shareUrl} + '&text=' + ${shareTitle}" target="_blank" rel="noopener noreferrer" title="X">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
</a>
<a class="wi-post__share-btn wi-post__share-btn--facebook" th:href="'https://www.facebook.com/sharer/sharer.php?u=' + ${shareUrl}" target="_blank" rel="noopener noreferrer" title="Facebook">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
</a>
<a class="wi-post__share-btn wi-post__share-btn--telegram" th:href="'https://t.me/share/url?url=' + ${shareUrl} + '&text=' + ${shareTitle}" target="_blank" rel="noopener noreferrer" title="Telegram">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.479.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/></svg>
</a>
<button class="wi-post__share-btn wi-post__share-btn--copy" type="button" th:attr="data-url=${shareUrl}" title="Copy Link">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
</button>
</div>
</div>
<th:block th:if="${theme.config?.article?.article_show_nav == true}"> <th:block th:if="${theme.config?.article?.article_show_nav == true}">
<nav class="wi-post__nav" aria-label="Post navigation"> <nav class="wi-post__nav" aria-label="Post navigation">
<th:block <th:block
@@ -138,7 +187,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">上一篇</span> <span class="wi-post__nav-label" th:text="#{article.previousPost}">上一篇</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}"
@@ -153,7 +202,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">下一篇</span> <span class="wi-post__nav-label" th:text="#{article.nextPost}">下一篇</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}"
@@ -167,6 +216,23 @@ import LightGallery from "../components/LightGallery.astro";
</nav> </nav>
</th:block> </th:block>
<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})}">
<h2 class="wi-post__related-title" th:text="${theme.config?.article?.article_related_title ?: #messages.msg('article.relatedPosts')}">相关推荐</h2>
<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">
<div class="wi-post__related-cover" th:if="${!#strings.isEmpty(rp.spec.cover)}">
<img th:src="${rp.spec.cover}" th:alt="${rp.spec.title}" loading="lazy" decoding="async" />
</div>
<div class="wi-post__related-body">
<h3 class="wi-post__related-name" th:text="${rp.spec.title}"></h3>
<time class="wi-post__related-date" th:text="${#dates.format(rp.spec.publishTime, 'yyyy-MM-dd')}"></time>
</div>
</a>
</div>
</section>
</th:block>
<div class="wi-post__comment" th:attr="data-comment-style=${theme.config?.comment?.comment_style ?: 'warm'},data-show-avatar=${theme.config?.comment?.comment_show_avatar != false}"> <div class="wi-post__comment" th:attr="data-comment-style=${theme.config?.comment?.comment_style ?: 'warm'},data-show-avatar=${theme.config?.comment?.comment_show_avatar != false}">
<halo:comment <halo:comment
th:if="${haloCommentEnabled}" th:if="${haloCommentEnabled}"
@@ -182,7 +248,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"
aria-label="打开目录" th:aria-label="#{common.openToc}"
> >
<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>
@@ -195,8 +261,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">目录</span> <span class="wi-toc-drawer__title" th:text="#{article.toc}">目录</span>
<button class="wi-toc-drawer__close" type="button" aria-label="关闭目录"> <button class="wi-toc-drawer__close" type="button" th:aria-label="#{common.closeToc}">
<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>
@@ -263,6 +329,9 @@ import LightGallery from "../components/LightGallery.astro";
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 = articleEl ? articleEl.getAttribute("data-i18n-words") : "words";
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();
@@ -270,10 +339,10 @@ import LightGallery from "../components/LightGallery.astro";
var readMinutes = Math.max(1, Math.ceil(charCount / 500)); var readMinutes = Math.max(1, Math.ceil(charCount / 500));
if (showWordCount) { if (showWordCount) {
showWordCount.textContent = charCount + " "; showWordCount.textContent = charCount + " " + wordsLabel;
} }
if (showReadTime) { if (showReadTime) {
showReadTime.textContent = readMinutes + " 分钟"; showReadTime.textContent = readMinutes + " " + readTimeLabel;
} }
} }
@@ -353,7 +422,13 @@ import LightGallery from "../components/LightGallery.astro";
entries.forEach(function (entry) { entries.forEach(function (entry) {
if (entry.isIntersecting) { if (entry.isIntersecting) {
tocLinks.forEach(function (l) { l.classList.remove("wi-toc__link--active"); }); tocLinks.forEach(function (l) { l.classList.remove("wi-toc__link--active"); });
tocGroups.forEach(function (g) { g.classList.remove("wi-toc__group--active"); g.classList.remove("wi-toc__group--expanded"); }); var tocExpandMode = tocAside ? (tocAside.getAttribute("data-expand") || "active") : "active";
tocGroups.forEach(function (g) {
g.classList.remove("wi-toc__group--active");
if (tocExpandMode !== "all") {
g.classList.remove("wi-toc__group--expanded");
}
});
var activeLink = tocNav.querySelector('a[href="#' + entry.target.id + '"]'); var activeLink = tocNav.querySelector('a[href="#' + entry.target.id + '"]');
if (activeLink) { if (activeLink) {
@@ -456,6 +531,28 @@ import LightGallery from "../components/LightGallery.astro";
}); });
} }
var copyBtn = document.querySelector(".wi-post__share-btn--copy");
if (copyBtn) {
copyBtn.addEventListener("click", function () {
var url = copyBtn.getAttribute("data-url");
if (navigator.clipboard) {
navigator.clipboard.writeText(url).then(function () {
copyBtn.classList.add("wi-post__share-btn--copied");
setTimeout(function () { copyBtn.classList.remove("wi-post__share-btn--copied"); }, 2000);
});
} else {
var ta = document.createElement("textarea");
ta.value = url;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
copyBtn.classList.add("wi-post__share-btn--copied");
setTimeout(function () { copyBtn.classList.remove("wi-post__share-btn--copied"); }, 2000);
}
});
}
var btn = document.querySelector(".wi-like-btn"); var btn = document.querySelector(".wi-like-btn");
if (!btn) return; if (!btn) return;
var postName = btn.getAttribute("data-post-name"); var postName = btn.getAttribute("data-post-name");
@@ -493,6 +590,21 @@ import LightGallery from "../components/LightGallery.astro";
console.error("Like error:", err); console.error("Like error:", err);
}); });
}); });
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>
@@ -680,7 +792,8 @@ import LightGallery from "../components/LightGallery.astro";
.wi-post__body .ai-summary, .wi-post__body .ai-summary,
.wi-post__body [class*="ai-summary"], .wi-post__body [class*="ai-summary"],
.wi-post__body [class*="ai-abstract"], .wi-post__body [class*="ai-abstract"],
.wi-post__body .abstract { .wi-post__body .abstract,
.wi-post__body [class*="likcc-article-summary"] {
margin-bottom: 15px; margin-bottom: 15px;
} }
@@ -693,6 +806,22 @@ import LightGallery from "../components/LightGallery.astro";
margin-block: 1.5em; 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 { .wi-post__body a {
text-decoration: underline; text-decoration: underline;
text-decoration-thickness: 1px; text-decoration-thickness: 1px;
@@ -928,6 +1057,85 @@ import LightGallery from "../components/LightGallery.astro";
100% { transform: scale(1); } 100% { transform: scale(1); }
} }
.wi-post__share {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-md);
padding-top: var(--space-md);
}
.wi-post__share-label {
font-size: var(--text-sm);
color: var(--ink-3);
font-weight: 500;
}
.wi-post__share-buttons {
display: flex;
align-items: center;
gap: 0.5rem;
}
.wi-post__share-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
border: 1px solid var(--rule);
background: var(--bg-raised);
color: var(--ink-3);
cursor: pointer;
text-decoration: none;
transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}
.wi-post__share-btn:hover {
color: var(--accent);
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 6%, var(--bg));
transform: translateY(-1px);
}
.wi-post__share-btn--weibo:hover {
color: #e6162d;
border-color: #e6162d;
background: color-mix(in srgb, #e6162d 6%, var(--bg));
}
.wi-post__share-btn--facebook:hover {
color: #1877f2;
border-color: #1877f2;
background: color-mix(in srgb, #1877f2 6%, var(--bg));
}
.wi-post__share-btn--telegram:hover {
color: #0088cc;
border-color: #0088cc;
background: color-mix(in srgb, #0088cc 6%, var(--bg));
}
.wi-post__share-btn--copied {
color: #22c55e;
border-color: #22c55e;
}
.wi-post__share-btn--copied::after {
content: '✓';
font-size: 14px;
position: absolute;
}
.wi-post__share-btn--copied svg {
display: none;
}
.wi-post__share-btn--copy {
position: relative;
}
.wi-post__comment { .wi-post__comment {
width: 100%; width: 100%;
max-width: 600px; max-width: 600px;
@@ -985,6 +1193,90 @@ import LightGallery from "../components/LightGallery.astro";
color: var(--accent); color: var(--accent);
} }
.wi-post__related {
padding-top: var(--space-xl);
border-top: 1px solid var(--rule);
}
.wi-post__related-title {
font-family: var(--font-sans);
font-size: var(--text-lg);
font-weight: 700;
color: var(--ink);
margin-bottom: var(--space-lg);
}
.wi-post__related-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-md);
}
.wi-post__related-card {
display: flex;
flex-direction: column;
border-radius: 12px;
overflow: hidden;
background: var(--bg-raised);
border: 1px solid var(--rule);
text-decoration: none;
color: inherit;
transition: transform var(--duration-fast) var(--ease-out-quart), box-shadow var(--duration-fast) var(--ease-out-quart);
}
.wi-post__related-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.wi-post__related-cover {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.wi-post__related-cover img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform var(--duration-fast) var(--ease-out-quart);
}
.wi-post__related-card:hover .wi-post__related-cover img {
transform: scale(1.03);
}
.wi-post__related-body {
padding: var(--space-md);
display: flex;
flex-direction: column;
gap: 0.25rem;
flex: 1;
}
.wi-post__related-name {
font-family: var(--font-sans);
font-size: var(--text-sm);
font-weight: 600;
color: var(--ink);
line-height: var(--leading-snug);
transition: color var(--duration-fast) var(--ease-out-quart);
}
.wi-post__related-card:hover .wi-post__related-name {
color: var(--accent);
}
.wi-post__related-date {
font-size: var(--text-xs);
color: var(--ink-3);
}
@media (max-width: 768px) {
.wi-post__related-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) { @media (max-width: 768px) {
.wi-post__cover-wrap { .wi-post__cover-wrap {
margin-inline: calc(-1 * var(--space-md)); margin-inline: calc(-1 * var(--space-md));
@@ -1049,6 +1341,11 @@ import LightGallery from "../components/LightGallery.astro";
opacity: 1; opacity: 1;
} }
.wi-toc[data-expand="all"] .wi-toc__sub {
max-height: 600px;
opacity: 1;
}
.wi-toc__link { .wi-toc__link {
display: block; display: block;
padding: 4px 0; padding: 4px 0;
+15 -15
View File
@@ -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"
>Steam 主页 →</a> th:text="#{steam.homepage}">Steam Homepage &rarr;</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">等级</span> <span class="wi-steam__level-label" th:text="#{steam.level}">Level</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">拥有游戏</span> <span class="wi-steam__stat-label" th:text="#{steam.ownedGames}">Owned Games</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 小时'">0 小时</span> <span class="wi-steam__stat-value" th:text="${stats.totalPlaytimeFormatted} ?: '0 hours'">0 hours</span>
<span class="wi-steam__stat-label">总游玩时长</span> <span class="wi-steam__stat-label" th:text="#{steam.totalPlaytime}">Total Playtime</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 小时'">0 小时</span> <span class="wi-steam__stat-value" th:text="${stats.recentPlaytimeFormatted} ?: '0 hours'">0 hours</span>
<span class="wi-steam__stat-label">近两周</span> <span class="wi-steam__stat-label" th:text="#{steam.recentPlaytime}">Recent 2 Weeks</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">徽章</span> <span class="wi-steam__stat-label" th:text="#{steam.badges}">Badges</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">最近游玩</h3> <h3 class="wi-steam__section-title" th:text="#{steam.recentlyPlayed}">Recently Played</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} ?: '0 小时'"></span> <span class="wi-steam__recent-time" th:text="${game.playtime2WeeksFormatted} ?: #{steam.hours}"></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">游戏库</h3> <h3 class="wi-steam__section-title" th:text="#{steam.gameLibrary}">Game Library</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} ?: '0 小时'"></span> <span class="wi-steam__game-time" th:text="${game.playtimeFormatted} ?: #{steam.hours}"></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="|最后游玩 ${game.lastPlayedFormatted}|" th:text="#{steam.lastPlayed(${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}"
>上一页</a> th:text="#{common.newer}">Previous</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}"
>下一页</a> th:text="#{common.older}">Next</a>
</nav> </nav>
</div> </div>
</section> </section>
+7 -7
View File
@@ -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="|共 ${posts.total} 篇文章|"></p> <p class="wi-tag__subtitle" th:text="#{common.postCount(${posts.total})}"></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,8 +55,8 @@ 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 ?: '暂无文章。'}" th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
>暂无文章。</div> >No posts yet.</div>
<nav <nav
th:if="${posts.totalPages gt 1}" th:if="${posts.totalPages gt 1}"
@@ -67,8 +67,8 @@ 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 ?: '较新'}" th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
>&larr; 较新</a> >&larr; Newer</a>
<span <span
class="wi-tag__page-info" class="wi-tag__page-info"
th:text="|${posts.page} / ${posts.totalPages}|" th:text="|${posts.page} / ${posts.totalPages}|"
@@ -77,8 +77,8 @@ 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 ?: '较旧'}" th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
>较旧 &rarr;</a> >Older &rarr;</a>
</nav> </nav>
</div> </div>
</Layout> </Layout>
+3 -3
View File
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
<Layout> <Layout>
<Fragment slot="head"> <Fragment slot="head">
<title th:text="|标签 - ${site.title}|"></title> <title th:text="|#{tags.title} - ${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">标签</h1> <h1 class="wi-tags__title" th:text="#{tags.title}">标签</h1>
<span class="wi-page-accent"></span> <span class="wi-page-accent"></span>
<p class="wi-tags__subtitle" th:text="|共 ${tags.size} 个标签|"></p> <p class="wi-tags__subtitle" th:text="${#messages.msg('common.tagCount', ${tags.size})}"></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">
+1 -1
View File
@@ -7,7 +7,7 @@ spec:
author: author:
name: 暖心向阳335 name: 暖心向阳335
website: https://nxxy335.top/ website: https://nxxy335.top/
description: 具有独特气质的生活博客主题 description: 暖如灯火,静如留白。暖屿以柔和的色调和舒缓的节奏回归内容本身,为记录与阅读留出一方安静从容的角落。
logo: /themes/warm-island/assets/logo.png logo: /themes/warm-island/assets/logo.png
homepage: https://nxxy335.top/WarmIsland/ homepage: https://nxxy335.top/WarmIsland/
repo: https://github.com/sunny-335/halo-theme-WarmIsland repo: https://github.com/sunny-335/halo-theme-WarmIsland