Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions app/components/admin/Registration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}

Expand Down
31 changes: 22 additions & 9 deletions app/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@
:style="{ color: branding?.siteNameColor || '' }"
v-html="branding?.siteName || 'Trackarr'"
></span>
<span class="text-[10px] text-text-muted font-mono">{{
branding?.siteSubtitle ||
`v${useRuntimeConfig().public.appVersion}`
}}</span>
<span
class="text-[10px] text-text-muted font-mono [&>p]:inline [&>p]:m-0"
v-html="
branding?.siteSubtitle ||
`v${useRuntimeConfig().public.appVersion}`
"
></span>
</div>
</NuxtLink>

Expand Down Expand Up @@ -292,10 +295,13 @@
<div
class="flex items-center gap-4 text-[10px] text-text-muted font-mono uppercase tracking-widest"
>
<span>{{
branding?.footerText ||
`© ${new Date().getFullYear()} ${(branding?.siteName || 'Trackarr').toUpperCase()}`
}}</span>
<span
class="[&>p]:inline [&>p]:m-0"
v-html="
branding?.footerText ||
`© ${new Date().getFullYear()} ${(branding?.siteName || 'Trackarr').toUpperCase()}`
"
></span>
<span class="w-1 h-1 bg-border rounded-full"></span>
<span>P2P PROTOCOL</span>
</div>
Expand Down Expand Up @@ -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',
Expand Down
6 changes: 3 additions & 3 deletions app/pages/auth/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
class="text-2xl font-bold tracking-tighter uppercase"
v-html="branding?.authTitle || branding?.siteName || 'Trackarr'"
></h1>
<p
class="text-text-muted text-sm mt-1"
<div
class="text-text-muted text-sm mt-1 [&>p]:m-0"
v-html="branding?.authSubtitle || 'Private BitTorrent Tracker'"
></p>
></div>
</div>

<div
Expand Down
6 changes: 3 additions & 3 deletions app/pages/auth/register.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
class="text-2xl font-bold tracking-tighter uppercase"
v-html="branding?.authTitle || branding?.siteName || 'Trackarr'"
></h1>
<p
class="text-text-muted text-sm mt-1"
<div
class="text-text-muted text-sm mt-1 [&>p]:m-0"
v-html="branding?.authSubtitle || 'Private BitTorrent Tracker'"
></p>
></div>
</div>

<!-- Registration Closed -->
Expand Down
11 changes: 1 addition & 10 deletions app/pages/search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,6 @@ const page = ref(parseInt((route.query.p as string) || '1', 10));
// Fetch categories
const { data: categories } = await useFetch<any[]>('/api/categories');

// Fetch branding for page title
const { data: branding } = await useFetch<{
siteName: string;
pageTitleSuffix: string | null;
}>('/api/branding');

// Fetch torrents
const {
data: torrentsData,
Expand Down Expand Up @@ -249,9 +243,6 @@ watch(
);

useHead({
title: computed(
() =>
`Search Torrents ${branding.value?.pageTitleSuffix || `- ${branding.value?.siteName || 'Trackarr'}`}`
),
title: 'Search Torrents',
});
</script>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "trackarr",
"version": "0.5.0",
"version": "0.5.1",
"type": "module",
"private": true,
"scripts": {
Expand Down
18 changes: 14 additions & 4 deletions server/utils/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <p></p> 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',
Expand Down Expand Up @@ -191,7 +201,7 @@ export async function getSiteFavicon(): Promise<string | null> {
*/
export async function getSiteSubtitle(): Promise<string | null> {
const value = await getSetting(SETTINGS_KEYS.SITE_SUBTITLE);
return value || null;
return isEmptyHtml(value) ? null : value;
}

/**
Expand All @@ -216,23 +226,23 @@ export async function isSiteNameBold(): Promise<boolean> {
*/
export async function getAuthTitle(): Promise<string> {
const value = await getSetting(SETTINGS_KEYS.AUTH_TITLE);
return value || '';
return isEmptyHtml(value) ? '' : value!;
}

/**
* Get auth page subtitle (login/register)
*/
export async function getAuthSubtitle(): Promise<string> {
const value = await getSetting(SETTINGS_KEYS.AUTH_SUBTITLE);
return value || 'Private BitTorrent Tracker';
return isEmptyHtml(value) ? 'Private BitTorrent Tracker' : value!;
}

/**
* Get footer text
*/
export async function getFooterText(): Promise<string> {
const value = await getSetting(SETTINGS_KEYS.FOOTER_TEXT);
return value || '';
return isEmptyHtml(value) ? '' : value!;
}

/**
Expand Down