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.
*/
function pathFromUri(uri: string, baseUrl: string): string {
let p: string;
try {
// Absolute URL → take just the pathname.
if (/^https?:\/\//i.test(uri)) {
const u = new URL(uri);
return normalizePath(decodeURIComponent(u.pathname));
p = u.pathname;
} else {
p = uri;
}
} catch {
/* ignore */
p = uri;
}
// Already-relative URI: strip a host prefix if the server added one,
// then ensure a leading `/`.
let p = uri;
// Strip a host prefix if the server added one (e.g. lighttpd).
try {
const host = new URL(baseUrl).hostname;
if (p.startsWith('/' + host + '/')) p = p.slice(host.length + 1);
} catch {
/* 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);
}
function toEntryInfo(e: WebDAVEntry, baseUrl: string): EntryInfo {
const rawPath = e.path && e.path.length > 0 ? e.path : e.uri;
const fullPath = pathFromUri(rawPath, baseUrl);
// Some servers (notably webdav3.py) report the full path as the
// <displayname> for collections. Always use the leaf of the resolved
// path so the UI shows a name, not a path.
const name = basename(fullPath) || e.name;
// Prefer the server-provided displayname when it looks like a leaf
// (no slashes). Some servers (notably older webdav3.py) used to
// return the full path as the displayname; in that case fall back to
// 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 {
name,
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.
const collectionUrl = pathToUrl(normalizePath(path), base);
const listing = await manager.open(collectionUrl);
const selfPath = normalizePath(path);
const entries: EntryInfo[] = [];
for (const e of Object.values(listing)) {
if (e && e.name) {
entries.push(toEntryInfo(e, base));
}
if (!e || !e.name) continue;
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).
entries.sort((a, b) => {