Skip to content

Commit 335bead

Browse files
committed
fix(quick-260504-414-01): 修复路径联想——按父目录查询+前缀过滤
PathAutocomplete 现在会解析输入值中的父目录和过滤前缀: - /Users/zaneliu/P → 查询 /Users/zaneliu,过滤以 P 开头的子目录 - 选中 Projects 后补全为 /Users/zaneliu/Projects 后端 API 对不存在的路径和普通文件也返回空列表(而非 404/400), 让自动补全体验更流畅。
1 parent a275d38 commit 335bead

3 files changed

Lines changed: 34 additions & 10 deletions

File tree

internal/controlplane/http/admin_host_files.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ func (h *AdminHostFilesHandler) List() nethttp.Handler {
4747
info, err := os.Stat(cleaned)
4848
if err != nil {
4949
if os.IsNotExist(err) {
50-
writeJSON(w, nethttp.StatusNotFound, map[string]string{"error": "path not found"})
50+
writeJSON(w, nethttp.StatusOK, map[string]any{"entries": []string{}})
5151
return
5252
}
5353
h.logger.Error("stat path failed", "path", cleaned, "error", err)
5454
writeJSON(w, nethttp.StatusInternalServerError, map[string]string{"error": "unable to read path"})
5555
return
5656
}
5757
if !info.IsDir() {
58-
writeJSON(w, nethttp.StatusBadRequest, map[string]string{"error": "path is not a directory"})
58+
writeJSON(w, nethttp.StatusOK, map[string]any{"entries": []string{}})
5959
return
6060
}
6161

internal/controlplane/http/admin_host_files_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,11 @@ func TestAdminHostFiles_NotFound(t *testing.T) {
8686
rr := httptest.NewRecorder()
8787
h.List().ServeHTTP(rr, req)
8888

89-
if rr.Code != http.StatusNotFound {
90-
t.Fatalf("expected 404, got %d: %s", rr.Code, rr.Body.String())
89+
if rr.Code != http.StatusOK {
90+
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
91+
}
92+
if !strings.Contains(rr.Body.String(), `"entries"`) {
93+
t.Errorf("expected empty entries array, got %s", rr.Body.String())
9194
}
9295
}
9396

@@ -98,8 +101,11 @@ func TestAdminHostFiles_NotADirectory(t *testing.T) {
98101
rr := httptest.NewRecorder()
99102
h.List().ServeHTTP(rr, req)
100103

101-
if rr.Code != http.StatusBadRequest {
102-
t.Fatalf("expected 400, got %d: %s", rr.Code, rr.Body.String())
104+
if rr.Code != http.StatusOK {
105+
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
106+
}
107+
if !strings.Contains(rr.Body.String(), `"entries"`) {
108+
t.Errorf("expected empty entries array, got %s", rr.Body.String())
103109
}
104110
}
105111

web/admin/src/components/hosts/path-autocomplete.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ interface PathAutocompleteProps {
1111
className?: string;
1212
}
1313

14+
function getQueryAndFilter(value: string): { queryPath: string; filter: string } {
15+
if (value.endsWith("/")) {
16+
return { queryPath: value.slice(0, -1) || "/", filter: "" };
17+
}
18+
const lastSlash = value.lastIndexOf("/");
19+
if (lastSlash === -1) return { queryPath: "/", filter: value };
20+
return {
21+
queryPath: value.slice(0, lastSlash) || "/",
22+
filter: value.slice(lastSlash + 1),
23+
};
24+
}
25+
1426
export function PathAutocomplete({
1527
value,
1628
onChange,
@@ -23,8 +35,14 @@ export function PathAutocomplete({
2335
const containerRef = useRef<HTMLDivElement>(null);
2436
const blurTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
2537

26-
const { data, isLoading } = useHostFiles(value);
27-
const entries = data?.entries ?? [];
38+
const { queryPath, filter } = getQueryAndFilter(value);
39+
const { data, isLoading } = useHostFiles(queryPath);
40+
const allEntries = data?.entries ?? [];
41+
const entries = filter
42+
? allEntries.filter((e) =>
43+
e.toLowerCase().startsWith(filter.toLowerCase()),
44+
)
45+
: allEntries;
2846

2947
const showDropdown = open && value.startsWith("/");
3048

@@ -42,8 +60,8 @@ export function PathAutocomplete({
4260

4361
const handleSelect = useCallback(
4462
(entry: string) => {
45-
const prefix = value.endsWith("/") ? value : value + "/";
46-
onChange(prefix + entry);
63+
const { queryPath: qp } = getQueryAndFilter(value);
64+
onChange(qp === "/" ? "/" + entry : qp + "/" + entry);
4765
setOpen(false);
4866
},
4967
[value, onChange],

0 commit comments

Comments
 (0)