feat: 友链/书库/移动端导航/排行榜等多模块功能
新增模块: - 友链(friendlink)后端处理 + 前端管理页与友链板 - 书库(library):导入、章节、封面、阅读页、横竖版自适应 AdaptiveCoverSlot - 顶栏导航(header_nav)配置与 MobileTabBar/MobileRailDrawers 移动端抽屉 - 排行榜 service 测试、站点页面测试、时间线发布(timeline_release) 其它改动: - 后端 handlers/services 全量小幅调整 - 前端组件、库函数、URL/品牌/站点 URL 工具更新 - Lightbox 图片、MdEntries 条目卡、coverColor 派生色相等前端能力
This commit is contained in:
@@ -4,6 +4,8 @@ import { createContext, useContext, useEffect, useMemo, useRef, useState, type R
|
||||
import { apiGetSettings, type PublicSettings } from "@/lib/api";
|
||||
import { siteDocumentTitle, normalizeBrandLogoFit, normalizeBrandLogoSize, normalizeBrandMark, normalizeFooterLinks } from "@/lib/brand";
|
||||
import { normalizeUrlStyle, type UrlStyle } from "@/lib/urlStyle";
|
||||
import { DEFAULT_MOBILE_TABS, normalizeMobileTabs, type MobileTabKey } from "@/lib/mobileTabs";
|
||||
import { DEFAULT_HEADER_NAV, normalizeHeaderNav, type HeaderNavItem } from "@/lib/headerNav";
|
||||
import { realtime, RT_SETTINGS_CHANGED, type RtSettingsData } from "@/lib/realtime";
|
||||
import { onDocumentVisible, VISIBLE_RECONCILE_GAP_MS } from "@/lib/visibilityReconcile";
|
||||
|
||||
@@ -19,6 +21,8 @@ export type SiteBrand = {
|
||||
brand_logo_fit: PublicSettings["brand_logo_fit"];
|
||||
footer_links: PublicSettings["footer_links"];
|
||||
url_style: UrlStyle;
|
||||
mobile_tabs: MobileTabKey[];
|
||||
header_nav: HeaderNavItem[];
|
||||
};
|
||||
|
||||
const SiteBrandContext = createContext<SiteBrand | null>(null);
|
||||
@@ -36,6 +40,8 @@ function fromSettings(s: PublicSettings): SiteBrand {
|
||||
brand_logo_fit: s.brand_logo_fit,
|
||||
footer_links: s.footer_links,
|
||||
url_style: s.url_style,
|
||||
mobile_tabs: s.mobile_tabs,
|
||||
header_nav: s.header_nav,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,7 +57,9 @@ function sameBrand(a: SiteBrand, b: SiteBrand): boolean {
|
||||
a.brand_logo_size === b.brand_logo_size &&
|
||||
a.brand_logo_fit === b.brand_logo_fit &&
|
||||
a.url_style === b.url_style &&
|
||||
JSON.stringify(a.footer_links) === JSON.stringify(b.footer_links)
|
||||
JSON.stringify(a.footer_links) === JSON.stringify(b.footer_links) &&
|
||||
JSON.stringify(a.mobile_tabs) === JSON.stringify(b.mobile_tabs) &&
|
||||
JSON.stringify(a.header_nav) === JSON.stringify(b.header_nav)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,6 +110,8 @@ export function useSiteBrand(): SiteBrand {
|
||||
brand_logo_fit: "contain",
|
||||
footer_links: [],
|
||||
url_style: "default",
|
||||
mobile_tabs: [...DEFAULT_MOBILE_TABS],
|
||||
header_nav: DEFAULT_HEADER_NAV.map((item) => ({ ...item })),
|
||||
};
|
||||
}
|
||||
return ctx;
|
||||
@@ -153,6 +163,8 @@ export default function SiteBrandProvider({
|
||||
? normalizeFooterLinks(raw.footer_links)
|
||||
: prev.footer_links,
|
||||
url_style: raw.url_style !== undefined ? normalizeUrlStyle(raw.url_style) : prev.url_style,
|
||||
mobile_tabs: raw.mobile_tabs !== undefined ? normalizeMobileTabs(raw.mobile_tabs) : prev.mobile_tabs,
|
||||
header_nav: raw.header_nav !== undefined ? normalizeHeaderNav(raw.header_nav) : prev.header_nav,
|
||||
};
|
||||
if (sameBrand(prev, next)) return;
|
||||
retitle(prev, next);
|
||||
|
||||
Reference in New Issue
Block a user