This commit is contained in:
@@ -7,6 +7,8 @@ import { useEnv } from '@/context/EnvContext';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useCustomFontStore } from '@/store/customFontStore';
|
||||
import { useParallelViewStore } from '@/store/parallelViewStore';
|
||||
import { useMouseEvent, useTouchEvent } from '../hooks/useIframeEvents';
|
||||
import { usePagination } from '../hooks/usePagination';
|
||||
@@ -21,7 +23,7 @@ import {
|
||||
getStyles,
|
||||
transformStylesheet,
|
||||
} from '@/utils/style';
|
||||
import { mountAdditionalFonts } from '@/utils/font';
|
||||
import { mountAdditionalFonts, mountCustomFont } from '@/styles/fonts';
|
||||
import { getBookDirFromLanguage, getBookDirFromWritingMode } from '@/utils/book';
|
||||
import { useUICSS } from '@/hooks/useUICSS';
|
||||
import {
|
||||
@@ -57,12 +59,14 @@ const FoliateViewer: React.FC<{
|
||||
config: BookConfig;
|
||||
contentInsets: Insets;
|
||||
}> = ({ bookKey, bookDoc, config, contentInsets: insets }) => {
|
||||
const { appService, envConfig } = useEnv();
|
||||
const { themeCode, isDarkMode } = useThemeStore();
|
||||
const { settings } = useSettingsStore();
|
||||
const { loadCustomFonts, getLoadedFonts } = useCustomFontStore();
|
||||
const { getView, setView: setFoliateView, setProgress } = useReaderStore();
|
||||
const { getViewSettings, setViewSettings } = useReaderStore();
|
||||
const { getParallels } = useParallelViewStore();
|
||||
const { getBookData } = useBookDataStore();
|
||||
const { appService } = useEnv();
|
||||
const { themeCode, isDarkMode } = useThemeStore();
|
||||
const viewSettings = getViewSettings(bookKey);
|
||||
|
||||
const viewRef = useRef<FoliateView | null>(null);
|
||||
@@ -79,12 +83,8 @@ const FoliateViewer: React.FC<{
|
||||
useUICSS(bookKey);
|
||||
useProgressSync(bookKey);
|
||||
useProgressAutoSave(bookKey);
|
||||
const {
|
||||
syncState,
|
||||
conflictDetails,
|
||||
resolveConflictWithLocal,
|
||||
resolveConflictWithRemote,
|
||||
} = useKOSync(bookKey);
|
||||
const { syncState, conflictDetails, resolveConflictWithLocal, resolveConflictWithRemote } =
|
||||
useKOSync(bookKey);
|
||||
useTextTranslation(bookKey, viewRef.current);
|
||||
|
||||
const progressRelocateHandler = (event: Event) => {
|
||||
@@ -144,6 +144,10 @@ const FoliateViewer: React.FC<{
|
||||
|
||||
mountAdditionalFonts(detail.doc, isCJKLang(bookData.book?.primaryLanguage));
|
||||
|
||||
getLoadedFonts().forEach((font) => {
|
||||
mountCustomFont(detail.doc, font);
|
||||
});
|
||||
|
||||
if (bookDoc.rendition?.layout === 'pre-paginated') {
|
||||
applyFixedlayoutStyles(detail.doc, viewSettings);
|
||||
}
|
||||
@@ -335,6 +339,21 @@ const FoliateViewer: React.FC<{
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [themeCode, isDarkMode, viewSettings?.overrideColor, viewSettings?.invertImgColorInDark]);
|
||||
|
||||
useEffect(() => {
|
||||
const mountCustomFonts = async () => {
|
||||
await loadCustomFonts(envConfig);
|
||||
getLoadedFonts().forEach((font) => {
|
||||
mountCustomFont(document, font);
|
||||
const docs = viewRef.current?.renderer.getContents();
|
||||
docs?.forEach(({ doc }) => mountCustomFont(doc, font));
|
||||
});
|
||||
};
|
||||
if (settings.customFonts) {
|
||||
mountCustomFonts();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [settings.customFonts, envConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
if (viewRef.current && viewRef.current.renderer) {
|
||||
doubleClickDisabled.current = !!viewSettings?.disableDoubleClick;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useFoliateEvents } from '../hooks/useFoliateEvents';
|
||||
import { getFootnoteStyles, getStyles, getThemeCode } from '@/utils/style';
|
||||
import { getPopupPosition, getPosition, Position } from '@/utils/sel';
|
||||
import { FootnoteHandler } from 'foliate-js/footnotes.js';
|
||||
import { mountAdditionalFonts } from '@/utils/font';
|
||||
import { mountAdditionalFonts } from '@/styles/fonts';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { FoliateView } from '@/types/view';
|
||||
import { isCJKLang } from '@/utils/lang';
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useDeviceControlStore } from '@/store/deviceStore';
|
||||
import { useScreenWakeLock } from '@/hooks/useScreenWakeLock';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { interceptWindowOpen } from '@/utils/open';
|
||||
import { mountAdditionalFonts } from '@/utils/font';
|
||||
import { mountAdditionalFonts } from '@/styles/fonts';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
import { getSysFontsList, setSystemUIVisibility } from '@/utils/bridge';
|
||||
import { AboutWindow } from '@/components/AboutWindow';
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import React, { useState } from 'react';
|
||||
import { MdAdd, MdDelete } from 'react-icons/md';
|
||||
import { IoMdCloseCircleOutline } from 'react-icons/io';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useCustomFontStore } from '@/store/customFontStore';
|
||||
import { FILE_SELECTION_PRESETS, useFileSelector } from '@/hooks/useFileSelector';
|
||||
import { mountCustomFont } from '@/styles/fonts';
|
||||
import { parseFontFamily } from '@/utils/font';
|
||||
import { getFilename } from '@/utils/path';
|
||||
|
||||
interface CustomFontsProps {
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
const CustomFonts: React.FC<CustomFontsProps> = ({ onBack }) => {
|
||||
const _ = useTranslation();
|
||||
const { appService, envConfig } = useEnv();
|
||||
const {
|
||||
fonts: customFonts,
|
||||
addFont,
|
||||
loadFont,
|
||||
removeFont,
|
||||
getAvailableFonts,
|
||||
saveCustomFonts,
|
||||
} = useCustomFontStore();
|
||||
const [isDeleteMode, setIsDeleteMode] = useState(false);
|
||||
|
||||
const { selectFiles } = useFileSelector(appService, _);
|
||||
|
||||
const handleImportFont = () => {
|
||||
selectFiles({ ...FILE_SELECTION_PRESETS.fonts, multiple: true }).then(async (result) => {
|
||||
if (result.error || result.files.length === 0) return;
|
||||
if (!(await appService!.fs.exists('', 'Fonts'))) {
|
||||
await appService!.fs.createDir('', 'Fonts');
|
||||
}
|
||||
for (const selectedFile of result.files) {
|
||||
let fontPath: string;
|
||||
let fontFile: File;
|
||||
if (selectedFile.path) {
|
||||
const filePath = selectedFile.path;
|
||||
fontPath = getFilename(filePath);
|
||||
await appService!.fs.copyFile(filePath, fontPath, 'Fonts');
|
||||
fontFile = await appService!.fs.openFile(fontPath, 'Fonts');
|
||||
} else if (selectedFile.file) {
|
||||
const file = selectedFile.file;
|
||||
fontPath = getFilename(file.name);
|
||||
await appService!.fs.writeFile(fontPath, 'Fonts', file);
|
||||
fontFile = file;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
const fontFamily = parseFontFamily(await fontFile.arrayBuffer(), fontPath);
|
||||
const customFont = addFont(fontPath, {
|
||||
name: fontFamily,
|
||||
});
|
||||
if (customFont && !customFont.error) {
|
||||
const loadedFont = await loadFont(envConfig, customFont.id);
|
||||
mountCustomFont(document, loadedFont);
|
||||
}
|
||||
}
|
||||
saveCustomFonts(envConfig);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteFont = (fontId: string) => {
|
||||
const font = customFonts.find((f) => f.id === fontId);
|
||||
if (font) {
|
||||
if (removeFont(fontId)) {
|
||||
appService!.fs.removeFile(font.path, 'Fonts');
|
||||
saveCustomFonts(envConfig);
|
||||
if (getAvailableFonts().length === 0) {
|
||||
setIsDeleteMode(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDeleteMode = () => {
|
||||
setIsDeleteMode(!isDeleteMode);
|
||||
};
|
||||
|
||||
const availableFonts = customFonts
|
||||
.filter((font) => !font.deletedAt)
|
||||
.sort((a, b) => (b.downloadedAt || 0) - (a.downloadedAt || 0));
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<div className='mb-6 flex h-8 items-center justify-between'>
|
||||
<div className='breadcrumbs py-1 text-sm font-medium'>
|
||||
<ul>
|
||||
<li>
|
||||
<a onClick={onBack}>{_('Font')}</a>
|
||||
</li>
|
||||
<li>{_('Custom Fonts')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
{availableFonts.length > 0 && (
|
||||
<button
|
||||
onClick={toggleDeleteMode}
|
||||
className={`btn btn-ghost btn-sm text-base-content gap-2`}
|
||||
title={isDeleteMode ? _('Cancel Delete') : _('Delete Font')}
|
||||
>
|
||||
{isDeleteMode ? (
|
||||
<>{_('Cancel')}</>
|
||||
) : (
|
||||
<>
|
||||
<MdDelete className='h-4 w-4' />
|
||||
{_('Delete')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<div className='card border-primary/50 hover:border-primary/75 group h-12 border-2 transition-colors'>
|
||||
<div
|
||||
className='card-body flex cursor-pointer items-center justify-center p-2 text-center'
|
||||
onClick={handleImportFont}
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='flex items-center justify-center'>
|
||||
<MdAdd className='text-primary/85 group-hover:text-primary h-6 w-6' />
|
||||
</div>
|
||||
<div className='text-primary/85 group-hover:text-primary font-medium'>
|
||||
{_('Import Font')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{availableFonts.map((font) => (
|
||||
<div key={font.id} className='card border-base-200 bg-base-200 h-12 border shadow-sm'>
|
||||
<div className='card-body flex items-center justify-center p-2'>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: font.loaded ? `"${font.name}", serif` : 'serif',
|
||||
fontWeight: 400,
|
||||
}}
|
||||
className='text-base-content line-clamp-1 max-w-[90%]'
|
||||
>
|
||||
{font.name}
|
||||
</div>
|
||||
{isDeleteMode && (
|
||||
<button
|
||||
onClick={() => handleDeleteFont(font.id)}
|
||||
className='btn btn-ghost btn-xs absolute right-[-10px] top-[-10px] h-6 min-h-0 w-6 p-0 hover:bg-transparent'
|
||||
title={_('Delete Font')}
|
||||
>
|
||||
<IoMdCloseCircleOutline className='text-base-content/75 h-6 w-6' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='bg-base-200/30 my-8 rounded-lg p-4'>
|
||||
<div className='text-base-content/70'>
|
||||
<div className='mb-1 text-xs font-medium'>{_('Tips')}:</div>
|
||||
<ul className='list-inside list-disc space-y-1 text-xs'>
|
||||
<li>{_('Custom fonts can be selected from the Font Face menu')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomFonts;
|
||||
@@ -14,10 +14,16 @@ interface DialogMenuProps {
|
||||
resetLabel?: string;
|
||||
}
|
||||
|
||||
const DialogMenu: React.FC<DialogMenuProps> = ({ setIsDropdownOpen, onReset, resetLabel }) => {
|
||||
const DialogMenu: React.FC<DialogMenuProps> = ({
|
||||
activePanel,
|
||||
setIsDropdownOpen,
|
||||
onReset,
|
||||
resetLabel,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const iconSize = useResponsiveSize(16);
|
||||
const { isFontLayoutSettingsGlobal, setFontLayoutSettingsGlobal } = useSettingsStore();
|
||||
const { setFontPanelView, isFontLayoutSettingsGlobal, setFontLayoutSettingsGlobal } =
|
||||
useSettingsStore();
|
||||
|
||||
const handleToggleGlobal = () => {
|
||||
setFontLayoutSettingsGlobal(!isFontLayoutSettingsGlobal);
|
||||
@@ -29,6 +35,11 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ setIsDropdownOpen, onReset, res
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
const handleManageCustomFont = () => {
|
||||
setFontPanelView('custom-fonts');
|
||||
setIsDropdownOpen?.(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
@@ -49,6 +60,9 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ setIsDropdownOpen, onReset, res
|
||||
onClick={handleToggleGlobal}
|
||||
/>
|
||||
<MenuItem label={resetLabel || _('Reset Settings')} onClick={handleResetToDefaults} />
|
||||
{activePanel === 'Font' && (
|
||||
<MenuItem label={_('Manage Custom Fonts')} onClick={handleManageCustomFont} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { MdSettings } from 'react-icons/md';
|
||||
|
||||
import {
|
||||
ANDROID_FONTS,
|
||||
CJK_EXCLUDE_PATTENS,
|
||||
CJK_FONTS_PATTENS,
|
||||
CJK_NAMES_PATTENS,
|
||||
CJK_SANS_SERIF_FONTS,
|
||||
CJK_SERIF_FONTS,
|
||||
IOS_FONTS,
|
||||
@@ -17,27 +17,33 @@ import {
|
||||
SERIF_FONTS,
|
||||
WINDOWS_FONTS,
|
||||
} from '@/services/constants';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useCustomFontStore } from '@/store/customFontStore';
|
||||
import { getOSPlatform, isCJKEnv } from '@/utils/misc';
|
||||
import { getSysFontsList } from '@/utils/bridge';
|
||||
import { isCJKStr } from '@/utils/lang';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
import { saveViewSettings } from '../../utils/viewSettingsHelper';
|
||||
import { useResetViewSettings } from '../../hooks/useResetSettings';
|
||||
import { SettingsPanelPanelProp } from './SettingsDialog';
|
||||
import NumberInput from './NumberInput';
|
||||
import FontDropdown from './FontDropDown';
|
||||
import CustomFonts from './CustomFonts';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
|
||||
const genCJKFontsList = (sysFonts: string[]) => {
|
||||
return Array.from(new Set([...sysFonts, ...CJK_SERIF_FONTS, ...CJK_SANS_SERIF_FONTS]))
|
||||
.filter((font) => CJK_FONTS_PATTENS.test(font) || CJK_NAMES_PATTENS.test(font))
|
||||
.filter((font) => CJK_FONTS_PATTENS.test(font) || isCJKStr(font))
|
||||
.filter((font) => !CJK_EXCLUDE_PATTENS.test(font))
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
};
|
||||
|
||||
const isSymbolicFontName = (font: string) =>
|
||||
/emoji|icons|symbol|dingbats|ornaments|webdings|wingdings|miuiex/i.test(font);
|
||||
|
||||
interface FontFaceProps {
|
||||
className?: string;
|
||||
family: string;
|
||||
@@ -85,8 +91,11 @@ const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
const _ = useTranslation();
|
||||
const { envConfig } = useEnv();
|
||||
const { getView, getViewSettings } = useReaderStore();
|
||||
const { fontPanelView, setFontPanelView } = useSettingsStore();
|
||||
const { fonts: allCustomFonts, getFontFamilies } = useCustomFontStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const view = getView(bookKey)!;
|
||||
const iconSize18 = useResponsiveSize(18);
|
||||
|
||||
const fontFamilyOptions = [
|
||||
{
|
||||
@@ -130,8 +139,10 @@ const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
const [sansSerifFont, setSansSerifFont] = useState(viewSettings.sansSerifFont!);
|
||||
const [monospaceFont, setMonospaceFont] = useState(viewSettings.monospaceFont!);
|
||||
const [fontWeight, setFontWeight] = useState(viewSettings.fontWeight!);
|
||||
|
||||
const [customFonts, setCustomFonts] = useState<string[]>(getFontFamilies());
|
||||
const [CJKFonts, setCJKFonts] = useState<string[]>(() => {
|
||||
return genCJKFontsList(sysFonts);
|
||||
return genCJKFontsList([...customFonts, ...sysFonts]);
|
||||
});
|
||||
|
||||
const resetToDefaults = useResetViewSettings();
|
||||
@@ -150,6 +161,14 @@ const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
});
|
||||
};
|
||||
|
||||
const handleManageCustomFonts = () => {
|
||||
setFontPanelView('custom-fonts');
|
||||
};
|
||||
|
||||
const handleBackToMain = () => {
|
||||
setFontPanelView('main-fonts');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onRegisterReset(handleReset);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -157,10 +176,14 @@ const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
|
||||
useEffect(() => {
|
||||
setCJKFonts((prev) => {
|
||||
const newFonts = genCJKFontsList(sysFonts);
|
||||
const newFonts = genCJKFontsList([...customFonts, ...sysFonts]);
|
||||
return prev.length !== newFonts.length ? newFonts : prev;
|
||||
});
|
||||
}, [sysFonts]);
|
||||
}, [customFonts, sysFonts]);
|
||||
|
||||
useEffect(() => {
|
||||
setCustomFonts(getFontFamilies());
|
||||
}, [allCustomFonts, getFontFamilies]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isTauriAppPlatform()) {
|
||||
@@ -247,6 +270,14 @@ const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
}
|
||||
};
|
||||
|
||||
if (fontPanelView === 'custom-fonts') {
|
||||
return (
|
||||
<div className='my-4 w-full'>
|
||||
<CustomFonts onBack={handleBackToMain} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='my-4 w-full space-y-6'>
|
||||
<div className='flex items-center justify-between'>
|
||||
@@ -326,14 +357,27 @@ const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>{_('Font Face')}</h2>
|
||||
<div className='mb-2 flex items-center justify-between'>
|
||||
<h2 className='font-medium'>{_('Font Face')}</h2>
|
||||
<button
|
||||
onClick={handleManageCustomFonts}
|
||||
className='btn btn-ghost btn-xs gap-1 hover:bg-transparent'
|
||||
title={_('Manage Custom Fonts')}
|
||||
>
|
||||
<MdSettings size={iconSize18} />
|
||||
</button>
|
||||
</div>
|
||||
<div className='card border-base-200 border shadow'>
|
||||
<div className='divide-base-200 divide-y'>
|
||||
<FontFace
|
||||
className='config-item-top'
|
||||
family='serif'
|
||||
label={_('Serif Font')}
|
||||
options={[...SERIF_FONTS.filter(filterNonFreeFonts), ...CJK_SERIF_FONTS]}
|
||||
options={[
|
||||
...customFonts,
|
||||
...SERIF_FONTS.filter(filterNonFreeFonts),
|
||||
...CJK_SERIF_FONTS,
|
||||
]}
|
||||
moreOptions={sysFonts}
|
||||
selected={serifFont}
|
||||
onSelect={setSerifFont}
|
||||
@@ -341,7 +385,11 @@ const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
<FontFace
|
||||
family='sans-serif'
|
||||
label={_('Sans-Serif Font')}
|
||||
options={[...SANS_SERIF_FONTS.filter(filterNonFreeFonts), ...CJK_SANS_SERIF_FONTS]}
|
||||
options={[
|
||||
...customFonts,
|
||||
...SANS_SERIF_FONTS.filter(filterNonFreeFonts),
|
||||
...CJK_SANS_SERIF_FONTS,
|
||||
]}
|
||||
moreOptions={sysFonts}
|
||||
selected={sansSerifFont}
|
||||
onSelect={setSansSerifFont}
|
||||
@@ -350,7 +398,7 @@ const FontPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
className='config-item-bottom'
|
||||
family='monospace'
|
||||
label={_('Monospace Font')}
|
||||
options={MONOSPACE_FONTS}
|
||||
options={[...customFonts, ...MONOSPACE_FONTS]}
|
||||
moreOptions={sysFonts}
|
||||
selected={monospaceFont}
|
||||
onSelect={setMonospaceFont}
|
||||
|
||||
@@ -93,6 +93,7 @@ const BookMenu: React.FC<BookMenuProps> = ({ menuClassName, setIsDropdownOpen })
|
||||
<ul className='max-h-60 overflow-y-auto'>
|
||||
{getVisibleLibrary()
|
||||
.filter((book) => book.format !== 'PDF' && book.format !== 'CBZ')
|
||||
.filter((book) => !!book.downloadedAt)
|
||||
.slice(0, 20)
|
||||
.map((book) => (
|
||||
<MenuItem
|
||||
|
||||
@@ -182,7 +182,7 @@ const TOCView: React.FC<{
|
||||
(activeItem as HTMLElement).setAttribute('aria-current', 'page');
|
||||
}
|
||||
}
|
||||
// es-lint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeHref]);
|
||||
|
||||
const virtualItemSize = useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user