Add user custom style in MiscPanel
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -15,18 +15,20 @@ const ColorPanel: React.FC<{ bookKey: string }> = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className='mb-2 font-medium'>Color Settings</h2>
|
||||
<div className='mt-4 grid grid-cols-3 gap-2'>
|
||||
{themes.map((theme) => (
|
||||
<button
|
||||
key={theme}
|
||||
className={`btn ${selectedTheme === theme ? 'btn-active' : ''}`}
|
||||
onClick={() => setSelectedTheme(theme)}
|
||||
>
|
||||
{theme}
|
||||
</button>
|
||||
))}
|
||||
<div className='my-4 w-full space-y-6'>
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Color Settings</h2>
|
||||
<div className='mt-4 grid grid-cols-3 gap-2'>
|
||||
{themes.map((theme) => (
|
||||
<button
|
||||
key={theme}
|
||||
className={`btn ${selectedTheme === theme ? 'btn-active' : ''}`}
|
||||
onClick={() => setSelectedTheme(theme)}
|
||||
>
|
||||
{theme}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -134,7 +134,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
|
||||
return (
|
||||
<div className='my-4 w-full space-y-6'>
|
||||
<div className='cell-font-size w-full'>
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Font Size</h2>
|
||||
<div className='card bg-base-100 border shadow'>
|
||||
<div className='divide-y'>
|
||||
@@ -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}
|
||||
/>
|
||||
<NumberInput
|
||||
@@ -158,7 +158,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='cell-font-family w-full'>
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Font Family</h2>
|
||||
<div className='card bg-base-100 border shadow'>
|
||||
<div className='divide-y'>
|
||||
@@ -185,7 +185,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='cell-font-family w-full'>
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Font Face</h2>
|
||||
<div className='card bg-base-100 border shadow'>
|
||||
<div className='divide-y'>
|
||||
|
||||
@@ -92,7 +92,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
|
||||
return (
|
||||
<div className='my-4 w-full space-y-6'>
|
||||
<div className='cell-font-size w-full'>
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Paragraph</h2>
|
||||
<div className='card bg-base-100 border shadow'>
|
||||
<div className='divide-y'>
|
||||
@@ -127,7 +127,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='cell-font-family w-full'>
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Page</h2>
|
||||
<div className='card bg-base-100 border shadow'>
|
||||
<div className='divide-y'>
|
||||
|
||||
@@ -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<string | null>(null);
|
||||
|
||||
let cssInput = userStylesheet;
|
||||
|
||||
const validateCSS = (css: string) => {
|
||||
return cssRegex.test(css);
|
||||
};
|
||||
|
||||
const handleUserStylesheetChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
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<HTMLTextAreaElement>) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
config.viewSettings!.animated = animated;
|
||||
@@ -25,7 +72,7 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
|
||||
return (
|
||||
<div className='my-4 w-full space-y-6'>
|
||||
<div className='cell-font-size w-full'>
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Animation</h2>
|
||||
<div className='card bg-base-100 border shadow'>
|
||||
<div className='divide-y'>
|
||||
@@ -41,6 +88,26 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>Custom CSS</h2>
|
||||
<div className={`card bg-base-100 border shadow ${error ? 'border-red-500' : ''}`}>
|
||||
<div className='divide-y'>
|
||||
<div className='css-text-area config-item-top config-item-bottom p-1'>
|
||||
<textarea
|
||||
className='textarea textarea-ghost h-48 w-full border-0 p-3 !outline-none'
|
||||
placeholder='Enter your custom CSS here...'
|
||||
spellCheck='false'
|
||||
value={cssInput}
|
||||
onInput={handleInput}
|
||||
onKeyDown={handleInput}
|
||||
onKeyUp={handleInput}
|
||||
onChange={handleUserStylesheetChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -30,10 +30,10 @@ const NumberInput: React.FC<NumberInputProps> = ({
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
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<NumberInputProps> = ({
|
||||
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()}
|
||||
/>
|
||||
<button
|
||||
|
||||
Generated
+17
@@ -59,6 +59,9 @@ importers:
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
cssbeautify:
|
||||
specifier: ^0.3.1
|
||||
version: 0.3.1
|
||||
epubjs:
|
||||
specifier: ^0.3.93
|
||||
version: 0.3.93
|
||||
@@ -90,6 +93,9 @@ importers:
|
||||
'@tauri-apps/cli':
|
||||
specifier: 2.0.4
|
||||
version: 2.0.4
|
||||
'@types/cssbeautify':
|
||||
specifier: ^0.3.5
|
||||
version: 0.3.5
|
||||
'@types/node':
|
||||
specifier: ^20
|
||||
version: 20.16.6
|
||||
@@ -700,6 +706,9 @@ packages:
|
||||
'@tsconfig/node16@1.0.4':
|
||||
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
|
||||
|
||||
'@types/cssbeautify@0.3.5':
|
||||
resolution: {integrity: sha512-bkxuJdUu7Liw14EouI8IFNvJXvd5IioAZdDvlCFBXv+Sel8uPK9rcxfvOchQKqw2AtppxRvcYaWmimi0t0Yuow==}
|
||||
|
||||
'@types/estree@1.0.6':
|
||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||
|
||||
@@ -1382,6 +1391,10 @@ packages:
|
||||
css-selector-tokenizer@0.8.0:
|
||||
resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==}
|
||||
|
||||
cssbeautify@0.3.1:
|
||||
resolution: {integrity: sha512-ljnSOCOiMbklF+dwPbpooyB78foId02vUrTDogWzu6ca2DCNB7Kc/BHEGBnYOlUYtwXvSW0mWTwaiO2pwFIoRg==}
|
||||
hasBin: true
|
||||
|
||||
cssesc@3.0.0:
|
||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3863,6 +3876,8 @@ snapshots:
|
||||
'@tsconfig/node16@1.0.4':
|
||||
optional: true
|
||||
|
||||
'@types/cssbeautify@0.3.5': {}
|
||||
|
||||
'@types/estree@1.0.6': {}
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
@@ -4933,6 +4948,8 @@ snapshots:
|
||||
cssesc: 3.0.0
|
||||
fastparse: 1.1.2
|
||||
|
||||
cssbeautify@0.3.1: {}
|
||||
|
||||
cssesc@3.0.0: {}
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
Reference in New Issue
Block a user