Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions templates/content/app/i18n-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3118,7 +3118,7 @@ const localFilesMessages = {
importedLocalFiles: "Imported local files",
importedSource: "Imported source",
lastSync: "Last sync",
localFolders: "Local folders",
localFolders: "Folders",
mainFolder: "Main folder",
metaTitle: "Local files - Content",
noFoldersLinked: "No folders linked",
Expand Down Expand Up @@ -3654,7 +3654,7 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
folderLinked: "已链接 {{count}} 个文件夹",
foldersLinked: "已链接 {{count}} 个文件夹",
lastSync: "上次同步",
localFolders: "本地文件夹",
localFolders: "文件夹",
mainFolder: "主文件夹",
metaTitle: "本地文件 - Content",
noFoldersLinked: "未链接文件夹",
Expand Down Expand Up @@ -3895,7 +3895,7 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
folderLinked: "{{count}} carpeta enlazada",
foldersLinked: "{{count}} carpetas enlazadas",
lastSync: "Última sincronización",
localFolders: "Carpetas locales",
localFolders: "Carpetas",
mainFolder: "Carpeta principal",
metaTitle: "Archivos locales - Content",
noFoldersLinked: "No hay carpetas enlazadas",
Expand Down
2 changes: 1 addition & 1 deletion templates/content/app/i18n/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1227,7 +1227,7 @@ const messages = {
importedLocalFiles: "匯入的本機檔案",
importedSource: "匯入的來源",
lastSync: "上次同步",
localFolders: "本機資料夾",
localFolders: "資料夾",
mainFolder: "主資料夾",
metaTitle: "本機檔案 - Content",
noFoldersLinked: "未連結資料夾",
Expand Down
28 changes: 28 additions & 0 deletions templates/content/app/lib/local-folder-picker-safety.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest";

import {
hasInterruptedNativeFolderPickerAttempt,
isUserCancelledFolderPickerError,
runNativeFolderPickerWithCrashSentinel,
} from "./local-folder-picker-safety";

Expand Down Expand Up @@ -57,3 +58,30 @@ describe("native folder picker crash sentinel", () => {
).toBe(true);
});
});

describe("isUserCancelledFolderPickerError", () => {
it("recognizes the DOMException showDirectoryPicker() rejects with on user cancel", () => {
expect(
isUserCancelledFolderPickerError(
new DOMException("The user aborted a request.", "AbortError"),
),
).toBe(true);
});

it("recognizes an abort by error code when the name is unavailable", () => {
expect(
isUserCancelledFolderPickerError({ code: DOMException.ABORT_ERR }),
).toBe(true);
});

it("does not treat a real failure as a user cancellation", () => {
expect(
isUserCancelledFolderPickerError(
new DOMException("Permission denied.", "NotAllowedError"),
),
).toBe(false);
expect(isUserCancelledFolderPickerError(new Error("boom"))).toBe(false);
expect(isUserCancelledFolderPickerError(null)).toBe(false);
expect(isUserCancelledFolderPickerError("nope")).toBe(false);
});
});
9 changes: 9 additions & 0 deletions templates/content/app/lib/local-folder-picker-safety.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ export function hasInterruptedNativeFolderPickerAttempt(
}
}

export function isUserCancelledFolderPickerError(error: unknown): boolean {
// DOMException does not extend Error, so check by name/code instead of
// `instanceof Error`. `showDirectoryPicker()` rejects with this shape when
// the user dismisses the native picker without choosing a folder.
if (typeof error !== "object" || error === null) return false;
const named = error as { name?: unknown; code?: unknown };
return named.name === "AbortError" || named.code === DOMException.ABORT_ERR;
}

export async function runNativeFolderPickerWithCrashSentinel<T>(
operation: () => Promise<T>,
options: {
Expand Down
5 changes: 5 additions & 0 deletions templates/content/app/routes/_app.local-files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
} from "@/lib/local-folder-live-sync";
import {
hasInterruptedNativeFolderPickerAttempt,
isUserCancelledFolderPickerError,
runNativeFolderPickerWithCrashSentinel,
} from "@/lib/local-folder-picker-safety";
import { isUnsafeNativeFolderPickerHost } from "@/lib/local-folder-picker-support";
Expand Down Expand Up @@ -223,7 +224,7 @@
function sourcePrefixBase(name: string, fallback = "Local folder") {
const prefix = name
.replace(/[\\/]/g, "-")
.replace(/\0/g, "")

Check warning on line 227 in templates/content/app/routes/_app.local-files.tsx

View workflow job for this annotation

GitHub Actions / Lint & format

eslint(no-control-regex)

Unexpected control character
.replace(/\s+/g, " ")
.trim();
return !prefix || prefix === "." || prefix === ".." ? fallback : prefix;
Expand Down Expand Up @@ -1163,6 +1164,10 @@
toast.success(t("localFiles.pulledLocalFiles"));
await connectLocalComponentWorkspaces([selected]);
} catch (err) {
if (isUserCancelledFolderPickerError(err)) {
// Backing out of the native picker isn't a failure worth surfacing.
return;
}
setStatus({
kind: "error",
title: t("localFiles.folderAddFailed"),
Expand Down
Loading