import clsx from 'clsx'; import cssbeautify from 'cssbeautify'; import React, { useEffect, useRef, useState } from 'react'; import { useEnv } from '@/context/EnvContext'; import { useReaderStore } from '@/store/readerStore'; import { useSettingsStore } from '@/store/settingsStore'; import { useTranslation } from '@/hooks/useTranslation'; import { useResetViewSettings } from '../../hooks/useResetSettings'; import { SettingsPanelPanelProp } from './SettingsDialog'; import { getStyles } from '@/utils/style'; import cssValidate from '@/utils/css'; type CSSType = 'book' | 'reader'; const MiscPanel: React.FC = ({ bookKey, onRegisterReset }) => { const _ = useTranslation(); const { appService } = useEnv(); const { settings, isFontLayoutSettingsGlobal, setSettings } = useSettingsStore(); const { getView, getViewSettings, setViewSettings } = useReaderStore(); const viewSettings = getViewSettings(bookKey)!; const [draftContentStylesheet, setDraftContentStylesheet] = useState(viewSettings.userStylesheet); const [draftContentStylesheetSaved, setDraftContentStylesheetSaved] = useState(true); const [contentError, setContentError] = useState(null); const [draftUIStylesheet, setDraftUIStylesheet] = useState(viewSettings.userUIStylesheet); const [draftUIStylesheetSaved, setDraftUIStylesheetSaved] = useState(true); const [uiError, setUIError] = useState(null); const [inputFocusInAndroid, setInputFocusInAndroid] = useState(false); const contentTextareaRef = useRef(null); const uiTextareaRef = useRef(null); const resetToDefaults = useResetViewSettings(); const handleReset = () => { resetToDefaults({ userStylesheet: setDraftContentStylesheet, userUIStylesheet: setDraftUIStylesheet, }); applyStyles('book', true); applyStyles('reader', true); }; useEffect(() => { onRegisterReset(handleReset); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const validateCSS = (cssInput: string): { isValid: boolean; error?: string } => { if (!cssInput.trim()) return { isValid: true }; try { const { isValid, error } = cssValidate(cssInput); if (!isValid) { return { isValid: false, error: error || 'Invalid CSS' }; } return { isValid: true }; } catch (err: unknown) { if (err instanceof Error) { return { isValid: false, error: err.message }; } return { isValid: false, error: 'Invalid CSS: Please check your input.' }; } }; const handleStylesheetChange = (e: React.ChangeEvent, type: CSSType) => { const cssInput = e.target.value; if (type === 'book') { setDraftContentStylesheet(cssInput); setDraftContentStylesheetSaved(false); const { isValid, error } = validateCSS(cssInput); setContentError(isValid ? null : error || 'Invalid CSS'); } else { setDraftUIStylesheet(cssInput); setDraftUIStylesheetSaved(false); const { isValid, error } = validateCSS(cssInput); setUIError(isValid ? null : error || 'Invalid CSS'); } }; const applyStyles = (type: CSSType, clear = false) => { const cssInput = type === 'book' ? draftContentStylesheet : draftUIStylesheet; const formattedCSS = cssbeautify(clear ? '' : cssInput, { indent: ' ', openbrace: 'end-of-line', autosemicolon: true, }); if (type === 'book') { setDraftContentStylesheet(formattedCSS); setDraftContentStylesheetSaved(true); viewSettings.userStylesheet = formattedCSS; if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.userStylesheet = formattedCSS; setSettings(settings); } } else { setDraftUIStylesheet(formattedCSS); setDraftUIStylesheetSaved(true); viewSettings.userUIStylesheet = formattedCSS; if (isFontLayoutSettingsGlobal) { settings.globalViewSettings.userUIStylesheet = formattedCSS; setSettings(settings); } } setViewSettings(bookKey, { ...viewSettings }); getView(bookKey)?.renderer.setStyles?.(getStyles(viewSettings)); }; const handleInput = (e: React.FormEvent) => { e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); }; const handleInputFocus = (textareaRef: React.RefObject) => { if (appService?.isAndroidApp) { setInputFocusInAndroid(true); } setTimeout(() => { textareaRef.current?.scrollIntoView({ behavior: 'instant', block: 'center', }); }, 300); }; const handleInputBlur = () => { if (appService?.isAndroidApp) { setTimeout(() => { setInputFocusInAndroid(false); }, 100); } }; const renderCSSEditor = ( type: CSSType, title: string, placeholder: string, value: string, error: string | null, saved: boolean, textareaRef: React.RefObject, ) => (

{_(title)}