Shorten thread route from /thread to /t

All internal links (tiles, prev/next nav, comments, notifications,
breadcrumb, dropzone route check) point to /t/<id>. A global route
middleware permanently redirects old /thread/<id> URLs including
query string and comment anchor.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-10-04 15:24:32 +02:00
co-authored by Devin
parent e2f2949586
commit 456776ea68
9 changed files with 16 additions and 10 deletions
+1 -1
View File
@@ -47,7 +47,7 @@ const mergedComments = computed(() => {
<NuxtLink <NuxtLink
v-for="comment in mergedComments" v-for="comment in mergedComments"
:key="comment.id" :key="comment.id"
:to="`/thread/${comment.uploadId}#comment-${comment.id}`" :to="`/t/${comment.uploadId}#comment-${comment.id}`"
class="block rounded-md border border-sidebar-border/60 bg-sidebar px-3 py-2.5 text-sm transition-colors hover:border-sidebar-border hover:bg-sidebar-accent" class="block rounded-md border border-sidebar-border/60 bg-sidebar px-3 py-2.5 text-sm transition-colors hover:border-sidebar-border hover:bg-sidebar-accent"
> >
<div class="flex items-center gap-2 text-xs text-muted-foreground"> <div class="flex items-center gap-2 text-xs text-muted-foreground">
+1 -1
View File
@@ -11,7 +11,7 @@ const uploading = ref(false)
// In Threads gilt die Zone nur für die obere Bildschirmhälfte — // In Threads gilt die Zone nur für die obere Bildschirmhälfte —
// die untere Hälfte gehört den Kommentaren (dort Anhänge, sonst nichts) // die untere Hälfte gehört den Kommentaren (dort Anhänge, sonst nichts)
const topHalfOnly = computed(() => route.path.startsWith('/thread/')) const topHalfOnly = computed(() => route.path.startsWith('/t/'))
const pointerY = ref(0) const pointerY = ref(0)
const inTopHalf = computed(() => !topHalfOnly.value || pointerY.value < (import.meta.client ? window.innerHeight : 0) / 2) const inTopHalf = computed(() => !topHalfOnly.value || pointerY.value < (import.meta.client ? window.innerHeight : 0) / 2)
+1 -1
View File
@@ -14,7 +14,7 @@ const timeAgo = useTimeAgo()
async function goTo(n: AppNotification) { async function goTo(n: AppNotification) {
open.value = false open.value = false
await markRead(n.id) await markRead(n.id)
await navigateTo(`/thread/${n.uploadId}#comment-${n.commentId}`) await navigateTo(`/t/${n.uploadId}#comment-${n.commentId}`)
} }
</script> </script>
+1 -1
View File
@@ -20,7 +20,7 @@ const crumbs = computed<Crumb[]>(() => {
const list: Crumb[] = [{ label: t('palette.home'), to: '/' }] const list: Crumb[] = [{ label: t('palette.home'), to: '/' }]
const p = route.path const p = route.path
if (p === '/') return [] if (p === '/') return []
if (p.startsWith('/thread/')) list.push({ label: t('nav.thread') }) if (p.startsWith('/t/')) list.push({ label: t('nav.thread') })
else if (p === '/users') list.push({ label: t('nav.users') }) else if (p === '/users') list.push({ label: t('nav.users') })
else if (p.startsWith('/user/')) { else if (p.startsWith('/user/')) {
list.push({ label: t('nav.users'), to: '/users' }, { label: t('nav.profile') }) list.push({ label: t('nav.users'), to: '/users' }, { label: t('nav.profile') })
+2 -2
View File
@@ -120,14 +120,14 @@ async function openUpload(e: MouseEvent) {
// Die eigentliche Navigation macht der NuxtLink bereits beim Klick — // Die eigentliche Navigation macht der NuxtLink bereits beim Klick —
// dieser Push ist nur noch Fallback und bei gleicher Route ein No-Op // dieser Push ist nur noch Fallback und bei gleicher Route ein No-Op
await router.push(`/thread/${props.upload.id}`) await router.push(`/t/${props.upload.id}`)
navigating.value = false navigating.value = false
} }
</script> </script>
<template> <template>
<NuxtLink <NuxtLink
:to="`/thread/${upload.id}`" :to="`/t/${upload.id}`"
class="upload-tile group block" class="upload-tile group block"
:class="[spanClass, navigating ? 'opacity-80' : '']" :class="[spanClass, navigating ? 'opacity-80' : '']"
@click.capture="captureClick" @click.capture="captureClick"
+6
View File
@@ -0,0 +1,6 @@
// Alter Thread-Pfad — /thread/<id> leitet dauerhaft auf /t/<id> um
export default defineNuxtRouteMiddleware((to) => {
if (to.path.startsWith('/thread/')) {
return navigateTo({ path: to.path.replace(/^\/thread\//, '/t/'), query: to.query, hash: to.hash }, { redirectCode: 301 })
}
})
@@ -598,7 +598,7 @@ async function deleteComment(commentId: number) {
class="absolute left-2 top-1/2 -translate-y-1/2 shadow opacity-80 hover:opacity-100" class="absolute left-2 top-1/2 -translate-y-1/2 shadow opacity-80 hover:opacity-100"
as-child as-child
> >
<NuxtLink :to="`/thread/${data.newerId}`" :aria-label="$t('thread.newer')"> <NuxtLink :to="`/t/${data.newerId}`" :aria-label="$t('thread.newer')">
<Icon name="ph:caret-left-fill" class="size-5" /> <Icon name="ph:caret-left-fill" class="size-5" />
</NuxtLink> </NuxtLink>
</Button> </Button>
@@ -609,7 +609,7 @@ async function deleteComment(commentId: number) {
class="absolute right-2 top-1/2 -translate-y-1/2 shadow opacity-80 hover:opacity-100" class="absolute right-2 top-1/2 -translate-y-1/2 shadow opacity-80 hover:opacity-100"
as-child as-child
> >
<NuxtLink :to="`/thread/${data.olderId}`" :aria-label="$t('thread.older')"> <NuxtLink :to="`/t/${data.olderId}`" :aria-label="$t('thread.older')">
<Icon name="ph:caret-right-fill" class="size-5" /> <Icon name="ph:caret-right-fill" class="size-5" />
</NuxtLink> </NuxtLink>
</Button> </Button>
+1 -1
View File
@@ -313,7 +313,7 @@ const timeAgo = useTimeAgo()
<NuxtLink <NuxtLink
v-for="comment in comments.items" v-for="comment in comments.items"
:key="comment.id" :key="comment.id"
:to="`/thread/${comment.uploadId}#comment-${comment.id}`" :to="`/t/${comment.uploadId}#comment-${comment.id}`"
class="flex items-center gap-3 px-3 py-2.5 text-sm transition-colors hover:bg-accent" class="flex items-center gap-3 px-3 py-2.5 text-sm transition-colors hover:bg-accent"
> >
<NuxtImg <NuxtImg
+1 -1
View File
@@ -54,7 +54,7 @@ export default defineNuxtPlugin(() => {
description: n.content?.slice(0, 80) || undefined, description: n.content?.slice(0, 80) || undefined,
action: { action: {
label: $i18n.t('notifications.view'), label: $i18n.t('notifications.view'),
onClick: () => navigateTo(`/thread/${n.uploadId}#comment-${n.commentId}`), onClick: () => navigateTo(`/t/${n.uploadId}#comment-${n.commentId}`),
}, },
}) })
} }