完善站点运营设置:分路由管理、登录可见评论、邮件模板内联与关闭注册 SSR。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-23 00:17:00 +08:00
parent acde7ee288
commit 6f054a903c
98 changed files with 8239 additions and 2073 deletions

View File

@@ -2,7 +2,7 @@
import { createContext, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import { apiGetSettings, type PublicSettings } from "@/lib/api";
import { siteDocumentTitle, normalizeBrandLogoFit, normalizeBrandLogoSize, normalizeBrandMark } from "@/lib/brand";
import { siteDocumentTitle, normalizeBrandLogoFit, normalizeBrandLogoSize, normalizeBrandMark, normalizeFooterLinks } from "@/lib/brand";
import { realtime, RT_SETTINGS_CHANGED, type RtSettingsData } from "@/lib/realtime";
import { onDocumentVisible, VISIBLE_RECONCILE_GAP_MS } from "@/lib/visibilityReconcile";
@@ -52,29 +52,18 @@ function sameBrand(a: SiteBrand, b: SiteBrand): boolean {
}
function applyFavicon(url: string) {
const existing = document.querySelectorAll<HTMLLinkElement>('link[rel~="icon"]');
if (!url) {
document.getElementById("j13-favicon")?.remove();
const hasDefault = [...existing].some((el) => el.id !== "j13-favicon" && el.href.includes("favicon"));
if (!hasDefault) {
const link = document.createElement("link");
link.rel = "icon";
link.href = "/favicon.ico";
document.head.appendChild(link);
}
return;
}
existing.forEach((el) => {
if (el.id !== "j13-favicon") el.remove();
});
// 只管理 #j13-favicon,禁止 remove 其它 icon 节点:
// Next Metadata 注入的 <link rel="icon"> 由 React 持有,手动删会导致 removeChild 空指针。
let link = document.getElementById("j13-favicon") as HTMLLinkElement | null;
if (!link) {
link = document.createElement("link");
link.id = "j13-favicon";
link.rel = "icon";
document.head.appendChild(link);
}
if (link.getAttribute("href") !== url) link.href = url;
const href = url || "/favicon.ico";
if (link.getAttribute("href") !== href) link.href = href;
// 挪到 head 末尾,优先于 SSR 注入的 icon
document.head.appendChild(link);
}
function retitle(prev: SiteBrand, next: SiteBrand) {
@@ -132,7 +121,7 @@ export default function SiteBrandProvider({
setBrand((prev) => {
if (sameBrand(prev, next)) return prev;
retitle(prev, next);
applyFavicon(next.favicon_url);
if (prev.favicon_url !== next.favicon_url) applyFavicon(next.favicon_url);
return next;
});
}, [initial]);
@@ -150,11 +139,13 @@ export default function SiteBrandProvider({
brand_mark: normalizeBrandMark(raw.brand_mark ?? prev.brand_mark),
brand_logo_size: normalizeBrandLogoSize(raw.brand_logo_size ?? prev.brand_logo_size),
brand_logo_fit: normalizeBrandLogoFit(raw.brand_logo_fit ?? prev.brand_logo_fit),
footer_links: Array.isArray(raw.footer_links) ? raw.footer_links : prev.footer_links,
footer_links: Array.isArray(raw.footer_links)
? normalizeFooterLinks(raw.footer_links)
: prev.footer_links,
};
if (sameBrand(prev, next)) return;
retitle(prev, next);
applyFavicon(next.favicon_url);
if (prev.favicon_url !== next.favicon_url) applyFavicon(next.favicon_url);
brandRef.current = next;
setBrand(next);
};