87 lines
2.5 KiB
TypeScript
87 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
|
|
export function FaviconUpdater() {
|
|
useEffect(() => {
|
|
let isDisposed = false;
|
|
const mediaQuery = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
|
|
const getFaviconUrl = () => {
|
|
const html = document.documentElement;
|
|
const hasDarkClass = html.classList.contains("dark");
|
|
const hasLightClass = html.classList.contains("light");
|
|
const prefersDark = Boolean(mediaQuery?.matches);
|
|
const isDark = hasDarkClass || (!hasLightClass && prefersDark);
|
|
return isDark ? "/api/favicon?theme=dark" : "/api/favicon";
|
|
};
|
|
|
|
const updateFavicon = () => {
|
|
if (isDisposed) return;
|
|
|
|
const urlWithTheme = getFaviconUrl();
|
|
const iconType = urlWithTheme.includes("theme=dark")
|
|
? "image/png"
|
|
: "image/x-icon";
|
|
const href =
|
|
`${urlWithTheme}${urlWithTheme.includes("?") ? "&" : "?"}v=${Date.now()}`;
|
|
const managedSelector =
|
|
'link[rel="icon"],link[rel="shortcut icon"],link[rel="apple-touch-icon"]';
|
|
const existingLinks = document.querySelectorAll<HTMLLinkElement>(
|
|
managedSelector
|
|
);
|
|
|
|
existingLinks.forEach((link) => {
|
|
link.href = href;
|
|
link.setAttribute("data-managed-favicon", "true");
|
|
if (link.rel === "icon" || link.rel === "shortcut icon") {
|
|
link.type = iconType;
|
|
link.setAttribute("sizes", "any");
|
|
}
|
|
});
|
|
|
|
const ensureLink = (rel: string, type?: string) => {
|
|
let link = document.querySelector(
|
|
`link[rel="${rel}"]`
|
|
) as HTMLLinkElement | null;
|
|
if (!link) {
|
|
link = document.createElement("link");
|
|
link.rel = rel;
|
|
document.head.appendChild(link);
|
|
}
|
|
link.href = href;
|
|
link.setAttribute("data-managed-favicon", "true");
|
|
if (type) {
|
|
link.type = type;
|
|
}
|
|
if (rel === "icon") {
|
|
link.setAttribute("sizes", "any");
|
|
}
|
|
};
|
|
|
|
ensureLink("icon", iconType);
|
|
ensureLink("apple-touch-icon");
|
|
ensureLink("shortcut icon", iconType);
|
|
};
|
|
|
|
const observer = new MutationObserver(updateFavicon);
|
|
observer.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ["class"],
|
|
});
|
|
mediaQuery?.addEventListener?.("change", updateFavicon);
|
|
|
|
Promise.resolve().then(() => {
|
|
updateFavicon();
|
|
});
|
|
|
|
return () => {
|
|
isDisposed = true;
|
|
observer.disconnect();
|
|
mediaQuery?.removeEventListener?.("change", updateFavicon);
|
|
};
|
|
}, []);
|
|
|
|
return null;
|
|
}
|