feat: improve path handling in pathFromUri and toEntryInfo functions for better display name resolution

This commit is contained in:
Jaime Idolpx 2026-06-07 18:13:03 -04:00
parent ed302d7156
commit d1270bc604

View File

@ -102,34 +102,47 @@ export interface EntryInfo {
* path-relative work everywhere else. * path-relative work everywhere else.
*/ */
function pathFromUri(uri: string, baseUrl: string): string { function pathFromUri(uri: string, baseUrl: string): string {
let p: string;
try { try {
// Absolute URL → take just the pathname. // Absolute URL → take just the pathname.
if (/^https?:\/\//i.test(uri)) { if (/^https?:\/\//i.test(uri)) {
const u = new URL(uri); const u = new URL(uri);
return normalizePath(decodeURIComponent(u.pathname)); p = u.pathname;
} else {
p = uri;
} }
} catch { } catch {
/* ignore */ p = uri;
} }
// Already-relative URI: strip a host prefix if the server added one, // Strip a host prefix if the server added one (e.g. lighttpd).
// then ensure a leading `/`.
let p = uri;
try { try {
const host = new URL(baseUrl).hostname; const host = new URL(baseUrl).hostname;
if (p.startsWith('/' + host + '/')) p = p.slice(host.length + 1); if (p.startsWith('/' + host + '/')) p = p.slice(host.length + 1);
} catch { } catch {
/* ignore */ /* ignore */
} }
// Always URL-decode so the rest of the app sees literal characters
// (e.g. spaces) instead of %20 in names and paths.
try {
p = decodeURIComponent(p);
} catch {
/* leave as-is on malformed encoding */
}
return normalizePath(p); return normalizePath(p);
} }
function toEntryInfo(e: WebDAVEntry, baseUrl: string): EntryInfo { function toEntryInfo(e: WebDAVEntry, baseUrl: string): EntryInfo {
const rawPath = e.path && e.path.length > 0 ? e.path : e.uri; const rawPath = e.path && e.path.length > 0 ? e.path : e.uri;
const fullPath = pathFromUri(rawPath, baseUrl); const fullPath = pathFromUri(rawPath, baseUrl);
// Some servers (notably webdav3.py) report the full path as the // Prefer the server-provided displayname when it looks like a leaf
// <displayname> for collections. Always use the leaf of the resolved // (no slashes). Some servers (notably older webdav3.py) used to
// path so the UI shows a name, not a path. // return the full path as the displayname; in that case fall back to
const name = basename(fullPath) || e.name; // the leaf of the resolved path.
const looksLikePath = (s: string) => s.includes('/');
const name =
(e.name && !looksLikePath(e.name) ? e.name : '') ||
basename(fullPath) ||
e.name;
return { return {
name, name,
path: fullPath, path: fullPath,
@ -149,11 +162,18 @@ export async function listDirectory(path: string): Promise<EntryInfo[]> {
// The manager's `open` requires an absolute URL on the configured server. // The manager's `open` requires an absolute URL on the configured server.
const collectionUrl = pathToUrl(normalizePath(path), base); const collectionUrl = pathToUrl(normalizePath(path), base);
const listing = await manager.open(collectionUrl); const listing = await manager.open(collectionUrl);
const selfPath = normalizePath(path);
const entries: EntryInfo[] = []; const entries: EntryInfo[] = [];
for (const e of Object.values(listing)) { for (const e of Object.values(listing)) {
if (e && e.name) { if (!e || !e.name) continue;
entries.push(toEntryInfo(e, base)); const info = toEntryInfo(e, base);
} // Filter out the listing root itself (it appears as an entry whose
// path equals the requested collection) and any entries whose path
// doesn't sit directly under the requested collection.
if (info.path === selfPath) continue;
const parent = splitPath(info.path).parent;
if (parent !== selfPath) continue;
entries.push(info);
} }
// Folders first, then alpha (case-insensitive). // Folders first, then alpha (case-insensitive).
entries.sort((a, b) => { entries.sort((a, b) => {