Download web/src/lib/branding.ts from zlcy/study: direct link, hf CLI and curl.
- Browser
- Download file 5.19 kB
-
https://huggingface.co/spaces/zlcy/study/resolve/main/web/src/lib/branding.ts
- Command line
-
hf download hf://spaces/zlcy/study/web/src/lib/branding.ts
-
curl -L -o branding.ts https://huggingface.co/spaces/zlcy/study/resolve/main/web/src/lib/branding.ts
5.19 kB
| "use client"; | |
| import { useEffect, useRef, useState } from "react"; | |
| import { fetchBranding, type BrandingConfig } from "@/lib/api"; | |
| const BRANDING_UPDATED_EVENT = "chatgpt2api:branding-updated"; | |
| export const DEFAULT_BRANDING: Required<BrandingConfig> = { | |
| site_title: "ChatGPT 号池管理", | |
| site_logo_url: "", | |
| site_header_title: "chatgpt2api", | |
| site_external_link_label: "GitHub", | |
| site_external_link_url: "https://github.com/basketikun/chatgpt2api", | |
| site_external_link_icon_url: "", | |
| site_external_link_icon_svg: "", | |
| }; | |
| function normalizeText(value: unknown, fallback: string) { | |
| const normalized = String(value ?? "").trim(); | |
| return normalized || fallback; | |
| } | |
| export function normalizeBranding(value: BrandingConfig | null | undefined): Required<BrandingConfig> { | |
| return { | |
| site_title: normalizeText(value?.site_title, DEFAULT_BRANDING.site_title), | |
| site_logo_url: String(value?.site_logo_url ?? "").trim(), | |
| site_header_title: normalizeText(value?.site_header_title, DEFAULT_BRANDING.site_header_title), | |
| site_external_link_label: String(value?.site_external_link_label ?? DEFAULT_BRANDING.site_external_link_label).trim(), | |
| site_external_link_url: String(value?.site_external_link_url ?? DEFAULT_BRANDING.site_external_link_url).trim(), | |
| site_external_link_icon_url: String(value?.site_external_link_icon_url ?? "").trim(), | |
| site_external_link_icon_svg: String(value?.site_external_link_icon_svg ?? "").trim(), | |
| }; | |
| } | |
| function updateFavicon(href: string) { | |
| const targetHref = href || "/favicon.ico"; | |
| let icon = document.querySelector<HTMLLinkElement>("link[data-branding-icon='favicon']"); | |
| if (!icon) { | |
| icon = document.createElement("link"); | |
| icon.rel = "icon"; | |
| icon.setAttribute("data-branding-icon", "favicon"); | |
| document.head.appendChild(icon); | |
| } | |
| icon.rel = "icon"; | |
| icon.href = targetHref; | |
| icon.type = guessIconType(targetHref); | |
| let appleIcon = document.querySelector<HTMLLinkElement>("link[data-branding-icon='apple-touch-icon']"); | |
| if (!appleIcon) { | |
| appleIcon = document.createElement("link"); | |
| appleIcon.rel = "apple-touch-icon"; | |
| appleIcon.setAttribute("data-branding-icon", "apple-touch-icon"); | |
| document.head.appendChild(appleIcon); | |
| } | |
| appleIcon.rel = "apple-touch-icon"; | |
| appleIcon.href = targetHref; | |
| } | |
| function guessIconType(href: string) { | |
| const cleanHref = href.split(/[?#]/, 1)[0]?.toLowerCase() || ""; | |
| if (cleanHref.endsWith(".svg")) { | |
| return "image/svg+xml"; | |
| } | |
| if (cleanHref.endsWith(".jpg") || cleanHref.endsWith(".jpeg")) { | |
| return "image/jpeg"; | |
| } | |
| if (cleanHref.endsWith(".webp")) { | |
| return "image/webp"; | |
| } | |
| if (cleanHref.endsWith(".ico")) { | |
| return "image/x-icon"; | |
| } | |
| return "image/png"; | |
| } | |
| function applyBrandingToDocument(branding: Required<BrandingConfig>) { | |
| document.title = branding.site_title; | |
| updateFavicon(branding.site_logo_url); | |
| } | |
| export function publishBrandingUpdate(value: BrandingConfig | null | undefined) { | |
| const branding = normalizeBranding(value); | |
| if (typeof window !== "undefined") { | |
| applyBrandingToDocument(branding); | |
| window.dispatchEvent(new CustomEvent(BRANDING_UPDATED_EVENT, { detail: branding })); | |
| } | |
| return branding; | |
| } | |
| export function useBranding(options: { applyDocument?: boolean; documentKey?: string | null } = {}) { | |
| const [branding, setBranding] = useState(() => normalizeBranding(null)); | |
| const [hasResolvedBranding, setHasResolvedBranding] = useState(false); | |
| const currentBrandingRef = useRef(branding); | |
| useEffect(() => { | |
| let active = true; | |
| void fetchBranding() | |
| .then((data) => { | |
| if (active) { | |
| const nextBranding = normalizeBranding(data.branding); | |
| currentBrandingRef.current = nextBranding; | |
| setBranding(nextBranding); | |
| setHasResolvedBranding(true); | |
| } | |
| }) | |
| .catch(() => undefined); | |
| return () => { | |
| active = false; | |
| }; | |
| }, []); | |
| useEffect(() => { | |
| const handleBrandingUpdate = (event: Event) => { | |
| const detail = (event as CustomEvent<BrandingConfig>).detail; | |
| const nextBranding = normalizeBranding(detail); | |
| currentBrandingRef.current = nextBranding; | |
| setBranding(nextBranding); | |
| setHasResolvedBranding(true); | |
| }; | |
| window.addEventListener(BRANDING_UPDATED_EVENT, handleBrandingUpdate); | |
| return () => window.removeEventListener(BRANDING_UPDATED_EVENT, handleBrandingUpdate); | |
| }, []); | |
| useEffect(() => { | |
| if (!options.applyDocument || !hasResolvedBranding) { | |
| return; | |
| } | |
| const apply = () => applyBrandingToDocument(branding); | |
| apply(); | |
| const animationFrame = window.requestAnimationFrame(apply); | |
| const observer = new MutationObserver(() => { | |
| const expectedTitle = currentBrandingRef.current.site_title; | |
| if (document.title !== expectedTitle) { | |
| document.title = expectedTitle; | |
| } | |
| }); | |
| observer.observe(document.head, { childList: true, subtree: true, characterData: true }); | |
| return () => { | |
| window.cancelAnimationFrame(animationFrame); | |
| observer.disconnect(); | |
| }; | |
| }, [branding, hasResolvedBranding, options.applyDocument, options.documentKey]); | |
| return branding; | |
| } | |