feat: reading custom css from external file (#2012)

* feat: reading custom css from external file

---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
This commit is contained in:
Simon Bråten
2026-05-25 23:53:53 +02:00
committed by GitHub
parent 22d37135ae
commit f098f848a3
5 changed files with 253 additions and 4 deletions
+73 -1
View File
@@ -15,7 +15,12 @@ import { useCheckForUpdates } from '/@/renderer/hooks/use-check-for-updates';
import { useNativeMenuSync } from '/@/renderer/hooks/use-native-menu-sync';
import { useSyncSettingsToMain } from '/@/renderer/hooks/use-sync-settings-to-main';
import { AppRouter } from '/@/renderer/router/app-router';
import { useCssSettings, useHotkeySettings, useLanguage } from '/@/renderer/store';
import {
useCssSettings,
useHotkeySettings,
useLanguage,
useSettingsStoreActions,
} from '/@/renderer/store';
import { useAppTheme } from '/@/renderer/themes/use-app-theme';
import { sanitizeCss } from '/@/renderer/utils/sanitize';
import { WebAudio } from '/@/shared/types/types';
@@ -31,6 +36,7 @@ const UpdateAvailableDialog = lazy(() =>
);
const ipc = isElectron() ? window.api.ipc : null;
const utils = isElectron() ? window.api.utils : null;
export const App = () => {
return <ThemedApp />;
@@ -89,6 +95,7 @@ const AppEffects = () => (
<>
<SyncSettingsEffect />
<UpdateCheckEffect />
<CustomCssFileEffect />
<CssSettingsEffect />
<GlobalShortcutsEffect />
<LanguageEffect />
@@ -142,6 +149,71 @@ const CssSettingsEffect = () => {
return null;
};
const CustomCssFileEffect = () => {
const { setSettings } = useSettingsStoreActions();
const { content } = useCssSettings();
const latestContentRef = useRef(content);
useEffect(() => {
latestContentRef.current = content;
}, [content]);
useEffect(() => {
if (!isElectron() || !utils) return;
let disposed = false;
const applyContent = (rawContent: string | undefined) => {
const sanitized = sanitizeCss(`<style>${rawContent ?? ''}`);
if (sanitized !== latestContentRef.current) {
setSettings({
css: {
content: sanitized,
},
});
}
};
const loadCustomCss = async () => {
try {
const result = await utils.getCustomCss();
if (disposed || !result) return;
if (!result.exists && latestContentRef.current) {
await utils.saveCustomCss(latestContentRef.current);
return;
}
applyContent(result.content);
} catch (error) {
console.error('Failed to load custom css', error);
}
};
const handleCustomCssUpdated = (data: { content?: string; exists?: boolean }) => {
if (disposed) return;
if (data?.exists === false) {
applyContent('');
return;
}
applyContent(data?.content);
};
const removeCustomCssUpdatedListener =
utils.customCssUpdatedListener(handleCustomCssUpdated);
loadCustomCss();
return () => {
disposed = true;
removeCustomCssUpdatedListener();
};
}, [setSettings]);
return null;
};
const GlobalShortcutsEffect = () => {
const { bindings } = useHotkeySettings();
@@ -1,3 +1,4 @@
import isElectron from 'is-electron';
import { memo, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -14,18 +15,39 @@ export const StylesSettings = memo(() => {
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const utils = isElectron() ? window.api.utils : null;
const isDesktop = isElectron();
const { content, enabled } = useCssSettings();
const [css, setCss] = useState(content);
const { setSettings } = useSettingsStoreActions();
const handleSave = () => {
const handleSave = async () => {
setSettings({
css: {
content: css,
enabled,
},
});
if (utils) {
try {
await utils.saveCustomCss(css);
} catch (error) {
console.error('Failed to save custom css file', error);
}
}
};
const handleOpenFolder = async () => {
if (!utils) return;
try {
await utils.openCustomCssFolder();
} catch (error) {
console.error('Failed to open custom css folder', error);
}
};
useEffect(() => {
@@ -62,6 +84,15 @@ export const StylesSettings = memo(() => {
<SettingsOptions
control={
<>
{isDesktop && (
<Button
onClick={handleOpenFolder}
size="compact-md"
variant="subtle"
>
{t('common.openFolder', { postProcess: 'titleCase' })}
</Button>
)}
{open && (
<Button
onClick={handleSave}