дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
feat(trust): let users add trusted folders from settings (#1812)
* feat(trust): let users add trusted folders from settings The trusted-locations check already matches projects by path prefix, but the only way to populate it was the "Trust projects in…" checkbox on the first-open dialog — a folder could never be trusted ahead of time. Add a project_trust_add_location command that canonicalizes the picked directory, rejects non-directories, and deduplicates, plus an "Add folder…" button on the Project Trust settings page backed by the native directory picker. Fixes #1743 * fix(trust): translate the trusted-location additions into Chinese The zh source-text catalog must cover every literal UI text; the three new strings in the trusted-locations settings page were missing.
Цей коміт міститься в:
1 джерело
954ad354a1
коміт
b54a745d6d
9 files changed
+115
-4
No files matched your search
@@ -28,6 +28,7 @@ export const commands = {
|
||||
projectTrustStatus: (path: string) => typedError<TrustStatus, IdeError>(__TAURI_INVOKE("project_trust_status", { path })),
|
||||
projectTrustList: () => typedError<TrustDecisions, IdeError>(__TAURI_INVOKE("project_trust_list")),
|
||||
projectTrustSet: (path: string, trusted: boolean, folder: boolean) => typedError<null, IdeError>(__TAURI_INVOKE("project_trust_set", { path, trusted, folder })),
|
||||
projectTrustAddLocation: (path: string) => typedError<null, IdeError>(__TAURI_INVOKE("project_trust_add_location", { path })),
|
||||
projectTrustRemoveLocation: (path: string) => typedError<null, IdeError>(__TAURI_INVOKE("project_trust_remove_location", { path })),
|
||||
workspaceCreate: (path: string, name: string, projects: string[]) => typedError<IpcPath, IdeError>(__TAURI_INVOKE("workspace_create", { path, name, projects })),
|
||||
workspaceAttach: (path: string) => typedError<IpcPath, IdeError>(__TAURI_INVOKE("workspace_attach", { path })),
|
||||
|
||||
@@ -115,6 +115,18 @@ function TrustSettings() {
|
||||
setError(String(e));
|
||||
}
|
||||
};
|
||||
const addLocation = async () => {
|
||||
try {
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const picked = await open({ directory: true, multiple: false, title: "Add Trusted Location" });
|
||||
const path = Array.isArray(picked) ? picked[0] : picked;
|
||||
if (!path) return;
|
||||
await call(commands.projectTrustAddLocation(path));
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<p>
|
||||
@@ -132,9 +144,17 @@ function TrustSettings() {
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<h3>
|
||||
<T>Trusted locations</T>
|
||||
</h3>
|
||||
<div className={styles.sectionHead}>
|
||||
<h3>
|
||||
<T>Trusted locations</T>
|
||||
</h3>
|
||||
<Button onClick={() => void addLocation()}>
|
||||
<T>Add folder…</T>
|
||||
</Button>
|
||||
</div>
|
||||
<p className={styles.hint}>
|
||||
<T>Every project inside a trusted location is trusted without asking.</T>
|
||||
</p>
|
||||
{decisions?.locations.map((path) => (
|
||||
<div className={styles.row} key={path}>
|
||||
<span className={styles.path}>{path}</span>
|
||||
|
||||
@@ -52,6 +52,18 @@
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.sectionHead {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.sectionHead h3 {
|
||||
margin: 0;
|
||||
}
|
||||
.hint {
|
||||
color: var(--fg-secondary);
|
||||
}
|
||||
.path {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { emitLocalIdeEvent } from "@/platform/ipc";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { invokeLocal } from "@/platform/tauriCore";
|
||||
import { OverlayHost } from "@/workbench/OverlayHost";
|
||||
import { useContributions } from "@/workbench/contributions";
|
||||
import { useContributions, useSettingsPages } from "@/workbench/contributions";
|
||||
import { activate } from "./index";
|
||||
|
||||
const status = vi.fn();
|
||||
@@ -15,6 +15,10 @@ vi.mock("@/platform/tauriCore", async (original) => ({
|
||||
...(await original<typeof import("@/platform/tauriCore")>()),
|
||||
invokeLocal: vi.fn(),
|
||||
}));
|
||||
const addLocation = vi.fn();
|
||||
const listDecisions = vi.fn();
|
||||
const pickFolder = vi.fn();
|
||||
vi.mock("@tauri-apps/plugin-dialog", () => ({ open: pickFolder }));
|
||||
vi.mock("@/platform/ipc", async (original) => {
|
||||
const real = await original<typeof import("@/platform/ipc")>();
|
||||
return {
|
||||
@@ -23,6 +27,8 @@ vi.mock("@/platform/ipc", async (original) => {
|
||||
...real.commands,
|
||||
projectTrustStatus: (path: string) => status(path),
|
||||
projectTrustSet: (...args: unknown[]) => decide(...args),
|
||||
projectTrustList: () => listDecisions(),
|
||||
projectTrustAddLocation: (...args: unknown[]) => addLocation(...args),
|
||||
projectCurrent: async () => null,
|
||||
},
|
||||
};
|
||||
@@ -34,6 +40,9 @@ beforeEach(() => {
|
||||
.mockReset()
|
||||
.mockImplementation(async (root: string) => ({ status: "ok", data: { root, trusted: false, decided: false } }));
|
||||
decide.mockReset().mockResolvedValue({ status: "ok", data: null });
|
||||
addLocation.mockReset().mockResolvedValue({ status: "ok", data: null });
|
||||
listDecisions.mockReset().mockResolvedValue({ status: "ok", data: { projects: [], locations: [], restricted: [] } });
|
||||
pickFolder.mockReset().mockResolvedValue(null);
|
||||
useProject.setState({ project: null });
|
||||
dispose = activate();
|
||||
});
|
||||
@@ -88,6 +97,26 @@ it("shows and trusts the canonical folder when opening through a symlink", async
|
||||
expect(decide).toHaveBeenCalledWith("/real/projects/new", true, true);
|
||||
});
|
||||
|
||||
it("adds a picked folder as a trusted location from settings", async () => {
|
||||
const Page = useSettingsPages.getState().pages.get("projectTrust")!.component!;
|
||||
pickFolder.mockResolvedValue("/projects");
|
||||
addLocation.mockImplementation(async () => {
|
||||
listDecisions.mockResolvedValue({
|
||||
status: "ok",
|
||||
data: { projects: [], locations: ["/projects"], restricted: [] },
|
||||
});
|
||||
return { status: "ok", data: null };
|
||||
});
|
||||
render(<Page />);
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Add folder…" }));
|
||||
await screen.findByText("/projects");
|
||||
expect(addLocation).toHaveBeenCalledWith("/projects");
|
||||
pickFolder.mockResolvedValue(null);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Add folder…" }));
|
||||
await act(async () => {});
|
||||
expect(addLocation).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each(["Trust Project", "Preview in Restricted Mode"])(
|
||||
"keeps a local opening's trust decision on the client: %s",
|
||||
async (button) => {
|
||||
|
||||
@@ -232,6 +232,8 @@
|
||||
"Activate virtualenv": "激活 virtualenv",
|
||||
"Add": "添加",
|
||||
"Add Column": "添加列",
|
||||
"Add Trusted Location": "添加受信任的位置",
|
||||
"Add folder…": "添加文件夹…",
|
||||
"Add Field Watchpoint": "添加字段监视点",
|
||||
"Add File to Git": "将文件添加到 Git",
|
||||
"Add GitHub Pattern": "添加 GitHub 模式",
|
||||
@@ -3155,6 +3157,7 @@
|
||||
"(empty line)": "(空行)",
|
||||
"Name cannot be empty": "名称不能为空",
|
||||
"Every scope needs a name": "每个范围都需要名称",
|
||||
"Every project inside a trusted location is trusted without asking.": "受信任的位置内的每个项目都会被信任,不再询问。",
|
||||
"Scopes": "范围",
|
||||
"Add Scope": "添加范围",
|
||||
"Remove Scope": "移除范围",
|
||||
|
||||
Reference in new issue
Заблокувати користувача