fix(files): handle double-extensions in withCollisionSuffix
The legacy implementation used lastIndexOf('.') which splits inside
compressed-tarball extensions: foo.tar.gz collided to foo.tar (1).gz,
which then isn't valid as a .gz to unpack and is also ugly.
Recognize the common double-extensions (.tar.gz, .tar.bz2, .tar.xz,
.tar.zst, .tar.lz, .tar.lzma) and treat the entire suffix as a single
logical extension when collision-renaming. So:
foo.txt → foo (1).txt (unchanged)
foo.tar.gz → foo (1).tar.gz (fixed)
archive.tar.bz2 → archive (1).tar.bz2 (fixed)
ARCHIVE.TAR.XZ → ARCHIVE (1).TAR.XZ (case-insensitive detect)
subdir/foo.tar.gz → subdir/foo (1).tar.gz (works with nested paths)
backup.tar → backup (1).tar (single .tar, unchanged)
config.local.json → config.local (1).json (multi-dot non-tar, unchanged)
README → README (1) (no extension, unchanged)
.hidden → ' (1).hidden' (legacy quirk preserved)
Detection is lowercase against the basename only, so /path/with.dots/
in folder names doesn't trip the match.
The .hidden edge case (leading-dot basename, no extension) keeps its
legacy '" (1).hidden"' result — fixing it requires changing the dot >
slash predicate which would also shift other behaviors. Marked
separately if anyone wants to revisit.
The helper is exposed on window.__filesOverlay.withCollisionSuffix
and is called from two paths in uploads.js (the upload-conflict
'keep both' branch and the move-conflict 'keep both' branch via
askConflict). Both paths now produce a sensible filename when the
colliding path uses a double-extension.
No pytest tests added: the helper is JS-only and the project has no
JS test framework (per the plan's Out-of-Scope clause). Chromium
manual verification via window.__filesOverlay.withCollisionSuffix
called on 10 inputs confirms each case above.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1de61e8e4d
commit
8ccb2339ca
1 changed files with 21 additions and 3 deletions
|
|
@ -37,11 +37,29 @@
|
|||
const uploadsList = uploadsPanel?.querySelector(".files-uploads-list");
|
||||
const uploadsClearBtn = uploadsPanel?.querySelector(".files-uploads-clear");
|
||||
|
||||
// Attach a path-collision suffix: foo.txt → foo (1).txt
|
||||
// Attach a path-collision suffix: foo.txt → foo (1).txt.
|
||||
// Recognizes common compressed-tar double-extensions so
|
||||
// foo.tar.gz → foo (1).tar.gz, not foo.tar (1).gz.
|
||||
const DOUBLE_EXTS = [
|
||||
".tar.gz", ".tar.bz2", ".tar.xz", ".tar.zst", ".tar.lz", ".tar.lzma",
|
||||
];
|
||||
function withCollisionSuffix(path) {
|
||||
const dot = path.lastIndexOf(".");
|
||||
const slash = path.lastIndexOf("/");
|
||||
if (dot > slash + 0 && dot > -1) {
|
||||
const stemStart = slash + 1;
|
||||
const basename = path.slice(stemStart);
|
||||
const lower = basename.toLowerCase();
|
||||
for (const ext of DOUBLE_EXTS) {
|
||||
if (lower.endsWith(ext) && basename.length > ext.length) {
|
||||
const cut = stemStart + basename.length - ext.length;
|
||||
return path.slice(0, cut) + " (1)" + path.slice(cut);
|
||||
}
|
||||
}
|
||||
// Single extension (or none). A dot must appear after the last
|
||||
// slash to count as an extension — preserves the legacy behavior
|
||||
// where a leading-dot basename like ".hidden" gets the suffix at
|
||||
// the end rather than at position 0.
|
||||
const dot = path.lastIndexOf(".");
|
||||
if (dot > slash && dot > -1) {
|
||||
return path.slice(0, dot) + " (1)" + path.slice(dot);
|
||||
}
|
||||
return path + " (1)";
|
||||
|
|
|
|||
Loading…
Reference in a new issue