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
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:
@@ -270,6 +270,7 @@ export interface ApiSiteSettings {
|
||||
social_email: string | null;
|
||||
location: string | null;
|
||||
tech_stack: string[] | null;
|
||||
music_enabled?: boolean | null;
|
||||
music_playlist: Array<{
|
||||
title: string;
|
||||
artist?: string | null;
|
||||
@@ -423,10 +424,10 @@ export const DEFAULT_SITE_SETTINGS: SiteSettings = {
|
||||
siteName: 'InitCool',
|
||||
siteShortName: 'Termi',
|
||||
siteUrl: 'https://init.cool',
|
||||
siteTitle: 'InitCool - 终端风格的内容平台',
|
||||
siteDescription: '一个基于终端美学的个人内容站,记录代码、设计和生活。',
|
||||
heroTitle: '欢迎来到我的极客终端博客',
|
||||
heroSubtitle: '这里记录技术、代码和生活点滴',
|
||||
siteTitle: 'InitCool · 技术笔记与内容档案',
|
||||
siteDescription: '围绕开发实践、产品观察与长期积累整理的中文内容站。',
|
||||
heroTitle: '欢迎来到 InitCool',
|
||||
heroSubtitle: '记录开发实践、产品观察与长期积累,分享清晰、耐读、可回看的内容。',
|
||||
ownerName: 'InitCool',
|
||||
ownerTitle: 'Rust / Go / Python Developer · Builder @ init.cool',
|
||||
ownerBio: 'InitCool,GitHub 用户名 limitcool。坚持不要重复造轮子,当前在维护 starter,平时主要写 Rust、Go、Python 相关项目,也在持续学习 AI 与 Web3。',
|
||||
@@ -437,6 +438,7 @@ export const DEFAULT_SITE_SETTINGS: SiteSettings = {
|
||||
email: 'mailto:initcoool@gmail.com',
|
||||
},
|
||||
techStack: ['Rust', 'Go', 'Python', 'Svelte', 'Astro', 'Loco.rs'],
|
||||
musicEnabled: true,
|
||||
musicPlaylist: [
|
||||
{
|
||||
title: '山中来信',
|
||||
@@ -597,28 +599,8 @@ const normalizeSiteSettings = (settings: ApiSiteSettings): SiteSettings => {
|
||||
settings.subscription_verification_mode,
|
||||
settings.subscription_turnstile_enabled ? 'turnstile' : 'off',
|
||||
);
|
||||
|
||||
return {
|
||||
id: String(settings.id),
|
||||
siteName: settings.site_name || DEFAULT_SITE_SETTINGS.siteName,
|
||||
siteShortName: settings.site_short_name || DEFAULT_SITE_SETTINGS.siteShortName,
|
||||
siteUrl: settings.site_url || DEFAULT_SITE_SETTINGS.siteUrl,
|
||||
siteTitle: settings.site_title || DEFAULT_SITE_SETTINGS.siteTitle,
|
||||
siteDescription: settings.site_description || DEFAULT_SITE_SETTINGS.siteDescription,
|
||||
heroTitle: settings.hero_title || DEFAULT_SITE_SETTINGS.heroTitle,
|
||||
heroSubtitle: settings.hero_subtitle || DEFAULT_SITE_SETTINGS.heroSubtitle,
|
||||
ownerName: settings.owner_name || DEFAULT_SITE_SETTINGS.ownerName,
|
||||
ownerTitle: settings.owner_title || DEFAULT_SITE_SETTINGS.ownerTitle,
|
||||
ownerBio: settings.owner_bio || DEFAULT_SITE_SETTINGS.ownerBio,
|
||||
ownerAvatarUrl: settings.owner_avatar_url ?? undefined,
|
||||
location: settings.location || DEFAULT_SITE_SETTINGS.location,
|
||||
social: {
|
||||
github: settings.social_github || DEFAULT_SITE_SETTINGS.social.github,
|
||||
twitter: settings.social_twitter || DEFAULT_SITE_SETTINGS.social.twitter,
|
||||
email: settings.social_email || DEFAULT_SITE_SETTINGS.social.email,
|
||||
},
|
||||
techStack: settings.tech_stack?.length ? settings.tech_stack : DEFAULT_SITE_SETTINGS.techStack,
|
||||
musicPlaylist:
|
||||
const musicEnabled = settings.music_enabled ?? true;
|
||||
const normalizedMusicPlaylist =
|
||||
settings.music_playlist?.filter((item) => item?.title?.trim() && item?.url?.trim())?.length
|
||||
? settings.music_playlist
|
||||
.filter((item) => item.title.trim() && item.url.trim())
|
||||
@@ -631,43 +613,66 @@ const normalizeSiteSettings = (settings: ApiSiteSettings): SiteSettings => {
|
||||
accentColor: item.accent_color ?? undefined,
|
||||
description: item.description ?? undefined,
|
||||
}))
|
||||
: DEFAULT_SITE_SETTINGS.musicPlaylist,
|
||||
ai: {
|
||||
enabled: Boolean(settings.ai_enabled),
|
||||
},
|
||||
comments: {
|
||||
verificationMode: commentVerificationMode,
|
||||
paragraphsEnabled: settings.paragraph_comments_enabled ?? true,
|
||||
turnstileEnabled: commentVerificationMode === 'turnstile',
|
||||
turnstileSiteKey:
|
||||
settings.turnstile_site_key || resolvePublicCommentTurnstileSiteKey() || undefined,
|
||||
},
|
||||
subscriptions: {
|
||||
popupEnabled:
|
||||
settings.subscription_popup_enabled ?? DEFAULT_SITE_SETTINGS.subscriptions.popupEnabled,
|
||||
popupTitle:
|
||||
settings.subscription_popup_title || DEFAULT_SITE_SETTINGS.subscriptions.popupTitle,
|
||||
popupDescription:
|
||||
settings.subscription_popup_description ||
|
||||
DEFAULT_SITE_SETTINGS.subscriptions.popupDescription,
|
||||
popupDelaySeconds:
|
||||
settings.subscription_popup_delay_seconds ??
|
||||
DEFAULT_SITE_SETTINGS.subscriptions.popupDelaySeconds,
|
||||
verificationMode: subscriptionVerificationMode,
|
||||
turnstileEnabled: subscriptionVerificationMode === 'turnstile',
|
||||
turnstileSiteKey:
|
||||
settings.turnstile_site_key || resolvePublicCommentTurnstileSiteKey() || undefined,
|
||||
webPushEnabled: Boolean(settings.web_push_enabled),
|
||||
webPushVapidPublicKey:
|
||||
settings.web_push_vapid_public_key ||
|
||||
resolvePublicWebPushVapidPublicKey() ||
|
||||
undefined,
|
||||
},
|
||||
seo: {
|
||||
defaultOgImage: settings.seo_default_og_image ?? undefined,
|
||||
defaultTwitterHandle: settings.seo_default_twitter_handle ?? undefined,
|
||||
wechatShareQrEnabled: Boolean(settings.seo_wechat_share_qr_enabled),
|
||||
},
|
||||
: DEFAULT_SITE_SETTINGS.musicPlaylist;
|
||||
|
||||
return {
|
||||
id: String(settings.id),
|
||||
siteName: settings.site_name || DEFAULT_SITE_SETTINGS.siteName,
|
||||
siteShortName: settings.site_short_name || DEFAULT_SITE_SETTINGS.siteShortName,
|
||||
siteUrl: settings.site_url || DEFAULT_SITE_SETTINGS.siteUrl,
|
||||
siteTitle: settings.site_title || DEFAULT_SITE_SETTINGS.siteTitle,
|
||||
siteDescription: settings.site_description || DEFAULT_SITE_SETTINGS.siteDescription,
|
||||
heroTitle: settings.hero_title || DEFAULT_SITE_SETTINGS.heroTitle,
|
||||
heroSubtitle: settings.hero_subtitle || DEFAULT_SITE_SETTINGS.heroSubtitle,
|
||||
ownerName: settings.owner_name || DEFAULT_SITE_SETTINGS.ownerName,
|
||||
ownerTitle: settings.owner_title || DEFAULT_SITE_SETTINGS.ownerTitle,
|
||||
ownerBio: settings.owner_bio || DEFAULT_SITE_SETTINGS.ownerBio,
|
||||
ownerAvatarUrl: settings.owner_avatar_url ?? undefined,
|
||||
location: settings.location || DEFAULT_SITE_SETTINGS.location,
|
||||
social: {
|
||||
github: settings.social_github || DEFAULT_SITE_SETTINGS.social.github,
|
||||
twitter: settings.social_twitter || DEFAULT_SITE_SETTINGS.social.twitter,
|
||||
email: settings.social_email || DEFAULT_SITE_SETTINGS.social.email,
|
||||
},
|
||||
techStack: settings.tech_stack?.length ? settings.tech_stack : DEFAULT_SITE_SETTINGS.techStack,
|
||||
musicEnabled,
|
||||
musicPlaylist: musicEnabled ? normalizedMusicPlaylist : [],
|
||||
ai: {
|
||||
enabled: Boolean(settings.ai_enabled),
|
||||
},
|
||||
comments: {
|
||||
verificationMode: commentVerificationMode,
|
||||
paragraphsEnabled: settings.paragraph_comments_enabled ?? true,
|
||||
turnstileEnabled: commentVerificationMode === 'turnstile',
|
||||
turnstileSiteKey:
|
||||
settings.turnstile_site_key || resolvePublicCommentTurnstileSiteKey() || undefined,
|
||||
},
|
||||
subscriptions: {
|
||||
popupEnabled:
|
||||
settings.subscription_popup_enabled ?? DEFAULT_SITE_SETTINGS.subscriptions.popupEnabled,
|
||||
popupTitle:
|
||||
settings.subscription_popup_title || DEFAULT_SITE_SETTINGS.subscriptions.popupTitle,
|
||||
popupDescription:
|
||||
settings.subscription_popup_description ||
|
||||
DEFAULT_SITE_SETTINGS.subscriptions.popupDescription,
|
||||
popupDelaySeconds:
|
||||
settings.subscription_popup_delay_seconds ??
|
||||
DEFAULT_SITE_SETTINGS.subscriptions.popupDelaySeconds,
|
||||
verificationMode: subscriptionVerificationMode,
|
||||
turnstileEnabled: subscriptionVerificationMode === 'turnstile',
|
||||
turnstileSiteKey:
|
||||
settings.turnstile_site_key || resolvePublicCommentTurnstileSiteKey() || undefined,
|
||||
webPushEnabled: Boolean(settings.web_push_enabled),
|
||||
webPushVapidPublicKey:
|
||||
settings.web_push_vapid_public_key ||
|
||||
resolvePublicWebPushVapidPublicKey() ||
|
||||
undefined,
|
||||
},
|
||||
seo: {
|
||||
defaultOgImage: settings.seo_default_og_image ?? undefined,
|
||||
defaultTwitterHandle: settings.seo_default_twitter_handle ?? undefined,
|
||||
wechatShareQrEnabled: Boolean(settings.seo_wechat_share_qr_enabled),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export interface TerminalConfig {
|
||||
|
||||
export const terminalConfig: TerminalConfig = {
|
||||
defaultCategory: 'blog',
|
||||
welcomeMessage: '欢迎来到我的博客!',
|
||||
welcomeMessage: '欢迎来到 InitCool!',
|
||||
prompt: {
|
||||
prefix: 'user@blog',
|
||||
separator: ':',
|
||||
@@ -100,8 +100,8 @@ I N NN I T C O O O O L
|
||||
I N N I T CCCC OOO OOO LLLLL`,
|
||||
title: '~/blog',
|
||||
welcome: {
|
||||
title: '欢迎来到我的极客终端博客',
|
||||
subtitle: '这里记录技术、代码和生活点滴'
|
||||
title: '欢迎来到 InitCool',
|
||||
subtitle: '记录开发实践、产品观察与长期积累,分享清晰、耐读、可回看的内容。'
|
||||
},
|
||||
navLinks: [
|
||||
{ icon: 'fa-file-code', text: '文章', href: '/articles' },
|
||||
|
||||
41
frontend/src/lib/maintenance.ts
Normal file
41
frontend/src/lib/maintenance.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export const MAINTENANCE_ACCESS_COOKIE_NAME = 'termi_maintenance_access'
|
||||
|
||||
export function sanitizeMaintenanceReturnTo(value: string | null | undefined): string {
|
||||
if (!value) {
|
||||
return '/'
|
||||
}
|
||||
|
||||
const trimmed = value.trim()
|
||||
if (!trimmed.startsWith('/') || trimmed.startsWith('//')) {
|
||||
return '/'
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(trimmed, 'https://termi.local')
|
||||
const nextPath = `${parsed.pathname}${parsed.search}${parsed.hash}`
|
||||
|
||||
if (
|
||||
nextPath === '/maintenance' ||
|
||||
nextPath.startsWith('/maintenance?') ||
|
||||
nextPath.startsWith('/api/maintenance')
|
||||
) {
|
||||
return '/'
|
||||
}
|
||||
|
||||
return nextPath || '/'
|
||||
} catch {
|
||||
return '/'
|
||||
}
|
||||
}
|
||||
|
||||
export function shouldBypassMaintenance(pathname: string): boolean {
|
||||
return (
|
||||
pathname === '/maintenance' ||
|
||||
pathname.startsWith('/api/maintenance') ||
|
||||
pathname === '/healthz' ||
|
||||
pathname === '/favicon.svg' ||
|
||||
pathname.startsWith('/_astro/') ||
|
||||
pathname.startsWith('/_image') ||
|
||||
pathname.startsWith('/_img')
|
||||
)
|
||||
}
|
||||
@@ -84,6 +84,7 @@ export interface SiteSettings {
|
||||
};
|
||||
techStack: string[];
|
||||
musicPlaylist: MusicTrack[];
|
||||
musicEnabled: boolean;
|
||||
ai: {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user