fix(recipes): make batch-import directory browser work on Windows (#1106)

The browse endpoint and its frontend were written with POSIX-only
assumptions, so on Windows pressing Browse immediately failed with
"Access denied to this directory":

- The frontend opened the browser at "/", which resolves to the
  current drive root on Windows.
- The allowlist check used Path("/"), which has no drive letter on
  Windows, so relative_to() rejected every drive-qualified path —
  anything outside the user profile was denied.

Fixes:
- Empty browse path now defaults to the user home directory instead of
  erroring; the frontend sends "" rather than the POSIX-only "/".
- The access check is platform-aware (drive-qualified on Windows,
  absolute on POSIX).
- Parent navigation uses the server-provided parent_path; the root
  check is now path.parent == path (the old str/anchor comparison
  self-looped at Windows drive roots).
- Browsing up from a Windows drive root shows a virtual list of
  available drives so users can switch drives without typing a path.
This commit is contained in:
Will Miao
2026-09-11 22:23:24 +08:00
parent 3112869a21
commit 91b2735dad
4 changed files with 297 additions and 43 deletions
+23 -16
View File
@@ -18,6 +18,7 @@ export class BatchImportManager {
this.results = null;
this.isCancelled = false;
this.isImporting = false;
this.currentParentPath = null;
}
/**
@@ -718,9 +719,10 @@ export class BatchImportManager {
browser.style.display = isVisible ? 'none' : 'block';
if (!isVisible) {
// Load initial directory when opening
// Load initial directory when opening. An empty path lets the
// server pick its default (user home); "/" would be POSIX-only.
const currentPath = document.getElementById('batchDirectoryInput').value;
this.loadDirectory(currentPath || '/');
this.loadDirectory(currentPath || '');
}
}
}
@@ -761,6 +763,10 @@ export class BatchImportManager {
const directoryCount = document.getElementById('batchDirectoryCount');
const imageCount = document.getElementById('batchImageCount');
// Remember the server-computed parent path so the "up" navigation
// works with Windows paths too (they cannot be split on "/").
this.currentParentPath = data.parent_path || null;
if (currentPathEl) {
currentPathEl.textContent = data.current_path;
}
@@ -811,11 +817,9 @@ export class BatchImportManager {
`;
item.addEventListener('click', () => {
if (isParent) {
this.navigateToParentDirectory();
} else {
this.loadDirectory(path);
}
// The parent entry uses the server-provided parent_path (or the
// Windows drive-list token) directly — both are plain load targets.
this.loadDirectory(path);
});
return item;
@@ -839,15 +843,12 @@ export class BatchImportManager {
}
/**
* Navigate to parent directory
* Navigate to parent directory using the path reported by the server.
* Deriving it client-side by splitting on "/" breaks Windows paths.
*/
navigateToParentDirectory() {
const currentPath = document.getElementById('batchCurrentPath')?.textContent;
if (currentPath) {
// Get parent path using path manipulation
const lastSeparator = currentPath.lastIndexOf('/');
const parentPath = lastSeparator > 0 ? currentPath.substring(0, lastSeparator) : currentPath;
this.loadDirectory(parentPath);
if (this.currentParentPath) {
this.loadDirectory(this.currentParentPath);
}
}
@@ -857,8 +858,14 @@ export class BatchImportManager {
selectCurrentDirectory() {
const currentPath = document.getElementById('batchCurrentPath')?.textContent;
const directoryInput = document.getElementById('batchDirectoryInput');
if (currentPath && directoryInput) {
if (!currentPath) {
// Virtual levels (e.g. the Windows drive list) have no path.
showToast('toast.recipes.batchImportNoDirectory', {}, 'error');
return;
}
if (directoryInput) {
directoryInput.value = currentPath;
this.toggleDirectoryBrowser(); // Close browser
showToast('toast.recipes.batchImportDirectorySelected', { path: currentPath }, 'success');