fix: update error handling to avoid unused catch variables and improve code clarity

This commit is contained in:
shiyu
2025-12-18 15:51:20 +08:00
parent b582a89d08
commit 7c38c0045b
26 changed files with 130 additions and 113 deletions

View File

@@ -15,6 +15,16 @@ export default tseslint.config([
reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite,
],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'react-refresh/only-export-components': [
'error',
{
allowConstantExport: true,
allowExportNames: ['routes', 'useAuth', 'useTheme', 'useAppWindows', 'useI18n'],
},
],
},
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,

View File

@@ -49,7 +49,7 @@ async function request<T = any>(url: string, options: RequestOptions = {}): Prom
} else {
errMsg = (typeof data?.detail === 'string') ? data.detail : (data.detail ? JSON.stringify(data.detail) : JSON.stringify(data));
}
} catch (_) { }
} catch { void 0; }
throw new Error(errMsg || `Request failed: ${resp.status}`);
}

View File

@@ -107,7 +107,7 @@ export const vfsApi = {
const json = JSON.parse(xhr.responseText);
if (json.code === 0) return resolve(json.data);
return reject(new Error(json.msg || json.message || 'Upload failed'));
} catch (e) {
} catch {
return reject(new Error('Invalid response'));
}
} else {
@@ -115,7 +115,7 @@ export const vfsApi = {
try {
const json = JSON.parse(xhr.responseText);
err = json.detail || json.msg || json.message || err;
} catch (_) {}
} catch { void 0; }
reject(new Error(err));
}
}

View File

@@ -57,8 +57,8 @@ export const AppWindowsLayer: React.FC<AppWindowsLayerProps> = ({ windows, onClo
const { id, startX, startY, originX, originY } = dragRef.current;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
let newX = Math.max(0, originX + dx);
let newY = Math.max(0, originY + dy);
const newX = Math.max(0, originX + dx);
const newY = Math.max(0, originY + dy);
dragRef.current.newX = newX;
dragRef.current.newY = newY;
const el = windowEls.current[id];

View File

@@ -6,6 +6,7 @@ import { useSystemStatus } from '../../contexts/SystemContext';
export const OfficeViewerApp: React.FC<AppComponentProps> = ({ filePath, onRequestClose }) => {
const systemStatus = useSystemStatus();
const fileDomain = systemStatus?.file_domain;
const [url, setUrl] = useState<string>();
const [loading, setLoading] = useState(true);
const [err, setErr] = useState<string>();
@@ -19,7 +20,7 @@ export const OfficeViewerApp: React.FC<AppComponentProps> = ({ filePath, onReque
vfsApi.getTempLinkToken(filePath.replace(/^\/+/, ''))
.then(res => {
if (cancelled) return;
const baseUrl = systemStatus?.file_domain || window.location.origin;
const baseUrl = fileDomain || window.location.origin;
const fullUrl = new URL(res.url, baseUrl).href;
const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(fullUrl)}`;
setUrl(officeUrl);
@@ -38,7 +39,7 @@ export const OfficeViewerApp: React.FC<AppComponentProps> = ({ filePath, onReque
return () => {
cancelled = true;
};
}, [filePath]);
}, [filePath, fileDomain]);
if (loading) {
return (

View File

@@ -47,7 +47,7 @@ export const PluginAppHost: React.FC<PluginAppHostProps> = ({ plugin, filePath,
if (pluginRef.current?.unmount && containerRef.current) {
pluginRef.current.unmount(containerRef.current);
}
} catch {}
} catch { void 0; }
},
);
@@ -96,7 +96,7 @@ export const PluginAppOpenHost: React.FC<PluginAppOpenHostProps> = ({ plugin, on
const p = pluginRef.current;
if (p?.unmountApp) return p.unmountApp(containerRef.current);
if (p?.unmount) return p.unmount(containerRef.current);
} catch { }
} catch { void 0; }
},
);

View File

@@ -9,13 +9,14 @@ const MarkdownEditor = React.lazy(() => import('@uiw/react-md-editor'));
const { Header, Content } = Layout;
const MAX_PREVIEW_BYTES = 1024 * 1024; // 1MB
export const TextEditorApp: React.FC<AppComponentProps> = ({ filePath, entry, onRequestClose }) => {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [content, setContent] = useState('');
const [initialContent, setInitialContent] = useState('');
const [truncated, setTruncated] = useState(false);
const MAX_PREVIEW_BYTES = 1024 * 1024; // 1MB
const isDirty = content !== initialContent;
const onRequestCloseRef = useRef(onRequestClose);
onRequestCloseRef.current = onRequestClose;

View File

@@ -21,8 +21,7 @@ async function loadApps() {
try {
const items = await pluginsApi.list();
items.filter(p => p.enabled !== false).forEach((p) => registerPluginAsApp(p));
} catch (e) {
}
} catch { void 0; }
}
function registerPluginAsApp(p: PluginItem) {
@@ -104,5 +103,5 @@ export async function reloadPluginApps() {
: undefined;
}
});
} catch { }
} catch { void 0; }
}

View File

@@ -27,7 +27,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const res = await authApi.login({ username, password });
if (res) {
setToken(res.access_token);
try { await refreshUser(); } catch (_) {}
try { await refreshUser(); } catch { void 0; }
}
};
@@ -52,7 +52,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
} else {
setUser(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]);
return (

View File

@@ -1,4 +1,4 @@
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { ConfigProvider, theme as antdTheme } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import enUS from 'antd/locale/en_US';
@@ -85,15 +85,22 @@ function buildThemeConfig(state: ThemeState, systemDark: boolean): ThemeConfig {
const baseComponents = { ...(baseTheme.components as any) };
if (resolvedMode === 'dark' && baseComponents) {
if (baseComponents.Menu) {
const { itemHoverColor, itemHoverBg, itemSelectedBg, itemSelectedColor, ...rest } = baseComponents.Menu;
const rest = { ...baseComponents.Menu };
delete rest.itemHoverColor;
delete rest.itemHoverBg;
delete rest.itemSelectedBg;
delete rest.itemSelectedColor;
baseComponents.Menu = rest;
}
if (baseComponents.Dropdown) {
const { controlItemBgHover, ...rest } = baseComponents.Dropdown;
const rest = { ...baseComponents.Dropdown };
delete rest.controlItemBgHover;
baseComponents.Dropdown = rest;
}
if (baseComponents.Table) {
const { headerBg, rowHoverBg, ...rest } = baseComponents.Table;
const rest = { ...baseComponents.Table };
delete rest.headerBg;
delete rest.rowHoverBg;
baseComponents.Table = rest;
}
}
@@ -106,9 +113,14 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
const { lang } = useI18n();
const systemDark = useSystemDarkPreferred();
const [state, setState] = useState<ThemeState>({ mode: 'light' });
const stateRef = useRef(state);
const styleTagRef = useRef<HTMLStyleElement | null>(null);
const ensureStyleTag = () => {
useEffect(() => {
stateRef.current = state;
}, [state]);
const ensureStyleTag = useCallback(() => {
if (styleTagRef.current) return styleTagRef.current;
let styleEl = document.getElementById('foxel-custom-css') as HTMLStyleElement | null;
if (!styleEl) {
@@ -118,22 +130,22 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
}
styleTagRef.current = styleEl;
return styleEl;
};
}, []);
const applyCustomCSS = (cssText: string | null | undefined) => {
const applyCustomCSS = useCallback((cssText: string | null | undefined) => {
const el = ensureStyleTag();
el.textContent = cssText || '';
};
}, [ensureStyleTag]);
const applyHtmlDataTheme = (mode: ThemeMode) => {
const applyHtmlDataTheme = useCallback((mode: ThemeMode) => {
const finalMode = mode === 'system' ? (systemDark ? 'dark' : 'light') : mode;
document.documentElement.setAttribute('data-theme', finalMode);
};
}, [systemDark]);
const refreshTheme = async () => {
const refreshTheme = useCallback(async () => {
if (!isAuthenticated) {
applyHtmlDataTheme(state.mode || 'light');
applyCustomCSS(state.customCSS || '');
applyHtmlDataTheme(stateRef.current.mode || 'light');
applyCustomCSS(stateRef.current.customCSS || '');
return;
}
try {
@@ -147,22 +159,23 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
setState({ mode, primaryColor: primary, borderRadius: radius, customTokens, customCSS });
applyHtmlDataTheme(mode);
applyCustomCSS(customCSS);
} catch (e) {
} catch {
applyHtmlDataTheme('light');
applyCustomCSS('');
}
};
}, [applyCustomCSS, applyHtmlDataTheme, isAuthenticated]);
const previewTheme = (patch: Partial<ThemeState>) => {
const next: ThemeState = { ...state, ...patch };
const previewTheme = useCallback((patch: Partial<ThemeState>) => {
const next: ThemeState = { ...stateRef.current, ...patch };
stateRef.current = next;
setState(next);
applyHtmlDataTheme(next.mode || 'light');
applyCustomCSS(next.customCSS || '');
};
}, [applyCustomCSS, applyHtmlDataTheme]);
useEffect(() => {
refreshTheme();
}, [isAuthenticated, systemDark]);
void refreshTheme();
}, [refreshTheme]);
const themeConfig = useMemo(() => buildThemeConfig(state, systemDark), [state, systemDark]);
const resolvedMode: ThemeMode = useMemo(() => (state.mode === 'system' ? (systemDark ? 'dark' : 'light') : state.mode), [state.mode, systemDark]);
@@ -173,7 +186,7 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
previewTheme,
mode: state.mode,
resolvedMode,
}), [state.mode, resolvedMode]);
}), [previewTheme, refreshTheme, resolvedMode, state.mode]);
return (
<Ctx.Provider value={ctxValue}>

View File

@@ -30,6 +30,5 @@ export function useAsyncSafeEffect(
ac.abort();
}
};
}, deps);
}, deps); // eslint-disable-line react-hooks/exhaustive-deps
}

View File

@@ -1,4 +1,4 @@
import { createContext, useContext, useMemo, useState, useEffect } from 'react';
import { createContext, useCallback, useContext, useMemo, useState, useEffect } from 'react';
import type { PropsWithChildren } from 'react';
import en from './locales/en.json';
import zhOverrides from './locales/zh.json';
@@ -27,22 +27,22 @@ function interpolate(template: string, params?: Record<string, string | number>)
export function I18nProvider({ children }: PropsWithChildren) {
const [lang, setLangState] = useState<Lang>(() => (localStorage.getItem('lang') as Lang) || 'zh');
const setLang = (l: Lang) => {
const setLang = useCallback((l: Lang) => {
setLangState(l);
localStorage.setItem('lang', l);
};
}, []);
useEffect(() => {
document.documentElement.lang = lang;
}, [lang]);
const t = (key: string, params?: Record<string, string | number>) => {
const t = useCallback((key: string, params?: Record<string, string | number>) => {
const dict = dicts[lang] || {};
const raw = dict[key] ?? key; // fallback to key (English)
return interpolate(raw, params);
};
}, [lang]);
const value = useMemo<I18nContextValue>(() => ({ lang, setLang, t }), [lang]);
const value = useMemo<I18nContextValue>(() => ({ lang, setLang, t }), [lang, setLang, t]);
return (
<I18nContext.Provider value={value}>

View File

@@ -302,7 +302,7 @@ const SearchDialog: React.FC<SearchDialogProps> = ({ open, onClose }) => {
} else {
setHasMore(false);
}
} catch (e) {
} catch {
if (requestId !== requestIdRef.current) {
return;
}

View File

@@ -27,7 +27,7 @@ const AdaptersPage = memo(function AdaptersPage() {
} finally {
setLoading(false);
}
}, []);
}, [t]);
useEffect(() => { fetchList(); }, [fetchList]);

View File

@@ -47,28 +47,24 @@ const AuditLogsPage = memo(function AuditLogsPage() {
const [selectedLog, setSelectedLog] = useState<AuditLogItem | null>(null);
const { t } = useI18n();
const buildParams = () => {
const params: any = { ...filters };
if (!params.action) delete params.action;
if (params.success === '') delete params.success;
if (!params.username) delete params.username;
if (!params.path) delete params.path;
if (!params.start_time) delete params.start_time;
if (!params.end_time) delete params.end_time;
return params;
};
const fetchList = useCallback(async () => {
setLoading(true);
try {
const res = await auditApi.list(buildParams());
const params: any = { ...filters };
if (!params.action) delete params.action;
if (params.success === '') delete params.success;
if (!params.username) delete params.username;
if (!params.path) delete params.path;
if (!params.start_time) delete params.start_time;
if (!params.end_time) delete params.end_time;
const res = await auditApi.list(params);
setData(res);
} catch (e: any) {
message.error(e.message || t('Load failed'));
} finally {
setLoading(false);
}
}, [filters]);
}, [filters, t]);
useEffect(() => {
fetchList();

View File

@@ -1,4 +1,4 @@
import { memo, useState, useEffect } from 'react';
import { memo, useState, useEffect, useCallback } from 'react';
import { Modal, Radio, message, Button, Typography, Input, Space } from 'antd';
import { CopyOutlined, FileMarkdownOutlined } from '@ant-design/icons';
import type { VfsEntry } from '../../../../api/client';
@@ -33,14 +33,7 @@ export const DirectLinkModal = memo(function DirectLinkModal({ entry, path, open
const [link, setLink] = useState('');
const { t } = useI18n();
useEffect(() => {
if (open && entry) {
setLink('');
generateLink();
}
}, [open, entry, expiresIn]);
const generateLink = async () => {
const generateLink = useCallback(async () => {
if (!entry) return;
setLoading(true);
try {
@@ -57,7 +50,14 @@ export const DirectLinkModal = memo(function DirectLinkModal({ entry, path, open
} finally {
setLoading(false);
}
};
}, [entry, expiresIn, path, t]);
useEffect(() => {
if (open && entry) {
setLink('');
void generateLink();
}
}, [open, entry, generateLink]);
const handleCopy = (text: string) => {
navigator.clipboard.writeText(text);

View File

@@ -41,9 +41,7 @@ export function MoveCopyModal({ mode, entries, open, defaultPath, onOk, onCancel
try {
await onOk(trimmed);
onCancel();
} catch (e) {
// 上层已处理提示,这里只需保持对话框
} finally {
} catch { void 0; } finally {
setLoading(false);
}
};

View File

@@ -47,7 +47,7 @@ export function useAppWindows(path: string) {
}
const defaultApp = getDefaultAppForEntry(entry) || apps[0];
openWithApp(entry, defaultApp);
}, [openWithApp]);
}, [openWithApp, t]);
const confirmOpenWithApp = useCallback((entry: VfsEntry, appKey: string) => {
const app = getAppByKey(appKey);
@@ -72,7 +72,7 @@ export function useAppWindows(path: string) {
openWithApp(entry, app);
}
});
}, [openWithApp]);
}, [openWithApp, t]);
const closeWindow = (id: string) => setAppWindows(ws => ws.filter(w => w.id !== id));
const toggleMax = (id: string) => setAppWindows(ws => ws.map(w => w.id === id ? { ...w, maximized: !w.maximized } : w));

View File

@@ -35,7 +35,7 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
} catch (e: any) {
message.error(e.message);
}
}, [path, refresh]);
}, [path, refresh, t]);
const doDelete = useCallback(async (entries: VfsEntry[]) => {
Modal.confirm({
@@ -51,7 +51,7 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
}
}
});
}, [path, refresh, clearSelection]);
}, [path, refresh, clearSelection, t]);
const doRename = useCallback(async (entry: VfsEntry, newName: string) => {
if (!newName.trim() || newName.trim() === entry.name) {
@@ -173,7 +173,7 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
} catch (e: any) {
message.error(e.message || t('Download failed'));
}
}, [path]);
}, [path, t]);
const doShare = useCallback((entries: VfsEntry[]) => {
if (entries.length === 0) {
@@ -181,7 +181,7 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
return;
}
onShare(entries);
}, [onShare]);
}, [onShare, t]);
const doGetDirectLink = useCallback((entry: VfsEntry) => {
if (entry.is_dir) {
@@ -189,7 +189,7 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
return;
}
onGetDirectLink(entry);
}, [onGetDirectLink]);
}, [onGetDirectLink, t]);
return {
doCreateDir,

View File

@@ -58,7 +58,7 @@ export function useProcessor({ path, processorTypes, refresh }: ProcessorParams)
} finally {
setLoading(false);
}
}, [modal.entry, selectedProcessor, processorTypes, config, path, overwrite, savingPath, refresh]);
}, [modal.entry, selectedProcessor, processorTypes, config, path, overwrite, savingPath, refresh, t]);
const handleCancel = useCallback(() => {
setModal({ entry: null, visible: false });

View File

@@ -1,4 +1,4 @@
import { memo, useEffect, useMemo, useState } from 'react';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { Button, Modal, Form, Input, Tag, message, Card, Typography, Popconfirm, Empty, Skeleton, theme, Divider, Tabs, Select, Pagination } from 'antd';
import { GithubOutlined, LinkOutlined } from '@ant-design/icons';
import { pluginsApi, type PluginItem } from '../api/plugins';
@@ -38,7 +38,7 @@ const PluginsPage = memo(function PluginsPage() {
try {
await ensureAppsLoaded();
setSystemApps(listSystemApps());
} catch {}
} catch { void 0; }
})();
}, []);
@@ -48,23 +48,23 @@ const PluginsPage = memo(function PluginsPage() {
return set;
}, [data]);
const reloadRepo = async () => {
const reloadRepo = useCallback(async () => {
try {
setRepoLoading(true);
const res = await fetchRepoList({ query: repoQ || undefined, sort: repoSort, page: repoPage, pageSize: repoPageSize });
setRepoItems(res.items || []);
setRepoTotal(res.total || 0);
} catch (e) {
} catch {
setRepoItems([]);
setRepoTotal(0);
} finally {
setRepoLoading(false);
}
};
}, [repoPage, repoPageSize, repoQ, repoSort]);
useEffect(() => {
if (tab === 'discover') reloadRepo();
}, [tab, repoQ, repoSort, repoPage, repoPageSize]);
}, [reloadRepo, tab]);
const handleAdd = async () => {
try {
@@ -73,13 +73,13 @@ const PluginsPage = memo(function PluginsPage() {
try {
const p = await loadPlugin(created);
await ensureManifest(created.id, p);
} catch {}
} catch { void 0; }
setAdding(false);
form.resetFields();
await reload();
await reloadPluginApps();
message.success(t('Installed successfully'));
} catch {}
} catch { void 0; }
};
const filtered = useMemo(() => {
@@ -290,7 +290,7 @@ const PluginsPage = memo(function PluginsPage() {
try {
const p = await loadPlugin(created);
await ensureManifest(created.id, p);
} catch {}
} catch { void 0; }
await reload();
await reloadPluginApps();
message.success(t('Installed successfully'));

View File

@@ -22,19 +22,19 @@ export const DirectoryViewer = memo(function DirectoryViewer({ token, shareInfo,
const [error, setError] = useState('');
const { t } = useI18n();
const loadData = useCallback(async (p: string) => {
setLoading(true);
setError('');
try {
const listing = await shareApi.listDir(token, p, password);
setEntries(listing.entries || []);
setCurrentPath(p);
} catch (e: any) {
setError(e.message || t('Share load failed'));
} finally {
setLoading(false);
}
}, [token, password]);
const loadData = useCallback(async (p: string) => {
setLoading(true);
setError('');
try {
const listing = await shareApi.listDir(token, p, password);
setEntries(listing.entries || []);
setCurrentPath(p);
} catch (e: any) {
setError(e.message || t('Share load failed'));
} finally {
setLoading(false);
}
}, [password, t, token]);
useEffect(() => {
loadData(currentPath);

View File

@@ -17,10 +17,10 @@ const PublicSharePage = memo(function PublicSharePage() {
const [verified, setVerified] = useState(false);
const { t } = useI18n();
const loadData = useCallback(async (pwd?: string) => {
if (!token) return;
setLoading(true);
setError('');
const loadData = useCallback(async (pwd?: string) => {
if (!token) return;
setLoading(true);
setError('');
try {
let info = shareInfo;
if (!info) {
@@ -50,10 +50,10 @@ const PublicSharePage = memo(function PublicSharePage() {
if (e.message === '需要密码') {
setVerified(false);
}
} finally {
setLoading(false);
}
}, [token, shareInfo, password, verified]);
} finally {
setLoading(false);
}
}, [password, shareInfo, t, token, verified]);
useEffect(() => {
loadData();

View File

@@ -23,7 +23,7 @@ const SharePage = memo(function SharePage() {
} finally {
setLoading(false);
}
}, []);
}, [t]);
useEffect(() => { fetchList(); }, [fetchList]);

View File

@@ -78,7 +78,7 @@ function parseEmailConfig(raw?: string | null): EmailFormValues {
timeout: data?.timeout !== undefined ? Number(data.timeout) : DEFAULT_FORM.timeout,
security: (data?.security ?? DEFAULT_FORM.security) as EmailFormValues['security'],
};
} catch (_err) {
} catch {
return { ...DEFAULT_FORM };
}
}

View File

@@ -133,12 +133,13 @@ export async function ensureManifest(pluginId: number, plugin: RegisteredPlugin)
website: plugin.website,
github: plugin.github,
};
try { console.debug('[foxel] report manifest', pluginId, manifest); } catch { }
try { console.debug('[foxel] report manifest', pluginId, manifest); } catch { void 0; }
const key = `foxel:manifestReported:${pluginId}`;
if (sessionStorage.getItem(key) === '1') return;
try {
await pluginsApi.updateManifest(pluginId, manifest);
sessionStorage.setItem(key, '1');
} catch {
void 0;
}
}