mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-07-01 11:45:54 +08:00
chore: 同步 v5.0 基础设施完善到 v5.1
从 v5.0 cherry-pick 的开源项目基础设施改进: 新增配置文件: - .editorconfig, .gitattributes, .prettierrc, .markdownlint.json - commitlint.config.js 新增 GitHub 社区文件: - .github/dependabot.yml — 依赖自动更新 - .github/CODEOWNERS — 自动 PR 审查分配 - .github/FUNDING.yml — 开源赞助入口 新增文档: - ROADMAP.md — 路线图 - SUPPORT.md — 帮助指南 - docs/screenshots/ — 应用截图 新增 Git Hooks: - .husky/pre-commit — lint-staged 自动格式化+修复 - .husky/commit-msg — commitlint 校验 CI 强化: - 新增 Prettier 格式检查 - 新增 Vitest 覆盖率 + Codecov 上报 - 保留 v5.1 已有的 rust-cache + jsdom 全局环境 修复: - index.html 标题 v4.0 → v5.1 - PathEditDialog set-state-in-effect 改用 useRef prevOpen 守卫 - merge-preview.test.tsx no-explicit-any 修复 - 所有 TS/TSX 文件 Prettier 格式化统一 v5.1 保留特性: - @tanstack/react-virtual 虚拟滚动 - jsdom 全局测试环境 - Swatinem/rust-cache CI 加速 - 105 测试全部通过
This commit is contained in:
@@ -65,20 +65,23 @@ export function ProfileDialog({ open, onClose }: Props) {
|
||||
if (!selected || !selectedData) return;
|
||||
if (!window.confirm(t('profile.applyConfirm', { name: selected }))) return;
|
||||
useAppStore.getState().replaceBothPaths(
|
||||
selectedData.sys.map(e => e.path),
|
||||
selectedData.user.map(e => e.path),
|
||||
selectedData.sys.map((e) => e.path),
|
||||
selectedData.user.map((e) => e.path),
|
||||
);
|
||||
// 同步 disabled 状态
|
||||
await invoke('save_disabled_state', {
|
||||
system: selectedData.sys.filter(e => !e.enabled).map(e => e.path),
|
||||
user: selectedData.user.filter(e => !e.enabled).map(e => e.path),
|
||||
system: selectedData.sys.filter((e) => !e.enabled).map((e) => e.path),
|
||||
user: selectedData.user.filter((e) => !e.enabled).map((e) => e.path),
|
||||
});
|
||||
const result = await useAppStore.getState().savePaths();
|
||||
if (result.kind === 'success') {
|
||||
onClose();
|
||||
} else if (result.kind === 'warning') {
|
||||
const { ask } = await import('@tauri-apps/plugin-dialog');
|
||||
const confirmed = await ask(t('status.saveWarningLongPaths'), { title: t('dialog.backupTitle'), kind: 'warning' });
|
||||
const confirmed = await ask(t('status.saveWarningLongPaths'), {
|
||||
title: t('dialog.backupTitle'),
|
||||
kind: 'warning',
|
||||
});
|
||||
if (confirmed) {
|
||||
const forceResult = await useAppStore.getState().savePaths(true);
|
||||
if (forceResult.kind === 'success') {
|
||||
@@ -91,7 +94,10 @@ export function ProfileDialog({ open, onClose }: Props) {
|
||||
const handleDelete = async (name: string) => {
|
||||
if (!window.confirm(`删除配置文件 "${name}"?`)) return;
|
||||
await invoke('delete_profile', { name });
|
||||
if (selected === name) { setSelected(null); setSelectedData(null); }
|
||||
if (selected === name) {
|
||||
setSelected(null);
|
||||
setSelectedData(null);
|
||||
}
|
||||
refreshProfiles();
|
||||
};
|
||||
|
||||
@@ -106,16 +112,23 @@ export function ProfileDialog({ open, onClose }: Props) {
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<div className="flex flex-col" style={{ width: 680, maxHeight: '75vh' }}>
|
||||
<div className="flex items-center justify-between px-5 py-3 border-b" style={{ borderColor: 'var(--app-border)' }}>
|
||||
<div
|
||||
className="flex items-center justify-between px-5 py-3 border-b"
|
||||
style={{ borderColor: 'var(--app-border)' }}
|
||||
>
|
||||
<h2 className="text-base font-semibold">{t('profile.title')}</h2>
|
||||
<div className="flex gap-2 items-center">
|
||||
<input
|
||||
type="text"
|
||||
value={newName}
|
||||
onChange={e => setNewName(e.target.value)}
|
||||
onChange={(e) => setNewName(e.target.value)}
|
||||
placeholder={t('profile.namePlaceholder')}
|
||||
className="px-2 py-1 text-sm rounded border outline-none w-44"
|
||||
style={{ backgroundColor: 'var(--app-list-bg)', color: 'var(--app-fg)', borderColor: 'var(--app-border)' }}
|
||||
style={{
|
||||
backgroundColor: 'var(--app-list-bg)',
|
||||
color: 'var(--app-fg)',
|
||||
borderColor: 'var(--app-border)',
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className="px-3 py-1 text-sm rounded text-white"
|
||||
@@ -138,11 +151,16 @@ export function ProfileDialog({ open, onClose }: Props) {
|
||||
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* 左侧:列表 */}
|
||||
<div className="w-48 border-r overflow-auto p-2" style={{ borderColor: 'var(--app-border)' }}>
|
||||
<div
|
||||
className="w-48 border-r overflow-auto p-2"
|
||||
style={{ borderColor: 'var(--app-border)' }}
|
||||
>
|
||||
{profiles.length === 0 ? (
|
||||
<div className="text-xs text-center py-6" style={{ opacity: 0.5 }}>{t('profile.noProfiles')}</div>
|
||||
<div className="text-xs text-center py-6" style={{ opacity: 0.5 }}>
|
||||
{t('profile.noProfiles')}
|
||||
</div>
|
||||
) : (
|
||||
profiles.map(p => (
|
||||
profiles.map((p) => (
|
||||
<div
|
||||
key={p.name}
|
||||
onClick={() => handleLoad(p.name)}
|
||||
@@ -168,7 +186,9 @@ export function ProfileDialog({ open, onClose }: Props) {
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="font-semibold text-sm">{selectedData.name}</span>
|
||||
<span className="text-xs" style={{ opacity: 0.5 }}>{selectedData.modified}</span>
|
||||
<span className="text-xs" style={{ opacity: 0.5 }}>
|
||||
{selectedData.modified}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-1.5 mb-3">
|
||||
@@ -182,7 +202,10 @@ export function ProfileDialog({ open, onClose }: Props) {
|
||||
<button
|
||||
className="px-3 py-1 text-xs rounded"
|
||||
style={{ backgroundColor: 'var(--app-list-bg)', color: 'var(--app-fg)' }}
|
||||
onClick={() => { setRenameOpen(true); setRenameValue(selectedData.name); }}
|
||||
onClick={() => {
|
||||
setRenameOpen(true);
|
||||
setRenameValue(selectedData.name);
|
||||
}}
|
||||
>
|
||||
{t('profile.rename')}
|
||||
</button>
|
||||
@@ -200,18 +223,32 @@ export function ProfileDialog({ open, onClose }: Props) {
|
||||
<input
|
||||
type="text"
|
||||
value={renameValue}
|
||||
onChange={e => setRenameValue(e.target.value)}
|
||||
onChange={(e) => setRenameValue(e.target.value)}
|
||||
className="px-2 py-1 text-xs rounded border outline-none"
|
||||
style={{ backgroundColor: 'var(--app-list-bg)', color: 'var(--app-fg)', borderColor: 'var(--app-border)' }}
|
||||
style={{
|
||||
backgroundColor: 'var(--app-list-bg)',
|
||||
color: 'var(--app-fg)',
|
||||
borderColor: 'var(--app-border)',
|
||||
}}
|
||||
/>
|
||||
<button className="px-2 py-1 text-xs rounded text-white" style={{ backgroundColor: '#3b82f6' }} onClick={handleRename}>
|
||||
<button
|
||||
className="px-2 py-1 text-xs rounded text-white"
|
||||
style={{ backgroundColor: '#3b82f6' }}
|
||||
onClick={handleRename}
|
||||
>
|
||||
{t('button.save')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<PathSection title={`系统 PATH (${selectedData.sys.length})`} paths={selectedData.sys} />
|
||||
<PathSection title={`用户 PATH (${selectedData.user.length})`} paths={selectedData.user} />
|
||||
<PathSection
|
||||
title={`系统 PATH (${selectedData.sys.length})`}
|
||||
paths={selectedData.sys}
|
||||
/>
|
||||
<PathSection
|
||||
title={`用户 PATH (${selectedData.user.length})`}
|
||||
paths={selectedData.user}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -225,9 +262,13 @@ function PathSection({ title, paths }: { title: string; paths: PathEntry[] }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<div className="text-xs font-medium mb-1" style={{ opacity: 0.7 }}>{title}</div>
|
||||
<div className="text-xs font-medium mb-1" style={{ opacity: 0.7 }}>
|
||||
{title}
|
||||
</div>
|
||||
{paths.length === 0 ? (
|
||||
<div className="text-xs" style={{ opacity: 0.4 }}>{t('profile.empty')}</div>
|
||||
<div className="text-xs" style={{ opacity: 0.4 }}>
|
||||
{t('profile.empty')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-0.5 max-h-48 overflow-auto">
|
||||
{paths.map((e) => (
|
||||
|
||||
Reference in New Issue
Block a user