mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(favicon): swap tab favicon for SFW vs NSFW boards
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 436 B |
@@ -7,6 +7,7 @@ import { useResolvedSubplebbitAddress } from './use-resolved-subplebbit-address'
|
|||||||
import { useAccountComment } from '@bitsocialhq/pkc-react-hooks';
|
import { useAccountComment } from '@bitsocialhq/pkc-react-hooks';
|
||||||
import useSpecialThemeStore from '../stores/use-special-theme-store';
|
import useSpecialThemeStore from '../stores/use-special-theme-store';
|
||||||
import { isChristmas } from '../lib/utils/time-utils';
|
import { isChristmas } from '../lib/utils/time-utils';
|
||||||
|
import { updateFavicon, isSfwBoard } from '../lib/update-favicon';
|
||||||
|
|
||||||
const themeClasses = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon'];
|
const themeClasses = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon'];
|
||||||
|
|
||||||
@@ -80,11 +81,26 @@ const useTheme = (): [string, (theme: string) => void] => {
|
|||||||
return storedTheme || 'yotsuba';
|
return storedTheme || 'yotsuba';
|
||||||
}, [location.pathname, isEnabled, isInAllView, isInSubscriptionsView, isInModView, subplebbitAddress, directories, themes]);
|
}, [location.pathname, isEnabled, isInAllView, isInSubscriptionsView, isInModView, subplebbitAddress, directories, themes]);
|
||||||
|
|
||||||
|
const sfw = isSfwBoard({
|
||||||
|
pathname: location.pathname,
|
||||||
|
isSpecialTheme: !!isEnabled,
|
||||||
|
isInAllView,
|
||||||
|
isInSubscriptionsView,
|
||||||
|
isInModView,
|
||||||
|
subplebbitAddress,
|
||||||
|
directories,
|
||||||
|
});
|
||||||
|
|
||||||
// Update DOM class when theme changes
|
// Update DOM class when theme changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateThemeClass(currentTheme);
|
updateThemeClass(currentTheme);
|
||||||
}, [currentTheme]);
|
}, [currentTheme]);
|
||||||
|
|
||||||
|
// Update favicon when SFW status changes (separate effect for independent lifecycle)
|
||||||
|
useEffect(() => {
|
||||||
|
updateFavicon(sfw);
|
||||||
|
}, [sfw]);
|
||||||
|
|
||||||
const setSubplebbitTheme = useCallback(
|
const setSubplebbitTheme = useCallback(
|
||||||
async (newTheme: string) => {
|
async (newTheme: string) => {
|
||||||
if (isInAllView || isInSubscriptionsView || isInModView) {
|
if (isInAllView || isInSubscriptionsView || isInModView) {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
const DEFAULT_FAVICON = '/favicon.ico';
|
||||||
|
const SFW_FAVICON = '/favicon2.ico';
|
||||||
|
|
||||||
|
let currentHref: string | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swap the tab favicon between the default (NSFW/home) and SFW variants.
|
||||||
|
* Uses remove-and-recreate to bypass aggressive browser favicon caching.
|
||||||
|
*/
|
||||||
|
export const updateFavicon = (isSfw: boolean): void => {
|
||||||
|
const href = isSfw ? SFW_FAVICON : DEFAULT_FAVICON;
|
||||||
|
if (href === currentHref) return;
|
||||||
|
currentHref = href;
|
||||||
|
|
||||||
|
const existing = document.querySelector<HTMLLinkElement>('link[rel="icon"]');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'icon';
|
||||||
|
link.href = href;
|
||||||
|
document.head.appendChild(link);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the current navigation context is a SFW board.
|
||||||
|
*
|
||||||
|
* Pure function — no hooks, no side-effects, fully testable.
|
||||||
|
*/
|
||||||
|
export const isSfwBoard = ({
|
||||||
|
pathname,
|
||||||
|
isSpecialTheme,
|
||||||
|
isInAllView,
|
||||||
|
isInSubscriptionsView,
|
||||||
|
isInModView,
|
||||||
|
subplebbitAddress,
|
||||||
|
directories,
|
||||||
|
}: {
|
||||||
|
pathname: string;
|
||||||
|
isSpecialTheme: boolean;
|
||||||
|
isInAllView: boolean;
|
||||||
|
isInSubscriptionsView: boolean;
|
||||||
|
isInModView: boolean;
|
||||||
|
subplebbitAddress: string | undefined;
|
||||||
|
directories: { address: string; nsfw?: boolean }[];
|
||||||
|
}): boolean => {
|
||||||
|
if (pathname === '/' || pathname.startsWith('/rules')) return false;
|
||||||
|
if (isSpecialTheme) return false;
|
||||||
|
if (isInAllView || isInSubscriptionsView || isInModView) return false;
|
||||||
|
|
||||||
|
if (!subplebbitAddress) return false;
|
||||||
|
|
||||||
|
const entry = directories.find((d) => d.address === subplebbitAddress);
|
||||||
|
return !entry?.nsfw;
|
||||||
|
};
|
||||||
+1
-1
@@ -39,7 +39,7 @@ export default defineConfig({
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
type: 'module',
|
type: 'module',
|
||||||
},
|
},
|
||||||
includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png'],
|
includeAssets: ['favicon.ico', 'favicon2.ico', 'robots.txt', 'apple-touch-icon.png'],
|
||||||
manifest: {
|
manifest: {
|
||||||
name: '5chan',
|
name: '5chan',
|
||||||
short_name: '5chan',
|
short_name: '5chan',
|
||||||
|
|||||||
Reference in New Issue
Block a user