增加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
+9 -9
View File
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
<Layout>
<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>
<div class="wi-archives">
<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>
<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>
<section class="wi-archives__timeline" aria-label="Archive timeline">
@@ -57,8 +57,8 @@ import Layout from "../layouts/Layout.astro";
<div
th:if="${archives.total == 0}"
class="wi-archives__empty"
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章。'}"
>暂无文章。</div>
th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
>No posts yet.</div>
</section>
<nav
@@ -70,8 +70,8 @@ import Layout from "../layouts/Layout.astro";
th:if="${archives.hasPrevious()}"
th:href="@{${archives.prevUrl}}"
class="wi-archives__page-link wi-archives__page-link--prev"
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
>&larr; 较新</a>
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
>&larr; Newer</a>
<span
class="wi-archives__page-info"
th:text="|${archives.page} / ${archives.totalPages}|"
@@ -80,8 +80,8 @@ import Layout from "../layouts/Layout.astro";
th:if="${archives.hasNext()}"
th:href="@{${archives.nextUrl}}"
class="wi-archives__page-link wi-archives__page-link--next"
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
>较旧 &rarr;</a>
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
>Older &rarr;</a>
</nav>
</div>
</Layout>
+3 -3
View File
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
<Layout>
<Fragment slot="head">
<title th:text="|分类 - ${site.title}|"></title>
<title th:text="|#{categories.title} - ${site.title}|"></title>
</Fragment>
<div class="wi-categories">
<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>
<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>
<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}"
></h1>
<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
th:if="${not #strings.isEmpty(category.spec.description)}"
class="wi-category__description"
@@ -22,13 +22,44 @@ import Layout from "../layouts/Layout.astro";
></p>
</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
th:if="${posts.total == 0}"
class="wi-category__empty"
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章。'}"
>暂无文章。</div>
th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
>No posts yet.</div>
<nav
th:if="${posts.totalPages gt 1}"
@@ -39,8 +70,8 @@ import Layout from "../layouts/Layout.astro";
th:if="${posts.hasPrevious()}"
th:href="@{${posts.prevUrl}}"
class="wi-category__page-link wi-category__page-link--prev"
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
>&larr; 较新</a>
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
>&larr; Newer</a>
<span
class="wi-category__page-info"
th:text="|${posts.page} / ${posts.totalPages}|"
@@ -49,8 +80,8 @@ import Layout from "../layouts/Layout.astro";
th:if="${posts.hasNext()}"
th:href="@{${posts.nextUrl}}"
class="wi-category__page-link wi-category__page-link--next"
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
>较旧 &rarr;</a>
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
>Older &rarr;</a>
</nav>
</div>
</Layout>
@@ -97,6 +128,90 @@ import Layout from "../layouts/Layout.astro";
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 {
color: var(--ink-3);
font-size: var(--text-md);
@@ -127,4 +242,16 @@ import Layout from "../layouts/Layout.astro";
.wi-category__page-info {
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>
+25 -13
View File
@@ -10,7 +10,7 @@ import Layout from "../layouts/Layout.astro";
<section class="wi-douban">
<div class="wi-container">
<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>
</div>
@@ -32,63 +32,63 @@ import Layout from "../layouts/Layout.astro";
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type=${currentType},status='wish')}"
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
>想看</a>
th:text="#{douban.wish}">Wish</a>
<a
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type=${currentType},status='done')}"
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:if="${currentType == 'book'}">
<a
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type='book',status='wish')}"
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
>想读</a>
th:text="#{douban.wishRead}">Wish</a>
<a
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type='book',status='doing')}"
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
>在读</a>
th:text="#{douban.reading}">Reading</a>
<a
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type='book',status='done')}"
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:if="${currentType == 'music'}">
<a
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type='music',status='wish')}"
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
>想听</a>
th:text="#{douban.wishListen}">Wish</a>
<a
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type='music',status='doing')}"
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
>在听</a>
th:text="#{douban.listening}">Listening</a>
<a
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type='music',status='done')}"
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:if="${currentType == 'game'}">
<a
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type='game',status='wish')}"
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
>想玩</a>
th:text="#{douban.wishPlay}">Wish</a>
<a
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type='game',status='doing')}"
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
>在玩</a>
th:text="#{douban.playing}">Playing</a>
<a
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
th:href="@{/douban(type='game',status='done')}"
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
>玩过</a>
th:text="#{douban.played}">Played</a>
</th:block>
</div>
@@ -120,6 +120,7 @@ import Layout from "../layouts/Layout.astro";
class="wi-douban__card"
th:each="item : ${doubans.items}"
th:if="${!hasGenre or #sets.contains(item.spec?.genres, currentGenre)}"
th:attr="data-type=${currentType}"
>
<a
class="wi-douban__card-link"
@@ -161,7 +162,7 @@ import Layout from "../layouts/Layout.astro";
class="wi-douban__my-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
class="wi-douban__year"
@@ -299,6 +300,12 @@ import Layout from "../layouts/Layout.astro";
background: var(--bg);
}
.wi-douban__card[data-type="music"] .wi-douban__poster {
width: 80px;
height: 80px;
border-radius: 8px;
}
.wi-douban__poster-img {
width: 100%;
height: 100%;
@@ -408,5 +415,10 @@ import Layout from "../layouts/Layout.astro";
width: 64px;
height: 90px;
}
.wi-douban__card[data-type="music"] .wi-douban__poster {
width: 64px;
height: 64px;
}
}
</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>
<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>
<section
@@ -15,9 +15,9 @@ import Layout from "../layouts/Layout.astro";
<div class="wi-friends-page__header">
<h1
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>
<span class="wi-page-accent"></span>
</div>
@@ -87,8 +87,9 @@ import Layout from "../layouts/Layout.astro";
th:if="${friends.hasPrevious()}"
th:href="@{${friends.prevUrl}}"
class="wi-friends-page__page-link"
th:text="#{common.newer}"
>
← 上一页
&larr; Newer
</a>
<span
class="wi-friends-page__page-info"
@@ -98,8 +99,9 @@ import Layout from "../layouts/Layout.astro";
th:if="${friends.hasNext()}"
th:href="@{${friends.nextUrl}}"
class="wi-friends-page__page-link"
th:text="#{common.older}"
>
下一页 →
Older &rarr;
</a>
</div>
</div>
@@ -111,11 +113,11 @@ import Layout from "../layouts/Layout.astro";
>
<div class="wi-container">
<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>
</div>
<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>
</section>
+76 -9
View File
@@ -13,6 +13,7 @@ import HeroSection from "../components/HeroSection.astro";
<div
class="wi-home-content"
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
class="wi-home-pinned"
@@ -20,7 +21,7 @@ import HeroSection from "../components/HeroSection.astro";
>
<div class="wi-container">
<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 class="wi-pinned">
<a
@@ -65,7 +66,7 @@ import HeroSection from "../components/HeroSection.astro";
<section class="wi-home-posts" id="wi-home-posts">
<div class="wi-container">
<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 class="wi-flow" id="wi-post-grid">
<article
@@ -128,7 +129,7 @@ import HeroSection from "../components/HeroSection.astro";
class="wi-flow__stat"
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>
</div>
</div>
@@ -152,7 +153,7 @@ import HeroSection from "../components/HeroSection.astro";
th:if="${posts.hasPrevious()}"
th:href="@{${posts.prevUrl}}"
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>
<span
class="wi-pagination__info"
@@ -162,18 +163,18 @@ import HeroSection from "../components/HeroSection.astro";
th:if="${posts.hasNext()}"
th:href="@{${posts.nextUrl}}"
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>
</nav>
<div
th:if="${posts.hasNext() and theme.config?.home?.home_post_loading == 'infinite_scroll'}"
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__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>
@@ -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'}"
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>
</section>
@@ -194,9 +195,10 @@ import HeroSection from "../components/HeroSection.astro";
if (!sentinel) return;
var grid = document.getElementById("wi-post-grid");
var i18nEl = document.querySelector(".wi-home-content");
var loading = false;
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(
function (entries) {
@@ -302,6 +304,71 @@ import HeroSection from "../components/HeroSection.astro";
})();
</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>
.wi-home-content {
position: relative;
+5 -3
View File
@@ -4,7 +4,7 @@ import Layout from "../layouts/Layout.astro";
<Layout wide>
<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>
<section
@@ -16,11 +16,11 @@ import Layout from "../layouts/Layout.astro";
<div class="wi-links__header">
<h1
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>
<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>
</div>
@@ -154,6 +154,7 @@ import Layout from "../layouts/Layout.astro";
text-decoration: none;
color: inherit;
overflow: hidden;
display: flex;
}
.wi-links__card-glow {
@@ -184,6 +185,7 @@ import Layout from "../layouts/Layout.astro";
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--glass-border);
border-radius: 16px;
flex: 1;
transition:
transform 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>
<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>
<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-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>
</div>
@@ -77,7 +77,7 @@ import LightGallery from "../components/LightGallery.astro";
</th:block>
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
<div class="wi-moments-page__empty">
<p>还没有瞬间,快去记录当下的心情吧 🌙</p>
<p th:text="#{moments.noMoments}">No moments yet</p>
</div>
</th:block>
</div>
@@ -133,7 +133,7 @@ import LightGallery from "../components/LightGallery.astro";
</th:block>
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
<div class="wi-moments-page__empty">
<p>还没有瞬间,快去记录当下的心情吧 🌙</p>
<p th:text="#{moments.noMoments}">No moments yet</p>
</div>
</th:block>
</div>
@@ -189,7 +189,7 @@ import LightGallery from "../components/LightGallery.astro";
</th:block>
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
<div class="wi-moments-page__empty">
<p>还没有瞬间,快去记录当下的心情吧 🌙</p>
<p th:text="#{moments.noMoments}">No moments yet</p>
</div>
</th:block>
</div>
@@ -204,13 +204,13 @@ import LightGallery from "../components/LightGallery.astro";
class="wi-moments-page__page-btn"
th:if="${momentsResult.hasPrevious()}"
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>
<a
class="wi-moments-page__page-btn"
th:if="${momentsResult.hasNext()}"
th:href="${momentsResult.nextUrl}"
>较旧 →</a>
th:text="#{common.older}">Older</a>
</nav>
<div
@@ -220,7 +220,7 @@ import LightGallery from "../components/LightGallery.astro";
></div>
<th:block th:if="${momentFinder == null}">
<div class="wi-moments-page__empty">
<p>瞬间插件加载异常,请检查插件状态</p>
<p th:text="#{common.pluginError}">Plugin error</p>
</div>
</th:block>
</div>
@@ -228,7 +228,7 @@ import LightGallery from "../components/LightGallery.astro";
<th:block th:unless="${pluginFinder.available('PluginMoments')}">
<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>
</th:block>
</div>
@@ -414,7 +414,9 @@ import LightGallery from "../components/LightGallery.astro";
sentinel.style.display = "none";
var endMsg = document.createElement("div");
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);
}
+3 -3
View File
@@ -5,7 +5,7 @@ import Layout from "../layouts/Layout.astro";
<Layout>
<Fragment slot="head">
<title
th:text="|${theme.config?.messageboard?.messageboard_title ?: '留言板'} - ${site.title}|"
th:text="|${theme.config?.messageboard?.messageboard_title ?: #messages.msg('messageboard.title')} - ${site.title}|"
></title>
</Fragment>
@@ -14,12 +14,12 @@ import Layout from "../layouts/Layout.astro";
<span class="wi-messageboard__deco">"</span>
<h1
class="wi-messageboard__title"
th:text="${theme.config?.messageboard?.messageboard_title ?: '留言板'}"
th:text="${theme.config?.messageboard?.messageboard_title ?: #messages.msg('messageboard.title')}"
></h1>
<span class="wi-page-accent"></span>
<p
class="wi-messageboard__subtitle"
th:text="${theme.config?.messageboard?.messageboard_subtitle}"
th:text="${theme.config?.messageboard?.messageboard_subtitle ?: #messages.msg('messageboard.subtitle')}"
></p>
</header>
+10 -14
View File
@@ -5,7 +5,7 @@ import LightGallery from "../components/LightGallery.astro";
<Layout wide>
<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>
<section class="wi-photos-page">
@@ -13,9 +13,9 @@ import LightGallery from "../components/LightGallery.astro";
<div class="wi-photos-page__header">
<h1
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>
<span class="wi-page-accent"></span>
</div>
@@ -30,8 +30,9 @@ import LightGallery from "../components/LightGallery.astro";
th:href="@{${photoUrl.list()}}"
class="wi-photos-page__group-link"
th:classappend="${#strings.isEmpty(currentGroup)} ? 'wi-photos-page__group-link--active' : ''"
th:text="#{common.all}"
>
全部
All
</a>
<a
th:each="group : ${groups}"
@@ -48,7 +49,7 @@ import LightGallery from "../components/LightGallery.astro";
<div class="wi-photos-page__all-groups">
<th:block th:if="${showUngrouped and photos != null and !#lists.isEmpty(photos.items)}">
<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
class="wi-photos-page__grid"
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 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>
</th:block>
</th:block>
@@ -248,25 +249,20 @@ import LightGallery from "../components/LightGallery.astro";
</th:block>
<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>
</th:block>
<th:block th:if="${groups == null}">
<div class="wi-photos-page__empty">
<p>图库插件加载异常,请检查插件状态</p>
<p th:text="#{common.pluginError}">Plugin error</p>
</div>
</th:block>
</th:block>
<th:block th:unless="${pluginFinder.available('PluginPhotos')}">
<div class="wi-photos-page__empty">
<p>
图库功能需要安装「图库」插件,<a
href="https://www.halo.run/store/apps/app-PgHgw"
>前往应用市场安装</a
>
</p>
<p th:utext="#{common.installPlugin('Gallery', 'https://www.halo.run/store/apps/app-PgHgw')}">This feature requires the Gallery plugin</p>
</div>
</th:block>
</div>
+313 -16
View File
@@ -3,14 +3,42 @@ import Layout from "../layouts/Layout.astro";
import LightGallery from "../components/LightGallery.astro";
---
<Layout>
<Layout wide={true}>
<Fragment slot="head">
<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>
<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">
<div
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}"
class="wi-post__stat-item"
id="wi-word-count"
>-- </span>
>-- words</span>
<span
th:if="${theme.config?.article?.article_show_visit_count ?: true}"
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
th:if="${theme.config?.article?.article_show_read_time ?: false}"
class="wi-post__stat-item"
id="wi-read-time"
>-- 分钟</span>
>-- min read</span>
</span>
</th:block>
</div>
@@ -109,10 +137,10 @@ import LightGallery from "../components/LightGallery.astro";
th:if="${theme.config?.article?.article_show_toc ?: true}"
class="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">
<span class="wi-toc__title">目录</span>
<span class="wi-toc__title" th:text="#{article.toc}">目录</span>
</div>
<nav class="wi-toc__nav" id="wi-toc-nav"></nav>
</aside>
@@ -128,6 +156,27 @@ import LightGallery from "../components/LightGallery.astro";
</button>
</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}">
<nav class="wi-post__nav" aria-label="Post navigation">
<th:block
@@ -138,7 +187,7 @@ import LightGallery from "../components/LightGallery.astro";
th:href="@{${cursor.previous.status.permalink}}"
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
class="wi-post__nav-title"
th:text="${cursor.previous.spec.title}"
@@ -153,7 +202,7 @@ import LightGallery from "../components/LightGallery.astro";
th:href="@{${cursor.next.status.permalink}}"
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
class="wi-post__nav-title"
th:text="${cursor.next.spec.title}"
@@ -167,6 +216,23 @@ import LightGallery from "../components/LightGallery.astro";
</nav>
</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}">
<halo:comment
th:if="${haloCommentEnabled}"
@@ -182,7 +248,7 @@ import LightGallery from "../components/LightGallery.astro";
class="wi-toc-fab"
id="wi-toc-fab"
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>
</button>
@@ -195,8 +261,8 @@ import LightGallery from "../components/LightGallery.astro";
<div class="wi-toc-drawer__overlay"></div>
<div class="wi-toc-drawer__panel">
<div class="wi-toc-drawer__header">
<span class="wi-toc-drawer__title">目录</span>
<button class="wi-toc-drawer__close" type="button" aria-label="关闭目录">
<span class="wi-toc-drawer__title" th:text="#{article.toc}">目录</span>
<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>
</button>
</div>
@@ -263,6 +329,9 @@ import LightGallery from "../components/LightGallery.astro";
var showWordCount = document.getElementById("wi-word-count");
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) {
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));
if (showWordCount) {
showWordCount.textContent = charCount + " ";
showWordCount.textContent = charCount + " " + wordsLabel;
}
if (showReadTime) {
showReadTime.textContent = readMinutes + " 分钟";
showReadTime.textContent = readMinutes + " " + readTimeLabel;
}
}
@@ -353,7 +422,13 @@ import LightGallery from "../components/LightGallery.astro";
entries.forEach(function (entry) {
if (entry.isIntersecting) {
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 + '"]');
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");
if (!btn) return;
var postName = btn.getAttribute("data-post-name");
@@ -493,6 +590,21 @@ import LightGallery from "../components/LightGallery.astro";
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>
</Layout>
@@ -680,7 +792,8 @@ import LightGallery from "../components/LightGallery.astro";
.wi-post__body .ai-summary,
.wi-post__body [class*="ai-summary"],
.wi-post__body [class*="ai-abstract"],
.wi-post__body .abstract {
.wi-post__body .abstract,
.wi-post__body [class*="likcc-article-summary"] {
margin-bottom: 15px;
}
@@ -693,6 +806,22 @@ import LightGallery from "../components/LightGallery.astro";
margin-block: 1.5em;
}
.wi-post__content img {
transition: filter 0.4s ease, opacity 0.4s ease;
}
.wi-post__content img[data-blur="true"] {
filter: blur(20px);
opacity: 0.6;
transform: scale(1.05);
}
.wi-post__content img[data-blur="loaded"] {
filter: blur(0);
opacity: 1;
transform: scale(1);
}
.wi-post__body a {
text-decoration: underline;
text-decoration-thickness: 1px;
@@ -928,6 +1057,85 @@ import LightGallery from "../components/LightGallery.astro";
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 {
width: 100%;
max-width: 600px;
@@ -985,6 +1193,90 @@ import LightGallery from "../components/LightGallery.astro";
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) {
.wi-post__cover-wrap {
margin-inline: calc(-1 * var(--space-md));
@@ -1049,6 +1341,11 @@ import LightGallery from "../components/LightGallery.astro";
opacity: 1;
}
.wi-toc[data-expand="all"] .wi-toc__sub {
max-height: 600px;
opacity: 1;
}
.wi-toc__link {
display: block;
padding: 4px 0;
+15 -15
View File
@@ -55,35 +55,35 @@ import Layout from "../layouts/Layout.astro";
th:href="${profile.summary?.profileUrl}"
target="_blank"
rel="noopener noreferrer"
>Steam 主页 →</a>
th:text="#{steam.homepage}">Steam Homepage &rarr;</a>
</div>
<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-label">等级</span>
<span class="wi-steam__level-label" th:text="#{steam.level}">Level</span>
</div>
</div>
<div class="wi-steam__stats" th:if="${stats != null}">
<div class="wi-steam__stat-card">
<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 class="wi-steam__stat-card">
<span class="wi-steam__stat-value" th:text="${stats.totalPlaytimeFormatted} ?: '0 小时'">0 小时</span>
<span class="wi-steam__stat-label">总游玩时长</span>
<span class="wi-steam__stat-value" th:text="${stats.totalPlaytimeFormatted} ?: '0 hours'">0 hours</span>
<span class="wi-steam__stat-label" th:text="#{steam.totalPlaytime}">Total Playtime</span>
</div>
<div class="wi-steam__stat-card">
<span class="wi-steam__stat-value" th:text="${stats.recentPlaytimeFormatted} ?: '0 小时'">0 小时</span>
<span class="wi-steam__stat-label">近两周</span>
<span class="wi-steam__stat-value" th:text="${stats.recentPlaytimeFormatted} ?: '0 hours'">0 hours</span>
<span class="wi-steam__stat-label" th:text="#{steam.recentPlaytime}">Recent 2 Weeks</span>
</div>
<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-label">徽章</span>
<span class="wi-steam__stat-label" th:text="#{steam.badges}">Badges</span>
</div>
</div>
<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">
<article class="wi-steam__recent-card" th:each="game : ${recentGames}">
<a
@@ -107,7 +107,7 @@ import Layout from "../layouts/Layout.astro";
<div class="wi-steam__recent-body">
<h4 class="wi-steam__recent-name" th:text="${game.name}"></h4>
<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
class="wi-steam__recent-achievements"
th:if="${game.achievementProgressText != null}"
@@ -122,7 +122,7 @@ import Layout from "../layouts/Layout.astro";
</th:block>
<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">
<article class="wi-steam__game-card" th:each="game : ${games.items}">
<a
@@ -146,11 +146,11 @@ import Layout from "../layouts/Layout.astro";
<div class="wi-steam__game-body">
<h4 class="wi-steam__game-name" th:text="${game.name}"></h4>
<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
class="wi-steam__game-last"
th:if="${!#strings.isEmpty(game.lastPlayedFormatted)}"
th:text="|最后游玩 ${game.lastPlayedFormatted}|"
th:text="#{steam.lastPlayed(${game.lastPlayedFormatted})}"
></span>
</div>
</div>
@@ -166,13 +166,13 @@ import Layout from "../layouts/Layout.astro";
class="wi-steam__page-btn"
th:if="${games.hasPrevious()}"
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>
<a
class="wi-steam__page-btn"
th:if="${games.hasNext()}"
th:href="${games.nextUrl}"
>下一页</a>
th:text="#{common.older}">Next</a>
</nav>
</div>
</section>
+7 -7
View File
@@ -11,7 +11,7 @@ import Layout from "../layouts/Layout.astro";
<header class="wi-tag__header">
<h1 class="wi-tag__title" th:text="${tag.spec.displayName}"></h1>
<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
th:if="${not #strings.isEmpty(tag.spec.description)}"
class="wi-tag__description"
@@ -55,8 +55,8 @@ import Layout from "../layouts/Layout.astro";
<div
th:if="${posts.total == 0}"
class="wi-tag__empty"
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章。'}"
>暂无文章。</div>
th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
>No posts yet.</div>
<nav
th:if="${posts.totalPages gt 1}"
@@ -67,8 +67,8 @@ import Layout from "../layouts/Layout.astro";
th:if="${posts.hasPrevious()}"
th:href="@{${posts.prevUrl}}"
class="wi-tag__page-link wi-tag__page-link--prev"
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
>&larr; 较新</a>
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
>&larr; Newer</a>
<span
class="wi-tag__page-info"
th:text="|${posts.page} / ${posts.totalPages}|"
@@ -77,8 +77,8 @@ import Layout from "../layouts/Layout.astro";
th:if="${posts.hasNext()}"
th:href="@{${posts.nextUrl}}"
class="wi-tag__page-link wi-tag__page-link--next"
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
>较旧 &rarr;</a>
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
>Older &rarr;</a>
</nav>
</div>
</Layout>
+3 -3
View File
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
<Layout>
<Fragment slot="head">
<title th:text="|标签 - ${site.title}|"></title>
<title th:text="|#{tags.title} - ${site.title}|"></title>
</Fragment>
<div class="wi-tags">
<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>
<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>
<div class="wi-tags__cloud" role="list" aria-label="Tag list">