diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json
index abda1948..39eee759 100644
--- a/apps/readest-app/package.json
+++ b/apps/readest-app/package.json
@@ -24,6 +24,7 @@
"@tauri-apps/plugin-os": "^2.0.0",
"@zip.js/zip.js": "^2.7.52",
"clsx": "^2.1.1",
+ "cssbeautify": "^0.3.1",
"epubjs": "^0.3.93",
"foliate-js": "workspace:*",
"js-md5": "^0.8.3",
@@ -36,6 +37,7 @@
},
"devDependencies": {
"@tauri-apps/cli": "2.0.4",
+ "@types/cssbeautify": "^0.3.5",
"@types/node": "^20",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
diff --git a/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx b/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx
index ce198392..d584926c 100644
--- a/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx
+++ b/apps/readest-app/src/app/reader/components/settings/ColorPanel.tsx
@@ -15,18 +15,20 @@ const ColorPanel: React.FC<{ bookKey: string }> = () => {
];
return (
-
-
Color Settings
-
- {themes.map((theme) => (
-
- ))}
+
+
+
Color Settings
+
+ {themes.map((theme) => (
+
+ ))}
+
);
diff --git a/apps/readest-app/src/app/reader/components/settings/FontPanel.tsx b/apps/readest-app/src/app/reader/components/settings/FontPanel.tsx
index 9d7936e5..b3b3213e 100644
--- a/apps/readest-app/src/app/reader/components/settings/FontPanel.tsx
+++ b/apps/readest-app/src/app/reader/components/settings/FontPanel.tsx
@@ -134,7 +134,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
return (
-
+
Font Size
@@ -143,7 +143,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
label='Default Font Size'
value={defaultFontSize}
onChange={setDefaultFontSize}
- min={1}
+ min={minFontSize}
max={120}
/>
= ({ bookKey }) => {
-
+
Font Family
@@ -185,7 +185,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
-
+
Font Face
diff --git a/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx b/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx
index 82b3836f..e0ecfd7b 100644
--- a/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx
+++ b/apps/readest-app/src/app/reader/components/settings/LayoutPanel.tsx
@@ -92,7 +92,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
return (
-
+
Paragraph
@@ -127,7 +127,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
-
+
Page
diff --git a/apps/readest-app/src/app/reader/components/settings/MiscPanel.tsx b/apps/readest-app/src/app/reader/components/settings/MiscPanel.tsx
index b34acd4c..1de6ae10 100644
--- a/apps/readest-app/src/app/reader/components/settings/MiscPanel.tsx
+++ b/apps/readest-app/src/app/reader/components/settings/MiscPanel.tsx
@@ -1,5 +1,10 @@
import React, { useEffect, useState } from 'react';
import { useReaderStore } from '@/store/readerStore';
+import cssbeautify from 'cssbeautify';
+import { getStyles } from '@/utils/style';
+
+const cssRegex =
+ /((?:\s*)([\w#.@*,:\-.:>+~\[\]\"=(),*\s]+)\s*{(?:[\s]*)((?:[A-Za-z\- \s]+[:]\s*['"0-9\w .,\/\()\-!#%]+;?)*)*\s*}(?:\s*))/gim;
const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { books, settings, setSettings, setConfig, getFoliateView } = useReaderStore();
@@ -8,6 +13,48 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const config = bookState.config!;
const [animated, setAnimated] = useState(config.viewSettings!.animated!);
+ const [userStylesheet, setUserStylesheet] = useState(config.viewSettings!.userStylesheet!);
+ const [error, setError] = useState
(null);
+
+ let cssInput = userStylesheet;
+
+ const validateCSS = (css: string) => {
+ return cssRegex.test(css);
+ };
+
+ const handleUserStylesheetChange = (e: React.ChangeEvent) => {
+ cssInput = e.target.value;
+
+ try {
+ const formattedCSS = cssbeautify(cssInput, {
+ indent: ' ',
+ openbrace: 'end-of-line',
+ autosemicolon: true,
+ });
+ setUserStylesheet(formattedCSS);
+
+ if (cssInput && !validateCSS(cssInput)) {
+ throw new Error('Invalid CSS');
+ }
+ setError(null);
+
+ config.viewSettings!.userStylesheet = formattedCSS;
+ setConfig(bookKey, config);
+ if (isFontLayoutSettingsGlobal) {
+ settings.globalViewSettings.userStylesheet = formattedCSS;
+ setSettings(settings);
+ }
+ getFoliateView(bookKey)?.renderer.setStyles?.(getStyles(config));
+ } catch (err) {
+ setError('Invalid CSS: Please check your input.');
+ console.log('CSS Error:', err);
+ }
+ };
+
+ const handleInput = (e: React.FormEvent) => {
+ e.stopPropagation();
+ e.nativeEvent.stopImmediatePropagation();
+ };
useEffect(() => {
config.viewSettings!.animated = animated;
@@ -25,7 +72,7 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
return (
-
+
Animation
@@ -41,6 +88,26 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
+
+
);
};
diff --git a/apps/readest-app/src/app/reader/components/settings/NumberInput.tsx b/apps/readest-app/src/app/reader/components/settings/NumberInput.tsx
index ccbaa0f9..67015c16 100644
--- a/apps/readest-app/src/app/reader/components/settings/NumberInput.tsx
+++ b/apps/readest-app/src/app/reader/components/settings/NumberInput.tsx
@@ -30,10 +30,10 @@ const NumberInput: React.FC
= ({
};
const handleChange = (e: React.ChangeEvent) => {
- const newValue = e.target.value === '' ? 0 : parseInt(e.target.value);
+ const newValue = e.target.value === '' ? 0 : parseFloat(e.target.value);
+ setLocalValue(newValue);
if (!isNaN(newValue)) {
const roundedValue = Math.round(newValue * 10) / 10;
- setLocalValue(roundedValue);
onChange(Math.max(min, Math.min(max, roundedValue)));
}
};
@@ -70,7 +70,7 @@ const NumberInput: React.FC = ({
onKeyDown={handleInput}
onKeyUp={handleInput}
onBlur={handleOnBlur}
- className='input input-ghost settings-content w-20 max-w-xs rounded px-3 py-1 text-right'
+ className='input input-ghost settings-content w-20 max-w-xs rounded border-0 bg-transparent px-3 py-1 text-right !outline-none'
onFocus={(e) => e.target.select()}
/>