feat: update tag and timeline share panel copy for clarity and conciseness
Some checks failed
docker-images / resolve-build-targets (push) Successful in 7s
ui-regression / playwright-regression (push) Failing after 13m4s
docker-images / build-and-push (admin) (push) Successful in 1m17s
docker-images / build-and-push (backend) (push) Successful in 28m13s
docker-images / build-and-push (frontend) (push) Successful in 47s
docker-images / submit-indexnow (push) Successful in 13s

style: enhance global CSS for better responsiveness of terminal chips and navigation pills

test: remove inline subscription test and add maintenance mode access code test

feat: implement media library picker dialog for selecting images from the media library

feat: add media URL controls for uploading and managing media assets

feat: add migration for music_enabled and maintenance_mode settings in site settings

feat: implement maintenance mode functionality with access control

feat: create maintenance page with access code input and error handling

chore: add TypeScript declaration for QR code module
This commit is contained in:
2026-04-02 23:05:49 +08:00
parent 6a50dd478c
commit 9665c933b5
94 changed files with 5266 additions and 1612 deletions

View File

@@ -16,7 +16,8 @@ const {
const { locale, t, buildLocaleUrl } = getI18n(Astro);
const aiEnabled = Boolean(Astro.props.siteSettings?.ai?.enabled);
const musicPlaylist = (Astro.props.siteSettings?.musicPlaylist || []).filter(
const musicEnabled = Astro.props.siteSettings?.musicEnabled ?? true;
const musicPlaylist = (musicEnabled ? Astro.props.siteSettings?.musicPlaylist : []).filter(
(item) => item?.title?.trim() && item?.url?.trim()
);
const musicPlaylistPayload = JSON.stringify(musicPlaylist);
@@ -60,11 +61,11 @@ const currentNavLabel =
</span>
</a>
<div class="relative hidden lg:block flex-1 min-w-0 max-w-[16rem] xl:max-w-[18rem]">
<div class="terminal-toolbar-module gap-2 px-2.5 py-1.5">
<div class="terminal-toolbar-label" id="search-label">{t('header.searchPromptKeyword')}</div>
<div class="relative hidden min-w-[20rem] grow basis-[24rem] lg:block xl:min-w-[24rem] xl:basis-[30rem]">
<div class="terminal-toolbar-module min-w-0 gap-2 px-2.5 py-1.5">
<div class="terminal-toolbar-label shrink-0 whitespace-nowrap" id="search-label">{t('header.searchPromptKeyword')}</div>
{aiEnabled && (
<div id="search-mode-panel" class="hidden 2xl:flex items-center gap-1 rounded-xl border border-[var(--border-color)] bg-[var(--header-bg)]/80 p-1">
<div id="search-mode-panel" class="hidden shrink-0 2xl:flex items-center gap-1 rounded-xl border border-[var(--border-color)] bg-[var(--header-bg)]/80 p-1">
<button
type="button"
class="search-mode-btn rounded-lg px-2.5 py-1.5 text-xs font-medium text-[var(--text-secondary)] transition hover:bg-[var(--header-bg)]"
@@ -91,7 +92,7 @@ const currentNavLabel =
placeholder={t('header.searchPlaceholderKeyword')}
class="terminal-console-input"
/>
<button id="search-btn" class="terminal-toolbar-iconbtn h-8 w-8" aria-label="Search">
<button id="search-btn" class="terminal-toolbar-iconbtn h-8 w-8 shrink-0" aria-label="Search">
<i id="search-btn-icon" class="fas fa-search text-sm"></i>
</button>
</div>
@@ -104,73 +105,89 @@ const currentNavLabel =
></div>
</div>
<div class="hidden 2xl:flex terminal-toolbar-module min-w-0 max-w-[13rem] gap-2 px-2.5 py-1.5">
<div class="flex h-9 w-9 shrink-0 items-center justify-center overflow-hidden rounded-xl border border-[var(--border-color)] bg-[var(--primary)]/8">
<img
id="desktop-music-cover"
src={currentMusicTrack?.coverImageUrl || ''}
alt={currentMusicTrack?.title || 'Music cover'}
class:list={[
'h-full w-full object-cover',
!currentMusicTrack?.coverImageUrl && 'hidden'
]}
/>
<i
id="desktop-music-cover-fallback"
class:list={[
'fas fa-compact-disc text-sm text-[var(--primary)]',
currentMusicTrack?.coverImageUrl && 'hidden'
]}
></i>
</div>
<div class="min-w-0 flex-1">
<p class="truncate text-[13px] font-semibold text-[var(--title-color)]" id="desktop-music-title">
{currentMusicTrack?.title || '未配置曲目'}
</p>
<div class="mt-1 flex items-center gap-1">
<button id="desktop-music-prev" class="terminal-toolbar-iconbtn h-7 w-7" aria-label="Previous track" disabled={!hasMusicPlaylist}>
<i class="fas fa-step-backward text-[11px]"></i>
</button>
<button id="desktop-music-play" class="terminal-toolbar-iconbtn h-7 w-7 bg-[var(--primary)]/12 text-[var(--primary)]" style="border-color: color-mix(in oklab, var(--primary) 28%, var(--border-color));" aria-label="Play or pause" disabled={!hasMusicPlaylist}>
<i class="fas fa-play text-[11px]" id="desktop-music-play-icon"></i>
</button>
<button id="desktop-music-next" class="terminal-toolbar-iconbtn h-7 w-7" aria-label="Next track" disabled={!hasMusicPlaylist}>
<i class="fas fa-step-forward text-[11px]"></i>
</button>
<div class="ml-auto hidden shrink-0 items-center gap-2 lg:flex">
{musicEnabled && (
<div class="hidden 2xl:flex terminal-toolbar-module min-w-0 max-w-[13rem] gap-2 px-2.5 py-1.5">
<div class="flex h-9 w-9 shrink-0 items-center justify-center overflow-hidden rounded-xl border border-[var(--border-color)] bg-[var(--primary)]/8">
<img
id="desktop-music-cover"
src={currentMusicTrack?.coverImageUrl || ''}
alt={currentMusicTrack?.title || 'Music cover'}
class:list={[
'h-full w-full object-cover',
!currentMusicTrack?.coverImageUrl && 'hidden'
]}
/>
<i
id="desktop-music-cover-fallback"
class:list={[
'fas fa-compact-disc text-sm text-[var(--primary)]',
currentMusicTrack?.coverImageUrl && 'hidden'
]}
></i>
</div>
<div class="min-w-0 flex-1">
<p class="truncate text-[13px] font-semibold text-[var(--title-color)]" id="desktop-music-title">
{currentMusicTrack?.title || '未配置曲目'}
</p>
<div class="mt-1 flex items-center gap-1">
<button id="desktop-music-prev" class="terminal-toolbar-iconbtn h-7 w-7" aria-label="Previous track" disabled={!hasMusicPlaylist}>
<i class="fas fa-step-backward text-[11px]"></i>
</button>
<button id="desktop-music-play" class="terminal-toolbar-iconbtn h-7 w-7 bg-[var(--primary)]/12 text-[var(--primary)]" style="border-color: color-mix(in oklab, var(--primary) 28%, var(--border-color));" aria-label="Play or pause" disabled={!hasMusicPlaylist}>
<i class="fas fa-play text-[11px]" id="desktop-music-play-icon"></i>
</button>
<button id="desktop-music-next" class="terminal-toolbar-iconbtn h-7 w-7" aria-label="Next track" disabled={!hasMusicPlaylist}>
<i class="fas fa-step-forward text-[11px]"></i>
</button>
</div>
</div>
</div>
)}
{aiEnabled && (
<a
href="/ask"
class="inline-flex shrink-0 items-center gap-2 rounded-xl border border-[var(--primary)]/18 bg-[var(--primary)]/8 px-2.5 py-1.5 text-sm font-medium text-[var(--primary)] transition hover:border-[var(--primary)]/32 hover:text-[var(--title-color)]"
>
<i class="fas fa-robot text-sm"></i>
<span class="hidden xl:inline">{t('nav.ask')}</span>
</a>
)}
<div class="flex shrink-0 items-center gap-1 rounded-xl border border-[var(--border-color)] bg-[var(--header-bg)]/80 p-0.5">
{localeLinks.map((item) => (
<a
href={item.href}
data-locale-switch={item.locale}
class:list={[
'rounded-lg px-2.5 py-1.5 text-xs font-semibold transition',
item.locale === locale
? 'bg-[var(--primary)] text-white shadow-sm'
: 'text-[var(--text-secondary)] hover:bg-[var(--header-bg)]'
]}
aria-current={item.locale === locale ? 'true' : undefined}
title={item.label}
>
{item.shortLabel}
</a>
))}
</div>
<div class="relative shrink-0">
<ThemeToggle
client:load
labels={{
toggle: t('header.themeToggle'),
system: t('header.themeSystem'),
light: t('header.themeLight'),
dark: t('header.themeDark'),
}}
/>
</div>
</div>
{aiEnabled && (
<a
href="/ask"
class="hidden lg:inline-flex items-center gap-2 rounded-xl border border-[var(--primary)]/18 bg-[var(--primary)]/8 px-2.5 py-1.5 text-sm font-medium text-[var(--primary)] transition hover:border-[var(--primary)]/32 hover:text-[var(--title-color)]"
>
<i class="fas fa-robot text-sm"></i>
<span class="hidden xl:inline">{t('nav.ask')}</span>
</a>
)}
<div class="hidden lg:flex items-center gap-1 rounded-xl border border-[var(--border-color)] bg-[var(--header-bg)]/80 p-0.5">
{localeLinks.map((item) => (
<a
href={item.href}
data-locale-switch={item.locale}
class:list={[
'rounded-lg px-2.5 py-1.5 text-xs font-semibold transition',
item.locale === locale
? 'bg-[var(--primary)] text-white shadow-sm'
: 'text-[var(--text-secondary)] hover:bg-[var(--header-bg)]'
]}
aria-current={item.locale === locale ? 'true' : undefined}
title={item.label}
>
{item.shortLabel}
</a>
))}
</div>
<div class="relative shrink-0">
<div class="relative shrink-0 lg:hidden">
<ThemeToggle
client:load
labels={{
@@ -262,51 +279,53 @@ const currentNavLabel =
</div>
</div>
<div class="terminal-toolbar-module items-center gap-3">
<div class="flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-2xl border border-[var(--border-color)] bg-[var(--primary)]/8">
<img
id="music-cover"
src={currentMusicTrack?.coverImageUrl || ''}
alt={currentMusicTrack?.title || 'Music cover'}
class:list={[
'h-full w-full object-cover',
!currentMusicTrack?.coverImageUrl && 'hidden'
]}
/>
<i
id="music-cover-fallback"
class:list={[
'fas fa-compact-disc text-base text-[var(--primary)]',
currentMusicTrack?.coverImageUrl && 'hidden'
]}
></i>
</div>
<div class="min-w-0 flex-1">
<div class="terminal-toolbar-label">{t('header.musicPanel')}</div>
<div class="mt-1 flex items-center gap-2">
<button id="music-prev" class="terminal-toolbar-iconbtn" aria-label="Previous track" disabled={!hasMusicPlaylist}>
<i class="fas fa-step-backward text-xs"></i>
</button>
<button id="music-play" class="terminal-toolbar-iconbtn bg-[var(--primary)]/12 text-[var(--primary)]" style="border-color: color-mix(in oklab, var(--primary) 28%, var(--border-color));" aria-label="Play or pause" disabled={!hasMusicPlaylist}>
<i class="fas fa-play text-xs" id="music-play-icon"></i>
</button>
<button id="music-next" class="terminal-toolbar-iconbtn" aria-label="Next track" disabled={!hasMusicPlaylist}>
<i class="fas fa-step-forward text-xs"></i>
</button>
<button id="music-volume" class="terminal-toolbar-iconbtn" aria-label="Mute or unmute" disabled={!hasMusicPlaylist}>
<i class="fas fa-volume-up text-xs"></i>
</button>
{musicEnabled && (
<div class="terminal-toolbar-module items-center gap-3">
<div class="flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-2xl border border-[var(--border-color)] bg-[var(--primary)]/8">
<img
id="music-cover"
src={currentMusicTrack?.coverImageUrl || ''}
alt={currentMusicTrack?.title || 'Music cover'}
class:list={[
'h-full w-full object-cover',
!currentMusicTrack?.coverImageUrl && 'hidden'
]}
/>
<i
id="music-cover-fallback"
class:list={[
'fas fa-compact-disc text-base text-[var(--primary)]',
currentMusicTrack?.coverImageUrl && 'hidden'
]}
></i>
</div>
<div class="mt-2 min-w-0">
<p class="truncate text-sm font-semibold text-[var(--title-color)]" id="music-title">
{currentMusicTrack?.title || '未配置曲目'}
</p>
<p class="truncate text-[11px] text-[var(--text-tertiary)]" id="music-artist">
{currentMusicTrack?.artist || currentMusicTrack?.album || '等待播放'}
</p>
<div class="min-w-0 flex-1">
<div class="terminal-toolbar-label">{t('header.musicPanel')}</div>
<div class="mt-1 flex items-center gap-2">
<button id="music-prev" class="terminal-toolbar-iconbtn" aria-label="Previous track" disabled={!hasMusicPlaylist}>
<i class="fas fa-step-backward text-xs"></i>
</button>
<button id="music-play" class="terminal-toolbar-iconbtn bg-[var(--primary)]/12 text-[var(--primary)]" style="border-color: color-mix(in oklab, var(--primary) 28%, var(--border-color));" aria-label="Play or pause" disabled={!hasMusicPlaylist}>
<i class="fas fa-play text-xs" id="music-play-icon"></i>
</button>
<button id="music-next" class="terminal-toolbar-iconbtn" aria-label="Next track" disabled={!hasMusicPlaylist}>
<i class="fas fa-step-forward text-xs"></i>
</button>
<button id="music-volume" class="terminal-toolbar-iconbtn" aria-label="Mute or unmute" disabled={!hasMusicPlaylist}>
<i class="fas fa-volume-up text-xs"></i>
</button>
</div>
<div class="mt-2 min-w-0">
<p class="truncate text-sm font-semibold text-[var(--title-color)]" id="music-title">
{currentMusicTrack?.title || '未配置曲目'}
</p>
<p class="truncate text-[11px] text-[var(--text-tertiary)]" id="music-artist">
{currentMusicTrack?.artist || currentMusicTrack?.album || '等待播放'}
</p>
</div>
</div>
</div>
</div>
)}
</div>
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
@@ -320,13 +339,13 @@ const currentNavLabel =
: ''
]}
>
<span class="flex items-center gap-3">
<span class="flex min-w-0 flex-1 items-center gap-3">
<span class="flex h-10 w-10 items-center justify-center rounded-xl border border-[var(--border-color)] bg-[var(--header-bg)]/82 text-[var(--primary)]">
<i class={`fas ${item.icon} text-sm`}></i>
</span>
<span class="min-w-0">
<span class="terminal-toolbar-label block">{t('header.navigation')}</span>
<span class="mt-1 block text-sm font-semibold text-[var(--title-color)]">{item.text}</span>
<span class="mt-1 block truncate text-sm font-semibold text-[var(--title-color)]">{item.text}</span>
</span>
</span>
<i class="fas fa-arrow-right text-[11px] text-[var(--text-tertiary)]"></i>