diff --git a/components/object/upload-picker.tsx b/components/object/upload-picker.tsx index a9d0b4a..0051ad2 100644 --- a/components/object/upload-picker.tsx +++ b/components/object/upload-picker.tsx @@ -8,6 +8,7 @@ import { Progress } from "@/components/ui/progress" import { useAddUploadFiles, useTaskPanelOpen } from "@/contexts/task-context" import { formatBytes } from "@/lib/functions" import { useMessage } from "@/lib/feedback/message" +import { buildUploadObjectKey, normalizeUploadPrefix } from "@/lib/object-upload" import { RiDeleteBinLine, RiFileAddLine, RiFolderAddLine, RiUploadCloudLine } from "@remixicon/react" import { useVirtualizer } from "@tanstack/react-virtual" import * as React from "react" @@ -228,7 +229,7 @@ export function ObjectUploadPicker({ } }, [canUpload, onShowChange, show]) - const effectivePrefix = editablePrefix.replace(/\/$/, "") || "" + const effectivePrefix = normalizeUploadPrefix(editablePrefix) const ensureCapacity = React.useCallback( (incoming: number): boolean => { @@ -416,7 +417,7 @@ export function ObjectUploadPicker({ try { const tasks = items.map(({ relativePath, file }) => ({ file, - key: effectivePrefix ? `${effectivePrefix}/${relativePath}` : relativePath, + key: buildUploadObjectKey(effectivePrefix, relativePath), })) const batchSize = 50 let processed = 0 diff --git a/lib/object-upload.js b/lib/object-upload.js new file mode 100644 index 0000000..3f9ea0b --- /dev/null +++ b/lib/object-upload.js @@ -0,0 +1,9 @@ +export function normalizeUploadPrefix(prefix) { + return prefix.trim().replace(/^\/+/, "").replace(/\/+$/, "") +} + +export function buildUploadObjectKey(prefix, relativePath) { + const normalizedPrefix = normalizeUploadPrefix(prefix) + const normalizedRelativePath = relativePath.replace(/^\/+/, "") + return normalizedPrefix ? `${normalizedPrefix}/${normalizedRelativePath}` : normalizedRelativePath +} diff --git a/lib/object-upload.ts b/lib/object-upload.ts new file mode 100644 index 0000000..1195fd4 --- /dev/null +++ b/lib/object-upload.ts @@ -0,0 +1,9 @@ +export function normalizeUploadPrefix(prefix: string): string { + return prefix.trim().replace(/^\/+/, "").replace(/\/+$/, "") +} + +export function buildUploadObjectKey(prefix: string, relativePath: string): string { + const normalizedPrefix = normalizeUploadPrefix(prefix) + const normalizedRelativePath = relativePath.replace(/^\/+/, "") + return normalizedPrefix ? `${normalizedPrefix}/${normalizedRelativePath}` : normalizedRelativePath +} diff --git a/tests/lib/object-upload.test.js b/tests/lib/object-upload.test.js new file mode 100644 index 0000000..dcc4d64 --- /dev/null +++ b/tests/lib/object-upload.test.js @@ -0,0 +1,21 @@ +import test from "node:test" +import assert from "node:assert/strict" +import { buildUploadObjectKey, normalizeUploadPrefix } from "../../lib/object-upload.js" + +test("normalizeUploadPrefix removes leading and trailing slashes", () => { + assert.equal(normalizeUploadPrefix("/Normal/"), "Normal") + assert.equal(normalizeUploadPrefix("//Normal//"), "Normal") +}) + +test("buildUploadObjectKey avoids leading slash when prefix starts with slash", () => { + assert.equal(buildUploadObjectKey("/Normal/", "file.txt"), "Normal/file.txt") +}) + +test("buildUploadObjectKey keeps nested relative paths under the normalized prefix", () => { + assert.equal(buildUploadObjectKey("Normal/", "folder/file.txt"), "Normal/folder/file.txt") +}) + +test("buildUploadObjectKey uses relative path when prefix is empty or slash only", () => { + assert.equal(buildUploadObjectKey("", "file.txt"), "file.txt") + assert.equal(buildUploadObjectKey("/", "file.txt"), "file.txt") +})