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!;
}
/**