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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ documentation/
# Test output
coverage/
.vitest/

# Upload CLI, bundled at build (scripts/build-cli.mjs)
public/tools/trackarr-upload.mjs
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Built with Nuxt 4 • Rust • PostgreSQL • Redis
| Forum, comments, reports, invitations, H&R | UI in 8 languages (EN, FR, ES, DE, IT, ZH, JA, RU) |
| Private messages between members, with blocks | Light & dark themes, ⌘K command palette |
| Paid sign-up in Bitcoin, Monero or Solana | On-chain payment checks, no processor, no spend key |
| Upload assistant: .torrent, MediaInfo, screens, BBCode | Web or CLI, one file or folder in, published torrent out |

| **Security** | **Emergency** |
| -------------------------------- | -------------------------------------------- |
Expand Down
2 changes: 1 addition & 1 deletion app/components/EditTorrentModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ const error = ref<string | null>(null);
const { data: categories } = await useFetch<Category[]>('/api/categories');

const renderedDescription = computed(() => {
return renderMarkdown(description.value);
return renderDescription(description.value);
});

// Initialize form data when torrent prop changes
Expand Down
46 changes: 46 additions & 0 deletions app/components/StepLine.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<template>
<div class="flex items-center gap-3">
<Icon :name="icon" class="shrink-0" :class="[iconClass, state === 'running' ? 'animate-spin' : '']" />
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between gap-3">
<span :class="state === 'skipped' ? 'text-text-muted' : 'text-text-primary'">{{ label }}</span>
<span v-if="detail" class="text-xs text-text-muted truncate">{{ detail }}</span>
</div>
<div v-if="progress !== undefined && state === 'running'" class="mt-1 w-full bg-bg-tertiary rounded-full h-1">
<div class="bg-text-primary h-1 rounded-full transition-all" :style="{ width: `${progress}%` }" />
</div>
</div>
</div>
</template>

<script setup lang="ts">
/** One line of a multi-step task: icon for its state, optional detail and progress */
const props = defineProps<{
label: string;
state: 'idle' | 'running' | 'done' | 'skipped' | 'error';
detail?: string;
progress?: number;
}>();

const icon = computed(
() =>
({
idle: 'ph:circle',
running: 'ph:spinner',
done: 'ph:check-circle',
skipped: 'ph:minus-circle',
error: 'ph:warning-circle',
})[props.state]
);

const iconClass = computed(
() =>
({
idle: 'text-text-muted',
running: 'text-text-primary',
done: 'text-success',
skipped: 'text-text-muted',
error: 'text-red-400',
})[props.state]
);
</script>
9 changes: 8 additions & 1 deletion app/components/UploadTorrentModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
{{ t('upload.title') }}
</h3>
</div>
<NuxtLink
to="/upload/assistant"
class="ml-auto mr-3 text-xs text-text-muted hover:text-text-primary underline underline-offset-2"
@click="close"
>
{{ t('assistant.entry') }}
</NuxtLink>
<button
class="text-text-muted hover:text-white transition-colors"
:aria-label="t('common.close')"
Expand Down Expand Up @@ -422,7 +429,7 @@ async function copyAnnounce() {
const { data: categories } = await useFetch('/api/categories');

const renderedDescription = computed(() => {
return renderMarkdown(description.value);
return renderDescription(description.value);
});

function getFlattenedCategories(
Expand Down
3 changes: 3 additions & 0 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
<NuxtLink to="/torrents" class="btn btn-secondary">
<Icon name="ph:files" aria-hidden="true" />{{ t('dashboard.browse') }}
</NuxtLink>
<NuxtLink to="/upload/assistant" class="btn btn-secondary">
<Icon name="ph:magic-wand" aria-hidden="true" />{{ t('assistant.entry') }}
</NuxtLink>
<button type="button" class="btn btn-primary" @click="showUpload = true">
<Icon name="ph:upload-simple" aria-hidden="true" />{{ t('dashboard.upload') }}
</button>
Expand Down
33 changes: 32 additions & 1 deletion app/pages/torrents/[hash].vue
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ const externalLinks = computed(() => {
return links;
});

const renderedDescription = computed(() => renderMarkdown(torrent.value?.description));
const renderedDescription = computed(() => renderDescription(torrent.value?.description));

async function copyHash() {
await navigator.clipboard.writeText(torrent.value!.infoHash).catch(() => {});
Expand Down Expand Up @@ -406,4 +406,35 @@ async function deleteTorrent() {
border-left: 2px solid rgb(var(--line-strong));
padding-left: 0.75rem;
}
/* BBCode: screenshots side by side, spoilers, MediaInfo report */
.description-content :deep(a > img) {
display: inline-block;
margin: 0.25rem;
vertical-align: middle;
}
.description-content :deep(details) {
margin-bottom: 0.75rem;
border: 1px solid rgb(var(--line));
border-radius: 4px;
padding: 0.5rem 0.75rem;
}
.description-content :deep(summary) {
cursor: pointer;
font-weight: 600;
color: rgb(var(--ink));
}
.description-content :deep(details[open] > summary) {
margin-bottom: 0.5rem;
}
.description-content :deep(pre) {
overflow-x: auto;
white-space: pre;
font-size: 0.8em;
line-height: 1.4;
}
.description-content :deep(cite) {
display: block;
font-style: normal;
font-weight: 600;
}
</style>
Loading