diff --git a/app/components/admin/Registration.vue b/app/components/admin/Registration.vue index 7c13dea..5c22db5 100644 --- a/app/components/admin/Registration.vue +++ b/app/components/admin/Registration.vue @@ -196,36 +196,37 @@ onMounted(async () => { }); function getStatusClass() { - if (registrationOpen && !inviteEnabled) + if (registrationOpen.value && !inviteEnabled.value) return 'bg-success/10 border-success/20'; - if (inviteEnabled) return 'bg-accent/10 border-accent/20'; + if (inviteEnabled.value) return 'bg-accent/10 border-accent/20'; return 'bg-bg-tertiary border-border'; } function getStatusIcon() { - if (registrationOpen && !inviteEnabled) return 'ph:lock-open'; - if (inviteEnabled) return 'ph:envelope-simple'; + if (registrationOpen.value && !inviteEnabled.value) return 'ph:lock-open'; + if (inviteEnabled.value) return 'ph:envelope-simple'; return 'ph:lock-simple'; } function getStatusIconClass() { - if (registrationOpen && !inviteEnabled) return 'text-success'; - if (inviteEnabled) return 'text-accent'; + if (registrationOpen.value && !inviteEnabled.value) return 'text-success'; + if (inviteEnabled.value) return 'text-accent'; return 'text-text-muted'; } function getStatusTextClass() { - if (registrationOpen && !inviteEnabled) return 'text-success'; - if (inviteEnabled) return 'text-accent'; + if (registrationOpen.value && !inviteEnabled.value) return 'text-success'; + if (inviteEnabled.value) return 'text-accent'; return 'text-text-secondary'; } function getStatusText() { - if (registrationOpen && !inviteEnabled) + if (registrationOpen.value && !inviteEnabled.value) return 'Registration is open to everyone'; - if (registrationOpen && inviteEnabled) + if (registrationOpen.value && inviteEnabled.value) return 'Registration open (invite optional)'; - if (!registrationOpen && inviteEnabled) return 'Invite-only registration'; + if (!registrationOpen.value && inviteEnabled.value) + return 'Invite-only registration'; return 'Registration is closed'; } diff --git a/app/layouts/default.vue b/app/layouts/default.vue index fe9237e..54e3c5e 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -35,10 +35,13 @@ :style="{ color: branding?.siteNameColor || '' }" v-html="branding?.siteName || 'Trackarr'" > - {{ - branding?.siteSubtitle || - `v${useRuntimeConfig().public.appVersion}` - }} + @@ -292,10 +295,13 @@
- {{ - branding?.footerText || - `© ${new Date().getFullYear()} ${(branding?.siteName || 'Trackarr').toUpperCase()}` - }} + P2P PROTOCOL
@@ -349,8 +355,15 @@ const { data: branding } = await useFetch<{ pageTitleSuffix: string | null; }>('/api/branding'); -// Set dynamic favicon +// Set dynamic favicon and title template useHead({ + titleTemplate: computed(() => { + const suffix = + branding.value?.pageTitleSuffix || + `- ${branding.value?.siteName || 'Trackarr'}`; + return (title?: string) => + title ? `${title} ${suffix}` : suffix.replace(/^- /, ''); + }), link: [ { rel: 'icon', diff --git a/app/pages/auth/login.vue b/app/pages/auth/login.vue index 7fc0fa8..338c9e0 100644 --- a/app/pages/auth/login.vue +++ b/app/pages/auth/login.vue @@ -22,10 +22,10 @@ class="text-2xl font-bold tracking-tighter uppercase" v-html="branding?.authTitle || branding?.siteName || 'Trackarr'" > -

+ >
-

+ >
diff --git a/app/pages/search.vue b/app/pages/search.vue index 439d1e6..6a9f127 100644 --- a/app/pages/search.vue +++ b/app/pages/search.vue @@ -165,12 +165,6 @@ const page = ref(parseInt((route.query.p as string) || '1', 10)); // Fetch categories const { data: categories } = await useFetch('/api/categories'); -// Fetch branding for page title -const { data: branding } = await useFetch<{ - siteName: string; - pageTitleSuffix: string | null; -}>('/api/branding'); - // Fetch torrents const { data: torrentsData, @@ -249,9 +243,6 @@ watch( ); useHead({ - title: computed( - () => - `Search Torrents ${branding.value?.pageTitleSuffix || `- ${branding.value?.siteName || 'Trackarr'}`}` - ), + title: 'Search Torrents', }); diff --git a/package.json b/package.json index d8f1714..baafd89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "trackarr", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "private": true, "scripts": { diff --git a/server/utils/settings.ts b/server/utils/settings.ts index 5248391..4c8d074 100644 --- a/server/utils/settings.ts +++ b/server/utils/settings.ts @@ -2,6 +2,16 @@ import { eq } from 'drizzle-orm'; import { db } from '../db'; import { settings } from '../db/schema'; +/** + * Check if HTML content is effectively empty (just empty tags like

or whitespace) + */ +function isEmptyHtml(html: string | null): boolean { + if (!html) return true; + // Strip all HTML tags and check if anything meaningful remains + const textContent = html.replace(/<[^>]*>/g, '').trim(); + return textContent.length === 0; +} + export const SETTINGS_KEYS = { REGISTRATION_OPEN: 'registration_open', MIN_RATIO: 'min_ratio', @@ -191,7 +201,7 @@ export async function getSiteFavicon(): Promise { */ export async function getSiteSubtitle(): Promise { const value = await getSetting(SETTINGS_KEYS.SITE_SUBTITLE); - return value || null; + return isEmptyHtml(value) ? null : value; } /** @@ -216,7 +226,7 @@ export async function isSiteNameBold(): Promise { */ export async function getAuthTitle(): Promise { const value = await getSetting(SETTINGS_KEYS.AUTH_TITLE); - return value || ''; + return isEmptyHtml(value) ? '' : value!; } /** @@ -224,7 +234,7 @@ export async function getAuthTitle(): Promise { */ export async function getAuthSubtitle(): Promise { const value = await getSetting(SETTINGS_KEYS.AUTH_SUBTITLE); - return value || 'Private BitTorrent Tracker'; + return isEmptyHtml(value) ? 'Private BitTorrent Tracker' : value!; } /** @@ -232,7 +242,7 @@ export async function getAuthSubtitle(): Promise { */ export async function getFooterText(): Promise { const value = await getSetting(SETTINGS_KEYS.FOOTER_TEXT); - return value || ''; + return isEmptyHtml(value) ? '' : value!; } /**