mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-06-29 01:45:54 +08:00
refactor: 全面代码质量提升 — StringList→string[], strict 模式, 死代码清理
架构重构: - StringList 类替换为不可变 string[](消除 dataVersion hack,Zustand 自然检测变化) - UndoRedoManager.undo/redo 返回新数组而非原地修改 - 删除 dataVersion 字段和 _bumpVersion() - 启用 TypeScript strict 模式 死代码清理: - 删除 string-list.ts, string-list.test.ts, use-path-validation.ts - Rust AppError 保留供未来使用 功能修复: - importFromJson 添加 try/catch - handleClean 使用真实格式验证替代 () => true - savePaths 保存前调用 backup_registry,处理部分保存失败 - importFromJson 校验非 object 类型输入 i18n 完善: - MergePreview/StatusBar 硬编码中文 → t() 调用 - 新增 merge.* 和 status.* 翻译键 Rust 改进: - registry.rs 抽取 load_paths/save_paths 通用函数,消除重复 - registry 新增 6 个单元测试(split/join/roundtrip) - backup.rs 时间戳加毫秒防覆盖,回退路径改为 home_dir 元数据: - package.json 名称→patheditor, 版本→4.0.0 - 新增 CHANGELOG.md - 移除 UndoRedoButtons 废弃注释 - tsconfig 添加 strict:true Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -49,7 +49,7 @@ export function AppShell() {
|
||||
const list = target === TargetType.SYSTEM
|
||||
? useAppStore.getState().sysPaths
|
||||
: useAppStore.getState().userPaths;
|
||||
const value = list.get(idx);
|
||||
const value = list[idx];
|
||||
if (value) {
|
||||
setEditDialog({ open: true, index: idx, value, target });
|
||||
}
|
||||
@@ -88,7 +88,7 @@ export function AppShell() {
|
||||
const handleClean = useCallback(() => {
|
||||
const removed = useAppStore.getState().cleanPaths(
|
||||
getCurrentTarget(),
|
||||
() => true, // 简化版,全有效
|
||||
(p) => p.includes('%') || p.includes('\\') || p.includes('/') || /^[a-zA-Z]:[/\\]/.test(p),
|
||||
);
|
||||
if (removed.length > 0) {
|
||||
useAppStore.getState().setStatusMessage(
|
||||
@@ -120,7 +120,7 @@ export function AppShell() {
|
||||
|
||||
const handleExport = useCallback(() => {
|
||||
const state = useAppStore.getState();
|
||||
const data = { system: state.sysPaths.toArray(), user: state.userPaths.toArray() };
|
||||
const data = { system: state.sysPaths, user: state.userPaths };
|
||||
|
||||
const content = exportToJson(data);
|
||||
const mime = 'application/json';
|
||||
@@ -137,8 +137,8 @@ export function AppShell() {
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
const state = useAppStore.getState();
|
||||
const sysJoined = state.sysPaths.toArray().join(';');
|
||||
const userJoined = state.userPaths.toArray().join(';');
|
||||
const sysJoined = state.sysPaths.join(';');
|
||||
const userJoined = state.userPaths.join(';');
|
||||
const combined = sysJoined + ';' + userJoined;
|
||||
|
||||
const warnings: string[] = [];
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useThemeStore } from '@/store/theme-store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function StatusBar() {
|
||||
const { t } = useTranslation();
|
||||
const statusMessage = useAppStore((s) => s.statusMessage);
|
||||
const isLoading = useAppStore((s) => s.isLoading);
|
||||
const isAdmin = useAppStore((s) => s.isAdmin);
|
||||
@@ -17,11 +19,11 @@ export function StatusBar() {
|
||||
color: 'var(--app-fg)',
|
||||
}}
|
||||
>
|
||||
<span>{isLoading ? '加载中...' : statusMessage}</span>
|
||||
<span>{isLoading ? t('status.loading') : statusMessage}</span>
|
||||
<div className="flex gap-3">
|
||||
{isModified && <span className="text-yellow-500">● 已修改</span>}
|
||||
{!isAdmin && <span className="text-yellow-500">只读</span>}
|
||||
<span style={{ opacity: 0.5 }}>{isDark ? '深色' : '浅色'}</span>
|
||||
{isModified && <span className="text-yellow-500">● {t('status.modified')}</span>}
|
||||
{!isAdmin && <span className="text-yellow-500">{t('status.readonly_label')}</span>}
|
||||
<span style={{ opacity: 0.5 }}>{isDark ? t('status.dark') : t('status.light')}</span>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function MergePreview() {
|
||||
const dataVersion = useAppStore((s) => s.dataVersion);
|
||||
void dataVersion; // 订阅版本号强制重渲染
|
||||
const sysPaths = useAppStore((s) => s.sysPaths);
|
||||
const userPaths = useAppStore((s) => s.userPaths);
|
||||
const searchQuery = useAppStore((s) => s.searchQuery);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const allPaths = useMemo(() => {
|
||||
const result: { path: string; source: '系统' | '用户'; index: number }[] = [];
|
||||
sysPaths.all.forEach((p, i) => result.push({ path: p, source: '系统' as const, index: i }));
|
||||
userPaths.all.forEach((p, i) => result.push({ path: p, source: '用户' as const, index: i }));
|
||||
const result: { path: string; source: string; index: number }[] = [];
|
||||
sysPaths.forEach((p, i) => result.push({ path: p, source: t('merge.system'), index: i }));
|
||||
userPaths.forEach((p, i) => result.push({ path: p, source: t('merge.user'), index: i }));
|
||||
|
||||
if (!searchQuery) return result;
|
||||
const q = searchQuery.toLowerCase();
|
||||
return result.filter((r) => r.path.toLowerCase().includes(q));
|
||||
}, [sysPaths, userPaths, searchQuery]);
|
||||
}, [sysPaths, userPaths, searchQuery, t]);
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-auto">
|
||||
@@ -27,8 +27,8 @@ export function MergePreview() {
|
||||
style={{ backgroundColor: 'var(--app-list-alt)', color: 'var(--app-fg)' }}
|
||||
>
|
||||
<th className="w-10 px-2 py-1">#</th>
|
||||
<th className="px-2 py-1">路径</th>
|
||||
<th className="w-16 px-2 py-1">来源</th>
|
||||
<th className="px-2 py-1">{t('dialog.pathLabel')}</th>
|
||||
<th className="w-16 px-2 py-1">{t('merge.source')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -12,8 +12,6 @@ interface PathRow {
|
||||
}
|
||||
|
||||
export function PathTable({ tabId }: PathTableProps) {
|
||||
const dataVersion = useAppStore((s) => s.dataVersion);
|
||||
void dataVersion; // 订阅版本号强制重渲染
|
||||
const sysPaths = useAppStore((s) => s.sysPaths);
|
||||
const userPaths = useAppStore((s) => s.userPaths);
|
||||
const searchQuery = useAppStore((s) => s.searchQuery);
|
||||
@@ -31,11 +29,11 @@ export function PathTable({ tabId }: PathTableProps) {
|
||||
|
||||
// 过滤搜索
|
||||
const filtered = useMemo<PathRow[]>(() => {
|
||||
if (!searchQuery) return paths.all.map((p, i) => ({ path: p, index: i }));
|
||||
if (!searchQuery) return paths.map((p, i) => ({ path: p, index: i }));
|
||||
const q = searchQuery.toLowerCase();
|
||||
const result: PathRow[] = [];
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
const p = paths.get(i)!;
|
||||
const p = paths[i];
|
||||
if (p.toLowerCase().includes(q)) result.push({ path: p, index: i });
|
||||
}
|
||||
return result;
|
||||
@@ -44,7 +42,7 @@ export function PathTable({ tabId }: PathTableProps) {
|
||||
// 异步验证未缓存的路径
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const allPaths = paths.all;
|
||||
const allPaths = paths;
|
||||
|
||||
// 找出未缓存的路径
|
||||
const toValidate = allPaths.filter((p) => !validationCache.has(p));
|
||||
@@ -81,7 +79,7 @@ export function PathTable({ tabId }: PathTableProps) {
|
||||
// 异步展开环境变量(用于 tooltip)
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const toExpand = paths.all.filter(
|
||||
const toExpand = paths.filter(
|
||||
(p) => p.includes('%') && !expandedCache.has(p),
|
||||
);
|
||||
if (toExpand.length === 0) return;
|
||||
@@ -146,7 +144,7 @@ export function PathTable({ tabId }: PathTableProps) {
|
||||
if (!isActive) return;
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('path-dblclick', {
|
||||
detail: { index: realIndex, path: paths.get(realIndex) },
|
||||
detail: { index: realIndex, path: paths[realIndex] },
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -16,9 +16,6 @@ export function UndoRedoButtons() {
|
||||
borderColor: 'var(--app-border)',
|
||||
};
|
||||
|
||||
// 订阅状态更新(canUndo/canRedo 不会触发 re-render,用 setTimeout 简单轮询不优雅,但 Zustand 的 subscribe 可以)
|
||||
// 这里简化为每次渲染时检查(因为 undo/redo 会修改列表触发重渲染)
|
||||
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user