// 自定义侧栏工具的站点开放接口: // - CSS:工具内直接使用站点主题变量(var(--accent) 等),随明暗主题与 // 后台自定义主题色自动变化(CSS 变量沿 DOM 继承,无需任何 JS); // - JS:window.J13 提供当前用户、主题读取/监听、站点概览懒加载。 // 本模块只在浏览器端(CustomWidgetBlock 的 effect 内)执行。 import type { OverviewResponse, User } from "@/lib/api"; export interface WidgetViewer { id: number; username: string; nickname: string; role: string; avatar: string | null; } export interface WidgetApi { version: 1; /** 当前登录用户(未登录为 null);每次工具挂载时刷新 */ user: WidgetViewer | null; theme: { get: () => "light" | "dark"; /** 监听明暗切换,返回取消监听函数 */ onChange: (cb: (theme: "light" | "dark") => void) => () => void; }; /** 站点公开概览统计(结果模块级缓存,多个工具共享一次请求) */ getOverview: () => Promise; } declare global { interface Window { J13?: WidgetApi; } } function toViewer(user: User | null | undefined): WidgetViewer | null { if (!user) return null; return { id: user.id, username: user.username, nickname: user.nickname, role: user.role, avatar: user.avatar || null, }; } // 概览请求全站只发一次(无论多少个工具、多少次挂载) let overviewPromise: Promise | null = null; function loadOverview(): Promise { if (!overviewPromise) { overviewPromise = fetch("/api/overview", { credentials: "include" }) .then((res) => (res.ok ? res.json() : null)) .catch(() => null); } return overviewPromise; } /** * 幂等初始化 window.J13 并刷新当前用户信息。 * 必须在注入站长 JS 之前调用;多个工具共享同一个实例。 */ export function ensureWidgetApi(viewer: User | null | undefined): WidgetApi { const root = document.documentElement; const readTheme = (): "light" | "dark" => root.classList.contains("dark") ? "dark" : "light"; if (!window.J13) { const listeners = new Set<(theme: "light" | "dark") => void>(); // 主题切换 = html 元素 .dark 类的增删,观察其变化广播给工具 const observer = new MutationObserver(() => { const theme = readTheme(); listeners.forEach((cb) => cb(theme)); }); observer.observe(root, { attributes: true, attributeFilter: ["class"] }); window.J13 = { version: 1, user: null, theme: { get: readTheme, onChange(cb) { listeners.add(cb); return () => listeners.delete(cb); }, }, getOverview: loadOverview, }; } window.J13.user = toViewer(viewer); return window.J13; }