From c8a986c9739dbe01c149a0788a4ebe5225009778 Mon Sep 17 00:00:00 2001 From: gunshiz Date: Thu, 8 Oct 2026 18:06:16 +0700 Subject: [PATCH] feat : create share function for comment --- app/[character]/comment/page.tsx | 34 ++--- components/public/guide-page-shell.test.tsx | 60 +++++++++ components/public/guide-page-shell.tsx | 139 ++++++++++++++++++++ components/public/structured-guide.tsx | 128 ++---------------- 4 files changed, 218 insertions(+), 143 deletions(-) create mode 100644 components/public/guide-page-shell.test.tsx create mode 100644 components/public/guide-page-shell.tsx diff --git a/app/[character]/comment/page.tsx b/app/[character]/comment/page.tsx index 482c4f1..ffe0d7f 100644 --- a/app/[character]/comment/page.tsx +++ b/app/[character]/comment/page.tsx @@ -1,18 +1,11 @@ import { Suspense } from "react"; import type { Metadata } from "next"; -import Link from "next/link"; import { notFound } from "next/navigation"; import { connection } from "next/server"; -import Image from "@/components/ui/resilient-image"; -import { SiteHeader } from "@/components/public/site-header"; -import { SiteFooter } from "@/components/public/site-footer"; -import { ScrollableNav } from "@/components/public/scrollable-nav"; +import { GuidePageShell } from "@/components/public/guide-page-shell"; import { CommentBoardContent } from "@/components/comments/comment-board-content"; import { CommentBoardSkeleton } from "@/components/comments/comment-skeleton"; import { getCachedPublicGuideSummary } from "@/lib/cache/public"; -import { commentNavigationSection } from "@/lib/comments/navigation"; -import { navigationIconPath } from "@/lib/navigation"; -import { cn } from "@/lib/utils"; export const instant = false; type Props = { params: Promise<{ character: string }> }; @@ -25,22 +18,11 @@ export default async function GuideCommentPage({ params }: Props) { const guide = await getCachedPublicGuideSummary((await params).character); if (!guide) notFound(); const target = `guide:${guide.id}`; - const sections = [{ id: "overview", slug: "", title: "Overview" }, ...guide.sections, commentNavigationSection(guide.id)]; - return
- -
-
-

{guide.name}

ความคิดเห็น

-
- - {sections.map((section) => - {navigationIconPath(section.slug || "overview") && } - {section.title} - )} - - }> -
-
; + return ( + + }> + + + + ); } diff --git a/components/public/guide-page-shell.test.tsx b/components/public/guide-page-shell.test.tsx new file mode 100644 index 0000000..609a722 --- /dev/null +++ b/components/public/guide-page-shell.test.tsx @@ -0,0 +1,60 @@ +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it, vi } from "vitest"; + +import { GuidePageShell } from "./guide-page-shell"; + +vi.mock("@/components/public/site-header", () => ({ SiteHeader: () => null })); +vi.mock("@/components/live-refresh", () => ({ LiveRefresh: () => null })); + +const guide = { + id: "test-guide", + slug: "raiden", + name: "Raiden", + characterKey: "raiden-key", + coverUrl: "/cover.png", +}; +const sections = [ + { id: "weapon", slug: "weapon", title: "Weapon" }, + { id: "extra", slug: "rotation", title: "Rotation" }, +]; + +describe("shared guide navigation", () => { + it("gives comments the home button and guide tabs around their content", () => { + const html = renderToStaticMarkup( + +
Comment board
+
, + ); + + expect(html).toMatch(/]*aria-label="กลับหน้ารวม"[^>]*href="\/"/); + expect(html).toMatch(/]*aria-current="page"[^>]*href="\/raiden\/comment"/); + expect(html).toContain('href="/raiden/weapon"'); + expect(html).toContain('href="/raiden/rotation"'); + expect(html).toContain("Weapons"); + expect(html).toContain("Rotation"); + expect(html).toContain("
Comment board
"); + }); + + it("keeps overview active and includes a single comments link", () => { + const html = renderToStaticMarkup( + Overview content, + ); + + expect(html).toMatch(/]*aria-current="page"[^>]*href="\/raiden"/); + expect(html.match(/href="\/raiden\/comment"/g)).toHaveLength(1); + }); + + it("keeps preview links under admin and omits comments", () => { + const html = renderToStaticMarkup( + + Preview content + , + ); + + expect(html).toMatch(/]*aria-label="กลับหน้าแก้ไข"[^>]*href="\/admin\/raiden-key"/); + expect(html).toMatch(/]*aria-current="page"[^>]*href="\/admin\/raiden-key\/preview\/weapon"/); + expect(html).toContain('href="/admin/raiden-key/preview/rotation"'); + expect(html).not.toContain("/comment"); + expect(html).toContain("หน้านี้แสดงข้อมูลที่บันทึกล่าสุด"); + }); +}); diff --git a/components/public/guide-page-shell.tsx b/components/public/guide-page-shell.tsx new file mode 100644 index 0000000..1dc40af --- /dev/null +++ b/components/public/guide-page-shell.tsx @@ -0,0 +1,139 @@ +import type { ReactNode } from "react"; +import Link from "next/link"; +import { ArrowLeftIcon } from "lucide-react"; + +import Image from "@/components/ui/resilient-image"; +import { Button } from "@/components/ui/button"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { SiteHeader } from "@/components/public/site-header"; +import { SiteFooter } from "@/components/public/site-footer"; +import { ScrollableNav } from "@/components/public/scrollable-nav"; +import { LiveRefresh } from "@/components/live-refresh"; +import type { PublicGuideSummary } from "@/lib/guides/queries"; +import { commentNavigationSection } from "@/lib/comments/navigation"; +import { navigationIconPath } from "@/lib/navigation"; +import { cn } from "@/lib/utils"; + +export function publicSectionTitle(slug: string, fallback: string): string { + if (slug === "weapon") return "Weapons"; + if (slug === "artifact") return "Artifacts"; + if (slug === "constellations") return "Constellations"; + if (slug === "team") return "Team calc"; + if (slug === "comment") return "Comment"; + return fallback; +} + +export function GuidePageShell({ + guide, + sections: guideSections, + section, + preview = false, + children, +}: { + guide: Pick; + sections: Pick[]; + section?: string; + preview?: boolean; + children: ReactNode; +}) { + const sections = preview ? guideSections : [...guideSections, commentNavigationSection(guide.id)]; + const baseHref = preview ? `/admin/${guide.characterKey}/preview` : `/${guide.slug}`; + const currentPageTitle = section + ? publicSectionTitle(section, sections.find((item) => item.slug === section)?.title ?? section) + : "Overview"; + return ( +
+ + + {preview ? ( +
+ + Preview + หน้านี้แสดงข้อมูลที่บันทึกล่าสุดและเปิดดูได้เฉพาะผู้ดูแล + +
+ ) : null} +
+
+ +
+ {guide.name} +
+
+

+ {guide.name} +

+

+ {currentPageTitle} +

+
+
+
+ + + + Overview + + {sections.map((item) => ( + + {navigationIconPath(item.slug) ? ( + + ) : null} + {publicSectionTitle(item.slug, item.title)} + + ))} + +
+
+
+ {children} +
+ +
+ ); +} diff --git a/components/public/structured-guide.tsx b/components/public/structured-guide.tsx index 4762a12..ba89e26 100644 --- a/components/public/structured-guide.tsx +++ b/components/public/structured-guide.tsx @@ -1,13 +1,11 @@ import Image from "@/components/ui/resilient-image"; import localFont from "next/font/local"; import Link from "next/link"; -import { ArrowLeftIcon, ArrowUpRightIcon } from "lucide-react"; +import { ArrowUpRightIcon } from "lucide-react"; import { Fragment, type ReactNode } from "react"; import { Badge } from "@/components/ui/badge"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { ComparisonContext } from "@/components/public/comparison-context"; -import { Button } from "@/components/ui/button"; import { Card, CardAction, @@ -25,9 +23,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { SiteFooter } from "@/components/public/site-footer"; -import { SiteHeader } from "@/components/public/site-header"; -import { ScrollableNav } from "@/components/public/scrollable-nav"; +import { GuidePageShell, publicSectionTitle } from "@/components/public/guide-page-shell"; import { TeamArtifactIcon, TeamSearch } from "@/components/public/team-search"; import { GuideImageExport } from "@/components/public/guide-image-export"; import { ExportText } from "@/components/public/export-language"; @@ -40,7 +36,6 @@ import { LunarisText } from "@/components/public/lunaris-text"; import { ConditionSwitcher } from "@/components/public/condition-switcher"; import { MaterialPoster } from "@/components/public/material-poster"; import { RichTextContent } from "@/components/rich-text-content"; -import { LiveRefresh } from "@/components/live-refresh"; import { rarityGradientClass, rarityTextClass } from "@/lib/catalog/rarity"; import { elementIconFilter } from "@/lib/catalog/element"; import type { PublicGuide } from "@/lib/guides/queries"; @@ -76,15 +71,6 @@ export function publicSections(guide: PublicGuide) { return publicGuideSections(guide); } -function publicSectionTitle(slug: string, fallback: string): string { - if (slug === "weapon") return "Weapons"; - if (slug === "artifact") return "Artifacts"; - if (slug === "constellations") return "Constellations"; - if (slug === "team") return "Team calc"; - if (slug === "comment") return "Comment"; - return fallback; -} - export function StructuredGuideShell({ guide, section, @@ -94,109 +80,17 @@ export function StructuredGuideShell({ section?: string; preview?: boolean; }) { - const sections = preview ? publicSections(guide) : [...publicSections(guide), commentNavigationSection(guide.id)]; + const guideSections = publicSections(guide); + const sections = preview ? guideSections : [...guideSections, commentNavigationSection(guide.id)]; const baseHref = preview ? `/admin/${guide.characterKey}/preview` : `/${guide.slug}`; - const currentPageTitle = section - ? publicSectionTitle(section, sections.find((item) => item.slug === section)?.title ?? section) - : "Overview"; return ( -
- - - {preview ? ( -
- - Preview - หน้านี้แสดงข้อมูลที่บันทึกล่าสุดและเปิดดูได้เฉพาะผู้ดูแล - -
- ) : null} -
-
- -
- {guide.name} -
-
-

- {guide.name} -

-

- {currentPageTitle} -

-
-
-
- - - - Overview - - {sections.map((item) => ( - - {navigationIconPath(item.slug) ? ( - - ) : null} - {publicSectionTitle(item.slug, item.title)} - - ))} - -
-
-
- {section ? ( - - ) : ( - - )} -
- -
+ + {section ? ( + + ) : ( + + )} + ); }