@@ -75,7 +77,7 @@ export function PWAInstallPrompt() {
)}
{logoSrc && (

diff --git a/components/settings/files-settings.tsx b/components/settings/files-settings.tsx
index 1bec3ddd..c686684a 100644
--- a/components/settings/files-settings.tsx
+++ b/components/settings/files-settings.tsx
@@ -6,6 +6,7 @@ import { Folder, FolderOpen, FileText, FileCode, ImageIcon, FileAudio, File, Hom
import { SettingsSection, SettingItem, ToggleSwitch, RadioGroup } from "./settings-section";
import { loadFilesSettings, saveFilesSettings, type FilesSettings, type FolderLayout } from "@/components/files/files-settings-dialog";
import { cn } from "@/lib/utils";
+import { withBasePath } from "@/lib/browser-navigation";
interface SampleFile {
name: string;
@@ -95,7 +96,7 @@ function FilesSettingsPreview({ settings }: { settings: FilesSettings }) {
)}
>
{settings.showThumbnails && file.thumbnailUrl ? (
-

+
})
) : settings.showIcons ? (
getPreviewIcon(file, settings.coloredIcons, "sm")
) : null}
@@ -125,7 +126,7 @@ function FilesSettingsPreview({ settings }: { settings: FilesSettings }) {
)}
>
{settings.showThumbnails && file.thumbnailUrl ? (
-

+
})
) : settings.showIcons ? (
getPreviewIcon(file, settings.coloredIcons, "lg")
) : (
diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx
index 13ac7c20..315aa53e 100644
--- a/components/ui/avatar.tsx
+++ b/components/ui/avatar.tsx
@@ -6,6 +6,7 @@ import { useSettingsStore } from "@/stores/settings-store";
import { useContactStore, getContactPhotoUri } from "@/stores/contact-store";
import { useConfig } from "@/hooks/use-config";
import { avatarHooks } from "@/lib/plugin-hooks";
+import { withBasePath } from "@/lib/browser-navigation";
const IS_DEV = process.env.NODE_ENV !== "production";
@@ -236,7 +237,7 @@ export function Avatar({ name, email, contactPhotoUri, size = "md", className, d
const customAvatar = devMode && email ? CUSTOM_AVATARS[email.toLowerCase()] : null;
const pluginAvatar = pluginAvatarFailed ? null : pluginAvatarUrl;
const photoSrc = resolvedContactPhoto || pluginAvatar || customAvatar || profilePic || null;
- const faviconSrc = !imgError && !domainFailed && showFavicon ? `/api/favicon?domain=${encodeURIComponent(faviconDomain!)}` : null;
+ const faviconSrc = !imgError && !domainFailed && showFavicon ? withBasePath(`/api/favicon?domain=${encodeURIComponent(faviconDomain!)}`) : null;
const imgSrc = disableImages ? null : (photoSrc || faviconSrc);
const isFavicon = imgSrc !== null && imgSrc === faviconSrc;
diff --git a/lib/browser-navigation.ts b/lib/browser-navigation.ts
index 5df6ff91..15369273 100644
--- a/lib/browser-navigation.ts
+++ b/lib/browser-navigation.ts
@@ -75,6 +75,26 @@ export function apiFetch(input: string, init?: RequestInit): Promise
{
return fetch(input, init);
}
+/**
+ * Mount-prefix-aware wrapper for URL strings used in `
`, ``,
+ * `window.location.*`, etc. — anything the browser resolves itself, where
+ * `apiFetch` can't help.
+ *
+ * Idempotent: passing an already-prefixed value, an external URL, a
+ * protocol-relative URL, or an empty/falsy value returns it unchanged. So it's
+ * safe to wrap admin-configurable values that might be either a local path
+ * (`/branding/foo.svg`, `/api/admin/branding/...`) or a full URL.
+ */
+export function withBasePath(url: string | null | undefined): string {
+ if (!url) return url ?? '';
+ if (url.charCodeAt(0) !== 47) return url; // not absolute (e.g. https://, data:, blob:)
+ if (url.charCodeAt(1) === 47) return url; // protocol-relative //cdn...
+ const prefix = getPathPrefix();
+ if (!prefix) return url;
+ if (url === prefix || url.startsWith(prefix + '/')) return url;
+ return prefix + url;
+}
+
/**
* Extracts the locale from the current URL, skipping any mount prefix.
diff --git a/lib/webdav/client.ts b/lib/webdav/client.ts
index 2772d37d..05486798 100644
--- a/lib/webdav/client.ts
+++ b/lib/webdav/client.ts
@@ -4,6 +4,7 @@
*/
import { getActiveAccountSlotHeaders } from '@/lib/auth/active-account-slot';
+import { apiFetch, withBasePath } from '@/lib/browser-navigation';
export interface WebDAVResource {
href: string;
@@ -32,7 +33,7 @@ export class WebDAVClient {
...options?.headers,
};
- return fetch(this.proxyUrl, {
+ return apiFetch(this.proxyUrl, {
method: 'POST',
headers,
body: options?.body,
@@ -118,7 +119,7 @@ export class WebDAVClient {
// Use XMLHttpRequest for progress tracking
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
- xhr.open('POST', this.proxyUrl);
+ xhr.open('POST', withBasePath(this.proxyUrl));
xhr.setRequestHeader('X-WebDAV-Method', 'PUT');
xhr.setRequestHeader('X-WebDAV-Path', path);
const slotHeaders = getActiveAccountSlotHeaders();