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
+10
View File
@@ -15,6 +15,16 @@ export default tseslint.config([
reactHooks.configs['recommended-latest'], reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite, 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: { languageOptions: {
ecmaVersion: 2020, ecmaVersion: 2020,
globals: globals.browser, globals: globals.browser,
+1 -1
View File
@@ -49,7 +49,7 @@ async function request<T = any>(url: string, options: RequestOptions = {}): Prom
} else { } else {
errMsg = (typeof data?.detail === 'string') ? data.detail : (data.detail ? JSON.stringify(data.detail) : JSON.stringify(data)); 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}`); throw new Error(errMsg || `Request failed: ${resp.status}`);
} }
+2 -2
View File
@@ -107,7 +107,7 @@ export const vfsApi = {
const json = JSON.parse(xhr.responseText); const json = JSON.parse(xhr.responseText);
if (json.code === 0) return resolve(json.data); if (json.code === 0) return resolve(json.data);
return reject(new Error(json.msg || json.message || 'Upload failed')); return reject(new Error(json.msg || json.message || 'Upload failed'));
} catch (e) { } catch {
return reject(new Error('Invalid response')); return reject(new Error('Invalid response'));
} }
} else { } else {
@@ -115,7 +115,7 @@ export const vfsApi = {
try { try {
const json = JSON.parse(xhr.responseText); const json = JSON.parse(xhr.responseText);
err = json.detail || json.msg || json.message || err; err = json.detail || json.msg || json.message || err;
} catch (_) {} } catch { void 0; }
reject(new Error(err)); reject(new Error(err));
} }
} }
+2 -2
View File
@@ -57,8 +57,8 @@ export const AppWindowsLayer: React.FC<AppWindowsLayerProps> = ({ windows, onClo
const { id, startX, startY, originX, originY } = dragRef.current; const { id, startX, startY, originX, originY } = dragRef.current;
const dx = e.clientX - startX; const dx = e.clientX - startX;
const dy = e.clientY - startY; const dy = e.clientY - startY;
let newX = Math.max(0, originX + dx); const newX = Math.max(0, originX + dx);
let newY = Math.max(0, originY + dy); const newY = Math.max(0, originY + dy);
dragRef.current.newX = newX; dragRef.current.newX = newX;
dragRef.current.newY = newY; dragRef.current.newY = newY;
const el = windowEls.current[id]; const el = windowEls.current[id];
+3 -2
View File
@@ -6,6 +6,7 @@ import { useSystemStatus } from '../../contexts/SystemContext';
export const OfficeViewerApp: React.FC<AppComponentProps> = ({ filePath, onRequestClose }) => { export const OfficeViewerApp: React.FC<AppComponentProps> = ({ filePath, onRequestClose }) => {
const systemStatus = useSystemStatus(); const systemStatus = useSystemStatus();
const fileDomain = systemStatus?.file_domain;
const [url, setUrl] = useState<string>(); const [url, setUrl] = useState<string>();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [err, setErr] = useState<string>(); const [err, setErr] = useState<string>();
@@ -19,7 +20,7 @@ export const OfficeViewerApp: React.FC<AppComponentProps> = ({ filePath, onReque
vfsApi.getTempLinkToken(filePath.replace(/^\/+/, '')) vfsApi.getTempLinkToken(filePath.replace(/^\/+/, ''))
.then(res => { .then(res => {
if (cancelled) return; 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 fullUrl = new URL(res.url, baseUrl).href;
const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(fullUrl)}`; const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(fullUrl)}`;
setUrl(officeUrl); setUrl(officeUrl);
@@ -38,7 +39,7 @@ export const OfficeViewerApp: React.FC<AppComponentProps> = ({ filePath, onReque
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [filePath]); }, [filePath, fileDomain]);
if (loading) { if (loading) {
return ( return (
+2 -2
View File
@@ -47,7 +47,7 @@ export const PluginAppHost: React.FC<PluginAppHostProps> = ({ plugin, filePath,
if (pluginRef.current?.unmount && containerRef.current) { if (pluginRef.current?.unmount && containerRef.current) {
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; const p = pluginRef.current;
if (p?.unmountApp) return p.unmountApp(containerRef.current); if (p?.unmountApp) return p.unmountApp(containerRef.current);
if (p?.unmount) return p.unmount(containerRef.current); if (p?.unmount) return p.unmount(containerRef.current);
} catch { } } catch { void 0; }
}, },
); );
+2 -1
View File
@@ -9,13 +9,14 @@ const MarkdownEditor = React.lazy(() => import('@uiw/react-md-editor'));
const { Header, Content } = Layout; const { Header, Content } = Layout;
const MAX_PREVIEW_BYTES = 1024 * 1024; // 1MB
export const TextEditorApp: React.FC<AppComponentProps> = ({ filePath, entry, onRequestClose }) => { export const TextEditorApp: React.FC<AppComponentProps> = ({ filePath, entry, onRequestClose }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [content, setContent] = useState(''); const [content, setContent] = useState('');
const [initialContent, setInitialContent] = useState(''); const [initialContent, setInitialContent] = useState('');
const [truncated, setTruncated] = useState(false); const [truncated, setTruncated] = useState(false);
const MAX_PREVIEW_BYTES = 1024 * 1024; // 1MB
const isDirty = content !== initialContent; const isDirty = content !== initialContent;
const onRequestCloseRef = useRef(onRequestClose); const onRequestCloseRef = useRef(onRequestClose);
onRequestCloseRef.current = onRequestClose; onRequestCloseRef.current = onRequestClose;
+2 -3
View File
@@ -21,8 +21,7 @@ async function loadApps() {
try { try {
const items = await pluginsApi.list(); const items = await pluginsApi.list();
items.filter(p => p.enabled !== false).forEach((p) => registerPluginAsApp(p)); items.filter(p => p.enabled !== false).forEach((p) => registerPluginAsApp(p));
} catch (e) { } catch { void 0; }
}
} }
function registerPluginAsApp(p: PluginItem) { function registerPluginAsApp(p: PluginItem) {
@@ -104,5 +103,5 @@ export async function reloadPluginApps() {
: undefined; : undefined;
} }
}); });
} catch { } } catch { void 0; }
} }
+1 -2
View File
@@ -27,7 +27,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const res = await authApi.login({ username, password }); const res = await authApi.login({ username, password });
if (res) { if (res) {
setToken(res.access_token); 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 { } else {
setUser(null); setUser(null);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]); }, [token]);
return ( return (
+34 -21
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 { ConfigProvider, theme as antdTheme } from 'antd';
import zhCN from 'antd/locale/zh_CN'; import zhCN from 'antd/locale/zh_CN';
import enUS from 'antd/locale/en_US'; import enUS from 'antd/locale/en_US';
@@ -85,15 +85,22 @@ function buildThemeConfig(state: ThemeState, systemDark: boolean): ThemeConfig {
const baseComponents = { ...(baseTheme.components as any) }; const baseComponents = { ...(baseTheme.components as any) };
if (resolvedMode === 'dark' && baseComponents) { if (resolvedMode === 'dark' && baseComponents) {
if (baseComponents.Menu) { 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; baseComponents.Menu = rest;
} }
if (baseComponents.Dropdown) { if (baseComponents.Dropdown) {
const { controlItemBgHover, ...rest } = baseComponents.Dropdown; const rest = { ...baseComponents.Dropdown };
delete rest.controlItemBgHover;
baseComponents.Dropdown = rest; baseComponents.Dropdown = rest;
} }
if (baseComponents.Table) { if (baseComponents.Table) {
const { headerBg, rowHoverBg, ...rest } = baseComponents.Table; const rest = { ...baseComponents.Table };
delete rest.headerBg;
delete rest.rowHoverBg;
baseComponents.Table = rest; baseComponents.Table = rest;
} }
} }
@@ -106,9 +113,14 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
const { lang } = useI18n(); const { lang } = useI18n();
const systemDark = useSystemDarkPreferred(); const systemDark = useSystemDarkPreferred();
const [state, setState] = useState<ThemeState>({ mode: 'light' }); const [state, setState] = useState<ThemeState>({ mode: 'light' });
const stateRef = useRef(state);
const styleTagRef = useRef<HTMLStyleElement | null>(null); const styleTagRef = useRef<HTMLStyleElement | null>(null);
const ensureStyleTag = () => { useEffect(() => {
stateRef.current = state;
}, [state]);
const ensureStyleTag = useCallback(() => {
if (styleTagRef.current) return styleTagRef.current; if (styleTagRef.current) return styleTagRef.current;
let styleEl = document.getElementById('foxel-custom-css') as HTMLStyleElement | null; let styleEl = document.getElementById('foxel-custom-css') as HTMLStyleElement | null;
if (!styleEl) { if (!styleEl) {
@@ -118,22 +130,22 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
} }
styleTagRef.current = styleEl; styleTagRef.current = styleEl;
return styleEl; return styleEl;
}; }, []);
const applyCustomCSS = (cssText: string | null | undefined) => { const applyCustomCSS = useCallback((cssText: string | null | undefined) => {
const el = ensureStyleTag(); const el = ensureStyleTag();
el.textContent = cssText || ''; el.textContent = cssText || '';
}; }, [ensureStyleTag]);
const applyHtmlDataTheme = (mode: ThemeMode) => { const applyHtmlDataTheme = useCallback((mode: ThemeMode) => {
const finalMode = mode === 'system' ? (systemDark ? 'dark' : 'light') : mode; const finalMode = mode === 'system' ? (systemDark ? 'dark' : 'light') : mode;
document.documentElement.setAttribute('data-theme', finalMode); document.documentElement.setAttribute('data-theme', finalMode);
}; }, [systemDark]);
const refreshTheme = async () => { const refreshTheme = useCallback(async () => {
if (!isAuthenticated) { if (!isAuthenticated) {
applyHtmlDataTheme(state.mode || 'light'); applyHtmlDataTheme(stateRef.current.mode || 'light');
applyCustomCSS(state.customCSS || ''); applyCustomCSS(stateRef.current.customCSS || '');
return; return;
} }
try { try {
@@ -147,22 +159,23 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
setState({ mode, primaryColor: primary, borderRadius: radius, customTokens, customCSS }); setState({ mode, primaryColor: primary, borderRadius: radius, customTokens, customCSS });
applyHtmlDataTheme(mode); applyHtmlDataTheme(mode);
applyCustomCSS(customCSS); applyCustomCSS(customCSS);
} catch (e) { } catch {
applyHtmlDataTheme('light'); applyHtmlDataTheme('light');
applyCustomCSS(''); applyCustomCSS('');
} }
}; }, [applyCustomCSS, applyHtmlDataTheme, isAuthenticated]);
const previewTheme = (patch: Partial<ThemeState>) => { const previewTheme = useCallback((patch: Partial<ThemeState>) => {
const next: ThemeState = { ...state, ...patch }; const next: ThemeState = { ...stateRef.current, ...patch };
stateRef.current = next;
setState(next); setState(next);
applyHtmlDataTheme(next.mode || 'light'); applyHtmlDataTheme(next.mode || 'light');
applyCustomCSS(next.customCSS || ''); applyCustomCSS(next.customCSS || '');
}; }, [applyCustomCSS, applyHtmlDataTheme]);
useEffect(() => { useEffect(() => {
refreshTheme(); void refreshTheme();
}, [isAuthenticated, systemDark]); }, [refreshTheme]);
const themeConfig = useMemo(() => buildThemeConfig(state, systemDark), [state, systemDark]); const themeConfig = useMemo(() => buildThemeConfig(state, systemDark), [state, systemDark]);
const resolvedMode: ThemeMode = useMemo(() => (state.mode === 'system' ? (systemDark ? 'dark' : 'light') : state.mode), [state.mode, 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, previewTheme,
mode: state.mode, mode: state.mode,
resolvedMode, resolvedMode,
}), [state.mode, resolvedMode]); }), [previewTheme, refreshTheme, resolvedMode, state.mode]);
return ( return (
<Ctx.Provider value={ctxValue}> <Ctx.Provider value={ctxValue}>
+1 -2
View File
@@ -30,6 +30,5 @@ export function useAsyncSafeEffect(
ac.abort(); ac.abort();
} }
}; };
}, deps); }, deps); // eslint-disable-line react-hooks/exhaustive-deps
} }
+6 -6
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 type { PropsWithChildren } from 'react';
import en from './locales/en.json'; import en from './locales/en.json';
import zhOverrides from './locales/zh.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) { export function I18nProvider({ children }: PropsWithChildren) {
const [lang, setLangState] = useState<Lang>(() => (localStorage.getItem('lang') as Lang) || 'zh'); const [lang, setLangState] = useState<Lang>(() => (localStorage.getItem('lang') as Lang) || 'zh');
const setLang = (l: Lang) => { const setLang = useCallback((l: Lang) => {
setLangState(l); setLangState(l);
localStorage.setItem('lang', l); localStorage.setItem('lang', l);
}; }, []);
useEffect(() => { useEffect(() => {
document.documentElement.lang = lang; document.documentElement.lang = 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 dict = dicts[lang] || {};
const raw = dict[key] ?? key; // fallback to key (English) const raw = dict[key] ?? key; // fallback to key (English)
return interpolate(raw, params); return interpolate(raw, params);
}; }, [lang]);
const value = useMemo<I18nContextValue>(() => ({ lang, setLang, t }), [lang]); const value = useMemo<I18nContextValue>(() => ({ lang, setLang, t }), [lang, setLang, t]);
return ( return (
<I18nContext.Provider value={value}> <I18nContext.Provider value={value}>
+1 -1
View File
@@ -302,7 +302,7 @@ const SearchDialog: React.FC<SearchDialogProps> = ({ open, onClose }) => {
} else { } else {
setHasMore(false); setHasMore(false);
} }
} catch (e) { } catch {
if (requestId !== requestIdRef.current) { if (requestId !== requestIdRef.current) {
return; return;
} }
+1 -1
View File
@@ -27,7 +27,7 @@ const AdaptersPage = memo(function AdaptersPage() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, [t]);
useEffect(() => { fetchList(); }, [fetchList]); useEffect(() => { fetchList(); }, [fetchList]);
+9 -13
View File
@@ -47,28 +47,24 @@ const AuditLogsPage = memo(function AuditLogsPage() {
const [selectedLog, setSelectedLog] = useState<AuditLogItem | null>(null); const [selectedLog, setSelectedLog] = useState<AuditLogItem | null>(null);
const { t } = useI18n(); 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 () => { const fetchList = useCallback(async () => {
setLoading(true); setLoading(true);
try { 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); setData(res);
} catch (e: any) { } catch (e: any) {
message.error(e.message || t('Load failed')); message.error(e.message || t('Load failed'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [filters]); }, [filters, t]);
useEffect(() => { useEffect(() => {
fetchList(); fetchList();
@@ -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 { Modal, Radio, message, Button, Typography, Input, Space } from 'antd';
import { CopyOutlined, FileMarkdownOutlined } from '@ant-design/icons'; import { CopyOutlined, FileMarkdownOutlined } from '@ant-design/icons';
import type { VfsEntry } from '../../../../api/client'; import type { VfsEntry } from '../../../../api/client';
@@ -33,14 +33,7 @@ export const DirectLinkModal = memo(function DirectLinkModal({ entry, path, open
const [link, setLink] = useState(''); const [link, setLink] = useState('');
const { t } = useI18n(); const { t } = useI18n();
useEffect(() => { const generateLink = useCallback(async () => {
if (open && entry) {
setLink('');
generateLink();
}
}, [open, entry, expiresIn]);
const generateLink = async () => {
if (!entry) return; if (!entry) return;
setLoading(true); setLoading(true);
try { try {
@@ -57,7 +50,14 @@ export const DirectLinkModal = memo(function DirectLinkModal({ entry, path, open
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; }, [entry, expiresIn, path, t]);
useEffect(() => {
if (open && entry) {
setLink('');
void generateLink();
}
}, [open, entry, generateLink]);
const handleCopy = (text: string) => { const handleCopy = (text: string) => {
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text);
@@ -41,9 +41,7 @@ export function MoveCopyModal({ mode, entries, open, defaultPath, onOk, onCancel
try { try {
await onOk(trimmed); await onOk(trimmed);
onCancel(); onCancel();
} catch (e) { } catch { void 0; } finally {
// 上层已处理提示,这里只需保持对话框
} finally {
setLoading(false); setLoading(false);
} }
}; };
@@ -47,7 +47,7 @@ export function useAppWindows(path: string) {
} }
const defaultApp = getDefaultAppForEntry(entry) || apps[0]; const defaultApp = getDefaultAppForEntry(entry) || apps[0];
openWithApp(entry, defaultApp); openWithApp(entry, defaultApp);
}, [openWithApp]); }, [openWithApp, t]);
const confirmOpenWithApp = useCallback((entry: VfsEntry, appKey: string) => { const confirmOpenWithApp = useCallback((entry: VfsEntry, appKey: string) => {
const app = getAppByKey(appKey); const app = getAppByKey(appKey);
@@ -72,7 +72,7 @@ export function useAppWindows(path: string) {
openWithApp(entry, app); openWithApp(entry, app);
} }
}); });
}, [openWithApp]); }, [openWithApp, t]);
const closeWindow = (id: string) => setAppWindows(ws => ws.filter(w => w.id !== id)); 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)); const toggleMax = (id: string) => setAppWindows(ws => ws.map(w => w.id === id ? { ...w, maximized: !w.maximized } : w));
@@ -35,7 +35,7 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
} catch (e: any) { } catch (e: any) {
message.error(e.message); message.error(e.message);
} }
}, [path, refresh]); }, [path, refresh, t]);
const doDelete = useCallback(async (entries: VfsEntry[]) => { const doDelete = useCallback(async (entries: VfsEntry[]) => {
Modal.confirm({ 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) => { const doRename = useCallback(async (entry: VfsEntry, newName: string) => {
if (!newName.trim() || newName.trim() === entry.name) { if (!newName.trim() || newName.trim() === entry.name) {
@@ -173,7 +173,7 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
} catch (e: any) { } catch (e: any) {
message.error(e.message || t('Download failed')); message.error(e.message || t('Download failed'));
} }
}, [path]); }, [path, t]);
const doShare = useCallback((entries: VfsEntry[]) => { const doShare = useCallback((entries: VfsEntry[]) => {
if (entries.length === 0) { if (entries.length === 0) {
@@ -181,7 +181,7 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
return; return;
} }
onShare(entries); onShare(entries);
}, [onShare]); }, [onShare, t]);
const doGetDirectLink = useCallback((entry: VfsEntry) => { const doGetDirectLink = useCallback((entry: VfsEntry) => {
if (entry.is_dir) { if (entry.is_dir) {
@@ -189,7 +189,7 @@ export function useFileActions({ path, refresh, clearSelection, onShare, onGetDi
return; return;
} }
onGetDirectLink(entry); onGetDirectLink(entry);
}, [onGetDirectLink]); }, [onGetDirectLink, t]);
return { return {
doCreateDir, doCreateDir,
@@ -58,7 +58,7 @@ export function useProcessor({ path, processorTypes, refresh }: ProcessorParams)
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [modal.entry, selectedProcessor, processorTypes, config, path, overwrite, savingPath, refresh]); }, [modal.entry, selectedProcessor, processorTypes, config, path, overwrite, savingPath, refresh, t]);
const handleCancel = useCallback(() => { const handleCancel = useCallback(() => {
setModal({ entry: null, visible: false }); setModal({ entry: null, visible: false });
+9 -9
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 { 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 { GithubOutlined, LinkOutlined } from '@ant-design/icons';
import { pluginsApi, type PluginItem } from '../api/plugins'; import { pluginsApi, type PluginItem } from '../api/plugins';
@@ -38,7 +38,7 @@ const PluginsPage = memo(function PluginsPage() {
try { try {
await ensureAppsLoaded(); await ensureAppsLoaded();
setSystemApps(listSystemApps()); setSystemApps(listSystemApps());
} catch {} } catch { void 0; }
})(); })();
}, []); }, []);
@@ -48,23 +48,23 @@ const PluginsPage = memo(function PluginsPage() {
return set; return set;
}, [data]); }, [data]);
const reloadRepo = async () => { const reloadRepo = useCallback(async () => {
try { try {
setRepoLoading(true); setRepoLoading(true);
const res = await fetchRepoList({ query: repoQ || undefined, sort: repoSort, page: repoPage, pageSize: repoPageSize }); const res = await fetchRepoList({ query: repoQ || undefined, sort: repoSort, page: repoPage, pageSize: repoPageSize });
setRepoItems(res.items || []); setRepoItems(res.items || []);
setRepoTotal(res.total || 0); setRepoTotal(res.total || 0);
} catch (e) { } catch {
setRepoItems([]); setRepoItems([]);
setRepoTotal(0); setRepoTotal(0);
} finally { } finally {
setRepoLoading(false); setRepoLoading(false);
} }
}; }, [repoPage, repoPageSize, repoQ, repoSort]);
useEffect(() => { useEffect(() => {
if (tab === 'discover') reloadRepo(); if (tab === 'discover') reloadRepo();
}, [tab, repoQ, repoSort, repoPage, repoPageSize]); }, [reloadRepo, tab]);
const handleAdd = async () => { const handleAdd = async () => {
try { try {
@@ -73,13 +73,13 @@ const PluginsPage = memo(function PluginsPage() {
try { try {
const p = await loadPlugin(created); const p = await loadPlugin(created);
await ensureManifest(created.id, p); await ensureManifest(created.id, p);
} catch {} } catch { void 0; }
setAdding(false); setAdding(false);
form.resetFields(); form.resetFields();
await reload(); await reload();
await reloadPluginApps(); await reloadPluginApps();
message.success(t('Installed successfully')); message.success(t('Installed successfully'));
} catch {} } catch { void 0; }
}; };
const filtered = useMemo(() => { const filtered = useMemo(() => {
@@ -290,7 +290,7 @@ const PluginsPage = memo(function PluginsPage() {
try { try {
const p = await loadPlugin(created); const p = await loadPlugin(created);
await ensureManifest(created.id, p); await ensureManifest(created.id, p);
} catch {} } catch { void 0; }
await reload(); await reload();
await reloadPluginApps(); await reloadPluginApps();
message.success(t('Installed successfully')); message.success(t('Installed successfully'));
@@ -22,19 +22,19 @@ export const DirectoryViewer = memo(function DirectoryViewer({ token, shareInfo,
const [error, setError] = useState(''); const [error, setError] = useState('');
const { t } = useI18n(); const { t } = useI18n();
const loadData = useCallback(async (p: string) => { const loadData = useCallback(async (p: string) => {
setLoading(true); setLoading(true);
setError(''); setError('');
try { try {
const listing = await shareApi.listDir(token, p, password); const listing = await shareApi.listDir(token, p, password);
setEntries(listing.entries || []); setEntries(listing.entries || []);
setCurrentPath(p); setCurrentPath(p);
} catch (e: any) { } catch (e: any) {
setError(e.message || t('Share load failed')); setError(e.message || t('Share load failed'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [token, password]); }, [password, t, token]);
useEffect(() => { useEffect(() => {
loadData(currentPath); loadData(currentPath);
+8 -8
View File
@@ -17,10 +17,10 @@ const PublicSharePage = memo(function PublicSharePage() {
const [verified, setVerified] = useState(false); const [verified, setVerified] = useState(false);
const { t } = useI18n(); const { t } = useI18n();
const loadData = useCallback(async (pwd?: string) => { const loadData = useCallback(async (pwd?: string) => {
if (!token) return; if (!token) return;
setLoading(true); setLoading(true);
setError(''); setError('');
try { try {
let info = shareInfo; let info = shareInfo;
if (!info) { if (!info) {
@@ -50,10 +50,10 @@ const PublicSharePage = memo(function PublicSharePage() {
if (e.message === '需要密码') { if (e.message === '需要密码') {
setVerified(false); setVerified(false);
} }
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [token, shareInfo, password, verified]); }, [password, shareInfo, t, token, verified]);
useEffect(() => { useEffect(() => {
loadData(); loadData();
+1 -1
View File
@@ -23,7 +23,7 @@ const SharePage = memo(function SharePage() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, [t]);
useEffect(() => { fetchList(); }, [fetchList]); useEffect(() => { fetchList(); }, [fetchList]);
@@ -78,7 +78,7 @@ function parseEmailConfig(raw?: string | null): EmailFormValues {
timeout: data?.timeout !== undefined ? Number(data.timeout) : DEFAULT_FORM.timeout, timeout: data?.timeout !== undefined ? Number(data.timeout) : DEFAULT_FORM.timeout,
security: (data?.security ?? DEFAULT_FORM.security) as EmailFormValues['security'], security: (data?.security ?? DEFAULT_FORM.security) as EmailFormValues['security'],
}; };
} catch (_err) { } catch {
return { ...DEFAULT_FORM }; return { ...DEFAULT_FORM };
} }
} }
+2 -1
View File
@@ -133,12 +133,13 @@ export async function ensureManifest(pluginId: number, plugin: RegisteredPlugin)
website: plugin.website, website: plugin.website,
github: plugin.github, 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}`; const key = `foxel:manifestReported:${pluginId}`;
if (sessionStorage.getItem(key) === '1') return; if (sessionStorage.getItem(key) === '1') return;
try { try {
await pluginsApi.updateManifest(pluginId, manifest); await pluginsApi.updateManifest(pluginId, manifest);
sessionStorage.setItem(key, '1'); sessionStorage.setItem(key, '1');
} catch { } catch {
void 0;
} }
} }