feat: 添加站点设置中的 favicon URL 支持,更新相关接口和页面
All checks were successful
ui-regression / playwright-regression (push) Successful in 6m20s
docker-images / resolve-build-targets (push) Successful in 6s
docker-images / build-and-push (admin) (push) Successful in 25s
docker-images / build-and-push (backend) (push) Successful in 35s
docker-images / build-and-push (frontend) (push) Successful in 1m46s
docker-images / submit-indexnow (push) Successful in 15s
All checks were successful
ui-regression / playwright-regression (push) Successful in 6m20s
docker-images / resolve-build-targets (push) Successful in 6s
docker-images / build-and-push (admin) (push) Successful in 25s
docker-images / build-and-push (backend) (push) Successful in 35s
docker-images / build-and-push (frontend) (push) Successful in 1m46s
docker-images / submit-indexnow (push) Successful in 15s
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 655 B |
@@ -148,7 +148,7 @@ const i18nPayload = JSON.stringify({ locale, messages });
|
||||
title={`${siteSettings.siteName} llms-full.txt`}
|
||||
href={`${siteUrl}/llms-full.txt`}
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>{title}</title>
|
||||
{jsonLd && <script type="application/ld+json" set:html={jsonLd}></script>}
|
||||
<slot name="head" />
|
||||
|
||||
@@ -293,6 +293,7 @@ export interface ApiSiteSettings {
|
||||
subscription_popup_title: string | null;
|
||||
subscription_popup_description: string | null;
|
||||
subscription_popup_delay_seconds: number | null;
|
||||
seo_favicon_url: string | null;
|
||||
seo_default_og_image: string | null;
|
||||
seo_default_twitter_handle: string | null;
|
||||
seo_wechat_share_qr_enabled: boolean;
|
||||
@@ -492,6 +493,7 @@ export const DEFAULT_SITE_SETTINGS: SiteSettings = {
|
||||
webPushVapidPublicKey: undefined,
|
||||
},
|
||||
seo: {
|
||||
faviconUrl: undefined,
|
||||
defaultOgImage: undefined,
|
||||
defaultTwitterHandle: undefined,
|
||||
wechatShareQrEnabled: false,
|
||||
@@ -669,6 +671,7 @@ const normalizeSiteSettings = (settings: ApiSiteSettings): SiteSettings => {
|
||||
undefined,
|
||||
},
|
||||
seo: {
|
||||
faviconUrl: settings.seo_favicon_url ?? undefined,
|
||||
defaultOgImage: settings.seo_default_og_image ?? undefined,
|
||||
defaultTwitterHandle: settings.seo_default_twitter_handle ?? undefined,
|
||||
wechatShareQrEnabled: Boolean(settings.seo_wechat_share_qr_enabled),
|
||||
|
||||
@@ -33,7 +33,9 @@ export function shouldBypassMaintenance(pathname: string): boolean {
|
||||
pathname === '/maintenance' ||
|
||||
pathname.startsWith('/api/maintenance') ||
|
||||
pathname === '/healthz' ||
|
||||
pathname === '/favicon.svg' ||
|
||||
pathname === '/favicon.ico' ||
|
||||
pathname.startsWith('/favicon.') ||
|
||||
pathname.startsWith('/apple-touch-icon') ||
|
||||
pathname.startsWith('/_astro/') ||
|
||||
pathname.startsWith('/_image') ||
|
||||
pathname.startsWith('/_img')
|
||||
|
||||
@@ -106,6 +106,7 @@ export interface SiteSettings {
|
||||
webPushVapidPublicKey?: string;
|
||||
};
|
||||
seo: {
|
||||
faviconUrl?: string;
|
||||
defaultOgImage?: string;
|
||||
defaultTwitterHandle?: string;
|
||||
wechatShareQrEnabled: boolean;
|
||||
|
||||
35
frontend/src/pages/favicon.ico.ts
Normal file
35
frontend/src/pages/favicon.ico.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { APIRoute } from 'astro'
|
||||
|
||||
import { createApiClient, DEFAULT_SITE_SETTINGS } from '../lib/api/client'
|
||||
|
||||
function resolveFaviconTarget(requestUrl: URL, configured: string | undefined) {
|
||||
const fallbackTarget = '/favicon.svg'
|
||||
const candidate = configured?.trim()
|
||||
|
||||
if (!candidate) {
|
||||
return fallbackTarget
|
||||
}
|
||||
|
||||
try {
|
||||
const resolved = new URL(candidate, requestUrl)
|
||||
if (resolved.pathname === '/favicon.ico' && resolved.origin === requestUrl.origin) {
|
||||
return fallbackTarget
|
||||
}
|
||||
|
||||
return resolved.toString()
|
||||
} catch {
|
||||
return fallbackTarget
|
||||
}
|
||||
}
|
||||
|
||||
export const GET: APIRoute = async ({ url, redirect }) => {
|
||||
let siteSettings = DEFAULT_SITE_SETTINGS
|
||||
|
||||
try {
|
||||
siteSettings = await createApiClient({ requestUrl: url }).getSiteSettings()
|
||||
} catch (error) {
|
||||
console.error('Failed to load site settings for favicon:', error)
|
||||
}
|
||||
|
||||
return redirect(resolveFaviconTarget(url, siteSettings.seo.faviconUrl), 302)
|
||||
}
|
||||
@@ -20,9 +20,9 @@ const errorMessage =
|
||||
? '请先输入访问口令。'
|
||||
: errorCode === 'invalid'
|
||||
? '口令不正确,请重新输入。'
|
||||
: errorCode === 'unavailable'
|
||||
? '当前无法校验访问口令,请稍后再试。'
|
||||
: ''
|
||||
: errorCode === 'unavailable'
|
||||
? '当前无法校验访问口令,请稍后再试。'
|
||||
: ''
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
@@ -31,6 +31,7 @@ const errorMessage =
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>{siteSettings.siteName} · 维护模式</title>
|
||||
</head>
|
||||
<body class="min-h-screen bg-[var(--bg)] text-[var(--text)]">
|
||||
|
||||
Reference in New Issue
Block a user