This commit is contained in:
@@ -19,7 +19,6 @@ import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { eventDispatcher } from '@/utils/event';
|
||||
import { saveViewSettings } from '../utils/viewSettingsHelper';
|
||||
import { PageInfo } from '@/types/book';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import Button from '@/components/Button';
|
||||
import Slider from '@/components/Slider';
|
||||
|
||||
@@ -59,14 +58,13 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
|
||||
const handleFontSizeChange = (value: number) => {
|
||||
saveViewSettings(envConfig, bookKey, 'defaultFontSize', value);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings!));
|
||||
};
|
||||
|
||||
const handleMarginChange = (value: number) => {
|
||||
const marginPx = Math.round((value / 100) * 88);
|
||||
const gapPercent = Math.round((value / 100) * 10);
|
||||
saveViewSettings(envConfig, bookKey, 'marginPx', marginPx);
|
||||
saveViewSettings(envConfig, bookKey, 'gapPercent', gapPercent);
|
||||
saveViewSettings(envConfig, bookKey, 'marginPx', marginPx, false, false);
|
||||
saveViewSettings(envConfig, bookKey, 'gapPercent', gapPercent, false, false);
|
||||
view?.renderer.setAttribute('margin', `${marginPx}px`);
|
||||
view?.renderer.setAttribute('gap', `${gapPercent}%`);
|
||||
if (viewSettings?.scrolled) {
|
||||
@@ -76,7 +74,6 @@ const FooterBar: React.FC<FooterBarProps> = ({
|
||||
|
||||
const handleLineHeightChange = (value: number) => {
|
||||
saveViewSettings(envConfig, bookKey, 'lineHeight', value / 10);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings!));
|
||||
};
|
||||
|
||||
const handleGoPrev = () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ThemeMode } from '@/styles/themes';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import { getMaxInlineSize } from '@/utils/config';
|
||||
import { tauriHandleToggleFullScreen } from '@/utils/window';
|
||||
import { saveViewSettings } from '../utils/viewSettingsHelper';
|
||||
import MenuItem from '@/components/MenuItem';
|
||||
|
||||
interface ViewMenuProps {
|
||||
@@ -28,11 +29,11 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
onSetSettingsDialogOpen,
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const { getView, getViews, getViewSettings, setViewSettings } = useReaderStore();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { getView, getViewSettings, setViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
|
||||
const { themeMode, themeCode, setThemeMode } = useThemeStore();
|
||||
const { themeMode, setThemeMode } = useThemeStore();
|
||||
const [isScrolledMode, setScrolledMode] = useState(viewSettings!.scrolled);
|
||||
const [zoomLevel, setZoomLevel] = useState(viewSettings!.zoomLevel!);
|
||||
|
||||
@@ -58,13 +59,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getViews().forEach((view) => {
|
||||
view.renderer.setStyles?.(getStyles(viewSettings!));
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [themeCode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isScrolledMode === viewSettings!.scrolled) return;
|
||||
viewSettings!.scrolled = isScrolledMode;
|
||||
getView(bookKey)?.renderer.setAttribute('flow', isScrolledMode ? 'scrolled' : 'paginated');
|
||||
getView(bookKey)?.renderer.setAttribute(
|
||||
@@ -77,11 +72,7 @@ const ViewMenu: React.FC<ViewMenuProps> = ({
|
||||
}, [isScrolledMode]);
|
||||
|
||||
useEffect(() => {
|
||||
const view = getView(bookKey);
|
||||
if (!view) return;
|
||||
viewSettings!.zoomLevel = zoomLevel;
|
||||
setViewSettings(bookKey, viewSettings!);
|
||||
view.renderer.setStyles?.(getStyles(viewSettings!));
|
||||
saveViewSettings(envConfig, bookKey, 'zoomLevel', zoomLevel, true, true);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [zoomLevel]);
|
||||
|
||||
|
||||
@@ -12,43 +12,25 @@ import {
|
||||
Theme,
|
||||
themes,
|
||||
} from '@/styles/themes';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useThemeStore } from '@/store/themeStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import ThemeEditor from './ThemeEditor';
|
||||
|
||||
const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const ColorPanel: React.FC<{ bookKey: string }> = ({}) => {
|
||||
const _ = useTranslation();
|
||||
const {
|
||||
themeMode,
|
||||
themeColor,
|
||||
themeCode,
|
||||
isDarkMode,
|
||||
setThemeMode,
|
||||
setThemeColor,
|
||||
saveCustomTheme,
|
||||
} = useThemeStore();
|
||||
const { themeMode, themeColor, isDarkMode, setThemeMode, setThemeColor, saveCustomTheme } =
|
||||
useThemeStore();
|
||||
const { envConfig } = useEnv();
|
||||
const { settings, setSettings } = useSettingsStore();
|
||||
const { getViews, getViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const iconSize16 = useResponsiveSize(16);
|
||||
const iconSize24 = useResponsiveSize(24);
|
||||
const [editTheme, setEditTheme] = useState<CustomTheme | null>(null);
|
||||
const [customThems, setCustomThemes] = useState<Theme[]>([]);
|
||||
const [showCustomThemeEditor, setShowCustomThemeEditor] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getViews().forEach((view) => {
|
||||
view.renderer.setStyles?.(getStyles(viewSettings));
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [themeCode]);
|
||||
|
||||
useEffect(() => {
|
||||
const customThemes = settings.globalReadSettings.customThemes ?? [];
|
||||
setCustomThemes(
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { getStyles } from '@/utils/style';
|
||||
import { getOSPlatform } from '@/utils/misc';
|
||||
import { getSysFontsList } from '@/utils/font';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
@@ -64,8 +63,7 @@ const FontFace = ({
|
||||
const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const _ = useTranslation();
|
||||
const { envConfig, appService } = useEnv();
|
||||
const { getView, getViewSettings } = useReaderStore();
|
||||
const view = getView(bookKey);
|
||||
const { getViewSettings } = useReaderStore();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
|
||||
const fontFamilyOptions = [
|
||||
@@ -121,49 +119,41 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'defaultFont', defaultFont);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultFont]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'defaultFontSize', defaultFontSize);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultFontSize]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'minimumFontSize', minFontSize);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minFontSize]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'fontWeight', fontWeight);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fontWeight]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'serifFont', serifFont);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [serifFont]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'sansSerifFont', sansSerifFont);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sansSerifFont]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'monospaceFont', monospaceFont);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [monospaceFont]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'overrideFont', overrideFont);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [overrideFont]);
|
||||
|
||||
|
||||
@@ -46,54 +46,49 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'paragraphMargin', paragraphMargin);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [paragraphMargin]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'lineHeight', lineHeight);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [lineHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'wordSpacing', wordSpacing);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wordSpacing]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'letterSpacing', letterSpacing);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [letterSpacing]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'textIndent', textIndent);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [textIndent]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'fullJustification', fullJustification);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fullJustification]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'hyphenation', hyphenation);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hyphenation]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'marginPx', marginPx);
|
||||
if (marginPx === viewSettings.marginPx) return;
|
||||
saveViewSettings(envConfig, bookKey, 'marginPx', marginPx, false, false);
|
||||
view?.renderer.setAttribute('margin', `${marginPx}px`);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [marginPx]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'gapPercent', gapPercent);
|
||||
if (gapPercent === viewSettings.gapPercent) return;
|
||||
saveViewSettings(envConfig, bookKey, 'gapPercent', gapPercent, false, false);
|
||||
view?.renderer.setAttribute('gap', `${gapPercent}%`);
|
||||
if (viewSettings.scrolled) {
|
||||
view?.renderer.setAttribute('flow', 'scrolled');
|
||||
@@ -102,25 +97,29 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
}, [gapPercent]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'maxColumnCount', maxColumnCount);
|
||||
if (maxColumnCount === viewSettings.maxColumnCount) return;
|
||||
saveViewSettings(envConfig, bookKey, 'maxColumnCount', maxColumnCount, false, false);
|
||||
view?.renderer.setAttribute('max-column-count', maxColumnCount);
|
||||
view?.renderer.setAttribute('max-inline-size', `${getMaxInlineSize(viewSettings)}px`);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [maxColumnCount]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'maxInlineSize', maxInlineSize);
|
||||
if (maxInlineSize === viewSettings.maxInlineSize) return;
|
||||
saveViewSettings(envConfig, bookKey, 'maxInlineSize', maxInlineSize, false, false);
|
||||
view?.renderer.setAttribute('max-inline-size', `${getMaxInlineSize(viewSettings)}px`);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [maxInlineSize]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'maxBlockSize', maxBlockSize);
|
||||
if (maxBlockSize === viewSettings.maxBlockSize) return;
|
||||
saveViewSettings(envConfig, bookKey, 'maxBlockSize', maxBlockSize, false, false);
|
||||
view?.renderer.setAttribute('max-block-size', `${maxBlockSize}px`);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [maxBlockSize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (writingMode === viewSettings.writingMode) return;
|
||||
// global settings are not supported for writing mode
|
||||
const prevWritingMode = viewSettings.writingMode;
|
||||
if (!writingMode.includes('vertical')) {
|
||||
@@ -143,11 +142,11 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'overrideLayout', overrideLayout);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [overrideLayout]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isScrolledMode === viewSettings.scrolled) return;
|
||||
saveViewSettings(envConfig, bookKey, 'scrolled', isScrolledMode);
|
||||
getView(bookKey)?.renderer.setAttribute('flow', isScrolledMode ? 'scrolled' : 'paginated');
|
||||
getView(bookKey)?.renderer.setAttribute(
|
||||
@@ -159,6 +158,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
}, [isScrolledMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (doubleBorder === viewSettings.doubleBorder) return;
|
||||
if (doubleBorder && viewSettings.vertical) {
|
||||
viewSettings.gapPercent = Math.max(
|
||||
viewSettings.gapPercent,
|
||||
@@ -167,16 +167,17 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
setGapPercent(viewSettings.gapPercent);
|
||||
setViewSettings(bookKey, viewSettings);
|
||||
}
|
||||
saveViewSettings(envConfig, bookKey, 'doubleBorder', doubleBorder);
|
||||
saveViewSettings(envConfig, bookKey, 'doubleBorder', doubleBorder, false, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [doubleBorder]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'borderColor', borderColor);
|
||||
saveViewSettings(envConfig, bookKey, 'borderColor', borderColor, false, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [borderColor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showHeader === viewSettings.showHeader) return;
|
||||
if (showHeader && !viewSettings.vertical) {
|
||||
viewSettings.marginPx = Math.max(viewSettings.marginPx, 44);
|
||||
setMarginPx(viewSettings.marginPx);
|
||||
@@ -189,11 +190,12 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
setGapPercent(viewSettings.gapPercent);
|
||||
setViewSettings(bookKey, viewSettings);
|
||||
}
|
||||
saveViewSettings(envConfig, bookKey, 'showHeader', showHeader);
|
||||
saveViewSettings(envConfig, bookKey, 'showHeader', showHeader, false, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [showHeader]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showFooter === viewSettings.showFooter) return;
|
||||
if (showFooter && !viewSettings.vertical) {
|
||||
viewSettings.marginPx = Math.max(viewSettings.marginPx, 44);
|
||||
setMarginPx(viewSettings.marginPx);
|
||||
@@ -206,7 +208,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
setGapPercent(viewSettings.gapPercent);
|
||||
setViewSettings(bookKey, viewSettings);
|
||||
}
|
||||
saveViewSettings(envConfig, bookKey, 'showFooter', showFooter);
|
||||
saveViewSettings(envConfig, bookKey, 'showFooter', showFooter, false, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [showFooter]);
|
||||
|
||||
|
||||
@@ -114,13 +114,12 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
};
|
||||
|
||||
const handleSelectUILang = (option: string) => {
|
||||
viewSettings.uiLanguage = option;
|
||||
saveViewSettings(envConfig, bookKey, 'uiLanguage', option);
|
||||
saveViewSettings(envConfig, bookKey, 'uiLanguage', option, false, false);
|
||||
i18n.changeLanguage(option ? option : navigator.language);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'animated', animated);
|
||||
saveViewSettings(envConfig, bookKey, 'animated', animated, false, false);
|
||||
if (animated) {
|
||||
getView(bookKey)?.renderer.setAttribute('animated', '');
|
||||
} else {
|
||||
@@ -130,17 +129,17 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
}, [animated]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'disableClick', isDisableClick);
|
||||
saveViewSettings(envConfig, bookKey, 'disableClick', isDisableClick, false, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isDisableClick]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'swapClickArea', swapClickArea);
|
||||
saveViewSettings(envConfig, bookKey, 'swapClickArea', swapClickArea, false, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [swapClickArea]);
|
||||
|
||||
useEffect(() => {
|
||||
saveViewSettings(envConfig, bookKey, 'continuousScroll', isContinuousScroll);
|
||||
saveViewSettings(envConfig, bookKey, 'continuousScroll', isContinuousScroll, false, false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isContinuousScroll]);
|
||||
|
||||
|
||||
@@ -3,21 +3,29 @@ import { EnvConfigType } from '@/services/environment';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { getStyles } from '@/utils/style';
|
||||
|
||||
export const saveViewSettings = async <K extends keyof ViewSettings>(
|
||||
envConfig: EnvConfigType,
|
||||
bookKey: string,
|
||||
key: K,
|
||||
value: ViewSettings[K],
|
||||
skipGlobal?: boolean,
|
||||
skipGlobal = false,
|
||||
applyStyles = true,
|
||||
) => {
|
||||
const { settings, isFontLayoutSettingsGlobal, setSettings, saveSettings } =
|
||||
useSettingsStore.getState();
|
||||
const { getViewSettings, setViewSettings } = useReaderStore.getState();
|
||||
const { getView, getViewSettings, setViewSettings } = useReaderStore.getState();
|
||||
const { getConfig, saveConfig } = useBookDataStore.getState();
|
||||
const viewSettings = getViewSettings(bookKey)!;
|
||||
const config = getConfig(bookKey)!;
|
||||
viewSettings[key] = value;
|
||||
if (viewSettings[key] !== value) {
|
||||
viewSettings[key] = value;
|
||||
if (applyStyles) {
|
||||
const view = getView(bookKey);
|
||||
view?.renderer.setStyles?.(getStyles(viewSettings));
|
||||
}
|
||||
}
|
||||
setViewSettings(bookKey, viewSettings);
|
||||
|
||||
if (isFontLayoutSettingsGlobal && !skipGlobal) {
|
||||
|
||||
Reference in New Issue
Block a user