feat: links cruzados autor->/autor y parodia->/franquicia en posts

This commit is contained in:
Renato
2026-08-05 02:38:06 +08:00
parent a237d08f59
commit 702c6a5781
2 changed files with 37 additions and 5 deletions
+20 -5
View File
@@ -1,7 +1,7 @@
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import Link from "next/link" import Link from "next/link"
import type { Metadata } from "next" import type { Metadata } from "next"
import { getPostBySlug, getAllPosts } from "@/lib/db" import { getPostBySlug, getAllPosts, getAuthorSlugByName } from "@/lib/db"
import { PostCard } from "@/components/post-card" import { PostCard } from "@/components/post-card"
import { TagBadge } from "@/components/tag-badge" import { TagBadge } from "@/components/tag-badge"
import { ArrowLeft, BookOpen, ExternalLink } from "lucide-react" import { ArrowLeft, BookOpen, ExternalLink } from "lucide-react"
@@ -78,6 +78,9 @@ export default async function PostDetailPage({
post.summary.length > 50 post.summary.length > 50
) )
// Enlaces cruzados: autor -> página de autor, parodia -> franquicia.
const authorSlug = post.artist ? getAuthorSlugByName(post.artist) : null
return ( return (
<div className="pb-16 md:pb-0"> <div className="pb-16 md:pb-0">
<Link <Link
@@ -117,11 +120,23 @@ export default async function PostDetailPage({
</div> </div>
<div className="flex flex-wrap items-center gap-2 text-xs sm:text-sm"> <div className="flex flex-wrap items-center gap-2 text-xs sm:text-sm">
{post.artist && ( {post.artist && authorSlug && (
<TagBadge tag={`artist:${post.artist}`} href={`/tag/${encodeURIComponent(`artist:${post.artist}`)}`} /> <Link
href={`/autor/${authorSlug}`}
className="inline-flex items-center gap-1.5 rounded-full border border-[var(--border)] bg-[var(--surface)] px-2.5 py-1 text-xs text-[var(--foreground)] transition-colors hover:border-[var(--accent)] hover:text-[var(--accent)]"
>
<span className="text-[var(--muted)]"> artista:</span>
{post.artist}
</Link>
)} )}
{post.parody && ( {post.parody && !["original", "sin parodia"].includes(post.parody) && (
<TagBadge tag={`parody:${post.parody}`} href={`/tag/${encodeURIComponent(`parody:${post.parody}`)}`} /> <Link
href={`/franquicia/${encodeURIComponent(post.parody)}`}
className="inline-flex items-center gap-1.5 rounded-full border border-[var(--border)] bg-[var(--surface)] px-2.5 py-1 text-xs text-[var(--foreground)] transition-colors hover:border-[var(--accent)] hover:text-[var(--accent)]"
>
<span className="text-[var(--muted)]"> franquicia:</span>
{post.parody}
</Link>
)} )}
{post.source && ( {post.source && (
<span className="tag-pill border border-[var(--border)] text-[var(--muted)]"> <span className="tag-pill border border-[var(--border)] text-[var(--muted)]">
+17
View File
@@ -420,6 +420,23 @@ export function getAuthorBySlug(slug: string): Author | null {
return row ? rowToAuthor(row) : null return row ? rowToAuthor(row) : null
} }
// Devuelve el slug de un autor por su nombre (para enlazar desde los posts).
// Si el autor no está registrado, lo registra sobre la marcha.
export function getAuthorSlugByName(name: string): string | null {
const d = getDb()
const clean = (name || "").trim()
if (!clean || clean === "sin artista" || clean === "dummy") return null
d.prepare("INSERT OR IGNORE INTO authors(name, slug) VALUES (?, ?)").run(
clean,
slugify(clean),
)
const row = d.prepare("SELECT slug FROM authors WHERE name = ?").get(clean) as
| { slug: string }
| undefined
return row ? row.slug : null
}
export function getSeriesBySlug(slug: string): Series | null { export function getSeriesBySlug(slug: string): Series | null {
const d = getDb() const d = getDb()
const row = d const row = d